JSFiddle - React, Tailwind, and code Playground
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.4.4/d3.min.js"></script>
<label>Image URL: <input id="input" value="https://picsum.photos/id/870/2900/4334"></label>
<div id=output></div>
<div id=preview></div>
CSS
label {
display: block;
padding: 10px;
box-sizing: border-box;
}
label input {
width: 100%;
}
#preview img {
max-width: 400px;
max-height: 400px;
}
JavaScript
const input = document.getElementById('input');
const output = document.getElementById('output');
const preview = document.getElementById('preview');
input.onchange = async () => {
clear(output);
clear(preview);
// Load the image.
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = input.value;
preview.appendChild(img);
await img.decode();
// Create and inject the histogram.
output.appendChild(histogramVisualization(img));
};
if(input.value) input.onchange();
function clear(element) {
element.innerHTML = '';
}
function histogramVisualization(img, {width = 400} = {}) {
const height = width / 3;
const margin = 2;
const histogram = getHistogram(getImageData(img).data);
const x = d3
.scaleLinear()
.domain([0, 256])
.range([0 + margin, width - margin]);
const y = d3
.scaleLinear()
.domain([0, d3.max(histogram.map(x => d3.max(x.data)))])
.range([height - margin, 0 + margin]);
const area = d3
.area()
.x((_, i) => x(i))
.y0(y(0))
.y1((d, _) => y(d))
.curve(d3.curveStepAfter);
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "histogram")
.style("background-color", "#333")
.style("isolation", "isolate");
for (const channel of histogram) {
svg
.append("path")
.attr("class", channel.name)
// duplicate the last data point so that the right-most step is drawn correctly
.attr("d", area([...channel.data, channel.data.slice(-1)]))
.attr("fill", channel.color)
.attr("stroke", channel.color)
.style("mix-blend-mode", "screen");
}
return svg.node();
}
function getHistogram(data) {
// dividing the histogram into channels like this is @lemonnish's idea
const histogram = [
{ name: "red", color: "#ff0000", data: new Uint32Array(256) },
{ name: "green", color: "#00ff00", data: new Uint32Array(256) },
{ name: "blue", color: "#0000ff", data: new Uint32Array(256) }
];
for (let i = 0; i <...