commit f5658211305f8e2f4b4624beea0fb53901cad7fe Author: ben-burlingham Date: Sun Nov 1 15:54:22 2015 -0800 Migration to GOGS repo. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..7bad6db --- /dev/null +++ b/css/style.css @@ -0,0 +1,25 @@ +.problem-description { + background:#f5f2f0; + font-family:consolas; + line-height:24px; + padding:20px; +} + +.results-matrix { + font-size:0; + text-align:center; +} + +.results-matrix img { + border:3px solid #414141; + vertical-align:middle; + width: 100%; +} + +.results-matrix a { + display:inline-block; + line-height:125px; + margin:5px 2px; + vertical-align:top; + width:110px; +} diff --git a/img/gothic-mona.png b/img/gothic-mona.png new file mode 100644 index 0000000..73940ef Binary files /dev/null and b/img/gothic-mona.png differ diff --git a/img/gothic-scream.png b/img/gothic-scream.png new file mode 100644 index 0000000..3049a06 Binary files /dev/null and b/img/gothic-scream.png differ diff --git a/img/gothic-spheres.png b/img/gothic-spheres.png new file mode 100644 index 0000000..29d9038 Binary files /dev/null and b/img/gothic-spheres.png differ diff --git a/img/gothic-starry.png b/img/gothic-starry.png new file mode 100644 index 0000000..6d1dcec Binary files /dev/null and b/img/gothic-starry.png differ diff --git a/img/gothic-stream.png b/img/gothic-stream.png new file mode 100644 index 0000000..10fef91 Binary files /dev/null and b/img/gothic-stream.png differ diff --git a/img/gothic.png b/img/gothic.png new file mode 100644 index 0000000..c8cfb08 Binary files /dev/null and b/img/gothic.png differ diff --git a/img/mona-gothic.png b/img/mona-gothic.png new file mode 100644 index 0000000..02db032 Binary files /dev/null and b/img/mona-gothic.png differ diff --git a/img/mona-scream.png b/img/mona-scream.png new file mode 100644 index 0000000..67d1a33 Binary files /dev/null and b/img/mona-scream.png differ diff --git a/img/mona-spheres.png b/img/mona-spheres.png new file mode 100644 index 0000000..c89d6c2 Binary files /dev/null and b/img/mona-spheres.png differ diff --git a/img/mona-starry.png b/img/mona-starry.png new file mode 100644 index 0000000..e178d47 Binary files /dev/null and b/img/mona-starry.png differ diff --git a/img/mona-stream.png b/img/mona-stream.png new file mode 100644 index 0000000..3f7020c Binary files /dev/null and b/img/mona-stream.png differ diff --git a/img/mona.png b/img/mona.png new file mode 100644 index 0000000..41cb75d Binary files /dev/null and b/img/mona.png differ diff --git a/img/noise-gothic.png b/img/noise-gothic.png new file mode 100644 index 0000000..5d40a03 Binary files /dev/null and b/img/noise-gothic.png differ diff --git a/img/noise-scream.png b/img/noise-scream.png new file mode 100644 index 0000000..b6b4f09 Binary files /dev/null and b/img/noise-scream.png differ diff --git a/img/noise-spheres.png b/img/noise-spheres.png new file mode 100644 index 0000000..d4d5751 Binary files /dev/null and b/img/noise-spheres.png differ diff --git a/img/noise-starry.png b/img/noise-starry.png new file mode 100644 index 0000000..ec440f6 Binary files /dev/null and b/img/noise-starry.png differ diff --git a/img/noise-stream.png b/img/noise-stream.png new file mode 100644 index 0000000..903a142 Binary files /dev/null and b/img/noise-stream.png differ diff --git a/img/noise.png b/img/noise.png new file mode 100644 index 0000000..0771edb Binary files /dev/null and b/img/noise.png differ diff --git a/img/scream-gothic.png b/img/scream-gothic.png new file mode 100644 index 0000000..b6f49bb Binary files /dev/null and b/img/scream-gothic.png differ diff --git a/img/scream-mona.png b/img/scream-mona.png new file mode 100644 index 0000000..9477b47 Binary files /dev/null and b/img/scream-mona.png differ diff --git a/img/scream-spheres.png b/img/scream-spheres.png new file mode 100644 index 0000000..b728e3f Binary files /dev/null and b/img/scream-spheres.png differ diff --git a/img/scream-starry.png b/img/scream-starry.png new file mode 100644 index 0000000..1db9ae0 Binary files /dev/null and b/img/scream-starry.png differ diff --git a/img/scream-stream.png b/img/scream-stream.png new file mode 100644 index 0000000..ca86444 Binary files /dev/null and b/img/scream-stream.png differ diff --git a/img/scream.png b/img/scream.png new file mode 100644 index 0000000..1f5d0ee Binary files /dev/null and b/img/scream.png differ diff --git a/img/spheres-gothic.png b/img/spheres-gothic.png new file mode 100644 index 0000000..898a8d1 Binary files /dev/null and b/img/spheres-gothic.png differ diff --git a/img/spheres-mona.png b/img/spheres-mona.png new file mode 100644 index 0000000..fdc8580 Binary files /dev/null and b/img/spheres-mona.png differ diff --git a/img/spheres-scream.png b/img/spheres-scream.png new file mode 100644 index 0000000..20fce5a Binary files /dev/null and b/img/spheres-scream.png differ diff --git a/img/spheres-starry.png b/img/spheres-starry.png new file mode 100644 index 0000000..1a03052 Binary files /dev/null and b/img/spheres-starry.png differ diff --git a/img/spheres-stream.png b/img/spheres-stream.png new file mode 100644 index 0000000..2edf24b Binary files /dev/null and b/img/spheres-stream.png differ diff --git a/img/spheres.png b/img/spheres.png new file mode 100644 index 0000000..3bfd739 Binary files /dev/null and b/img/spheres.png differ diff --git a/img/starry-gothic.png b/img/starry-gothic.png new file mode 100644 index 0000000..9214b40 Binary files /dev/null and b/img/starry-gothic.png differ diff --git a/img/starry-mona.png b/img/starry-mona.png new file mode 100644 index 0000000..1279e3c Binary files /dev/null and b/img/starry-mona.png differ diff --git a/img/starry-scream.png b/img/starry-scream.png new file mode 100644 index 0000000..bb717e4 Binary files /dev/null and b/img/starry-scream.png differ diff --git a/img/starry-spheres.png b/img/starry-spheres.png new file mode 100644 index 0000000..982c3b6 Binary files /dev/null and b/img/starry-spheres.png differ diff --git a/img/starry-stream.png b/img/starry-stream.png new file mode 100644 index 0000000..a7651bc Binary files /dev/null and b/img/starry-stream.png differ diff --git a/img/starry.png b/img/starry.png new file mode 100644 index 0000000..2195aa0 Binary files /dev/null and b/img/starry.png differ diff --git a/img/stream-gothic.png b/img/stream-gothic.png new file mode 100644 index 0000000..70d816b Binary files /dev/null and b/img/stream-gothic.png differ diff --git a/img/stream-mona.png b/img/stream-mona.png new file mode 100644 index 0000000..0d0f646 Binary files /dev/null and b/img/stream-mona.png differ diff --git a/img/stream-scream.png b/img/stream-scream.png new file mode 100644 index 0000000..f7dbb02 Binary files /dev/null and b/img/stream-scream.png differ diff --git a/img/stream-spheres.png b/img/stream-spheres.png new file mode 100644 index 0000000..9e29952 Binary files /dev/null and b/img/stream-spheres.png differ diff --git a/img/stream-starry.png b/img/stream-starry.png new file mode 100644 index 0000000..b728e68 Binary files /dev/null and b/img/stream-starry.png differ diff --git a/img/stream.png b/img/stream.png new file mode 100644 index 0000000..0e79ae3 Binary files /dev/null and b/img/stream.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..9441066 --- /dev/null +++ b/index.html @@ -0,0 +1,377 @@ + + + + + D3 + + + + + + + +

Pixel Palettes

+ + + +Tried my hand at this interesting challenge: + +

+ +You are given two true color images, the Source and the Palette. They do not necessarily have the same dimensions but it is guaranteed that their areas are the same, i.e. they have the same number of pixels. + +

+ +Your task is to create an algorithm that makes the most accurate looking copy of the Source by only using the pixels in the Palette. Each pixel in the Palette must be used exactly once in a unique position in this copy. The copy must have the same dimensions as the Source. +

+ +

+My solution is not everything I wished, but it was a fun exercise. +

+ +

+The code basically takes a random pixel and uses a binary search to find a match in the target palette, weighted for the red component. Here are the results for that approach. +

+ +

Results

+ +

+ Images are links, diagonals are originals. +

+ +
+ + Gothic original + + + Gothic -> Mona Lisa + + + Gothic -> Scream + + + Gothic -> Spheres + + + Gothic -> Starry Night + + + Gothic -> Stream + + + + Mona -> Gothic + + + Mona Lisa original + + + Mona -> Scream + + + Mona -> Spheres + + + Mona -> Starry Night + + + Mona -> Stream + + + + Scream -> Gothic + + + Scream -> Mona Lisa + + + Scream original + + + Scream -> Spheres + + + Scream -> Starry Night + + + Scream -> Stream + + + + Spheres -> Gothic + + + Spheres -> Mona Lisa + + + Spheres -> Scream + + + Spheres original + + + Spheres -> Starry Night + + + Spheres -> Stream + + + + Starry -> Gothic + + + Starry -> Mona Lisa + + + Starry -> Scream + + + Starry -> Spheres + + + Starry Night original + + + Starry -> Stream + + + + Stream -> Gothic + + + Stream -> Mona Lisa + + + Stream -> Scream + + + Stream -> Spheres + + + Stream -> Starry Night + + + Stream original + +
+ + +

Source code

+ +
var startTime = new Date().getTime();
+
+var fs = require("fs");
+var pngjs = require("pngjs").PNG;
+
+/**
+ *
+ */
+var Pixels = function() {};
+
+/**
+ *
+ */
+Pixels.prototype = {
+    source: null,
+    confirm: null,
+    target: null,
+    result: {},
+};
+
+/**
+ * Heavy lifting done here.
+ */
+Pixels.prototype.repalettize = function() {
+    // Indices is count from 0 to LxW, used to reference pixels in the tgtPalette, which are 8? bits wide.
+    var indices = [];
+    for (var y = 0; y < P.target.height; y++) {
+        for (var x = 0; x < P.target.width; x++) {
+            indices.push((P.target.width * y + x) << 2);
+        }
+    }
+
+    var len = indices.length;
+    P.result.asBuffer = new Buffer(P.target.height * P.target.width * 4);
+    P.result.asArray = [];
+
+    var i, ii;
+
+    while (len) {
+        i = Math.floor(Math.random() * len);
+        ii = indices[i];
+
+        // Find RGB in source, no need for alpha channel.
+        matchIndex = P.findMatch(
+            zeropad(P.target.asBuffer[ii], 3) +
+            zeropad(P.target.asBuffer[ii + 1], 3) +
+            zeropad(P.target.asBuffer[ii + 2], 3)
+        );
+
+        matchRgb = P.source.asArray[matchIndex];
+
+        P.result.asArray.push(matchRgb);
+
+        P.result.asBuffer[ii] = matchRgb.substr(0, 3) * 1;
+        P.result.asBuffer[ii + 1] = matchRgb.substr(3, 3) * 1;
+        P.result.asBuffer[ii + 2] = matchRgb.substr(6, 3) * 1;
+        P.result.asBuffer[ii + 3] = 255;
+
+        indices.splice(i, 1);
+        P.source.asArray.splice(matchIndex, 1);
+        len = indices.length;
+    }
+
+    var resultImg = new pngjs({
+        filterType: 4
+    });
+
+    resultImg.data = P.result.asBuffer;
+    resultImg.width = P.target.width;
+    resultImg.height = P.target.height;
+    resultImg.pack().pipe(fs.createWriteStream('result.png'));
+
+    var endTime = new Date().getTime();
+    console.log((endTime - startTime) / 1000 + " seconds, confirming");
+
+    P.doConfirm(P.confirm.asArray, P.result.asArray) ?
+        console.log('OK - Source array and result array match.') :
+        console.log('ERROR! Source array and result array do not match!');
+};
+
+/**
+ * Slightly modified binary search tree.
+ */
+Pixels.prototype.findMatch = function(rgb0) {
+    var start = 0;
+    var end = P.source.asArray.length;
+    var mid;
+
+    while (start + 1 < end) {
+        mid = Math.floor((end - start) / 2 + start);
+        if (P.source.asArray[mid] < rgb0) {
+            start = mid;
+        }
+        else {
+            end = mid;
+        }
+    }
+
+    return start;
+};
+
+/**
+ *
+ */
+Pixels.prototype.doConfirm = function(arr1, arr2) {
+    var len1 = arr1.length;
+    var len2 = arr2.length;
+
+    if (len1 !== len2) {
+        return false;
+    }
+
+    arr1.sort();
+    arr2.sort();
+
+    for (var i = 0; i < len1; i++) {
+        if (arr1[i] !== arr2[i]) {
+            return false;
+        }
+    }
+
+    return true;
+};
+
+/**
+ * Reads an image from a path, generates required information, and passes information to callback.
+ */
+Pixels.prototype.read = function(path0, callback0) {
+    var width = 0;
+    var height = 0;
+    var asBuffer = null;
+    var asArray = null;
+
+    fs.createReadStream(path0).pipe(new pngjs({ filterType: 4 }))
+    .on('metadata', function(meta0) {
+        width = meta0.width;
+        height = meta0.height;
+    })
+    .on('parsed', function(buffer0) {
+        var x, y, i;
+        var arr = [];
+        for (y = 0; y < height; y++) {
+            for (x = 0; x < width; x++) {
+                i = y * width + x << 2;
+
+                arr.push(zeropad(buffer0[i], 3) + zeropad(buffer0[i + 1], 3) + zeropad(buffer0[i + 2], 3));
+            }
+        }
+
+        callback0({
+            width: width,
+            height: height,
+            asBuffer: buffer0,
+            asArray: arr,
+        });
+    });
+};
+
+/**
+ * sprintf implementation to ensure 9-digit pixels for sorting.
+ */
+var zeropad = function(str0, len0) {
+    str0 = str0.toString();
+    while (str0.length < len0) {
+        str0 = "0" + str0;
+    }
+    return str0;
+};
+
+/**
+ * Ansynchronous file reads will execute and call this function. After they're all finished, the processing can begin.
+ */
+var filesRead = 0;
+var thenContinue = function(data0) {
+    filesRead++;
+
+    if (filesRead === 3) {
+        P.source.asArray.sort();
+        P.repalettize();
+    }
+}
+
+/**
+ * Information for the source image, where the pixels are taken from.
+ */
+var thenSaveSource = function(obj0) {
+    P.source = obj0;
+    thenContinue();
+}
+
+/**
+ * A copy of the source data used after processing to ensure source pixels match result pixels.
+ */
+var thenSaveConfirm = function(obj0) {
+    P.confirm = obj0;
+    thenContinue();
+}
+
+/**
+ * Information for the target images, which the pixels are matched to.
+ */
+var thenSaveTarget = function(obj0) {
+    P.target = obj0;
+    thenContinue();
+}
+
+//===== Entry point
+var P = new Pixels();
+P.read('scream.png', thenSaveSource);
+P.read('scream.png', thenSaveConfirm);
+P.read('starry.png', thenSaveTarget);
+
+ + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..bd7b4d7 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "name": "pixels", + "version": "1.0.0", + "devDependencies": { + "chalk": "^0.5.1", + "pngjs": "^0.4.0" + } +} diff --git a/pixels.js b/pixels.js new file mode 100644 index 0000000..671e4b3 --- /dev/null +++ b/pixels.js @@ -0,0 +1,211 @@ +var startTime = new Date().getTime(); + +var fs = require("fs"); +var pngjs = require("pngjs").PNG; + +/** + * + */ +var Pixels = function() {}; + +/** + * + */ +Pixels.prototype = { + source: null, + confirm: null, + target: null, + result: {}, +}; + +/** + * Heavy lifting done here. + */ +Pixels.prototype.repalettize = function() { + // Indices is count from 0 to LxW, used to reference pixels in the tgtPalette, which are 8? bits wide. + var indices = []; + for (var y = 0; y < P.target.height; y++) { + for (var x = 0; x < P.target.width; x++) { + indices.push((P.target.width * y + x) << 2); + } + } + + var len = indices.length; + P.result.asBuffer = new Buffer(P.target.height * P.target.width * 4); + P.result.asArray = []; + + var i, ii; + + while (len) { + i = Math.floor(Math.random() * len); + ii = indices[i]; + + // Find RGB in source, no need for alpha channel. + matchIndex = P.findMatch( + zeropad(P.target.asBuffer[ii], 3) + + zeropad(P.target.asBuffer[ii + 1], 3) + + zeropad(P.target.asBuffer[ii + 2], 3) + ); + + matchRgb = P.source.asArray[matchIndex]; + + P.result.asArray.push(matchRgb); + + P.result.asBuffer[ii] = matchRgb.substr(0, 3) * 1; + P.result.asBuffer[ii + 1] = matchRgb.substr(3, 3) * 1; + P.result.asBuffer[ii + 2] = matchRgb.substr(6, 3) * 1; + P.result.asBuffer[ii + 3] = 255; + + indices.splice(i, 1); + P.source.asArray.splice(matchIndex, 1); + len = indices.length; + } + + var resultImg = new pngjs({ + filterType: 4 + }); + + resultImg.data = P.result.asBuffer; + resultImg.width = P.target.width; + resultImg.height = P.target.height; + resultImg.pack().pipe(fs.createWriteStream('result.png')); + + var endTime = new Date().getTime(); + console.log((endTime - startTime) / 1000 + " seconds, confirming"); + + P.doConfirm(P.confirm.asArray, P.result.asArray) ? + console.log('OK - Source array and result array match.') : + console.log('ERROR! Source array and result array do not match!'); +}; + +/** + * Slightly modified binary search tree. + */ +Pixels.prototype.findMatch = function(rgb0) { + var start = 0; + var end = P.source.asArray.length; + var mid; + + while (start + 1 < end) { + mid = Math.floor((end - start) / 2 + start); + if (P.source.asArray[mid] < rgb0) { + start = mid; + } + else { + end = mid; + } + } + + return start; +}; + +/** + * + */ +Pixels.prototype.doConfirm = function(arr1, arr2) { + var len1 = arr1.length; + var len2 = arr2.length; + + if (len1 !== len2) { + return false; + } + + arr1.sort(); + arr2.sort(); + + for (var i = 0; i < len1; i++) { + if (arr1[i] !== arr2[i]) { + return false; + } + } + + return true; +}; + +/** + * Reads an image from a path, generates required information, and passes information to callback. + */ +Pixels.prototype.read = function(path0, callback0) { + var width = 0; + var height = 0; + var asBuffer = null; + var asArray = null; + + fs.createReadStream(path0).pipe(new pngjs({ filterType: 4 })) + .on('metadata', function(meta0) { + width = meta0.width; + height = meta0.height; + }) + .on('parsed', function(buffer0) { + var x, y, i; + var arr = []; + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + i = y * width + x << 2; + + arr.push(zeropad(buffer0[i], 3) + zeropad(buffer0[i + 1], 3) + zeropad(buffer0[i + 2], 3)); + } + } + + callback0({ + width: width, + height: height, + asBuffer: buffer0, + asArray: arr, + }); + }); +}; + +/** + * sprintf implementation to ensure 9-digit pixels for sorting. + */ +var zeropad = function(str0, len0) { + str0 = str0.toString(); + while (str0.length < len0) { + str0 = "0" + str0; + } + return str0; +}; + +/** + * Ansynchronous file reads will execute and call this function. After they're all finished, the processing can begin. + */ +var filesRead = 0; +var thenContinue = function(data0) { + filesRead++; + + if (filesRead === 3) { + P.source.asArray.sort(); + P.repalettize(); + } +} + +/** + * Information for the source image, where the pixels are taken from. + */ +var thenSaveSource = function(obj0) { + P.source = obj0; + thenContinue(); +} + +/** + * A copy of the source data used after processing to ensure source pixels match result pixels. + */ +var thenSaveConfirm = function(obj0) { + P.confirm = obj0; + thenContinue(); +} + +/** + * Information for the target images, which the pixels are matched to. + */ +var thenSaveTarget = function(obj0) { + P.target = obj0; + thenContinue(); +} + +//===== Entry point +var P = new Pixels(); +P.read('scream.png', thenSaveSource); +P.read('scream.png', thenSaveConfirm); +P.read('starry.png', thenSaveTarget);