Public
Edited
Dec 6, 2020
Insert cell
Insert cell
Insert cell
image = FileAttachment("iu@1.jpg").image()
Insert cell
Insert cell
imagedata = {
let ctx = DOM.canvas(image.width, image.height).getContext("2d");
ctx.drawImage(image, 0, 0);
return {
data: ctx.getImageData(0, 0, image.width, image.height),
width: image.width,
height: image.height
};
}
Insert cell
Insert cell
data = colorList(imagedata.data.data).filter( ({r, g, b}) => {
return r != 255 && g != 255 && b != 255;
});
Insert cell
function colorList(imageData) {
const colors = [];
for (let i = 0; i < imageData.length; i += 4) {
colors.push(d3.rgb(imageData[i], imageData[i + 1], imageData[i + 2]));
}
return colors;
}
Insert cell
Insert cell
pixel = data[8000]
Insert cell
swatches([pixel])
Insert cell
md `## Look at the colors
I want to know the frequency of colors`
Insert cell
md `First I'll break it down to by color to understand the data better.`
Insert cell
red = data.map(d => d.r)
Insert cell
height = 200
Insert cell
redbars = "red"
Insert cell
import {chart as redHistogram} with {red as data, height, redbars as color} from "@llad/histogram-with-color-attribute"
Insert cell
redHistogram
Insert cell
green = data.map(d => d.g)
Insert cell
greenbars = "green"
Insert cell
import {chart as greenHistogram} with {green as data, height, greenbars as color} from "@llad/histogram-with-color-attribute"
Insert cell
greenHistogram
Insert cell
blue = data.map(d => d.b)
Insert cell
bluebars = "blue"
Insert cell
import {chart as blueHistogram} with {blue as data, height, bluebars as color} from "@llad/histogram-with-color-attribute"
Insert cell
blueHistogram
Insert cell
md `## Colors vs channels
We want to look at the colors not the R,B,G components of the pixel so let's start treating the data a full colors. This can be slow depending on the number of pixels so lets just look at a subset and visualize it.`
Insert cell
somedata = data.slice(0,1000)
Insert cell
html`<svg viewBox="0 0 800 50">${somedata.map(
(color, i) =>
`<rect x="${i * (800 / somedata.length)}" y=0 width=${800 /
somedata.length} height=50 fill="${color}"></rect>`
)}</svg>`
Insert cell
md `Is it helpful to sort this? Let's try sorting. This ended up being a rabbit hole exploring RGB vs hex colors and conversions.`
Insert cell
sorteddata = somedata.map( ({r,g,b,}) => d3.rgb(r,g,b) ).sort()
// Note: can't just sort the data or make a shallow copy because these are D3 color objects. So this just recreates an array of objects then sorts them.
Insert cell
Insert cell
Insert cell
Insert cell
topColors = countBy(data, c => c.toString()).sort( (a,b) => b.count - a.count).slice(0,9)
Insert cell
Insert cell
swatches(topColors.map( (c,i) => c.name))
Insert cell
Insert cell
md `## Grouping Colors with Median Cut
Just like [@kerryrodden](https://observablehq.com/@kerryrodden/extract-a-color-palette-from-an-image), I'm going to implement an median cut algorithm to find a palette for my image.

First I need to find the color with the greatest variance.`
Insert cell
function greatestVariance(colors) {
const dimensions = ["r", "g", "b"];
const variances = dimensions.map(dimension =>
d3.variance(colors, c => c[dimension])
);
return dimensions[d3.maxIndex(variances)];
}
Insert cell
greatestColor = greatestVariance(data)
Insert cell
md `Right, based on the breaking down the color channels earlier, we could tell blue had the greatest variance.

Next sort on blue and divide into two buckets.`
Insert cell
function bucket(data, color) {
const sorteddata = data.map( ({r,g,b,}) => d3.rgb(r,g,b) ).sort((a, b) => a[color] - b[color])
const median = Math.floor(sorteddata.length/2);
return [ sorteddata.slice(0,median), sorteddata.slice(median+1) ]
}
Insert cell
buckets = bucket(data,greatestColor)
Insert cell
md `Great, two buckets of colors. So we want to know the average color in each of those buckets.`
Insert cell
function averageColor(colors) {
const dimensions = ["r", "g", "b"];
const means = dimensions.map(dimension =>
d3.mean(colors, c => c[dimension])
);
return d3.rgb(...means);
}
Insert cell
firstAverage = averageColor(buckets[0])
Insert cell
swatches([averageColor(buckets[0]), averageColor(buckets[1])])
Insert cell
md `Great, we've bucketed the colors and found a two color palette. Now we want to be configurable in how many colors we want to divide the full set of colors into.

To do that I'm going to create a recursive function that makes bucket up to some "depth" or how many times do I split each bucket?`
Insert cell
function makeBuckets (colors, depth, current) {
if (current == depth) return [colors];
current++;
const buckets = bucket(colors, greatestVariance(colors));
return makeBuckets(buckets[0], depth, current)
.concat(makeBuckets(buckets[1], depth, current));
}

Insert cell
Insert cell
Insert cell
md `---
# Appendix`
Insert cell
function swatches(colors) {
return html`${colors.map(c => `<div title="${c}" style="
display: inline-block;
margin-right: 3px;
width: 50px;
height: 50px;
background: ${c};
"></div>`)}`;
}
Insert cell
import { slider } from "@jashkenas/inputs"
Insert cell
d3 = require("d3@6")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more