|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<title>HSL Decomposition</title> |
|
<style> |
|
.images { |
|
margin-left: 75px; |
|
margin-right: 75px; |
|
width: 200px; |
|
float: left; |
|
} |
|
|
|
.chart { |
|
width: 500px; |
|
float: left; |
|
} |
|
|
|
.axis { |
|
font: 10px sans-serif; |
|
} |
|
|
|
.axis path, |
|
.axis line { |
|
fill: none; |
|
stroke: #000; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
ul { |
|
list-style-type: none; |
|
margin: 0; |
|
padding: 0; |
|
} |
|
|
|
li { |
|
display: inline; |
|
} |
|
|
|
img { |
|
cursor: pointer; |
|
border: 2px solid white; |
|
} |
|
|
|
.selected { |
|
border: 2px solid red; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
|
|
<div class="images"> |
|
<ul> |
|
<li><img src="van-gogh.jpg" alt="Van Gogh" class="selected" /></li> |
|
<li><img src="fragonard.jpg" alt="Fragonard" /></li> |
|
<li><img src="turner.jpg" alt="Turner" /></li> |
|
<li><img src="whistler.jpg" alt="Whistler"></li> |
|
<li><img src="da-vinci.jpg" alt="Da Vinci"></li> |
|
<li><img src="picasso.jpg" alt="Picasso"></li> |
|
<li><img src="rubens.jpg" alt="Rubens"></li> |
|
<li><img src="vermeer.jpg" alt="Vermeer"></li> |
|
<li><img src="lichtenstein.jpg" alt="Lichtenstein"></li> |
|
<li><img src="matisse.jpg" alt="Matisse"></li> |
|
</ul> |
|
</div> |
|
|
|
<div class="chart"></div> |
|
|
|
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script> |
|
<script> |
|
var margin = { top: 10, left: 50, bottom: 50, right: 10 }, |
|
width = 500 - margin.left - margin.right, |
|
height = 500 - margin.top - margin.bottom; |
|
|
|
var bins = { |
|
h: d3.scale.quantize().domain([0, 360]).range(d3.range(0, 1, 1/15)), |
|
s: d3.scale.quantize().domain([0, 1]).range(d3.range(0, 1, 1/15)), |
|
l: d3.scale.quantize().domain([0, 1]).range(d3.range(0, 1, 1/15)), |
|
}; |
|
|
|
var scale = { |
|
x: d3.scale.ordinal().domain(d3.range(0, 1, 1/15)).rangeRoundBands([0, width]), |
|
y: d3.scale.ordinal().domain(d3.range(0, 1, 1/15)).rangeRoundBands([height, 0]), |
|
size: d3.scale.pow().exponent(.25).range([0, 1]) |
|
}; |
|
|
|
var axis = { |
|
x: d3.svg.axis().scale(scale.x).orient("bottom").ticks(5).tickFormat(d3.format(".2f")), |
|
y: d3.svg.axis().scale(scale.y).orient("left").tickFormat(d3.format(".2f")) |
|
}; |
|
|
|
var svg = d3.select(".chart").append("svg") |
|
.attr("width", width + margin.left + margin.right) |
|
.attr("height", height + margin.top + margin.bottom) |
|
.append("g") |
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
|
|
|
svg.append("g") |
|
.attr("class", "x axis") |
|
.attr("transform", "translate(0," + height + ")") |
|
.call(axis.x) |
|
.append("text") |
|
.attr("x", width) |
|
.attr("y", -6) |
|
.style("text-anchor", "end") |
|
.text("Lightness"); |
|
|
|
svg.append("g") |
|
.attr("class", "y axis") |
|
.call(axis.y) |
|
.append("text") |
|
.attr("transform", "rotate(-90)") |
|
.attr("y", 6) |
|
.attr("dy", ".71em") |
|
.style("text-anchor", "end") |
|
.text("Saturation"); |
|
|
|
var images = d3.selectAll("img") |
|
.on("click", function() { |
|
images.classed("selected", false); |
|
var clicked = d3.select(this).classed("selected", true), |
|
url = clicked.attr("src"); |
|
decomposeImage(url); |
|
}); |
|
|
|
decomposeImage("van-gogh.jpg"); |
|
|
|
function decomposeImage(url) { |
|
getImageData(url, function(imageData) { |
|
svg.call(render, binHsl(imageData, bins)); |
|
}); |
|
} |
|
|
|
function render(selection, data) { |
|
scale.size.domain([0, d3.max(data, function(d) { return d.freq; })]); |
|
|
|
var treemap = d3.layout.treemap() |
|
.children(function(d) { return d.values; }) |
|
.value(function(d) { return d.freq; }) |
|
.sort(function(a,b) { return a.value - b.value; }); |
|
|
|
var treemaps = selection.selectAll(".treemap").data(data); |
|
|
|
treemaps.enter().append("g") |
|
.attr("class", "treemap"); |
|
|
|
treemaps |
|
.attr("transform", function(d) { |
|
var size = scale.size(d.freq), |
|
xShift = scale.x.rangeBand()*(1-size)/2, |
|
yShift = scale.y.rangeBand()*(1-size)/2; |
|
return "translate(" + (scale.x(d.l) + xShift) + "," + |
|
(scale.y(d.s) + yShift) + ")"; |
|
}); |
|
|
|
treemaps.exit().remove(); |
|
|
|
var rects = treemaps.selectAll("rect") |
|
.data(function(d) { |
|
var size = scale.size(d.freq), |
|
dx = scale.x.rangeBand(), |
|
dy = scale.y.rangeBand(); |
|
treemap = treemap |
|
.size([dx*size, dy*size]); |
|
return treemap.nodes(d); |
|
}); |
|
|
|
rects.enter().append("rect"); |
|
|
|
rects |
|
.filter(function(d) { return d.color !== undefined; }) |
|
.attr("x", function(d) { return d.x + d.dx/2; }) |
|
.attr("y", function(d) { return d.y + d.dy/2; }) |
|
.attr("width", 0) |
|
.attr("height", 0) |
|
.style("fill", "white") |
|
.transition().delay(function(d,i) { return d.s * d.l * 1000; }) |
|
.attr("x", function(d) { return d.x; }) |
|
.attr("y", function(d) { return d.y; }) |
|
.attr("width", function(d) { return d.dx; }) |
|
.attr("height", function(d) { return d.dy; }) |
|
.style("fill", function(d) { return d.color; }); |
|
|
|
rects.exit().remove(); |
|
} |
|
|
|
function getImageData(url, callback) { |
|
var img = new Image(); |
|
img.src = url; |
|
var canvas = document.createElement("canvas"); |
|
var context = canvas.getContext("2d"); |
|
img.onload = function() { |
|
canvas.width = img.width; |
|
canvas.height = img.height; |
|
context.drawImage(img, 0, 0); |
|
img.style.display = "none"; |
|
var imageData = context.getImageData(0, 0, canvas.width, canvas.height); |
|
callback(imageData); |
|
} |
|
} |
|
|
|
function toMatrix(imageData) { |
|
var data = imageData.data, |
|
matrix = new Array(data.length); |
|
for (var i = 0; i < data.length; i += 4) { |
|
matrix[i/4] = data.subarray(i, i+4); |
|
} |
|
return matrix; |
|
} |
|
|
|
function binHsl(imageData, bins) { |
|
var color = toMatrix(imageData) |
|
.map(function(d) { |
|
return d3.hsl("rgb(" + d[0] + "," + d[1] + "," + d[2] + ")"); |
|
}) |
|
.filter(function(d) { return d !== undefined; }); |
|
|
|
color = d3.nest() |
|
.key(function(d) { |
|
return "s" + bins.s(d.s) + "-l" + bins.l(d.l); |
|
}) |
|
.key(function(d) { |
|
return "h" + bins.h(d.h); |
|
}) |
|
.rollup(function(d) { return d.length; }) |
|
.entries(color) |
|
.map(function(sl) { |
|
var s = +sl.key.split("-")[0].slice(1), |
|
l = +sl.key.split("-")[1].slice(1), |
|
freq = sl.values.reduce(function(a, b) { |
|
return { values: a.values + b.values }; |
|
}).values; |
|
return { |
|
key: sl.key, |
|
s: s, |
|
l: l, |
|
freq: freq, |
|
values: sl.values.map(function(d) { |
|
var h = +d.key.slice(1), |
|
freq = d.values, |
|
color = d3.hsl( |
|
d3.mean(bins.h.invertExtent(h)), |
|
d3.mean(bins.s.invertExtent(s)), |
|
d3.mean(bins.l.invertExtent(l)) |
|
).toString(); |
|
return { h:h, s:s, l:l, freq:freq, color:color }; |
|
}) |
|
}; |
|
}); |
|
|
|
return color; |
|
} |
|
</script> |
|
</body> |
|
</html> |