homepage/content/tartans/weave.js

260 lines
6.4 KiB
JavaScript

// © 2015 Neale Pickett <neale@woozle.org>
//
// 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);