Created
October 29, 2019 02:05
-
-
Save mgd020/a6ab84904c365b31c35df5262dc57dfb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var image_nes_filter = (function() { | |
var PIXEL_SIZE = 4; | |
dither = (function() { | |
// Floyd–Steinberg dither | |
var ERROR_DIFFUSION_1_16 = 1 / 16, | |
ERROR_DIFFUSION_3_16 = 3 / 16, | |
ERROR_DIFFUSION_5_16 = 5 / 16, | |
ERROR_DIFFUSION_7_16 = 7 / 16, | |
RED = 0, | |
GREEN = 1, | |
BLUE = 2, | |
ALPHA = 3, | |
CHANNELS = 4; | |
return function (imageData, clamp_color) { | |
var W_CHANNELS = imageData.width * CHANNELS, | |
data = imageData.data, | |
error = new Int8Array(data.length); | |
function clamp_error(color) { | |
var new_color = clamp_color(color), | |
error = [ | |
color[RED] - new_color[RED], | |
color[GREEN] - new_color[GREEN], | |
color[BLUE] - new_color[BLUE], | |
color[ALPHA] - new_color[ALPHA] | |
]; | |
return [new_color, error]; | |
} | |
for (var i = 0; i < data.length; i += CHANNELS) { | |
var r = i + RED, | |
g = i + GREEN, | |
b = i + BLUE, | |
a = i + ALPHA, | |
rgba_color_error = clamp_error([ | |
data[r] + error[r], | |
data[g] + error[g], | |
data[b] + error[b], | |
data[a] + error[a] | |
]), | |
i_right = i + CHANNELS, | |
i_down = i + W_CHANNELS, | |
i_down_left = i_down - CHANNELS, | |
i_down_right = i_down + CHANNELS, | |
rgba = rgba_color_error[0], | |
rgba_error = rgba_color_error[1], | |
red_error = rgba_error[RED], | |
green_error = rgba_error[GREEN], | |
blue_error = rgba_error[BLUE], | |
alpha_error = rgba_error[ALPHA]; | |
// update color channels | |
data[r] = rgba[RED]; | |
data[g] = rgba[GREEN]; | |
data[b] = rgba[BLUE]; | |
data[a] = rgba[ALPHA]; | |
// distribute errors | |
if (i_down > data.length) { | |
// last row (no down) | |
i_down_right = i_down_left = i_down = null; | |
} else if (i % W_CHANNELS === 0) { | |
// first column (no left) | |
i_down_left = null; | |
} else if (i_down_right % W_CHANNELS === 0) { | |
// last column (no right) | |
i_down_right = i_right = null; | |
} | |
if (i_right) { | |
error[i_right + RED] += red_error * ERROR_DIFFUSION_7_16; | |
error[i_right + GREEN] += green_error * ERROR_DIFFUSION_7_16; | |
error[i_right + BLUE] += blue_error * ERROR_DIFFUSION_7_16; | |
error[i_right + ALPHA] += alpha_error * ERROR_DIFFUSION_7_16; | |
} | |
if (i_down_left) { | |
error[i_down_left + RED] += red_error * ERROR_DIFFUSION_3_16; | |
error[i_down_left + GREEN] += green_error * ERROR_DIFFUSION_3_16; | |
error[i_down_left + BLUE] += blue_error * ERROR_DIFFUSION_3_16; | |
error[i_down_left + ALPHA] += alpha_error * ERROR_DIFFUSION_3_16; | |
} | |
if (i_down) { | |
error[i_down + RED] += red_error * ERROR_DIFFUSION_5_16; | |
error[i_down + GREEN] += green_error * ERROR_DIFFUSION_5_16; | |
error[i_down + BLUE] += blue_error * ERROR_DIFFUSION_5_16; | |
error[i_down + ALPHA] += alpha_error * ERROR_DIFFUSION_5_16; | |
} | |
if (i_down_right) { | |
error[i_down_right + RED] += red_error * ERROR_DIFFUSION_1_16; | |
error[i_down_right + GREEN] += green_error * ERROR_DIFFUSION_1_16; | |
error[i_down_right + BLUE] += blue_error * ERROR_DIFFUSION_1_16; | |
error[i_down_right + ALPHA] += alpha_error * ERROR_DIFFUSION_1_16; | |
} | |
} | |
} | |
})(); | |
var get_closest_nes_color = (function() { | |
// https://wiki.nesdev.com/w/index.php/PPU_palettes#2C02 | |
var nes_colors = [ | |
[0, 0, 0], | |
[0, 102, 120], | |
[0, 118, 40], | |
[0, 30, 116], | |
[0, 50, 60], | |
[0, 60, 0], | |
[0, 64, 0], | |
[116, 196, 0], | |
[120, 124, 236], | |
[120, 60, 0], | |
[136, 20, 176], | |
[152, 150, 152], | |
[152, 226, 180], | |
[152, 34, 32], | |
[160, 162, 160], | |
[160, 170, 0], | |
[160, 20, 100], | |
[160, 214, 228], | |
[168, 204, 236], | |
[168, 226, 144], | |
[176, 98, 236], | |
[180, 222, 120], | |
[188, 188, 236], | |
[204, 210, 120], | |
[212, 136, 32], | |
[212, 178, 236], | |
[228, 196, 144], | |
[228, 84, 236], | |
[236, 106, 100], | |
[236, 174, 212], | |
[236, 174, 236], | |
[236, 180, 176], | |
[236, 238, 236], | |
[236, 88, 180], | |
[32, 42, 0], | |
[40, 114, 0], | |
[48, 0, 136], | |
[48, 50, 236], | |
[56, 180, 204], | |
[56, 204, 108], | |
[60, 24, 0], | |
[60, 60, 60], | |
[68, 0, 100], | |
[76, 154, 236], | |
[76, 208, 32], | |
[8, 124, 0], | |
[8, 16, 144], | |
[8, 58, 0], | |
[8, 76, 196], | |
[84, 4, 0], | |
[84, 84, 84], | |
[84, 90, 0], | |
[92, 0, 48], | |
[92, 30, 228] | |
]; | |
var cache = {}; | |
function distance(p1, p2) { | |
// euclidean distance (squared) | |
// in order for this to be better, we should convert colors to CIE LAB | |
var p_0=p2[0] - p1[0], p_1=p2[1] - p1[1], p_2=p2[2] - p1[2]; | |
return p_0*p_0 + p_1*p_1 + p_2*p_2; | |
} | |
function find(rgb) { | |
var min_distance = null; | |
var min_value = null; | |
for (var i = 0; i < nes_colors.length; ++i) { | |
var nes = nes_colors[i]; | |
var d = distance(rgb, nes); | |
if (min_distance === null || d < min_distance) { | |
min_distance = d; | |
min_value = nes; | |
} | |
} | |
return min_value; | |
} | |
return function(rgb) { | |
var val = cache[rgb] || find(rgb); | |
cache[rgb] = val; | |
return val; | |
} | |
})(); | |
function nes_color(color) { | |
var rgb = get_closest_nes_color([color[0], color[1], color[2]]); | |
return [rgb[0], rgb[1], rgb[2], (color[3] >> 6) * 85]; | |
} | |
function clamp(color) { | |
return [ | |
(color[0] >> 6) * 85, | |
(color[1] >> 6) * 85, | |
(color[2] >> 6) * 85, | |
(color[3] >> 6) * 85, | |
]; | |
} | |
return function (img) { | |
var w = img.offsetWidth / PIXEL_SIZE | 0, | |
h = img.offsetHeight / PIXEL_SIZE | 0, | |
canvas = document.createElement('canvas'); | |
// set canvas size | |
canvas.width = w * PIXEL_SIZE; | |
canvas.height = h * PIXEL_SIZE; | |
// get context | |
var context = canvas.getContext('2d'); | |
// draw scaled down | |
context.drawImage(img, 0, 0, w, h); | |
// dither and clamp colors | |
var imageData = context.getImageData(0, 0, w, h); | |
dither(imageData, nes_color); | |
context.putImageData(imageData, 0, 0); | |
// draw scaled up (no antialiasing) | |
context.msImageSmoothingEnabled = false; | |
context.mozImageSmoothingEnabled = false; | |
context.webkitImageSmoothingEnabled = false; | |
context.imageSmoothingEnabled = false; | |
context.drawImage(canvas, 0, 0, w, h, 0, 0, canvas.width, canvas.height); | |
// update element | |
img.src = canvas.toDataURL("image/png"); | |
}; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment