From cbe5a189dca1c228186fb1e433071578614c4e91 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 23 May 2016 01:07:46 -0400 Subject: genetic algorhehrithm --- js/color.js | 1 + js/ui/evolver.js | 199 ++++++++++++++++++++++++++++++++++++++++++++++++ js/ui/nopaint.js | 17 ++++- js/vendor/image-ssim.js | 129 +++++++++++++++++++++++++++++++ 4 files changed, 344 insertions(+), 2 deletions(-) create mode 100644 js/ui/evolver.js create mode 100644 js/vendor/image-ssim.js (limited to 'js') diff --git a/js/color.js b/js/color.js index 23fb13f..0ba29e7 100644 --- a/js/color.js +++ b/js/color.js @@ -33,6 +33,7 @@ function all_hue (n) { return colors[all_color_hue_order[mod(n, 16)|0]] } function all_inv_hue (n) { return colors[all_color_inv_order[mod(n, 16)|0]] } function hue (n) { return colors[color_hue_order[mod(n, 11)|0]] } function rand_hue () { return colors[color_hue_order[randint(11)]] } +function rand_gray () { return colors[gray_names[randint(4)]] } function inv_hue (n) { return colors[color_inv_order[mod(n, 11)|0]] } function gray (n) { return colors[gray_names[mod(n, 4)|0]] } function fire (n) { return colors[fire_names[mod(n, 7)|0]] } diff --git a/js/ui/evolver.js b/js/ui/evolver.js new file mode 100644 index 0000000..e65df20 --- /dev/null +++ b/js/ui/evolver.js @@ -0,0 +1,199 @@ +var evolver = (function(){ + + setTimeout(init) + + var hash = window.location.hash + var opt = { + src: "img/sistene-chapel.jpg", + w: 40, + h: 16, + } + if (hash.length) { + hash.split("#").forEach(function(s){ + var kv = s.split("=") + if (opt.hasOwnProperty(kv[0])) { + opt[kv[0]] = kv[1] + } + }) + } + + var target, last_score = 0 + var clones = [], clone_count = 10, strokes_per_iteration = 1 + var clone_index = 0 + var main_canvas + + var compare_w = 64, compare_h = 64 + + var target = document.createElement('canvas') + target.width = compare_w + target.height = compare_h + var t_ctx = target.getContext('2d') + + var compare = document.createElement('canvas') + compare.width = compare_w + compare.height = compare_h + var c_ctx = compare.getContext('2d') + + + brush.rebuild = function(){ + this.initialize() + } + + function init () { + nopaint.debug = false + + main_canvas = canvas + canvas.resize(opt.w, opt.h) +// canvas.forEach(function(lex,x,y){ +// lex.bg = randint(16) +// }) + for (var i = 0; i < clone_count; i++) { + clones[i] = { + el: document.createElement("div"), + canvas: canvas.clone(), + score: 0, + hash: -1, + } +// clones[i].canvas.append(clones[i].el) +// clones[i].el.className = "rapper" +// document.getElementById("clones").appendChild( clones[i].el ) + clones[i].canvas.forEach(function(lex){ + lex.build = noop + }) + } + load(opt.src, go) + } + function load(src, fn){ + var img = new Image () + img.onload = function(){ + target = drawImage(t_ctx, img) + last_score = 0 + go() + } + if (opt.src.match(/^http/)) { + opt.src = "http://asdf.us/cgi-bin/proxy?" + opt.src + } + img.src = src + } + function drawImage (ctx, img) { + ctx.drawImage(img, 0, 0, compare_w, compare_h) + return { width: compare_w, height: compare_h, data: ctx.getImageData(0,0,compare_w,compare_h).data, channels: 4 } + } + function go () { + if (evolver.paused) return + clone_index = 0 + paint_next() + } + + function paint_next () { + canvas = clones[clone_index].canvas + for (var i = 0; i < strokes_per_iteration; i++) { + nopaint.paint() + } + render(canvas, function(c){ + compare = drawImage(c_ctx, c) + + clones[clone_index].score = ImageSSIM.compare(target, compare).ssim + if (++clone_index == clone_count) { + fitness() + changed = false + requestAnimationFrame(go) + } + else { + paint_next() + } + }) + } + function fitness () { + clones.sort(function(a,b){ return b.score - a.score }) + + var best_clone, next_best_clone, third_best_clone + var clones_to_keep + + var max_score = clones[0].score + +// console.log(clones.filter(function(c,i){ return i < 10 }).map(function(c){ return c.score.toFixed(2) }).join(" ")) + + if (max_score < last_score) { + console.log("no improvement [%s] [%s]", max_score.toFixed(3), last_score.toFixed(3)) + clones_to_keep = 2 + best_clone = main_canvas + next_best_clone = clones[0].canvas + third_best_clone = clones[1].canvas + } + else { + last_score = max_score + + clones_to_keep = 3 + best_clone = clones[0].canvas + next_best_clone = clones[1].canvas + third_best_clone = clones[2].canvas + + console.log("top clone [%s]", max_score.toFixed(5)) + main_canvas.forEach(function(lex,x,y){ + lex.assign( best_clone.getCell(x,y) ) + }) + } + + var best_clone = clones[0].canvas + clones.forEach(function(clone, i){ + if (i < clones_to_keep) return + var clone_to_copy + if (i < clones.length / 4) { + clone_to_copy = third_best_clone + } + else if (i < clones.length / 2) { + clone_to_copy = next_best_clone + } + else { + clone_to_copy = best_clone + } + clone.canvas.forEach(function(lex,x,y) { + lex.assign( clone_to_copy.getCell(x,y) ) + }) + }) + + } + +/* + function check () { + clipboard.export_canvas(function(canvas){ + var hash = simi.hash(canvas) + var score = simi.compare(hash, target) + if (score > last_score) { + last_score = score + console.log(score.toFixed(3), "PAINT") + nopaint.paint() + } + else { + console.log(score.toFixed(3), "NO") + nopaint.no() + } + requestAnimationFrame(check) + }) + } +*/ + + var buffer_canvas = document.createElement('canvas') + + function render (canvas, done_fn) { + var opts = { + palette: 'mirc', + font: 'fixedsys_8x15', + fg: 0, + bg: 1, + canvas: buffer_canvas + } + opts.done = function(c){ + done_fn(c) + } + colorcode.to_canvas(canvas.mirc(), opts) + } + + return { + paused: false, + pause: function(){ + evolver.paused = ! evolver.paused + } + } +})() \ No newline at end of file diff --git a/js/ui/nopaint.js b/js/ui/nopaint.js index bf94bdc..d3741a9 100644 --- a/js/ui/nopaint.js +++ b/js/ui/nopaint.js @@ -33,6 +33,7 @@ var nopaint = (function(){ oktween.raf = function(){} var nopaint = {} + nopaint.debug = true nopaint.delay = nopaint.normal_delay = 100 nopaint.turbo_delay = 0 nopaint.tool = null @@ -83,7 +84,7 @@ var nopaint = (function(){ last_tool && last_tool.finish() nopaint.tool = nopaint.get_random_tool( last_tool ) nopaint.tool.start( last_tool ) - console.log("> %s", nopaint.tool.type) + nopaint.debug && console.log("> %s", nopaint.tool.type) } nopaint.add_tool = function(fn){ nopaint.tools[fn.type] = fn @@ -143,6 +144,9 @@ var nopaint = (function(){ upload_interval: 100, step: 0, timeout: null, + delay: function(){ + return nopaint.is_turbo ? randrange(150, 300) : randrange(400, 800) + }, reset: function(){ this.no_count = 0 this.paint_count = 0 @@ -153,7 +157,7 @@ var nopaint = (function(){ }, play: function(){ clearTimeout(this.timeout) - var delay = nopaint.is_turbo ? randrange(150, 300) : randrange(400, 800) + var delay = this.delay() this.timeout = setTimeout(this.play.bind(this), delay) this.check_fitness() this.step += 1 @@ -451,6 +455,14 @@ var nopaint = (function(){ }, }) + var GrayBrush = SolidBrush.extend({ + type: "hue", + recolor: function(){ + this.fg = this.bg = rand_gray() + this.char = " " + }, + }) + var LetterBrush = SolidBrush.extend({ type: "letter", recolor: function(){ @@ -742,6 +754,7 @@ var nopaint = (function(){ nopaint.add_tool( new EraseBrush({ weight: 5 }) ) nopaint.add_tool( new RandomBrush({ weight: 4 }) ) nopaint.add_tool( new HueBrush({ weight: 5 }) ) + nopaint.add_tool( new GrayBrush({ weight: 5 }) ) nopaint.add_tool( new LetterBrush({ weight: 2 }) ) nopaint.add_tool( new RandomLetterBrush({ weight: 12 }) ) nopaint.add_tool( new CloneBrush({ weight: 8 }) ) diff --git a/js/vendor/image-ssim.js b/js/vendor/image-ssim.js new file mode 100644 index 0000000..e1f07b4 --- /dev/null +++ b/js/vendor/image-ssim.js @@ -0,0 +1,129 @@ +var ImageSSIM; +(function (ImageSSIM) { + 'use strict'; + /** + * Grey = 1, GreyAlpha = 2, RGB = 3, RGBAlpha = 4 + */ + (function (Channels) { + Channels[Channels["Grey"] = 1] = "Grey"; + Channels[Channels["GreyAlpha"] = 2] = "GreyAlpha"; + Channels[Channels["RGB"] = 3] = "RGB"; + Channels[Channels["RGBAlpha"] = 4] = "RGBAlpha"; + })(ImageSSIM.Channels || (ImageSSIM.Channels = {})); + var Channels = ImageSSIM.Channels; + /** + * Entry point. + * @throws new Error('Images have different sizes!') + */ + function compare(image1, image2, windowSize, K1, K2, luminance, bitsPerComponent) { + if (windowSize === void 0) { windowSize = 8; } + if (K1 === void 0) { K1 = 0.01; } + if (K2 === void 0) { K2 = 0.03; } + if (luminance === void 0) { luminance = true; } + if (bitsPerComponent === void 0) { bitsPerComponent = 8; } + if (image1.width !== image2.width || image1.height !== image2.height) { + throw new Error('Images have different sizes!'); + } + /* tslint:disable:no-bitwise */ + var L = (1 << bitsPerComponent) - 1; + /* tslint:enable:no-bitwise */ + var c1 = Math.pow((K1 * L), 2), c2 = Math.pow((K2 * L), 2), numWindows = 0, mssim = 0.0; + var mcs = 0.0; + function iteration(lumaValues1, lumaValues2, averageLumaValue1, averageLumaValue2) { + // calculate variance and covariance + var sigxy, sigsqx, sigsqy; + sigxy = sigsqx = sigsqy = 0.0; + for (var i = 0; i < lumaValues1.length; i++) { + sigsqx += Math.pow((lumaValues1[i] - averageLumaValue1), 2); + sigsqy += Math.pow((lumaValues2[i] - averageLumaValue2), 2); + sigxy += (lumaValues1[i] - averageLumaValue1) * (lumaValues2[i] - averageLumaValue2); + } + var numPixelsInWin = lumaValues1.length - 1; + sigsqx /= numPixelsInWin; + sigsqy /= numPixelsInWin; + sigxy /= numPixelsInWin; + // perform ssim calculation on window + var numerator = (2 * averageLumaValue1 * averageLumaValue2 + c1) * (2 * sigxy + c2); + var denominator = (Math.pow(averageLumaValue1, 2) + Math.pow(averageLumaValue2, 2) + c1) * (sigsqx + sigsqy + c2); + mssim += numerator / denominator; + mcs += (2 * sigxy + c2) / (sigsqx + sigsqy + c2); + numWindows++; + } + // calculate SSIM for each window + Internals._iterate(image1, image2, windowSize, luminance, iteration); + return { ssim: mssim / numWindows, mcs: mcs / numWindows }; + } + ImageSSIM.compare = compare; + /** + * Internal functions. + */ + var Internals; + (function (Internals) { + function _iterate(image1, image2, windowSize, luminance, callback) { + var width = image1.width, height = image1.height; + for (var y = 0; y < height; y += windowSize) { + for (var x = 0; x < width; x += windowSize) { + // avoid out-of-width/height + var windowWidth = Math.min(windowSize, width - x), windowHeight = Math.min(windowSize, height - y); + var lumaValues1 = _lumaValuesForWindow(image1, x, y, windowWidth, windowHeight, luminance), lumaValues2 = _lumaValuesForWindow(image2, x, y, windowWidth, windowHeight, luminance), averageLuma1 = _averageLuma(lumaValues1), averageLuma2 = _averageLuma(lumaValues2); + callback(lumaValues1, lumaValues2, averageLuma1, averageLuma2); + } + } + } + Internals._iterate = _iterate; + function _lumaValuesForWindow(image, x, y, width, height, luminance) { + var array = image.data, lumaValues = new Float32Array(new ArrayBuffer(width * height * 4)), counter = 0; + var maxj = y + height; + for (var j = y; j < maxj; j++) { + var offset = j * image.width; + var i = (offset + x) * image.channels; + var maxi = (offset + x + width) * image.channels; + switch (image.channels) { + case 1 /* Grey */: + while (i < maxi) { + // (0.212655 + 0.715158 + 0.072187) === 1 + lumaValues[counter++] = array[i++]; + } + break; + case 2 /* GreyAlpha */: + while (i < maxi) { + lumaValues[counter++] = array[i++] * (array[i++] / 255); + } + break; + case 3 /* RGB */: + if (luminance) { + while (i < maxi) { + lumaValues[counter++] = (array[i++] * 0.212655 + array[i++] * 0.715158 + array[i++] * 0.072187); + } + } + else { + while (i < maxi) { + lumaValues[counter++] = (array[i++] + array[i++] + array[i++]); + } + } + break; + case 4 /* RGBAlpha */: + if (luminance) { + while (i < maxi) { + lumaValues[counter++] = (array[i++] * 0.212655 + array[i++] * 0.715158 + array[i++] * 0.072187) * (array[i++] / 255); + } + } + else { + while (i < maxi) { + lumaValues[counter++] = (array[i++] + array[i++] + array[i++]) * (array[i++] / 255); + } + } + break; + } + } + return lumaValues; + } + function _averageLuma(lumaValues) { + var sumLuma = 0.0; + for (var i = 0; i < lumaValues.length; i++) { + sumLuma += lumaValues[i]; + } + return sumLuma / lumaValues.length; + } + })(Internals || (Internals = {})); +})(ImageSSIM || (ImageSSIM = {})); \ No newline at end of file -- cgit v1.2.3-70-g09d2