// © 2015 Neale Pickett // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // The software is provided "as is", without warranty of any kind, express or // implied, including but not limited to the warranties of merchantability, // fitness for a particular purpose and noninfringement. In no event shall the // authors or copyright holders be liable for any claim, damages or other // liability, whether in an action of contract, tort or otherwise, arising from, // out of or in connection with the software or the use or other dealings in // the software. // var maxSaturation = 200; var yarnWeight = 3; function Yarn(r, g, b) { var rgb = [Math.round(r*maxSaturation), Math.round(g*maxSaturation), Math.round(b*maxSaturation)] return "rgb(" + rgb.join(",") + ")" } var colors = { "R": Yarn(0.50, 0.00, 0.00), // Red "G": Yarn(0.00, 0.40, 0.00), // Green "B": Yarn(0.00, 0.00, 0.50), // Blue "C": Yarn(0.00, 0.50, 0.50), // Cyan "Y": Yarn(0.80, 0.80, 0.00), // Yellow "P": Yarn(0.60, 0.00, 0.60), // Purple "W": Yarn(0.90, 0.90, 0.90), // White "K": Yarn(0.00, 0.00, 0.00), // Black "BK": Yarn(0.00, 0.00, 0.00), // Black "GR": Yarn(0.50, 0.50, 0.50), // Gray "DR": Yarn(0.30, 0.00, 0.00), // Dark Red "DG": Yarn(0.00, 0.25, 0.00), // Dark Green "DB": Yarn(0.00, 0.00, 0.30), // Dark Blue "LB": Yarn(0.00, 0.40, 0.90), // Light Blue "LR": Yarn(0.80, 0.00, 0.00), // Light Red "LG": Yarn(0.00, 0.60, 0.00), // Light Green "LV": Yarn(0.50, 0.25, 0.60), // Lavender "BR": Yarn(0.60, 0.40, 0.20), // Brown "LGR": Yarn(0.60, 0.60, 0.60), // Light Gray "LBR": Yarn(0.80, 0.70, 0.50), // Light Brown "": "" } function Loom(ctx, warp, threadwidth) { var width = ctx.canvas.width; var height = ctx.canvas.height; ctx.lineWidth = Number(threadwidth || yarnWeight); // Avoid infinite loop if (warp.length == 0) { return; } // Draw the weft for (var x = 0; x <= width; ) { for (var i in warp) { ctx.strokeStyle = warp[i]; ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, height); ctx.stroke(); x += ctx.lineWidth; if (x > width) { break; } } } this.nrows = 0; this.weave = function(yarn, up, down, skip, repeat) { up = up || 1 down = down || 1 skip = skip || 0 repeat = repeat || 0 ctx.strokeStyle = yarn; var offset = (this.nrows / (repeat + 1)) * (skip + 1); var harness = up + down; var row = []; for (var i = 0; i*ctx.lineWidth < width; i += 1) { var ox = (i + offset) % harness if (ox < up) { ctx.beginPath(); ctx.moveTo(i*ctx.lineWidth, this.nrows*ctx.lineWidth); ctx.lineTo((i+1)*ctx.lineWidth, this.nrows*ctx.lineWidth); ctx.stroke(); } } this.nrows += 1; } this.plainWeave = function(yarn) { this.weave(yarn); } this.twill = function(yarn, width) { this.weave(yarn, width || 2, width || 2); } this.satinWeave = function(yarn) { // 1/16, skip 4 this.weave(yarn, 1, 16, 4); } this.basketWeave = function(yarn) { // 2/2, skip 1, repeat 1 this.weave(yarn, 2, 2, 1, 1); } this.plaid = function() { for (var i = 0; i*ctx.lineWidth < height; i += 1) { var j = i % warp.length; this.twill(warp[j]); } } } var sett_re = /([A-Za-z]{1,3}|\([A-Fa-f0-9]{3}\))(\d{1,3})/; function settOfString(str) { var sett = [] while (str.length > 0) { var result = sett_re.exec(str); if (result == null) { break } var color; var cs = result[1]; if (cs[0] == "(") { color = "#" + cs.substr(1, cs.length-2); } else { color = colors[cs]; } for (var i = 0; i < result[2]; i += 1) { sett.push(color); } str = str.substr(result[0].length + result.index); } if (str[str.length - 1] != ".") { var ttes = sett.concat(); ttes.reverse(); sett = sett.concat(ttes); } if (sett.length % 4 != 0) { sett = sett.concat(sett); } return sett; } function weave(pattern, width) { var canvas = document.getElementById("loom"); var ctx = canvas.getContext("2d"); ctx.clearRect(0, 0, canvas.width, canvas.height); var sett = settOfString(pattern); var x = width * sett.length * 2; canvas.width = x; canvas.height = x; var l = new Loom(ctx, sett, width); l.plaid(); } function update(reset) { var settInput = document.getElementById("sett"); weave(settInput.value, yarnWeight); if (reset) { var s = encodeURIComponent(settInput.value).replace(/%20/g, "+"); window.location.hash = "s=" + s; document.getElementById("preset").value = "Presets:"; document.getElementById("desc").innerText = ""; } } function preset() { var presetName = document.getElementById("preset").value; var tartan = tartans[presetName]; if (! tartan) { var keys = Object.keys(tartans); presetName = keys[Math.floor(Math.random() * keys.length)]; tartan = tartans[presetName]; document.getElementById("preset").value = presetName; } document.getElementById("sett").value = tartan["sett"]; document.getElementById("desc").innerText = tartan["description"]; window.location.hash = "p=" + encodeURIComponent(presetName).replace(/%20/g, "+"); update(); } // Build up query string var qs = {}; if (window.location.hash) { parts = window.location.hash.substr(1).split("&"); for (var i in parts) { var p = parts[i].split("=", 2); if (p.length == 1) { qs[p[0]] = ""; } else { qs[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); } } } function init() { document.getElementById("sett").addEventListener("input", update); if (qs["s"]) { document.getElementById("sett").value = qs["s"]; } var presetName = qs["p"]; var presetInput = document.getElementById("preset") presetInput.addEventListener("change", preset); for (var name in tartans) { var opt = document.createElement("option"); opt.text = name; if (name == presetName) { opt.selected = true; } presetInput.appendChild(opt); } if (presetName) { preset(); } else if (qs["s"]) { update(); } else { preset(); } } window.addEventListener("load", init);