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 +21 -0
- package/README.md +174 -0
- package/dist/index.cjs +7 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +126 -0
- package/dist/index.d.ts +126 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
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
|
+
[](https://www.npmjs.com/package/huebrew)
|
|
8
|
+
[](https://bundlephobia.com/package/huebrew)
|
|
9
|
+
[](https://github.com/didrod205/huebrew/actions/workflows/ci.yml)
|
|
10
|
+
[](https://www.npmjs.com/package/huebrew)
|
|
11
|
+
[](./LICENSE)
|
|
12
|
+
|
|
13
|
+
**[π Try the free web studio β](https://didrod205.github.io/huebrew/)** Β· 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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|