|
<!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> |