huebrew 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 huebrew contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ <div align="center">
2
+
3
+ # 🎨 huebrew
4
+
5
+ ### Brew a color palette β€” and a ready-to-use theme β€” from any image.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/huebrew.svg?color=success)](https://www.npmjs.com/package/huebrew)
8
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/huebrew?label=gzip)](https://bundlephobia.com/package/huebrew)
9
+ [![CI](https://github.com/didrod205/huebrew/actions/workflows/ci.yml/badge.svg)](https://github.com/didrod205/huebrew/actions/workflows/ci.yml)
10
+ [![types](https://img.shields.io/npm/types/huebrew.svg)](https://www.npmjs.com/package/huebrew)
11
+ [![license](https://img.shields.io/npm/l/huebrew.svg)](./LICENSE)
12
+
13
+ **[🌐 Try the free web studio β†’](https://didrod205.github.io/huebrew/)** &nbsp;Β·&nbsp; drop an image, copy your theme. Nothing uploaded.
14
+
15
+ </div>
16
+
17
+ ---
18
+
19
+ You found the perfect screenshot, photo, logo, or moodboard β€” now you need its
20
+ colors *in your code*. Today that means eyedropper-ing pixels one at a time, or
21
+ pasting your image into some website that wants your email.
22
+
23
+ **huebrew** extracts the real dominant colors from any image (deterministic
24
+ median-cut quantization β€” not an AI guess), builds **perceptually-even tint/shade
25
+ ramps** in OKLab, and hands you a theme you can paste straight in: **CSS
26
+ variables, Tailwind colors, SCSS, JSON, or an SVG strip.** All locally, with
27
+ **zero dependencies** and **no API key**.
28
+
29
+ > πŸ“Έ _Screenshot / demo GIF:_ `./web/screenshot.png` β€” record the [live studio](https://didrod205.github.io/huebrew/) dropping an image and copying a Tailwind config.
30
+
31
+ ## Why it exists
32
+
33
+ - **AI can't do this reliably.** Ask a chatbot for "the colors in this image" and
34
+ it hallucinates hex codes. Exact dominant-color extraction is a precise,
35
+ pixel-level algorithm β€” perfect for a tiny, deterministic library.
36
+ - **Privacy.** Every "extract palette" site makes you upload your image. huebrew
37
+ never sends a byte anywhere.
38
+ - **It's a theme, not just colors.** Color Thief gives you 6 swatches. huebrew
39
+ gives you 50β†’950 ramps and copy-paste configs for the tools you actually use.
40
+
41
+ ## Who it's for
42
+
43
+ **Designers** (palette from a moodboard), **developers** (theme from a mockup or
44
+ screenshot), **marketers** (brand colors from a logo), **content creators**
45
+ (match graphics to a photo), and anyone who's ever needed "those colors, as code."
46
+
47
+ ## Install
48
+
49
+ **No install β€”** just open the **[web studio](https://didrod205.github.io/huebrew/)**.
50
+
51
+ For the library:
52
+
53
+ ```bash
54
+ npm install huebrew
55
+ ```
56
+
57
+ Zero dependencies. Ships ESM + CJS + TypeScript types. Works in the browser, Node, Deno and Bun.
58
+
59
+ ## Usage
60
+
61
+ ### In the browser (canvas)
62
+
63
+ ```ts
64
+ import { palette, toTailwind, toCSS } from "huebrew";
65
+
66
+ const ctx = canvas.getContext("2d")!;
67
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
68
+
69
+ const swatches = palette(imageData, { colors: 6 });
70
+ swatches[0].hex; // dominant color, e.g. "#0f4c81"
71
+ swatches[0].textColor; // "#ffffff" β€” readable text on it (WCAG)
72
+
73
+ toCSS(swatches); // ":root { --color-1: #0f4c81; ... }"
74
+ toTailwind(swatches, ["brand"]); // { brand: { 50: "#...", ..., 950: "#..." } }
75
+ ```
76
+
77
+ ### In Node (with any decoder you already have)
78
+
79
+ ```ts
80
+ import sharp from "sharp";
81
+ import { palette } from "huebrew";
82
+
83
+ const { data, info } = await sharp("logo.png")
84
+ .ensureAlpha()
85
+ .raw()
86
+ .toBuffer({ resolveWithObject: true });
87
+
88
+ const swatches = palette({ data, width: info.width, height: info.height }, { colors: 5 });
89
+ ```
90
+
91
+ huebrew itself decodes nothing and depends on nothing β€” it works on raw RGBA
92
+ pixels, so you bring whatever decoder fits your environment (or just use a
93
+ `<canvas>` in the browser).
94
+
95
+ ### Build a ramp from one color
96
+
97
+ ```ts
98
+ import { ramp } from "huebrew";
99
+
100
+ ramp([15, 76, 129]); // ["#eaf2fb", ... 11 perceptual stops ..., "#08233d"]
101
+ ```
102
+
103
+ ### Export formats
104
+
105
+ `toArray`, `toCSS`, `toSCSS`, `toJSON`, `toSVG`, and `toTailwind` β€” pick the one
106
+ that matches your stack.
107
+
108
+ ## API
109
+
110
+ | Member | Description |
111
+ | ------ | ----------- |
112
+ | `palette(source, { colors?, step? })` | Extract swatches (most dominant first). `source` = RGBA array or `ImageData`. |
113
+ | `dominant(source, options?)` | The single most dominant `Swatch`. |
114
+ | `quantize(pixels, maxColors, step?)` | Low-level median-cut β†’ `RGB[]`. |
115
+ | `ramp(rgb)` | 11-stop perceptual (OKLab) tint/shade ramp β†’ `string[]`. |
116
+ | `rgbToHex` / `hexToRgb` / `rgbToHsl` / `luminance` / `contrast` / `textColorFor` | Color helpers. |
117
+ | `toArray` / `toCSS` / `toSCSS` / `toJSON` / `toSVG` / `toTailwind` | Exporters. |
118
+
119
+ A `Swatch` is `{ rgb, hex, hsl, population, isLight, textColor }`.
120
+
121
+ ## FAQ
122
+
123
+ **Is my image uploaded anywhere?**
124
+ No. Everything runs on your device β€” web studio and library alike. No server, no
125
+ network request, works offline.
126
+
127
+ **How is this different from Color Thief?**
128
+ Color Thief extracts swatches (and pulls in canvas/node-canvas). huebrew is
129
+ dependency-free, works on raw pixels in any runtime, **and** turns the palette
130
+ into a usable theme β€” OKLab ramps plus CSS/SCSS/Tailwind/JSON/SVG exporters β€” via
131
+ a polished local web app.
132
+
133
+ **Why are the colors slightly different from the original pixels?**
134
+ Median-cut groups similar colors and returns each group's average, so a swatch is
135
+ the *representative* of a region, not necessarily an exact source pixel. Lower
136
+ `colors` = broader groups; higher = finer.
137
+
138
+ **Does it work on photos, screenshots, logos, SVGs?**
139
+ Anything you can draw to a canvas (or decode to RGBA). Screenshots and logos give
140
+ especially clean palettes.
141
+
142
+ **Is it fast?**
143
+ Yes β€” it samples pixels (auto `step`) and quantizes a compact histogram, so even
144
+ large images resolve in milliseconds.
145
+
146
+ ## Contributing
147
+
148
+ Contributions welcome! See [CONTRIBUTING.md](./CONTRIBUTING.md) and the
149
+ [Code of Conduct](./CODE_OF_CONDUCT.md).
150
+
151
+ ```bash
152
+ git clone https://github.com/didrod205/huebrew.git
153
+ cd huebrew
154
+ npm install
155
+ npm test # run the suite
156
+ npm run dev # run the web studio locally
157
+ ```
158
+
159
+ ## πŸ’– Sponsor
160
+
161
+ huebrew is free, MIT-licensed, and built in spare time. If it saved you an hour
162
+ of eyedropping, please consider supporting it:
163
+
164
+ - ⭐ **Star this repo** β€” free, and it genuinely helps others find it.
165
+ - πŸ‹ **[Sponsor via Lemon Squeezy](https://elab-studio.lemonsqueezy.com/checkout/buy/5d059b89-51d0-456b-b33a-ed56994f7010)** β€” one-time or recurring support.
166
+
167
+ **Where your support goes:** more export targets (Figma tokens, Style Dictionary,
168
+ ASE/Adobe swatches), smarter palette roles (background/accent/neutral detection),
169
+ accessibility-aware ramp tuning, keeping the free web studio online, and fast
170
+ issue responses.
171
+
172
+ ## License
173
+
174
+ [MIT](./LICENSE) Β© huebrew contributors
package/dist/index.cjs ADDED
@@ -0,0 +1,7 @@
1
+ 'use strict';var T=(t,r,n)=>t<r?r:t>n?n:t;function B([t,r,n]){let e=o=>T(Math.round(o),0,255).toString(16).padStart(2,"0");return `#${e(t)}${e(r)}${e(n)}`}function C(t){let r=t.replace(/^#/,"");r.length===3&&(r=r.split("").map(e=>e+e).join(""));let n=parseInt(r,16);return [n>>16&255,n>>8&255,n&255]}function I([t,r,n]){t/=255,r/=255,n/=255;let e=Math.max(t,r,n),o=Math.min(t,r,n),u=(e+o)/2;if(e===o)return {h:0,s:0,l:Math.round(u*100)};let i=e-o,s=u>.5?i/(2-e-o):i/(e+o),a;return e===t?a=(r-n)/i+(r<n?6:0):e===r?a=(n-t)/i+2:a=(t-r)/i+4,{h:Math.round(a*60),s:Math.round(s*100),l:Math.round(u*100)}}var R=t=>t<=.04045?t/12.92:((t+.055)/1.055)**2.4,x=t=>t<=.0031308?12.92*t:1.055*t**(1/2.4)-.055;function M([t,r,n]){return .2126*R(t/255)+.7152*R(r/255)+.0722*R(n/255)}function F(t,r){let n=M(t),e=M(r),[o,u]=n>=e?[n,e]:[e,n];return (o+.05)/(u+.05)}function w(t){return M(t)>=.179?"#000000":"#ffffff"}var O=t=>M(t)>=.179;function z([t,r,n]){let e=R(t/255),o=R(r/255),u=R(n/255),i=Math.cbrt(.4122214708*e+.5363325363*o+.0514459929*u),s=Math.cbrt(.2119034982*e+.6806995451*o+.1073969566*u),a=Math.cbrt(.0883024619*e+.2817188376*o+.6299787005*u);return {L:.2104542553*i+.793617785*s-.0040720468*a,a:1.9779984951*i-2.428592205*s+.4505937099*a,b:.0259040371*i+.7827717662*s-.808675766*a}}function j({L:t,a:r,b:n}){let e=(t+.3963377774*r+.2158037573*n)**3,o=(t-.1055613458*r-.0638541728*n)**3,u=(t-.0894841775*r-1.291485548*n)**3,i=4.0767416621*e-3.3077115913*o+.2309699292*u,s=-1.2684380046*e+2.6097574011*o-.3413193965*u,a=-0.0041960863*e-.7034186147*o+1.707614701*u;return [T(x(i),0,1)*255,T(x(s),0,1)*255,T(x(a),0,1)*255]}var G=[50,100,200,300,400,500,600,700,800,900,950],q=[.971,.936,.885,.808,.704,.602,.515,.43,.366,.317,.235];function $(t){let{a:r,b:n}=z(t);return q.map(e=>B(j({L:e,a:r,b:n})))}var g=(t,r,n)=>(t<<10)+(r<<5)+n,A=class t{constructor(r,n,e,o,u,i,s){this.r1=r;this.r2=n;this.g1=e;this.g2=o;this.b1=u;this.b2=i;this.histo=s;}r1;r2;g1;g2;b1;b2;histo;_count=-1;_avg=null;volume(){return (this.r2-this.r1+1)*(this.g2-this.g1+1)*(this.b2-this.b1+1)}count(){if(this._count>=0)return this._count;let r=0;for(let n=this.r1;n<=this.r2;n++)for(let e=this.g1;e<=this.g2;e++)for(let o=this.b1;o<=this.b2;o++)r+=this.histo[g(n,e,o)];return this._count=r,r}average(){if(this._avg)return this._avg;let r=0,n=0,e=0,o=0,u=8;for(let i=this.r1;i<=this.r2;i++)for(let s=this.g1;s<=this.g2;s++)for(let a=this.b1;a<=this.b2;a++){let c=this.histo[g(i,s,a)];r+=c,n+=c*(i+.5)*u,e+=c*(s+.5)*u,o+=c*(a+.5)*u;}return this._avg=r?[Math.round(n/r),Math.round(e/r),Math.round(o/r)]:[Math.round(u*(this.r1+this.r2+1)/2),Math.round(u*(this.g1+this.g2+1)/2),Math.round(u*(this.b1+this.b2+1)/2)],this._avg}clone(r,n,e,o,u,i){return new t(r,n,e,o,u,i,this.histo)}};function V(t,r){let n=new Int32Array(32768);for(let e=0;e<t.length;e+=4*r){let o=t[e+3];if(o!==void 0&&o<125)continue;let u=t[e]>>3,i=t[e+1]>>3,s=t[e+2]>>3,a=g(u,i,s);n[a]=n[a]+1;}return n}function E(t){let r=31,n=0,e=31,o=0,u=31,i=0;for(let s=0;s<32;s++)for(let a=0;a<32;a++)for(let c=0;c<32;c++)t[g(s,a,c)]>0&&(r=Math.min(r,s),n=Math.max(n,s),e=Math.min(e,a),o=Math.max(o,a),u=Math.min(u,c),i=Math.max(i,c));return new A(r,n,e,o,u,i,t)}function N(t){let r=t.count();if(r===0)return [t];let n=t.r2-t.r1+1,e=t.g2-t.g1+1,o=t.b2-t.b1+1,u=Math.max(n,e,o)===n?"r":Math.max(e,o)===e?"g":"b",i=[],s=0,{histo:a}=t,c=(p,k)=>{for(let S=p;S<=k;S++){let d=0;if(u==="r")for(let m=t.g1;m<=t.g2;m++)for(let h=t.b1;h<=t.b2;h++)d+=a[g(S,m,h)];else if(u==="g")for(let m=t.r1;m<=t.r2;m++)for(let h=t.b1;h<=t.b2;h++)d+=a[g(m,S,h)];else for(let m=t.r1;m<=t.r2;m++)for(let h=t.g1;h<=t.g2;h++)d+=a[g(m,h,S)];s+=d,i[S]=s;}},b=u==="r"?t.r1:u==="g"?t.g1:t.b1,f=u==="r"?t.r2:u==="g"?t.g2:t.b2;if(c(b,f),b===f)return [t];let l=b;for(let p=b;p<=f;p++)if(i[p]>r/2){l=Math.max(b,Math.min(f-1,p));break}let L=t.clone(t.r1,u==="r"?l:t.r2,t.g1,u==="g"?l:t.g2,t.b1,u==="b"?l:t.b2),H=t.clone(u==="r"?l+1:t.r1,t.r2,u==="g"?l+1:t.g1,t.g2,u==="b"?l+1:t.b1,t.b2);return [L,H]}function P(t,r,n){let e=V(t,Math.max(1,n)),o=[E(e)];if(o[0].count()===0)return [];let u=(i,s)=>{let a=0;for(;a++<1e3&&o.length<i;){o.sort((f,l)=>s?f.count()*f.volume()-l.count()*l.volume():f.count()-l.count());let c=o.pop();if(!c||c.count()===0){c&&o.push(c);break}let b=N(c);if(b.length===1){o.push(b[0]);break}o.push(b[0],b[1]);}};return u(Math.max(1,Math.floor(.75*r)),false),u(r,true),o.filter(i=>i.count()>0).sort((i,s)=>s.count()-i.count()).slice(0,r)}function U(t,r=6,n=1){return r<1?[]:P(t,r,n).map(e=>e.average())}function y(t,r=6,n=1){return r<1?[]:P(t,r,n).map(e=>({rgb:e.average(),population:e.count()}))}var _=(t,r)=>`${r}-${t+1}`;function Q(t){return t.map(r=>r.hex)}function v(t,r="color"){return `:root {
2
+ ${t.map((e,o)=>` --${_(o,r)}: ${e.hex};`).join(`
3
+ `)}
4
+ }`}function tt(t,r="color"){return t.map((n,e)=>`$${_(e,r)}: ${n.hex};`).join(`
5
+ `)}function rt(t){return JSON.stringify(t.map(r=>({hex:r.hex,rgb:r.rgb,hsl:r.hsl,population:r.population})),null,2)}function nt(t,r={}){let n=r.size??80,e=n*t.length,o=t.map((u,i)=>{let s=i*n,a=u.hex.toUpperCase();return `<rect x="${s}" y="0" width="${n}" height="${n}" fill="${u.hex}"/><text x="${s+n/2}" y="${n-8}" font-family="monospace" font-size="10" text-anchor="middle" fill="${u.textColor}">${a}</text>`}).join("");return `<svg xmlns="http://www.w3.org/2000/svg" width="${e}" height="${n}" viewBox="0 0 ${e} ${n}">${o}</svg>`}function et(t,r=[]){let n={};return t.forEach((e,o)=>{let u=r[o]??`palette-${o+1}`,i=$(e.rgb),s={};G.forEach((a,c)=>{s[a]=i[c];}),n[u]=s;}),n}function K(t){return typeof t=="object"&&t!==null&&"data"in t?t.data:t}function W(t){return Math.max(1,Math.round(Math.sqrt(t/2e4)))}function J(t,r){return {rgb:t,hex:B(t),hsl:I(t),population:r,isLight:O(t),textColor:w(t)}}function X(t,r={}){let n=K(t),e=r.colors??6,o=r.step??W(n.length/4);return y(n,e,o).map(({rgb:u,population:i})=>J(u,i))}function at(t,r={}){return X(t,{...r,colors:Math.max(r.colors??5,5)})[0]??null}
6
+ exports.RAMP_STOPS=G;exports.contrast=F;exports.dominant=at;exports.hexToRgb=C;exports.luminance=M;exports.palette=X;exports.quantize=U;exports.quantizeWithCounts=y;exports.ramp=$;exports.rgbToHex=B;exports.rgbToHsl=I;exports.textColorFor=w;exports.toArray=Q;exports.toCSS=v;exports.toJSON=rt;exports.toSCSS=tt;exports.toSVG=nt;exports.toTailwind=et;//# sourceMappingURL=index.cjs.map
7
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/color.ts","../src/quantize.ts","../src/format.ts","../src/index.ts"],"names":["clamp","n","lo","hi","rgbToHex","r","g","b","h","hexToRgb","hex","c","rgbToHsl","max","min","l","d","srgbToLinear","linearToSrgb","luminance","contrast","a","la","lb","textColorFor","bg","isLight","rgb","rgbToOklab","lr","lg","m","s","oklabToRgb","L","RAMP_STOPS","RAMP_L","ramp","base","colorIndex","VBox","_VBox","r1","r2","g1","g2","b1","b2","histo","total","rs","gs","bs","mult","buildHisto","pixels","step","i","idx","vboxFromHisto","medianCut","vbox","rw","gw","bw","axis","partial","sum","accumulate","outer1","outer2","slice","splitPoint","run","maxColors","boxes","splitUntil","target","byVolume","iterations","x","y","biggest","parts","box","quantize","quantizeWithCounts","slug","prefix","toArray","swatches","toCSS","toSCSS","toJSON","toSVG","options","size","w","rects","label","toTailwind","names","out","name","stops","obj","stop","j","toPixels","source","autoStep","pixelCount","makeSwatch","population","palette","colors","dominant"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Color math for huebrew: hex/rgb/hsl conversion, WCAG luminance, and an\n * OKLab-based lightness ramp (perceptually even tints/shades).\n */\n\nexport type RGB = [number, number, number];\n\nconst clamp = (n: number, lo: number, hi: number): number => (n < lo ? lo : n > hi ? hi : n);\n\nexport function rgbToHex([r, g, b]: RGB): string {\n const h = (n: number) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, \"0\");\n return `#${h(r)}${h(g)}${h(b)}`;\n}\n\nexport function hexToRgb(hex: string): RGB {\n let h = hex.replace(/^#/, \"\");\n if (h.length === 3) h = h.split(\"\").map((c) => c + c).join(\"\");\n const n = parseInt(h, 16);\n return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\n\nexport function rgbToHsl([r, g, b]: RGB): { h: number; s: number; l: number } {\n r /= 255;\n g /= 255;\n b /= 255;\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2;\n if (max === min) return { h: 0, s: 0, l: Math.round(l * 100) };\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n let h: number;\n if (max === r) h = (g - b) / d + (g < b ? 6 : 0);\n else if (max === g) h = (b - r) / d + 2;\n else h = (r - g) / d + 4;\n return { h: Math.round(h * 60), s: Math.round(s * 100), l: Math.round(l * 100) };\n}\n\nconst srgbToLinear = (c: number): number =>\n c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\nconst linearToSrgb = (c: number): number =>\n c <= 0.0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055;\n\n/** WCAG relative luminance (0–1). */\nexport function luminance([r, g, b]: RGB): number {\n return 0.2126 * srgbToLinear(r / 255) + 0.7152 * srgbToLinear(g / 255) + 0.0722 * srgbToLinear(b / 255);\n}\n\n/** WCAG contrast ratio between two colors (1–21). */\nexport function contrast(a: RGB, b: RGB): number {\n const la = luminance(a);\n const lb = luminance(b);\n const [hi, lo] = la >= lb ? [la, lb] : [lb, la];\n return (hi + 0.05) / (lo + 0.05);\n}\n\n/** Black or white β€” whichever is more readable on `bg`. */\nexport function textColorFor(bg: RGB): \"#000000\" | \"#ffffff\" {\n return luminance(bg) >= 0.179 ? \"#000000\" : \"#ffffff\";\n}\n\nexport const isLight = (rgb: RGB): boolean => luminance(rgb) >= 0.179;\n\n// --- OKLab ---\ninterface OKLab {\n L: number;\n a: number;\n b: number;\n}\n\nfunction rgbToOklab([r, g, b]: RGB): OKLab {\n const lr = srgbToLinear(r / 255);\n const lg = srgbToLinear(g / 255);\n const lb = srgbToLinear(b / 255);\n const l = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);\n const m = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);\n const s = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb);\n return {\n L: 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,\n a: 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,\n b: 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,\n };\n}\n\nfunction oklabToRgb({ L, a, b }: OKLab): RGB {\n const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;\n const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;\n const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;\n const lr = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;\n const lg = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;\n const lb = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;\n return [\n clamp(linearToSrgb(lr), 0, 1) * 255,\n clamp(linearToSrgb(lg), 0, 1) * 255,\n clamp(linearToSrgb(lb), 0, 1) * 255,\n ];\n}\n\n/** Tailwind-style ramp stops and their target OKLab lightness. */\nexport const RAMP_STOPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as const;\nconst RAMP_L = [0.971, 0.936, 0.885, 0.808, 0.704, 0.602, 0.515, 0.43, 0.366, 0.317, 0.235];\n\n/**\n * Generate a perceptually-even tint/shade ramp from a base color by holding its\n * OKLab hue & chroma and walking lightness through Tailwind-like stops.\n * Returns hex strings, light β†’ dark, one per {@link RAMP_STOPS} entry.\n */\nexport function ramp(base: RGB): string[] {\n const { a, b } = rgbToOklab(base);\n return RAMP_L.map((L) => rgbToHex(oklabToRgb({ L, a, b })));\n}\n","/**\n * Modified Median Cut Quantization (MMCQ) β€” extract a small, representative\n * palette from a large set of pixels, deterministically and dependency-free.\n *\n * The classic approach: bucket colors into a 3D histogram, then repeatedly\n * split the box containing the most pixels along its longest axis at the\n * population median, prioritizing first by population and then by populationΓ—volume.\n */\n\nimport type { RGB } from \"./color.js\";\n\nconst SIGBITS = 5;\nconst RSHIFT = 8 - SIGBITS;\nconst HISTO_SIZE = 1 << (3 * SIGBITS);\nconst FRACT_BY_POPULATION = 0.75;\nconst MAX_ITERATIONS = 1000;\n\nconst colorIndex = (r: number, g: number, b: number): number =>\n (r << (2 * SIGBITS)) + (g << SIGBITS) + b;\n\nclass VBox {\n constructor(\n public r1: number,\n public r2: number,\n public g1: number,\n public g2: number,\n public b1: number,\n public b2: number,\n public readonly histo: Int32Array,\n ) {}\n\n private _count = -1;\n private _avg: RGB | null = null;\n\n volume(): number {\n return (this.r2 - this.r1 + 1) * (this.g2 - this.g1 + 1) * (this.b2 - this.b1 + 1);\n }\n\n count(): number {\n if (this._count >= 0) return this._count;\n let n = 0;\n for (let r = this.r1; r <= this.r2; r++)\n for (let g = this.g1; g <= this.g2; g++)\n for (let b = this.b1; b <= this.b2; b++) n += this.histo[colorIndex(r, g, b)] as number;\n this._count = n;\n return n;\n }\n\n average(): RGB {\n if (this._avg) return this._avg;\n let total = 0;\n let rs = 0;\n let gs = 0;\n let bs = 0;\n const mult = 1 << RSHIFT;\n for (let r = this.r1; r <= this.r2; r++)\n for (let g = this.g1; g <= this.g2; g++)\n for (let b = this.b1; b <= this.b2; b++) {\n const h = this.histo[colorIndex(r, g, b)] as number;\n total += h;\n rs += h * (r + 0.5) * mult;\n gs += h * (g + 0.5) * mult;\n bs += h * (b + 0.5) * mult;\n }\n this._avg = total\n ? [Math.round(rs / total), Math.round(gs / total), Math.round(bs / total)]\n : [\n Math.round((mult * (this.r1 + this.r2 + 1)) / 2),\n Math.round((mult * (this.g1 + this.g2 + 1)) / 2),\n Math.round((mult * (this.b1 + this.b2 + 1)) / 2),\n ];\n return this._avg;\n }\n\n clone(r1: number, r2: number, g1: number, g2: number, b1: number, b2: number): VBox {\n return new VBox(r1, r2, g1, g2, b1, b2, this.histo);\n }\n}\n\nfunction buildHisto(pixels: ArrayLike<number>, step: number): Int32Array {\n const histo = new Int32Array(HISTO_SIZE);\n for (let i = 0; i < pixels.length; i += 4 * step) {\n const a = pixels[i + 3];\n if (a !== undefined && a < 125) continue; // skip mostly-transparent pixels\n const r = (pixels[i] as number) >> RSHIFT;\n const g = (pixels[i + 1] as number) >> RSHIFT;\n const b = (pixels[i + 2] as number) >> RSHIFT;\n const idx = colorIndex(r, g, b);\n histo[idx] = (histo[idx] as number) + 1;\n }\n return histo;\n}\n\nfunction vboxFromHisto(histo: Int32Array): VBox {\n let r1 = 31, r2 = 0, g1 = 31, g2 = 0, b1 = 31, b2 = 0;\n for (let r = 0; r < 32; r++)\n for (let g = 0; g < 32; g++)\n for (let b = 0; b < 32; b++) {\n if ((histo[colorIndex(r, g, b)] as number) > 0) {\n r1 = Math.min(r1, r); r2 = Math.max(r2, r);\n g1 = Math.min(g1, g); g2 = Math.max(g2, g);\n b1 = Math.min(b1, b); b2 = Math.max(b2, b);\n }\n }\n return new VBox(r1, r2, g1, g2, b1, b2, histo);\n}\n\nfunction medianCut(vbox: VBox): [VBox, VBox] | [VBox] {\n const total = vbox.count();\n if (total === 0) return [vbox];\n\n const rw = vbox.r2 - vbox.r1 + 1;\n const gw = vbox.g2 - vbox.g1 + 1;\n const bw = vbox.b2 - vbox.b1 + 1;\n const axis = Math.max(rw, gw, bw) === rw ? \"r\" : Math.max(gw, bw) === gw ? \"g\" : \"b\";\n\n const partial: number[] = [];\n let sum = 0;\n const { histo } = vbox;\n\n const accumulate = (outer1: number, outer2: number) => {\n for (let i = outer1; i <= outer2; i++) {\n let slice = 0;\n if (axis === \"r\") {\n for (let g = vbox.g1; g <= vbox.g2; g++)\n for (let b = vbox.b1; b <= vbox.b2; b++) slice += histo[colorIndex(i, g, b)] as number;\n } else if (axis === \"g\") {\n for (let r = vbox.r1; r <= vbox.r2; r++)\n for (let b = vbox.b1; b <= vbox.b2; b++) slice += histo[colorIndex(r, i, b)] as number;\n } else {\n for (let r = vbox.r1; r <= vbox.r2; r++)\n for (let g = vbox.g1; g <= vbox.g2; g++) slice += histo[colorIndex(r, g, i)] as number;\n }\n sum += slice;\n partial[i] = sum;\n }\n };\n\n const lo = axis === \"r\" ? vbox.r1 : axis === \"g\" ? vbox.g1 : vbox.b1;\n const hi = axis === \"r\" ? vbox.r2 : axis === \"g\" ? vbox.g2 : vbox.b2;\n accumulate(lo, hi);\n\n if (lo === hi) return [vbox]; // can't split a single slice\n\n let splitPoint = lo;\n for (let i = lo; i <= hi; i++) {\n if ((partial[i] as number) > total / 2) {\n splitPoint = Math.max(lo, Math.min(hi - 1, i));\n break;\n }\n }\n\n const a = vbox.clone(\n vbox.r1, axis === \"r\" ? splitPoint : vbox.r2,\n vbox.g1, axis === \"g\" ? splitPoint : vbox.g2,\n vbox.b1, axis === \"b\" ? splitPoint : vbox.b2,\n );\n const b = vbox.clone(\n axis === \"r\" ? splitPoint + 1 : vbox.r1, vbox.r2,\n axis === \"g\" ? splitPoint + 1 : vbox.g1, vbox.g2,\n axis === \"b\" ? splitPoint + 1 : vbox.b1, vbox.b2,\n );\n return [a, b];\n}\n\nfunction run(pixels: ArrayLike<number>, maxColors: number, step: number): VBox[] {\n const histo = buildHisto(pixels, Math.max(1, step));\n const boxes = [vboxFromHisto(histo)];\n if (boxes[0]!.count() === 0) return [];\n\n const splitUntil = (target: number, byVolume: boolean) => {\n let iterations = 0;\n while (iterations++ < MAX_ITERATIONS && boxes.length < target) {\n boxes.sort((x, y) =>\n byVolume ? x.count() * x.volume() - y.count() * y.volume() : x.count() - y.count(),\n );\n const biggest = boxes.pop();\n if (!biggest || biggest.count() === 0) {\n if (biggest) boxes.push(biggest);\n break;\n }\n const parts = medianCut(biggest);\n if (parts.length === 1) {\n boxes.push(parts[0]); // unsplittable; leave it and stop trying\n break;\n }\n boxes.push(parts[0], parts[1]);\n }\n };\n\n splitUntil(Math.max(1, Math.floor(FRACT_BY_POPULATION * maxColors)), false);\n splitUntil(maxColors, true);\n\n return boxes\n .filter((box) => box.count() > 0)\n .sort((x, y) => y.count() - x.count())\n .slice(0, maxColors);\n}\n\n/**\n * Quantize `pixels` (a flat RGBA array) down to at most `maxColors` colors.\n * `step` samples every Nth pixel for speed. Returns RGB triplets ordered by\n * population (most common first).\n */\nexport function quantize(pixels: ArrayLike<number>, maxColors = 6, step = 1): RGB[] {\n if (maxColors < 1) return [];\n return run(pixels, maxColors, step).map((box) => box.average());\n}\n\n/** Like {@link quantize}, but each color carries its pixel population (sampled). */\nexport function quantizeWithCounts(\n pixels: ArrayLike<number>,\n maxColors = 6,\n step = 1,\n): { rgb: RGB; population: number }[] {\n if (maxColors < 1) return [];\n return run(pixels, maxColors, step).map((box) => ({ rgb: box.average(), population: box.count() }));\n}\n","/**\n * Export a palette into developer-ready formats: hex array, CSS variables,\n * SCSS, JSON, an SVG preview strip, and a Tailwind color config (with\n * perceptual ramps).\n */\n\nimport { ramp, RAMP_STOPS } from \"./color.js\";\nimport type { Swatch } from \"./index.js\";\n\nconst slug = (i: number, prefix: string): string => `${prefix}-${i + 1}`;\n\n/** Just the hex strings, in palette order. */\nexport function toArray(swatches: Swatch[]): string[] {\n return swatches.map((s) => s.hex);\n}\n\n/** `:root { --color-1: #...; ... }` */\nexport function toCSS(swatches: Swatch[], prefix = \"color\"): string {\n const lines = swatches.map((s, i) => ` --${slug(i, prefix)}: ${s.hex};`);\n return `:root {\\n${lines.join(\"\\n\")}\\n}`;\n}\n\n/** `$color-1: #...;` SCSS variables. */\nexport function toSCSS(swatches: Swatch[], prefix = \"color\"): string {\n return swatches.map((s, i) => `$${slug(i, prefix)}: ${s.hex};`).join(\"\\n\");\n}\n\n/** Pretty JSON of the palette (hex, rgb, hsl, population). */\nexport function toJSON(swatches: Swatch[]): string {\n return JSON.stringify(\n swatches.map((s) => ({ hex: s.hex, rgb: s.rgb, hsl: s.hsl, population: s.population })),\n null,\n 2,\n );\n}\n\n/** A standalone SVG strip of swatches β€” great for READMEs and previews. */\nexport function toSVG(swatches: Swatch[], options: { size?: number } = {}): string {\n const size = options.size ?? 80;\n const w = size * swatches.length;\n const rects = swatches\n .map((s, i) => {\n const x = i * size;\n const label = s.hex.toUpperCase();\n return (\n `<rect x=\"${x}\" y=\"0\" width=\"${size}\" height=\"${size}\" fill=\"${s.hex}\"/>` +\n `<text x=\"${x + size / 2}\" y=\"${size - 8}\" font-family=\"monospace\" font-size=\"10\" ` +\n `text-anchor=\"middle\" fill=\"${s.textColor}\">${label}</text>`\n );\n })\n .join(\"\");\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${w}\" height=\"${size}\" viewBox=\"0 0 ${w} ${size}\">${rects}</svg>`;\n}\n\n/**\n * A Tailwind `theme.extend.colors` object. Each swatch becomes a named color\n * with a full 50–950 perceptual ramp (great for dropping into a config).\n *\n * ```ts\n * toTailwind(palette(img), [\"brand\", \"accent\"]);\n * // { brand: { 50: \"#...\", ..., 950: \"#...\" }, accent: { ... }, ... }\n * ```\n */\nexport function toTailwind(\n swatches: Swatch[],\n names: string[] = [],\n): Record<string, Record<string, string>> {\n const out: Record<string, Record<string, string>> = {};\n swatches.forEach((s, i) => {\n const name = names[i] ?? `palette-${i + 1}`;\n const stops = ramp(s.rgb);\n const obj: Record<string, string> = {};\n RAMP_STOPS.forEach((stop, j) => {\n obj[stop] = stops[j] as string;\n });\n out[name] = obj;\n });\n return out;\n}\n","/**\n * huebrew β€” brew a usable color palette & theme tokens from any image.\n *\n * Zero dependencies. The core works on raw RGBA pixels, so it runs in the\n * browser (via canvas `ImageData`), in Node (via any decoder you already use),\n * Deno or Bun β€” and never makes a network call.\n */\n\nimport { isLight, rgbToHex, rgbToHsl, textColorFor, type RGB } from \"./color.js\";\nimport { quantize, quantizeWithCounts } from \"./quantize.js\";\n\nexport type { RGB } from \"./color.js\";\nexport { rgbToHex, hexToRgb, rgbToHsl, ramp, contrast, luminance, textColorFor, RAMP_STOPS } from \"./color.js\";\nexport { quantize, quantizeWithCounts } from \"./quantize.js\";\nexport * from \"./format.js\";\n\nexport interface Swatch {\n rgb: RGB;\n hex: string;\n hsl: { h: number; s: number; l: number };\n /** Sampled pixel population for this color (higher = more dominant). */\n population: number;\n isLight: boolean;\n /** `\"#000000\"` or `\"#ffffff\"` β€” whichever is readable on this swatch (WCAG). */\n textColor: string;\n}\n\n/** An RGBA pixel buffer, or anything `ImageData`-shaped (`{ data, width, height }`). */\nexport type PixelSource = ArrayLike<number> | { data: ArrayLike<number>; width?: number; height?: number };\n\nexport interface PaletteOptions {\n /** How many colors to extract. Default `6`. */\n colors?: number;\n /** Sample every Nth pixel. Default: auto (targets ~20k samples for speed). */\n step?: number;\n}\n\nfunction toPixels(source: PixelSource): ArrayLike<number> {\n if (typeof source === \"object\" && source !== null && \"data\" in source) return source.data;\n return source;\n}\n\nfunction autoStep(pixelCount: number): number {\n return Math.max(1, Math.round(Math.sqrt(pixelCount / 20000)));\n}\n\nfunction makeSwatch(rgb: RGB, population: number): Swatch {\n return {\n rgb,\n hex: rgbToHex(rgb),\n hsl: rgbToHsl(rgb),\n population,\n isLight: isLight(rgb),\n textColor: textColorFor(rgb),\n };\n}\n\n/**\n * Extract a palette from an image's pixels, most-dominant first.\n *\n * ```ts\n * // Browser\n * const ctx = canvas.getContext(\"2d\")!;\n * const swatches = palette(ctx.getImageData(0, 0, canvas.width, canvas.height), { colors: 6 });\n * swatches[0].hex; // dominant color\n * ```\n */\nexport function palette(source: PixelSource, options: PaletteOptions = {}): Swatch[] {\n const pixels = toPixels(source);\n const colors = options.colors ?? 6;\n const step = options.step ?? autoStep(pixels.length / 4);\n return quantizeWithCounts(pixels, colors, step).map(({ rgb, population }) =>\n makeSwatch(rgb, population),\n );\n}\n\n/** The single most dominant color, or `null` for an empty/transparent image. */\nexport function dominant(source: PixelSource, options: PaletteOptions = {}): Swatch | null {\n return palette(source, { ...options, colors: Math.max(options.colors ?? 5, 5) })[0] ?? null;\n}\n"]}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Color math for huebrew: hex/rgb/hsl conversion, WCAG luminance, and an
3
+ * OKLab-based lightness ramp (perceptually even tints/shades).
4
+ */
5
+ type RGB = [number, number, number];
6
+ declare function rgbToHex([r, g, b]: RGB): string;
7
+ declare function hexToRgb(hex: string): RGB;
8
+ declare function rgbToHsl([r, g, b]: RGB): {
9
+ h: number;
10
+ s: number;
11
+ l: number;
12
+ };
13
+ /** WCAG relative luminance (0–1). */
14
+ declare function luminance([r, g, b]: RGB): number;
15
+ /** WCAG contrast ratio between two colors (1–21). */
16
+ declare function contrast(a: RGB, b: RGB): number;
17
+ /** Black or white β€” whichever is more readable on `bg`. */
18
+ declare function textColorFor(bg: RGB): "#000000" | "#ffffff";
19
+ /** Tailwind-style ramp stops and their target OKLab lightness. */
20
+ declare const RAMP_STOPS: readonly [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
21
+ /**
22
+ * Generate a perceptually-even tint/shade ramp from a base color by holding its
23
+ * OKLab hue & chroma and walking lightness through Tailwind-like stops.
24
+ * Returns hex strings, light β†’ dark, one per {@link RAMP_STOPS} entry.
25
+ */
26
+ declare function ramp(base: RGB): string[];
27
+
28
+ /**
29
+ * Modified Median Cut Quantization (MMCQ) β€” extract a small, representative
30
+ * palette from a large set of pixels, deterministically and dependency-free.
31
+ *
32
+ * The classic approach: bucket colors into a 3D histogram, then repeatedly
33
+ * split the box containing the most pixels along its longest axis at the
34
+ * population median, prioritizing first by population and then by populationΓ—volume.
35
+ */
36
+
37
+ /**
38
+ * Quantize `pixels` (a flat RGBA array) down to at most `maxColors` colors.
39
+ * `step` samples every Nth pixel for speed. Returns RGB triplets ordered by
40
+ * population (most common first).
41
+ */
42
+ declare function quantize(pixels: ArrayLike<number>, maxColors?: number, step?: number): RGB[];
43
+ /** Like {@link quantize}, but each color carries its pixel population (sampled). */
44
+ declare function quantizeWithCounts(pixels: ArrayLike<number>, maxColors?: number, step?: number): {
45
+ rgb: RGB;
46
+ population: number;
47
+ }[];
48
+
49
+ /**
50
+ * Export a palette into developer-ready formats: hex array, CSS variables,
51
+ * SCSS, JSON, an SVG preview strip, and a Tailwind color config (with
52
+ * perceptual ramps).
53
+ */
54
+
55
+ /** Just the hex strings, in palette order. */
56
+ declare function toArray(swatches: Swatch[]): string[];
57
+ /** `:root { --color-1: #...; ... }` */
58
+ declare function toCSS(swatches: Swatch[], prefix?: string): string;
59
+ /** `$color-1: #...;` SCSS variables. */
60
+ declare function toSCSS(swatches: Swatch[], prefix?: string): string;
61
+ /** Pretty JSON of the palette (hex, rgb, hsl, population). */
62
+ declare function toJSON(swatches: Swatch[]): string;
63
+ /** A standalone SVG strip of swatches β€” great for READMEs and previews. */
64
+ declare function toSVG(swatches: Swatch[], options?: {
65
+ size?: number;
66
+ }): string;
67
+ /**
68
+ * A Tailwind `theme.extend.colors` object. Each swatch becomes a named color
69
+ * with a full 50–950 perceptual ramp (great for dropping into a config).
70
+ *
71
+ * ```ts
72
+ * toTailwind(palette(img), ["brand", "accent"]);
73
+ * // { brand: { 50: "#...", ..., 950: "#..." }, accent: { ... }, ... }
74
+ * ```
75
+ */
76
+ declare function toTailwind(swatches: Swatch[], names?: string[]): Record<string, Record<string, string>>;
77
+
78
+ /**
79
+ * huebrew β€” brew a usable color palette & theme tokens from any image.
80
+ *
81
+ * Zero dependencies. The core works on raw RGBA pixels, so it runs in the
82
+ * browser (via canvas `ImageData`), in Node (via any decoder you already use),
83
+ * Deno or Bun β€” and never makes a network call.
84
+ */
85
+
86
+ interface Swatch {
87
+ rgb: RGB;
88
+ hex: string;
89
+ hsl: {
90
+ h: number;
91
+ s: number;
92
+ l: number;
93
+ };
94
+ /** Sampled pixel population for this color (higher = more dominant). */
95
+ population: number;
96
+ isLight: boolean;
97
+ /** `"#000000"` or `"#ffffff"` β€” whichever is readable on this swatch (WCAG). */
98
+ textColor: string;
99
+ }
100
+ /** An RGBA pixel buffer, or anything `ImageData`-shaped (`{ data, width, height }`). */
101
+ type PixelSource = ArrayLike<number> | {
102
+ data: ArrayLike<number>;
103
+ width?: number;
104
+ height?: number;
105
+ };
106
+ interface PaletteOptions {
107
+ /** How many colors to extract. Default `6`. */
108
+ colors?: number;
109
+ /** Sample every Nth pixel. Default: auto (targets ~20k samples for speed). */
110
+ step?: number;
111
+ }
112
+ /**
113
+ * Extract a palette from an image's pixels, most-dominant first.
114
+ *
115
+ * ```ts
116
+ * // Browser
117
+ * const ctx = canvas.getContext("2d")!;
118
+ * const swatches = palette(ctx.getImageData(0, 0, canvas.width, canvas.height), { colors: 6 });
119
+ * swatches[0].hex; // dominant color
120
+ * ```
121
+ */
122
+ declare function palette(source: PixelSource, options?: PaletteOptions): Swatch[];
123
+ /** The single most dominant color, or `null` for an empty/transparent image. */
124
+ declare function dominant(source: PixelSource, options?: PaletteOptions): Swatch | null;
125
+
126
+ export { type PaletteOptions, type PixelSource, RAMP_STOPS, type RGB, type Swatch, contrast, dominant, hexToRgb, luminance, palette, quantize, quantizeWithCounts, ramp, rgbToHex, rgbToHsl, textColorFor, toArray, toCSS, toJSON, toSCSS, toSVG, toTailwind };
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Color math for huebrew: hex/rgb/hsl conversion, WCAG luminance, and an
3
+ * OKLab-based lightness ramp (perceptually even tints/shades).
4
+ */
5
+ type RGB = [number, number, number];
6
+ declare function rgbToHex([r, g, b]: RGB): string;
7
+ declare function hexToRgb(hex: string): RGB;
8
+ declare function rgbToHsl([r, g, b]: RGB): {
9
+ h: number;
10
+ s: number;
11
+ l: number;
12
+ };
13
+ /** WCAG relative luminance (0–1). */
14
+ declare function luminance([r, g, b]: RGB): number;
15
+ /** WCAG contrast ratio between two colors (1–21). */
16
+ declare function contrast(a: RGB, b: RGB): number;
17
+ /** Black or white β€” whichever is more readable on `bg`. */
18
+ declare function textColorFor(bg: RGB): "#000000" | "#ffffff";
19
+ /** Tailwind-style ramp stops and their target OKLab lightness. */
20
+ declare const RAMP_STOPS: readonly [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
21
+ /**
22
+ * Generate a perceptually-even tint/shade ramp from a base color by holding its
23
+ * OKLab hue & chroma and walking lightness through Tailwind-like stops.
24
+ * Returns hex strings, light β†’ dark, one per {@link RAMP_STOPS} entry.
25
+ */
26
+ declare function ramp(base: RGB): string[];
27
+
28
+ /**
29
+ * Modified Median Cut Quantization (MMCQ) β€” extract a small, representative
30
+ * palette from a large set of pixels, deterministically and dependency-free.
31
+ *
32
+ * The classic approach: bucket colors into a 3D histogram, then repeatedly
33
+ * split the box containing the most pixels along its longest axis at the
34
+ * population median, prioritizing first by population and then by populationΓ—volume.
35
+ */
36
+
37
+ /**
38
+ * Quantize `pixels` (a flat RGBA array) down to at most `maxColors` colors.
39
+ * `step` samples every Nth pixel for speed. Returns RGB triplets ordered by
40
+ * population (most common first).
41
+ */
42
+ declare function quantize(pixels: ArrayLike<number>, maxColors?: number, step?: number): RGB[];
43
+ /** Like {@link quantize}, but each color carries its pixel population (sampled). */
44
+ declare function quantizeWithCounts(pixels: ArrayLike<number>, maxColors?: number, step?: number): {
45
+ rgb: RGB;
46
+ population: number;
47
+ }[];
48
+
49
+ /**
50
+ * Export a palette into developer-ready formats: hex array, CSS variables,
51
+ * SCSS, JSON, an SVG preview strip, and a Tailwind color config (with
52
+ * perceptual ramps).
53
+ */
54
+
55
+ /** Just the hex strings, in palette order. */
56
+ declare function toArray(swatches: Swatch[]): string[];
57
+ /** `:root { --color-1: #...; ... }` */
58
+ declare function toCSS(swatches: Swatch[], prefix?: string): string;
59
+ /** `$color-1: #...;` SCSS variables. */
60
+ declare function toSCSS(swatches: Swatch[], prefix?: string): string;
61
+ /** Pretty JSON of the palette (hex, rgb, hsl, population). */
62
+ declare function toJSON(swatches: Swatch[]): string;
63
+ /** A standalone SVG strip of swatches β€” great for READMEs and previews. */
64
+ declare function toSVG(swatches: Swatch[], options?: {
65
+ size?: number;
66
+ }): string;
67
+ /**
68
+ * A Tailwind `theme.extend.colors` object. Each swatch becomes a named color
69
+ * with a full 50–950 perceptual ramp (great for dropping into a config).
70
+ *
71
+ * ```ts
72
+ * toTailwind(palette(img), ["brand", "accent"]);
73
+ * // { brand: { 50: "#...", ..., 950: "#..." }, accent: { ... }, ... }
74
+ * ```
75
+ */
76
+ declare function toTailwind(swatches: Swatch[], names?: string[]): Record<string, Record<string, string>>;
77
+
78
+ /**
79
+ * huebrew β€” brew a usable color palette & theme tokens from any image.
80
+ *
81
+ * Zero dependencies. The core works on raw RGBA pixels, so it runs in the
82
+ * browser (via canvas `ImageData`), in Node (via any decoder you already use),
83
+ * Deno or Bun β€” and never makes a network call.
84
+ */
85
+
86
+ interface Swatch {
87
+ rgb: RGB;
88
+ hex: string;
89
+ hsl: {
90
+ h: number;
91
+ s: number;
92
+ l: number;
93
+ };
94
+ /** Sampled pixel population for this color (higher = more dominant). */
95
+ population: number;
96
+ isLight: boolean;
97
+ /** `"#000000"` or `"#ffffff"` β€” whichever is readable on this swatch (WCAG). */
98
+ textColor: string;
99
+ }
100
+ /** An RGBA pixel buffer, or anything `ImageData`-shaped (`{ data, width, height }`). */
101
+ type PixelSource = ArrayLike<number> | {
102
+ data: ArrayLike<number>;
103
+ width?: number;
104
+ height?: number;
105
+ };
106
+ interface PaletteOptions {
107
+ /** How many colors to extract. Default `6`. */
108
+ colors?: number;
109
+ /** Sample every Nth pixel. Default: auto (targets ~20k samples for speed). */
110
+ step?: number;
111
+ }
112
+ /**
113
+ * Extract a palette from an image's pixels, most-dominant first.
114
+ *
115
+ * ```ts
116
+ * // Browser
117
+ * const ctx = canvas.getContext("2d")!;
118
+ * const swatches = palette(ctx.getImageData(0, 0, canvas.width, canvas.height), { colors: 6 });
119
+ * swatches[0].hex; // dominant color
120
+ * ```
121
+ */
122
+ declare function palette(source: PixelSource, options?: PaletteOptions): Swatch[];
123
+ /** The single most dominant color, or `null` for an empty/transparent image. */
124
+ declare function dominant(source: PixelSource, options?: PaletteOptions): Swatch | null;
125
+
126
+ export { type PaletteOptions, type PixelSource, RAMP_STOPS, type RGB, type Swatch, contrast, dominant, hexToRgb, luminance, palette, quantize, quantizeWithCounts, ramp, rgbToHex, rgbToHsl, textColorFor, toArray, toCSS, toJSON, toSCSS, toSVG, toTailwind };
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ var T=(t,r,n)=>t<r?r:t>n?n:t;function B([t,r,n]){let e=o=>T(Math.round(o),0,255).toString(16).padStart(2,"0");return `#${e(t)}${e(r)}${e(n)}`}function C(t){let r=t.replace(/^#/,"");r.length===3&&(r=r.split("").map(e=>e+e).join(""));let n=parseInt(r,16);return [n>>16&255,n>>8&255,n&255]}function I([t,r,n]){t/=255,r/=255,n/=255;let e=Math.max(t,r,n),o=Math.min(t,r,n),u=(e+o)/2;if(e===o)return {h:0,s:0,l:Math.round(u*100)};let i=e-o,s=u>.5?i/(2-e-o):i/(e+o),a;return e===t?a=(r-n)/i+(r<n?6:0):e===r?a=(n-t)/i+2:a=(t-r)/i+4,{h:Math.round(a*60),s:Math.round(s*100),l:Math.round(u*100)}}var R=t=>t<=.04045?t/12.92:((t+.055)/1.055)**2.4,x=t=>t<=.0031308?12.92*t:1.055*t**(1/2.4)-.055;function M([t,r,n]){return .2126*R(t/255)+.7152*R(r/255)+.0722*R(n/255)}function F(t,r){let n=M(t),e=M(r),[o,u]=n>=e?[n,e]:[e,n];return (o+.05)/(u+.05)}function w(t){return M(t)>=.179?"#000000":"#ffffff"}var O=t=>M(t)>=.179;function z([t,r,n]){let e=R(t/255),o=R(r/255),u=R(n/255),i=Math.cbrt(.4122214708*e+.5363325363*o+.0514459929*u),s=Math.cbrt(.2119034982*e+.6806995451*o+.1073969566*u),a=Math.cbrt(.0883024619*e+.2817188376*o+.6299787005*u);return {L:.2104542553*i+.793617785*s-.0040720468*a,a:1.9779984951*i-2.428592205*s+.4505937099*a,b:.0259040371*i+.7827717662*s-.808675766*a}}function j({L:t,a:r,b:n}){let e=(t+.3963377774*r+.2158037573*n)**3,o=(t-.1055613458*r-.0638541728*n)**3,u=(t-.0894841775*r-1.291485548*n)**3,i=4.0767416621*e-3.3077115913*o+.2309699292*u,s=-1.2684380046*e+2.6097574011*o-.3413193965*u,a=-0.0041960863*e-.7034186147*o+1.707614701*u;return [T(x(i),0,1)*255,T(x(s),0,1)*255,T(x(a),0,1)*255]}var G=[50,100,200,300,400,500,600,700,800,900,950],q=[.971,.936,.885,.808,.704,.602,.515,.43,.366,.317,.235];function $(t){let{a:r,b:n}=z(t);return q.map(e=>B(j({L:e,a:r,b:n})))}var g=(t,r,n)=>(t<<10)+(r<<5)+n,A=class t{constructor(r,n,e,o,u,i,s){this.r1=r;this.r2=n;this.g1=e;this.g2=o;this.b1=u;this.b2=i;this.histo=s;}r1;r2;g1;g2;b1;b2;histo;_count=-1;_avg=null;volume(){return (this.r2-this.r1+1)*(this.g2-this.g1+1)*(this.b2-this.b1+1)}count(){if(this._count>=0)return this._count;let r=0;for(let n=this.r1;n<=this.r2;n++)for(let e=this.g1;e<=this.g2;e++)for(let o=this.b1;o<=this.b2;o++)r+=this.histo[g(n,e,o)];return this._count=r,r}average(){if(this._avg)return this._avg;let r=0,n=0,e=0,o=0,u=8;for(let i=this.r1;i<=this.r2;i++)for(let s=this.g1;s<=this.g2;s++)for(let a=this.b1;a<=this.b2;a++){let c=this.histo[g(i,s,a)];r+=c,n+=c*(i+.5)*u,e+=c*(s+.5)*u,o+=c*(a+.5)*u;}return this._avg=r?[Math.round(n/r),Math.round(e/r),Math.round(o/r)]:[Math.round(u*(this.r1+this.r2+1)/2),Math.round(u*(this.g1+this.g2+1)/2),Math.round(u*(this.b1+this.b2+1)/2)],this._avg}clone(r,n,e,o,u,i){return new t(r,n,e,o,u,i,this.histo)}};function V(t,r){let n=new Int32Array(32768);for(let e=0;e<t.length;e+=4*r){let o=t[e+3];if(o!==void 0&&o<125)continue;let u=t[e]>>3,i=t[e+1]>>3,s=t[e+2]>>3,a=g(u,i,s);n[a]=n[a]+1;}return n}function E(t){let r=31,n=0,e=31,o=0,u=31,i=0;for(let s=0;s<32;s++)for(let a=0;a<32;a++)for(let c=0;c<32;c++)t[g(s,a,c)]>0&&(r=Math.min(r,s),n=Math.max(n,s),e=Math.min(e,a),o=Math.max(o,a),u=Math.min(u,c),i=Math.max(i,c));return new A(r,n,e,o,u,i,t)}function N(t){let r=t.count();if(r===0)return [t];let n=t.r2-t.r1+1,e=t.g2-t.g1+1,o=t.b2-t.b1+1,u=Math.max(n,e,o)===n?"r":Math.max(e,o)===e?"g":"b",i=[],s=0,{histo:a}=t,c=(p,k)=>{for(let S=p;S<=k;S++){let d=0;if(u==="r")for(let m=t.g1;m<=t.g2;m++)for(let h=t.b1;h<=t.b2;h++)d+=a[g(S,m,h)];else if(u==="g")for(let m=t.r1;m<=t.r2;m++)for(let h=t.b1;h<=t.b2;h++)d+=a[g(m,S,h)];else for(let m=t.r1;m<=t.r2;m++)for(let h=t.g1;h<=t.g2;h++)d+=a[g(m,h,S)];s+=d,i[S]=s;}},b=u==="r"?t.r1:u==="g"?t.g1:t.b1,f=u==="r"?t.r2:u==="g"?t.g2:t.b2;if(c(b,f),b===f)return [t];let l=b;for(let p=b;p<=f;p++)if(i[p]>r/2){l=Math.max(b,Math.min(f-1,p));break}let L=t.clone(t.r1,u==="r"?l:t.r2,t.g1,u==="g"?l:t.g2,t.b1,u==="b"?l:t.b2),H=t.clone(u==="r"?l+1:t.r1,t.r2,u==="g"?l+1:t.g1,t.g2,u==="b"?l+1:t.b1,t.b2);return [L,H]}function P(t,r,n){let e=V(t,Math.max(1,n)),o=[E(e)];if(o[0].count()===0)return [];let u=(i,s)=>{let a=0;for(;a++<1e3&&o.length<i;){o.sort((f,l)=>s?f.count()*f.volume()-l.count()*l.volume():f.count()-l.count());let c=o.pop();if(!c||c.count()===0){c&&o.push(c);break}let b=N(c);if(b.length===1){o.push(b[0]);break}o.push(b[0],b[1]);}};return u(Math.max(1,Math.floor(.75*r)),false),u(r,true),o.filter(i=>i.count()>0).sort((i,s)=>s.count()-i.count()).slice(0,r)}function U(t,r=6,n=1){return r<1?[]:P(t,r,n).map(e=>e.average())}function y(t,r=6,n=1){return r<1?[]:P(t,r,n).map(e=>({rgb:e.average(),population:e.count()}))}var _=(t,r)=>`${r}-${t+1}`;function Q(t){return t.map(r=>r.hex)}function v(t,r="color"){return `:root {
2
+ ${t.map((e,o)=>` --${_(o,r)}: ${e.hex};`).join(`
3
+ `)}
4
+ }`}function tt(t,r="color"){return t.map((n,e)=>`$${_(e,r)}: ${n.hex};`).join(`
5
+ `)}function rt(t){return JSON.stringify(t.map(r=>({hex:r.hex,rgb:r.rgb,hsl:r.hsl,population:r.population})),null,2)}function nt(t,r={}){let n=r.size??80,e=n*t.length,o=t.map((u,i)=>{let s=i*n,a=u.hex.toUpperCase();return `<rect x="${s}" y="0" width="${n}" height="${n}" fill="${u.hex}"/><text x="${s+n/2}" y="${n-8}" font-family="monospace" font-size="10" text-anchor="middle" fill="${u.textColor}">${a}</text>`}).join("");return `<svg xmlns="http://www.w3.org/2000/svg" width="${e}" height="${n}" viewBox="0 0 ${e} ${n}">${o}</svg>`}function et(t,r=[]){let n={};return t.forEach((e,o)=>{let u=r[o]??`palette-${o+1}`,i=$(e.rgb),s={};G.forEach((a,c)=>{s[a]=i[c];}),n[u]=s;}),n}function K(t){return typeof t=="object"&&t!==null&&"data"in t?t.data:t}function W(t){return Math.max(1,Math.round(Math.sqrt(t/2e4)))}function J(t,r){return {rgb:t,hex:B(t),hsl:I(t),population:r,isLight:O(t),textColor:w(t)}}function X(t,r={}){let n=K(t),e=r.colors??6,o=r.step??W(n.length/4);return y(n,e,o).map(({rgb:u,population:i})=>J(u,i))}function at(t,r={}){return X(t,{...r,colors:Math.max(r.colors??5,5)})[0]??null}
6
+ export{G as RAMP_STOPS,F as contrast,at as dominant,C as hexToRgb,M as luminance,X as palette,U as quantize,y as quantizeWithCounts,$ as ramp,B as rgbToHex,I as rgbToHsl,w as textColorFor,Q as toArray,v as toCSS,rt as toJSON,tt as toSCSS,nt as toSVG,et as toTailwind};//# sourceMappingURL=index.js.map
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/color.ts","../src/quantize.ts","../src/format.ts","../src/index.ts"],"names":["clamp","n","lo","hi","rgbToHex","r","g","b","h","hexToRgb","hex","c","rgbToHsl","max","min","l","d","srgbToLinear","linearToSrgb","luminance","contrast","a","la","lb","textColorFor","bg","isLight","rgb","rgbToOklab","lr","lg","m","s","oklabToRgb","L","RAMP_STOPS","RAMP_L","ramp","base","colorIndex","VBox","_VBox","r1","r2","g1","g2","b1","b2","histo","total","rs","gs","bs","mult","buildHisto","pixels","step","i","idx","vboxFromHisto","medianCut","vbox","rw","gw","bw","axis","partial","sum","accumulate","outer1","outer2","slice","splitPoint","run","maxColors","boxes","splitUntil","target","byVolume","iterations","x","y","biggest","parts","box","quantize","quantizeWithCounts","slug","prefix","toArray","swatches","toCSS","toSCSS","toJSON","toSVG","options","size","w","rects","label","toTailwind","names","out","name","stops","obj","stop","j","toPixels","source","autoStep","pixelCount","makeSwatch","population","palette","colors","dominant"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Color math for huebrew: hex/rgb/hsl conversion, WCAG luminance, and an\n * OKLab-based lightness ramp (perceptually even tints/shades).\n */\n\nexport type RGB = [number, number, number];\n\nconst clamp = (n: number, lo: number, hi: number): number => (n < lo ? lo : n > hi ? hi : n);\n\nexport function rgbToHex([r, g, b]: RGB): string {\n const h = (n: number) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, \"0\");\n return `#${h(r)}${h(g)}${h(b)}`;\n}\n\nexport function hexToRgb(hex: string): RGB {\n let h = hex.replace(/^#/, \"\");\n if (h.length === 3) h = h.split(\"\").map((c) => c + c).join(\"\");\n const n = parseInt(h, 16);\n return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\n\nexport function rgbToHsl([r, g, b]: RGB): { h: number; s: number; l: number } {\n r /= 255;\n g /= 255;\n b /= 255;\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2;\n if (max === min) return { h: 0, s: 0, l: Math.round(l * 100) };\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n let h: number;\n if (max === r) h = (g - b) / d + (g < b ? 6 : 0);\n else if (max === g) h = (b - r) / d + 2;\n else h = (r - g) / d + 4;\n return { h: Math.round(h * 60), s: Math.round(s * 100), l: Math.round(l * 100) };\n}\n\nconst srgbToLinear = (c: number): number =>\n c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\nconst linearToSrgb = (c: number): number =>\n c <= 0.0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055;\n\n/** WCAG relative luminance (0–1). */\nexport function luminance([r, g, b]: RGB): number {\n return 0.2126 * srgbToLinear(r / 255) + 0.7152 * srgbToLinear(g / 255) + 0.0722 * srgbToLinear(b / 255);\n}\n\n/** WCAG contrast ratio between two colors (1–21). */\nexport function contrast(a: RGB, b: RGB): number {\n const la = luminance(a);\n const lb = luminance(b);\n const [hi, lo] = la >= lb ? [la, lb] : [lb, la];\n return (hi + 0.05) / (lo + 0.05);\n}\n\n/** Black or white β€” whichever is more readable on `bg`. */\nexport function textColorFor(bg: RGB): \"#000000\" | \"#ffffff\" {\n return luminance(bg) >= 0.179 ? \"#000000\" : \"#ffffff\";\n}\n\nexport const isLight = (rgb: RGB): boolean => luminance(rgb) >= 0.179;\n\n// --- OKLab ---\ninterface OKLab {\n L: number;\n a: number;\n b: number;\n}\n\nfunction rgbToOklab([r, g, b]: RGB): OKLab {\n const lr = srgbToLinear(r / 255);\n const lg = srgbToLinear(g / 255);\n const lb = srgbToLinear(b / 255);\n const l = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);\n const m = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);\n const s = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb);\n return {\n L: 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,\n a: 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,\n b: 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,\n };\n}\n\nfunction oklabToRgb({ L, a, b }: OKLab): RGB {\n const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;\n const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;\n const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;\n const lr = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;\n const lg = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;\n const lb = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;\n return [\n clamp(linearToSrgb(lr), 0, 1) * 255,\n clamp(linearToSrgb(lg), 0, 1) * 255,\n clamp(linearToSrgb(lb), 0, 1) * 255,\n ];\n}\n\n/** Tailwind-style ramp stops and their target OKLab lightness. */\nexport const RAMP_STOPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as const;\nconst RAMP_L = [0.971, 0.936, 0.885, 0.808, 0.704, 0.602, 0.515, 0.43, 0.366, 0.317, 0.235];\n\n/**\n * Generate a perceptually-even tint/shade ramp from a base color by holding its\n * OKLab hue & chroma and walking lightness through Tailwind-like stops.\n * Returns hex strings, light β†’ dark, one per {@link RAMP_STOPS} entry.\n */\nexport function ramp(base: RGB): string[] {\n const { a, b } = rgbToOklab(base);\n return RAMP_L.map((L) => rgbToHex(oklabToRgb({ L, a, b })));\n}\n","/**\n * Modified Median Cut Quantization (MMCQ) β€” extract a small, representative\n * palette from a large set of pixels, deterministically and dependency-free.\n *\n * The classic approach: bucket colors into a 3D histogram, then repeatedly\n * split the box containing the most pixels along its longest axis at the\n * population median, prioritizing first by population and then by populationΓ—volume.\n */\n\nimport type { RGB } from \"./color.js\";\n\nconst SIGBITS = 5;\nconst RSHIFT = 8 - SIGBITS;\nconst HISTO_SIZE = 1 << (3 * SIGBITS);\nconst FRACT_BY_POPULATION = 0.75;\nconst MAX_ITERATIONS = 1000;\n\nconst colorIndex = (r: number, g: number, b: number): number =>\n (r << (2 * SIGBITS)) + (g << SIGBITS) + b;\n\nclass VBox {\n constructor(\n public r1: number,\n public r2: number,\n public g1: number,\n public g2: number,\n public b1: number,\n public b2: number,\n public readonly histo: Int32Array,\n ) {}\n\n private _count = -1;\n private _avg: RGB | null = null;\n\n volume(): number {\n return (this.r2 - this.r1 + 1) * (this.g2 - this.g1 + 1) * (this.b2 - this.b1 + 1);\n }\n\n count(): number {\n if (this._count >= 0) return this._count;\n let n = 0;\n for (let r = this.r1; r <= this.r2; r++)\n for (let g = this.g1; g <= this.g2; g++)\n for (let b = this.b1; b <= this.b2; b++) n += this.histo[colorIndex(r, g, b)] as number;\n this._count = n;\n return n;\n }\n\n average(): RGB {\n if (this._avg) return this._avg;\n let total = 0;\n let rs = 0;\n let gs = 0;\n let bs = 0;\n const mult = 1 << RSHIFT;\n for (let r = this.r1; r <= this.r2; r++)\n for (let g = this.g1; g <= this.g2; g++)\n for (let b = this.b1; b <= this.b2; b++) {\n const h = this.histo[colorIndex(r, g, b)] as number;\n total += h;\n rs += h * (r + 0.5) * mult;\n gs += h * (g + 0.5) * mult;\n bs += h * (b + 0.5) * mult;\n }\n this._avg = total\n ? [Math.round(rs / total), Math.round(gs / total), Math.round(bs / total)]\n : [\n Math.round((mult * (this.r1 + this.r2 + 1)) / 2),\n Math.round((mult * (this.g1 + this.g2 + 1)) / 2),\n Math.round((mult * (this.b1 + this.b2 + 1)) / 2),\n ];\n return this._avg;\n }\n\n clone(r1: number, r2: number, g1: number, g2: number, b1: number, b2: number): VBox {\n return new VBox(r1, r2, g1, g2, b1, b2, this.histo);\n }\n}\n\nfunction buildHisto(pixels: ArrayLike<number>, step: number): Int32Array {\n const histo = new Int32Array(HISTO_SIZE);\n for (let i = 0; i < pixels.length; i += 4 * step) {\n const a = pixels[i + 3];\n if (a !== undefined && a < 125) continue; // skip mostly-transparent pixels\n const r = (pixels[i] as number) >> RSHIFT;\n const g = (pixels[i + 1] as number) >> RSHIFT;\n const b = (pixels[i + 2] as number) >> RSHIFT;\n const idx = colorIndex(r, g, b);\n histo[idx] = (histo[idx] as number) + 1;\n }\n return histo;\n}\n\nfunction vboxFromHisto(histo: Int32Array): VBox {\n let r1 = 31, r2 = 0, g1 = 31, g2 = 0, b1 = 31, b2 = 0;\n for (let r = 0; r < 32; r++)\n for (let g = 0; g < 32; g++)\n for (let b = 0; b < 32; b++) {\n if ((histo[colorIndex(r, g, b)] as number) > 0) {\n r1 = Math.min(r1, r); r2 = Math.max(r2, r);\n g1 = Math.min(g1, g); g2 = Math.max(g2, g);\n b1 = Math.min(b1, b); b2 = Math.max(b2, b);\n }\n }\n return new VBox(r1, r2, g1, g2, b1, b2, histo);\n}\n\nfunction medianCut(vbox: VBox): [VBox, VBox] | [VBox] {\n const total = vbox.count();\n if (total === 0) return [vbox];\n\n const rw = vbox.r2 - vbox.r1 + 1;\n const gw = vbox.g2 - vbox.g1 + 1;\n const bw = vbox.b2 - vbox.b1 + 1;\n const axis = Math.max(rw, gw, bw) === rw ? \"r\" : Math.max(gw, bw) === gw ? \"g\" : \"b\";\n\n const partial: number[] = [];\n let sum = 0;\n const { histo } = vbox;\n\n const accumulate = (outer1: number, outer2: number) => {\n for (let i = outer1; i <= outer2; i++) {\n let slice = 0;\n if (axis === \"r\") {\n for (let g = vbox.g1; g <= vbox.g2; g++)\n for (let b = vbox.b1; b <= vbox.b2; b++) slice += histo[colorIndex(i, g, b)] as number;\n } else if (axis === \"g\") {\n for (let r = vbox.r1; r <= vbox.r2; r++)\n for (let b = vbox.b1; b <= vbox.b2; b++) slice += histo[colorIndex(r, i, b)] as number;\n } else {\n for (let r = vbox.r1; r <= vbox.r2; r++)\n for (let g = vbox.g1; g <= vbox.g2; g++) slice += histo[colorIndex(r, g, i)] as number;\n }\n sum += slice;\n partial[i] = sum;\n }\n };\n\n const lo = axis === \"r\" ? vbox.r1 : axis === \"g\" ? vbox.g1 : vbox.b1;\n const hi = axis === \"r\" ? vbox.r2 : axis === \"g\" ? vbox.g2 : vbox.b2;\n accumulate(lo, hi);\n\n if (lo === hi) return [vbox]; // can't split a single slice\n\n let splitPoint = lo;\n for (let i = lo; i <= hi; i++) {\n if ((partial[i] as number) > total / 2) {\n splitPoint = Math.max(lo, Math.min(hi - 1, i));\n break;\n }\n }\n\n const a = vbox.clone(\n vbox.r1, axis === \"r\" ? splitPoint : vbox.r2,\n vbox.g1, axis === \"g\" ? splitPoint : vbox.g2,\n vbox.b1, axis === \"b\" ? splitPoint : vbox.b2,\n );\n const b = vbox.clone(\n axis === \"r\" ? splitPoint + 1 : vbox.r1, vbox.r2,\n axis === \"g\" ? splitPoint + 1 : vbox.g1, vbox.g2,\n axis === \"b\" ? splitPoint + 1 : vbox.b1, vbox.b2,\n );\n return [a, b];\n}\n\nfunction run(pixels: ArrayLike<number>, maxColors: number, step: number): VBox[] {\n const histo = buildHisto(pixels, Math.max(1, step));\n const boxes = [vboxFromHisto(histo)];\n if (boxes[0]!.count() === 0) return [];\n\n const splitUntil = (target: number, byVolume: boolean) => {\n let iterations = 0;\n while (iterations++ < MAX_ITERATIONS && boxes.length < target) {\n boxes.sort((x, y) =>\n byVolume ? x.count() * x.volume() - y.count() * y.volume() : x.count() - y.count(),\n );\n const biggest = boxes.pop();\n if (!biggest || biggest.count() === 0) {\n if (biggest) boxes.push(biggest);\n break;\n }\n const parts = medianCut(biggest);\n if (parts.length === 1) {\n boxes.push(parts[0]); // unsplittable; leave it and stop trying\n break;\n }\n boxes.push(parts[0], parts[1]);\n }\n };\n\n splitUntil(Math.max(1, Math.floor(FRACT_BY_POPULATION * maxColors)), false);\n splitUntil(maxColors, true);\n\n return boxes\n .filter((box) => box.count() > 0)\n .sort((x, y) => y.count() - x.count())\n .slice(0, maxColors);\n}\n\n/**\n * Quantize `pixels` (a flat RGBA array) down to at most `maxColors` colors.\n * `step` samples every Nth pixel for speed. Returns RGB triplets ordered by\n * population (most common first).\n */\nexport function quantize(pixels: ArrayLike<number>, maxColors = 6, step = 1): RGB[] {\n if (maxColors < 1) return [];\n return run(pixels, maxColors, step).map((box) => box.average());\n}\n\n/** Like {@link quantize}, but each color carries its pixel population (sampled). */\nexport function quantizeWithCounts(\n pixels: ArrayLike<number>,\n maxColors = 6,\n step = 1,\n): { rgb: RGB; population: number }[] {\n if (maxColors < 1) return [];\n return run(pixels, maxColors, step).map((box) => ({ rgb: box.average(), population: box.count() }));\n}\n","/**\n * Export a palette into developer-ready formats: hex array, CSS variables,\n * SCSS, JSON, an SVG preview strip, and a Tailwind color config (with\n * perceptual ramps).\n */\n\nimport { ramp, RAMP_STOPS } from \"./color.js\";\nimport type { Swatch } from \"./index.js\";\n\nconst slug = (i: number, prefix: string): string => `${prefix}-${i + 1}`;\n\n/** Just the hex strings, in palette order. */\nexport function toArray(swatches: Swatch[]): string[] {\n return swatches.map((s) => s.hex);\n}\n\n/** `:root { --color-1: #...; ... }` */\nexport function toCSS(swatches: Swatch[], prefix = \"color\"): string {\n const lines = swatches.map((s, i) => ` --${slug(i, prefix)}: ${s.hex};`);\n return `:root {\\n${lines.join(\"\\n\")}\\n}`;\n}\n\n/** `$color-1: #...;` SCSS variables. */\nexport function toSCSS(swatches: Swatch[], prefix = \"color\"): string {\n return swatches.map((s, i) => `$${slug(i, prefix)}: ${s.hex};`).join(\"\\n\");\n}\n\n/** Pretty JSON of the palette (hex, rgb, hsl, population). */\nexport function toJSON(swatches: Swatch[]): string {\n return JSON.stringify(\n swatches.map((s) => ({ hex: s.hex, rgb: s.rgb, hsl: s.hsl, population: s.population })),\n null,\n 2,\n );\n}\n\n/** A standalone SVG strip of swatches β€” great for READMEs and previews. */\nexport function toSVG(swatches: Swatch[], options: { size?: number } = {}): string {\n const size = options.size ?? 80;\n const w = size * swatches.length;\n const rects = swatches\n .map((s, i) => {\n const x = i * size;\n const label = s.hex.toUpperCase();\n return (\n `<rect x=\"${x}\" y=\"0\" width=\"${size}\" height=\"${size}\" fill=\"${s.hex}\"/>` +\n `<text x=\"${x + size / 2}\" y=\"${size - 8}\" font-family=\"monospace\" font-size=\"10\" ` +\n `text-anchor=\"middle\" fill=\"${s.textColor}\">${label}</text>`\n );\n })\n .join(\"\");\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${w}\" height=\"${size}\" viewBox=\"0 0 ${w} ${size}\">${rects}</svg>`;\n}\n\n/**\n * A Tailwind `theme.extend.colors` object. Each swatch becomes a named color\n * with a full 50–950 perceptual ramp (great for dropping into a config).\n *\n * ```ts\n * toTailwind(palette(img), [\"brand\", \"accent\"]);\n * // { brand: { 50: \"#...\", ..., 950: \"#...\" }, accent: { ... }, ... }\n * ```\n */\nexport function toTailwind(\n swatches: Swatch[],\n names: string[] = [],\n): Record<string, Record<string, string>> {\n const out: Record<string, Record<string, string>> = {};\n swatches.forEach((s, i) => {\n const name = names[i] ?? `palette-${i + 1}`;\n const stops = ramp(s.rgb);\n const obj: Record<string, string> = {};\n RAMP_STOPS.forEach((stop, j) => {\n obj[stop] = stops[j] as string;\n });\n out[name] = obj;\n });\n return out;\n}\n","/**\n * huebrew β€” brew a usable color palette & theme tokens from any image.\n *\n * Zero dependencies. The core works on raw RGBA pixels, so it runs in the\n * browser (via canvas `ImageData`), in Node (via any decoder you already use),\n * Deno or Bun β€” and never makes a network call.\n */\n\nimport { isLight, rgbToHex, rgbToHsl, textColorFor, type RGB } from \"./color.js\";\nimport { quantize, quantizeWithCounts } from \"./quantize.js\";\n\nexport type { RGB } from \"./color.js\";\nexport { rgbToHex, hexToRgb, rgbToHsl, ramp, contrast, luminance, textColorFor, RAMP_STOPS } from \"./color.js\";\nexport { quantize, quantizeWithCounts } from \"./quantize.js\";\nexport * from \"./format.js\";\n\nexport interface Swatch {\n rgb: RGB;\n hex: string;\n hsl: { h: number; s: number; l: number };\n /** Sampled pixel population for this color (higher = more dominant). */\n population: number;\n isLight: boolean;\n /** `\"#000000\"` or `\"#ffffff\"` β€” whichever is readable on this swatch (WCAG). */\n textColor: string;\n}\n\n/** An RGBA pixel buffer, or anything `ImageData`-shaped (`{ data, width, height }`). */\nexport type PixelSource = ArrayLike<number> | { data: ArrayLike<number>; width?: number; height?: number };\n\nexport interface PaletteOptions {\n /** How many colors to extract. Default `6`. */\n colors?: number;\n /** Sample every Nth pixel. Default: auto (targets ~20k samples for speed). */\n step?: number;\n}\n\nfunction toPixels(source: PixelSource): ArrayLike<number> {\n if (typeof source === \"object\" && source !== null && \"data\" in source) return source.data;\n return source;\n}\n\nfunction autoStep(pixelCount: number): number {\n return Math.max(1, Math.round(Math.sqrt(pixelCount / 20000)));\n}\n\nfunction makeSwatch(rgb: RGB, population: number): Swatch {\n return {\n rgb,\n hex: rgbToHex(rgb),\n hsl: rgbToHsl(rgb),\n population,\n isLight: isLight(rgb),\n textColor: textColorFor(rgb),\n };\n}\n\n/**\n * Extract a palette from an image's pixels, most-dominant first.\n *\n * ```ts\n * // Browser\n * const ctx = canvas.getContext(\"2d\")!;\n * const swatches = palette(ctx.getImageData(0, 0, canvas.width, canvas.height), { colors: 6 });\n * swatches[0].hex; // dominant color\n * ```\n */\nexport function palette(source: PixelSource, options: PaletteOptions = {}): Swatch[] {\n const pixels = toPixels(source);\n const colors = options.colors ?? 6;\n const step = options.step ?? autoStep(pixels.length / 4);\n return quantizeWithCounts(pixels, colors, step).map(({ rgb, population }) =>\n makeSwatch(rgb, population),\n );\n}\n\n/** The single most dominant color, or `null` for an empty/transparent image. */\nexport function dominant(source: PixelSource, options: PaletteOptions = {}): Swatch | null {\n return palette(source, { ...options, colors: Math.max(options.colors ?? 5, 5) })[0] ?? null;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "huebrew",
3
+ "version": "0.1.0",
4
+ "description": "Brew a color palette & ready-to-use theme tokens from any image β€” dominant colors via median-cut, perceptual OKLab ramps, and CSS/SCSS/Tailwind/JSON/SVG export. Zero dependencies, 100% local.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "sideEffects": false,
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "build:web": "vite build",
26
+ "dev": "vite",
27
+ "test": "vitest run",
28
+ "test:watch": "vitest",
29
+ "typecheck": "tsc --noEmit",
30
+ "lint": "tsc --noEmit",
31
+ "prepublishOnly": "npm run build"
32
+ },
33
+ "keywords": [
34
+ "color-palette",
35
+ "palette-extractor",
36
+ "image-colors",
37
+ "dominant-color",
38
+ "color-thief",
39
+ "median-cut",
40
+ "oklch",
41
+ "tailwind-colors",
42
+ "design-tokens",
43
+ "color-scheme",
44
+ "theme-generator",
45
+ "zero-dependency"
46
+ ],
47
+ "author": "didrod205 (https://github.com/didrod205)",
48
+ "license": "MIT",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/didrod205/huebrew.git"
52
+ },
53
+ "bugs": {
54
+ "url": "https://github.com/didrod205/huebrew/issues"
55
+ },
56
+ "homepage": "https://didrod205.github.io/huebrew/",
57
+ "devDependencies": {
58
+ "tsup": "^8.3.5",
59
+ "typescript": "^5.7.2",
60
+ "vite": "^6.0.0",
61
+ "vitest": "^2.1.8"
62
+ }
63
+ }