Skip to content

Instantly share code, notes, and snippets.

@veltman
Last active January 16, 2020 10:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save veltman/20a3f4e81d9d54ab5547 to your computer and use it in GitHub Desktop.
Save veltman/20a3f4e81d9d54ab5547 to your computer and use it in GitHub Desktop.
Nintendo pixelation

Convert image to a somewhat NES-style image:

  1. Get the colors of each square of n pixels.
  2. Convert those colors from RGB to LAB and average them.
  3. Round that color to the closest color in the NES palette.
  4. Convert back to RGB and set all n pixels to that color.
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.10/d3.min.js"></script>
<script src="nes-palette.js"></script>
<script type="text/javascript">
var width = 480,
height = 640,
pixelSize = 4,
batchSize = pixelSize * 2,
imageData;
nesColors = nesColors.map(rgbToLab);
var ctx = d3.select("body").append("canvas")
.attr("width",width * 2)
.attr("height",height)
.node()
.getContext("2d");
var img = new Image();
var batches = [];
d3.range(0,width,batchSize).map(function(x){
d3.range(0,height,batchSize).map(function(y){
batches.push({x: x, y: y});
});
});
d3.shuffle(batches);
img.onload = function() {
ctx.drawImage(img,0,0,width,height);
imageData = ctx.getImageData(0,0,width,height);
ctx.putImageData(imageData,width,0);
setTimeout(processBatch,500);
};
img.src = "magritte.jpg";
d3.select(self.frameElement).style("height", height + "px");
// Process one batch of pixels
function processBatch() {
var batch = batches.pop();
d3.range(0,batchSize,pixelSize).forEach(function(x){
d3.range(0,batchSize,pixelSize).forEach(function(y){
processPixel(batch.x + x,batch.y + y);
});
});
// Repaint occasionally
if (batches.length % 12 === 1) {
ctx.putImageData(imageData,width,0);
requestAnimationFrame(processBatch);
} else if (batches.length) {
processBatch();
}
// Could do a second pass to re-round colors to most common results
}
// Average sub-pixels, update each one to the collective average
function processPixel(px,py) {
var indices = [];
d3.range(pixelSize).map(function(x){
d3.range(pixelSize).map(function(y){
indices.push(4 * ((py + y) * width + px + x));
});
});
var average = averageColor(indices.map(function(i){
return imageData.data.slice(i,i + 3);
}));
indices.forEach(function(i){
for (var j = 0; j < 4; j++) {
imageData.data[i + j] = average[j];
}
});
}
// Average colors, then round to nearest in palette
function averageColor(colors) {
var lab = colors.map(rgbToLab);
var average = d3.range(3).map(function(i){
return d3.mean(lab.map(function(color){
return color[i];
}));
});
var closest = closestPoint(average,nesColors);
var rgb = d3.lab.apply(null,closest).rgb();
return [rgb.r,rgb.g,rgb.b,255];
}
// Closest point from list - faster than sort?
function closestPoint(point,list) {
var minDistance = Infinity,
value;
list.forEach(function(p){
var distance = euclidean(point,p);
if (distance < minDistance) {
minDistance = distance;
value = p;
}
});
return value;
};
// Distance between n-dimensional points
function euclidean(a,b) {
var sum = 0;
a.forEach(function(val,i){
sum += Math.pow(b[i] - val,2);
});
return Math.sqrt(sum);
}
function rgbToLab(rgb) {
var lab = d3.lab(d3.rgb.apply(null,rgb));
return [lab.l,lab.a,lab.b];
}
</script>
var nesColors = [
[0,0,252],
[0,0,188],
[68,40,188],
[148,0,132],
[168,0,32],
[168,16,0],
[136,20,0],
[80,48,0],
[0,120,0],
[0,104,0],
[0,88,0],
[0,64,88],
[0,120,248],
[0,88,248],
[104,68,252],
[216,0,204],
[228,0,88],
[248,56,0],
[228,92,16],
[172,124,0],
[0,184,0],
[0,168,0],
[0,168,68],
[0,136,136],
[60,188,252],
[104,136,252],
[152,120,248],
[248,120,248],
[248,88,152],
[248,120,88],
[252,160,68],
[248,184,0],
[184,248,24],
[88,216,84],
[88,248,152],
[0,232,216],
[252,252,252],
[164,228,252],
[184,184,248],
[216,184,248],
[248,184,248],
[248,164,192],
[240,208,176],
[252,224,168],
[248,216,120],
[216,248,120],
[184,248,184],
[184,248,216],
[0,252,252],
[248,216,248],
[248,248,248],
[0,0,0]
];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment