Readme

Get common or "dominant" color from an image given a source URL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/** @jsxImportSource npm:hono@3/jsx */
import { Hono } from "https://esm.sh/hono@3";
const EXAMPLE_URL = "https://letsenhance.io/static/8f5e523ee6b2479e26ecc91b9c25261e/1015f/MainAfter.jpg";
const PERCENT_COVERAGE = 10; // lower is faster, but higher is more accurate
function getPixelIndex(numToRound) {
// Each pixel is 4 units long: r,g,b,a
const remainder = numToRound % 4;
if (remainder == 0) return numToRound;
return numToRound + 4 - remainder;
}
export async function getColor(src: string = EXAMPLE_URL): Promise<string> {
const { default: Jimp } = await import("npm:jimp");
const image = await Jimp.read(src);
const store = {};
const total_pixels = image.bitmap.width * image.bitmap.height;
const coverage = total_pixels * PERCENT_COVERAGE / 100;
const data = image.bitmap.data;
const max_pixel_index = total_pixels - 1;
for (let i = 0; i < coverage; ++i) {
const x = getPixelIndex(Math.floor(Math.random() * max_pixel_index));
const key = `${data[x]},${data[x + 1]},${data[x + 2]}`;
const val = store[key];
store[key] = val ? val + 1 : 1;
}
const rgb_code = Object.keys(store).reduce((a, b) => store[a] > store[b] ? a : b);
return `rgb(${rgb_code})`;
}
const template = (children) => (
<html>
<head>
<title>Dominant Color from Image</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com" />
</head>
<body class="p-20">
<div class="flex flex-col p-10 space-y-10">
<a href="/">
<h1 class="text-5xl font-extrabold hover:underline">Dominant Color from Image</h1>
</a>
<div class="flex flex-col flex-grow justify-center items-center w-full">
{children}
</div>
<a href="https://val.town/v/stevekrouse/getColor" class="hover:underline text-center">
view code
</a>
</div>
</body>
</html>
);
const form = (url) => (
<form
action="/"
class="flex w-full"
>
<input name="url" type="url" value={url} class="border border-black p-4 text-2xl w-full"></input>
<button class="border border-black p-4 text-2xl w-40 hover:bg-black hover:text-white">Get Color</button>
</form>
);
const app = new Hono();
export default app.fetch;
app.get("/", async (c) => {
const url = c.req.query("url");
if (url) {
const color = await getColor(url);
return c.html(template(
<div class="text-3xl space-y-4">
<div class="flex items-center">{form(url)}</div>
<img src={url} />
<div class="flex space-x-2">
<div class="m-auto"></div>
<div class="w-10 h-10" style={{ backgroundColor: color }}></div>{" "}
<span class="text-4xl font-bold">{color}</span>
</div>
</div>,
));
}
else {
return c.html(template(form(EXAMPLE_URL)));
}
});
👆 This is a val. Vals are TypeScript snippets of code, written in the browser and run on our servers. Create scheduled functions, email yourself, and persist small pieces of data — all from the browser.