|
1 | | -import iwanthue from "iwanthue"; |
2 | | -import { every, values as getValues } from "lodash"; |
| 1 | +import iwanthue, { ColorSpaceArray, ColorSpacePreset } from "iwanthue"; |
| 2 | +import { labToHcl, rgbHexToLab } from "iwanthue/helpers"; |
| 3 | +import presets from "iwanthue/presets"; |
| 4 | +import { every, values as getValues, reverse, sortBy, toPairs } from "lodash"; |
3 | 5 |
|
4 | 6 | export function isColor(strColor: string): boolean { |
5 | 7 | const s = new Option().style; |
6 | 8 | s.color = strColor; |
7 | 9 | return s.color !== ""; |
8 | 10 | } |
9 | 11 |
|
| 12 | +const colorSpacePresetsAreas = sortBy( |
| 13 | + (toPairs(presets) as [ColorSpacePreset, ColorSpaceArray][]).map(([presetKey, preset]) => { |
| 14 | + // hRange can be expressed as a range from 330 to 360 and from 0 to 20 as [330, 20] |
| 15 | + // in that case the range has to be calculated differently |
| 16 | + const hRange = preset[1] >= preset[0] ? preset[1] - preset[0] : 360 - preset[0] + preset[1]; |
| 17 | + return { |
| 18 | + key: presetKey, |
| 19 | + area: hRange * (preset[3] - preset[2]) * (preset[5] - preset[4]), |
| 20 | + }; |
| 21 | + }), |
| 22 | + ({ area }) => area, |
| 23 | +); |
| 24 | + |
| 25 | +export const colorSpacePresetsSortedByArea = colorSpacePresetsAreas.map(({ key }) => key); |
| 26 | + |
| 27 | +export function detectSmallestCompatibleColorSpace(hexColors: string[]) { |
| 28 | + const colorSpace = colorSpacePresetsSortedByArea.find((presetKey) => { |
| 29 | + // test that all colors are include din the color area |
| 30 | + const areaBounds = presets[presetKey]; |
| 31 | + return hexColors |
| 32 | + .map((c) => labToHcl(rgbHexToLab(c))) |
| 33 | + .every( |
| 34 | + ([h, c, l]) => |
| 35 | + (areaBounds[0] <= areaBounds[1] |
| 36 | + ? areaBounds[0] <= h && h <= areaBounds[1] |
| 37 | + : areaBounds[0] <= h || h <= areaBounds[1]) && |
| 38 | + areaBounds[2] <= c && |
| 39 | + c <= areaBounds[3] && |
| 40 | + areaBounds[4] <= l && |
| 41 | + l <= areaBounds[5], |
| 42 | + ); |
| 43 | + }); |
| 44 | + |
| 45 | + return colorSpace; |
| 46 | +} |
| 47 | + |
10 | 48 | export function getPalette(values: string[], originalPalette?: Record<string, string | null>): Record<string, string> { |
11 | 49 | if (every(values, (v) => isColor(v))) { |
12 | 50 | return values.reduce((iter, v) => ({ ...iter, [v]: v }), {}); |
13 | 51 | } else { |
14 | 52 | const currentColors = getValues(originalPalette).filter((c) => c !== null); |
15 | | - const palette = iwanthue(values.length, { originalColorsToExpand: currentColors, colorSpace: "all" }); |
16 | | - const newColors = palette.filter((c) => !currentColors.includes(c)); |
17 | | - console.log(currentColors, palette, newColors); |
| 53 | + // heuristics to detect colorSpace: find the smallest area which contains all origin colors |
| 54 | + const colorSpace = detectSmallestCompatibleColorSpace(currentColors); |
| 55 | + console.log(colorSpace); |
| 56 | + const palette = iwanthue(values.length, { originalColorsToExpand: currentColors, colorSpace }); |
| 57 | + const newColors = reverse(palette.filter((c) => !currentColors.includes(c))); |
18 | 58 | return values.reduce( |
19 | | - (iter, v, i) => ({ ...iter, [v]: originalPalette && originalPalette[v] ? originalPalette[v] : newColors[i] }), |
| 59 | + (iter, v) => ({ ...iter, [v]: originalPalette && originalPalette[v] ? originalPalette[v] : newColors.pop() }), |
20 | 60 | {}, |
21 | 61 | ); |
22 | 62 | } |
|
0 commit comments