@tokens-studio/tokenscript-schemas 0.0.11 → 0.0.13
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/dist/cli/index.cjs +31 -8
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +30 -8
- package/dist/cli/index.js.map +1 -1
- package/package.json +2 -1
- package/src/bundler/bundle-schema.ts +146 -0
- package/src/bundler/index.ts +151 -0
- package/src/bundler/schema-dependency-resolver.ts +299 -0
- package/src/bundler/selective-bundler.test.ts +94 -0
- package/src/bundler/selective-bundler.ts +159 -0
- package/src/bundler/types.ts +93 -0
- package/src/bundler/utils.ts +74 -0
- package/src/cli/commands/bundle.integration.test.ts +153 -0
- package/src/cli/commands/bundle.test.ts +57 -0
- package/src/cli/commands/bundle.ts +237 -0
- package/src/cli/commands/list.ts +109 -0
- package/src/cli/config-schema.ts +36 -0
- package/src/cli/index.ts +50 -0
- package/src/cli/output-generator.ts +63 -0
- package/src/downloader/index.ts +248 -0
- package/src/downloader/types.ts +48 -0
- package/src/index.ts +8 -0
- package/src/schemas/functions/adjust_chroma/adjust-chroma.tokenscript +27 -0
- package/src/schemas/functions/adjust_chroma/schema.json +48 -0
- package/src/schemas/functions/adjust_chroma/unit.test.ts +76 -0
- package/src/schemas/functions/adjust_hue/adjust-hue.tokenscript +32 -0
- package/src/schemas/functions/adjust_hue/schema.json +48 -0
- package/src/schemas/functions/adjust_hue/unit.test.ts +68 -0
- package/src/schemas/functions/adjust_lightness/adjust-lightness.tokenscript +31 -0
- package/src/schemas/functions/adjust_lightness/schema.json +48 -0
- package/src/schemas/functions/adjust_lightness/unit.test.ts +88 -0
- package/src/schemas/functions/adjust_to_contrast/adjust-to-contrast.tokenscript +131 -0
- package/src/schemas/functions/adjust_to_contrast/schema.json +56 -0
- package/src/schemas/functions/adjust_to_contrast/unit.test.ts +81 -0
- package/src/schemas/functions/alpha_blend/alpha-blend.tokenscript +46 -0
- package/src/schemas/functions/alpha_blend/schema.json +28 -0
- package/src/schemas/functions/alpha_blend/unit.test.ts +135 -0
- package/src/schemas/functions/alpha_scale/alpha-scale.tokenscript +38 -0
- package/src/schemas/functions/alpha_scale/schema.json +24 -0
- package/src/schemas/functions/alpha_scale/unit.test.ts +50 -0
- package/src/schemas/functions/analogous/analogous.tokenscript +47 -0
- package/src/schemas/functions/analogous/schema.json +28 -0
- package/src/schemas/functions/analogous/unit.test.ts +64 -0
- package/src/schemas/functions/apca_contrast/apca-contrast.tokenscript +129 -0
- package/src/schemas/functions/apca_contrast/schema.json +24 -0
- package/src/schemas/functions/apca_contrast/unit.test.ts +259 -0
- package/src/schemas/functions/are_similar/are-similar.tokenscript +24 -0
- package/src/schemas/functions/are_similar/schema.json +57 -0
- package/src/schemas/functions/are_similar/unit.test.ts +57 -0
- package/src/schemas/functions/auto_text_color/auto-text-color.tokenscript +41 -0
- package/src/schemas/functions/auto_text_color/schema.json +53 -0
- package/src/schemas/functions/auto_text_color/unit.test.ts +122 -0
- package/src/schemas/functions/best_contrast/best-contrast.tokenscript +66 -0
- package/src/schemas/functions/best_contrast/schema.json +24 -0
- package/src/schemas/functions/best_contrast/unit.test.ts +64 -0
- package/src/schemas/functions/chroma/chroma.tokenscript +12 -0
- package/src/schemas/functions/chroma/schema.json +44 -0
- package/src/schemas/functions/chroma/unit.test.ts +86 -0
- package/src/schemas/functions/clamp_chroma/clamp-chroma.tokenscript +21 -0
- package/src/schemas/functions/clamp_chroma/schema.json +52 -0
- package/src/schemas/functions/clamp_chroma/unit.test.ts +60 -0
- package/src/schemas/functions/clamp_lightness/clamp-lightness.tokenscript +21 -0
- package/src/schemas/functions/clamp_lightness/schema.json +52 -0
- package/src/schemas/functions/clamp_lightness/unit.test.ts +76 -0
- package/src/schemas/functions/clamp_to_gamut/clamp-to-gamut.tokenscript +41 -0
- package/src/schemas/functions/clamp_to_gamut/schema.json +20 -0
- package/src/schemas/functions/clamp_to_gamut/unit.test.ts +167 -0
- package/src/schemas/functions/complement/complement.tokenscript +21 -0
- package/src/schemas/functions/complement/schema.json +20 -0
- package/src/schemas/functions/complement/unit.test.ts +81 -0
- package/src/schemas/functions/contrast_ratio/contrast-ratio.tokenscript +36 -0
- package/src/schemas/functions/contrast_ratio/schema.json +24 -0
- package/src/schemas/functions/contrast_ratio/unit.test.ts +91 -0
- package/src/schemas/functions/cooler/cooler.tokenscript +45 -0
- package/src/schemas/functions/cooler/schema.json +43 -0
- package/src/schemas/functions/cooler/unit.test.ts +69 -0
- package/src/schemas/functions/darken/darken.tokenscript +37 -0
- package/src/schemas/functions/darken/schema.json +24 -0
- package/src/schemas/functions/darken/unit.test.ts +105 -0
- package/src/schemas/functions/delta_e_2000/delta-e-2000.tokenscript +184 -0
- package/src/schemas/functions/delta_e_2000/schema.json +36 -0
- package/src/schemas/functions/delta_e_2000/unit.test.ts +243 -0
- package/src/schemas/functions/delta_e_76/delta-e-76.tokenscript +45 -0
- package/src/schemas/functions/delta_e_76/schema.json +24 -0
- package/src/schemas/functions/delta_e_76/unit.test.ts +123 -0
- package/src/schemas/functions/delta_e_ok/delta-e-ok.tokenscript +43 -0
- package/src/schemas/functions/delta_e_ok/schema.json +24 -0
- package/src/schemas/functions/delta_e_ok/unit.test.ts +235 -0
- package/src/schemas/functions/desaturate/desaturate.tokenscript +32 -0
- package/src/schemas/functions/desaturate/schema.json +24 -0
- package/src/schemas/functions/desaturate/unit.test.ts +54 -0
- package/src/schemas/functions/distributed/distributed.tokenscript +54 -0
- package/src/schemas/functions/distributed/schema.json +32 -0
- package/src/schemas/functions/distributed/unit.test.ts +58 -0
- package/src/schemas/functions/diverging/diverging.tokenscript +58 -0
- package/src/schemas/functions/diverging/schema.json +32 -0
- package/src/schemas/functions/diverging/unit.test.ts +70 -0
- package/src/schemas/functions/grayscale/grayscale.tokenscript +17 -0
- package/src/schemas/functions/grayscale/schema.json +20 -0
- package/src/schemas/functions/grayscale/unit.test.ts +79 -0
- package/src/schemas/functions/harmonize/harmonize.tokenscript +61 -0
- package/src/schemas/functions/harmonize/schema.json +52 -0
- package/src/schemas/functions/harmonize/unit.test.ts +56 -0
- package/src/schemas/functions/hue/hue.tokenscript +12 -0
- package/src/schemas/functions/hue/schema.json +44 -0
- package/src/schemas/functions/hue/unit.test.ts +75 -0
- package/src/schemas/functions/hue_difference/hue-difference.tokenscript +42 -0
- package/src/schemas/functions/hue_difference/schema.json +24 -0
- package/src/schemas/functions/hue_difference/unit.test.ts +125 -0
- package/src/schemas/functions/in_gamut/in-gamut.tokenscript +51 -0
- package/src/schemas/functions/in_gamut/schema.json +24 -0
- package/src/schemas/functions/in_gamut/unit.test.ts +178 -0
- package/src/schemas/functions/interpolate/interpolate.tokenscript +61 -0
- package/src/schemas/functions/interpolate/schema.json +52 -0
- package/src/schemas/functions/interpolate/unit.test.ts +96 -0
- package/src/schemas/functions/invert/invert-initializer.tokenscript +29 -0
- package/src/schemas/functions/invert/schema.json +20 -0
- package/src/schemas/functions/invert/unit.test.ts +216 -0
- package/src/schemas/functions/is_cool/is-cool.tokenscript +41 -0
- package/src/schemas/functions/is_cool/schema.json +20 -0
- package/src/schemas/functions/is_cool/unit.test.ts +189 -0
- package/src/schemas/functions/is_dark/is-dark.tokenscript +16 -0
- package/src/schemas/functions/is_dark/schema.json +24 -0
- package/src/schemas/functions/is_dark/unit.test.ts +87 -0
- package/src/schemas/functions/is_light/is-light.tokenscript +16 -0
- package/src/schemas/functions/is_light/schema.json +24 -0
- package/src/schemas/functions/is_light/unit.test.ts +86 -0
- package/src/schemas/functions/is_neutral/is-neutral.tokenscript +16 -0
- package/src/schemas/functions/is_neutral/schema.json +53 -0
- package/src/schemas/functions/is_neutral/unit.test.ts +85 -0
- package/src/schemas/functions/is_warm/is-warm.tokenscript +62 -0
- package/src/schemas/functions/is_warm/schema.json +20 -0
- package/src/schemas/functions/is_warm/unit.test.ts +161 -0
- package/src/schemas/functions/lighten/lighten.tokenscript +37 -0
- package/src/schemas/functions/lighten/schema.json +24 -0
- package/src/schemas/functions/lighten/unit.test.ts +109 -0
- package/src/schemas/functions/lightness/lightness.tokenscript +12 -0
- package/src/schemas/functions/lightness/schema.json +49 -0
- package/src/schemas/functions/lightness/unit.test.ts +99 -0
- package/src/schemas/functions/luminance/luminance.tokenscript +16 -0
- package/src/schemas/functions/luminance/schema.json +20 -0
- package/src/schemas/functions/luminance/unit.test.ts +105 -0
- package/src/schemas/functions/meets_contrast/meets-contrast.tokenscript +49 -0
- package/src/schemas/functions/meets_contrast/schema.json +28 -0
- package/src/schemas/functions/meets_contrast/unit.test.ts +170 -0
- package/src/schemas/functions/mix/mix.tokenscript +47 -0
- package/src/schemas/functions/mix/schema.json +28 -0
- package/src/schemas/functions/mix/unit.test.ts +95 -0
- package/src/schemas/functions/monochromatic/monochromatic.tokenscript +72 -0
- package/src/schemas/functions/monochromatic/schema.json +24 -0
- package/src/schemas/functions/monochromatic/unit.test.ts +91 -0
- package/src/schemas/functions/muted/muted.tokenscript +25 -0
- package/src/schemas/functions/muted/schema.json +48 -0
- package/src/schemas/functions/muted/unit.test.ts +100 -0
- package/src/schemas/functions/neutral_variant/neutral-variant.tokenscript +23 -0
- package/src/schemas/functions/neutral_variant/schema.json +48 -0
- package/src/schemas/functions/neutral_variant/unit.test.ts +102 -0
- package/src/schemas/functions/relative_luminance/relative-luminance.tokenscript +15 -0
- package/src/schemas/functions/relative_luminance/schema.json +49 -0
- package/src/schemas/functions/relative_luminance/unit.test.ts +104 -0
- package/src/schemas/functions/rotate_hue/rotate-hue.tokenscript +20 -0
- package/src/schemas/functions/rotate_hue/schema.json +24 -0
- package/src/schemas/functions/rotate_hue/unit.test.ts +86 -0
- package/src/schemas/functions/saturate/saturate.tokenscript +33 -0
- package/src/schemas/functions/saturate/schema.json +24 -0
- package/src/schemas/functions/saturate/unit.test.ts +59 -0
- package/src/schemas/functions/scale_chroma/scale-chroma.tokenscript +22 -0
- package/src/schemas/functions/scale_chroma/schema.json +48 -0
- package/src/schemas/functions/scale_chroma/unit.test.ts +79 -0
- package/src/schemas/functions/scale_lightness/scale-lightness.tokenscript +23 -0
- package/src/schemas/functions/scale_lightness/schema.json +48 -0
- package/src/schemas/functions/scale_lightness/unit.test.ts +73 -0
- package/src/schemas/functions/sepia/schema.json +48 -0
- package/src/schemas/functions/sepia/sepia.tokenscript +54 -0
- package/src/schemas/functions/sepia/unit.test.ts +88 -0
- package/src/schemas/functions/set_chroma/schema.json +24 -0
- package/src/schemas/functions/set_chroma/set-chroma.tokenscript +18 -0
- package/src/schemas/functions/set_chroma/unit.test.ts +79 -0
- package/src/schemas/functions/set_hue/schema.json +24 -0
- package/src/schemas/functions/set_hue/set-hue.tokenscript +18 -0
- package/src/schemas/functions/set_hue/unit.test.ts +90 -0
- package/src/schemas/functions/set_lightness/schema.json +24 -0
- package/src/schemas/functions/set_lightness/set-lightness.tokenscript +18 -0
- package/src/schemas/functions/set_lightness/unit.test.ts +80 -0
- package/src/schemas/functions/shade_scale/schema.json +24 -0
- package/src/schemas/functions/shade_scale/shade-scale.tokenscript +61 -0
- package/src/schemas/functions/shade_scale/unit.test.ts +64 -0
- package/src/schemas/functions/split_complement/schema.json +24 -0
- package/src/schemas/functions/split_complement/split-complement.tokenscript +38 -0
- package/src/schemas/functions/split_complement/unit.test.ts +53 -0
- package/src/schemas/functions/steps/schema.json +28 -0
- package/src/schemas/functions/steps/steps.tokenscript +54 -0
- package/src/schemas/functions/steps/unit.test.ts +71 -0
- package/src/schemas/functions/tetradic/schema.json +20 -0
- package/src/schemas/functions/tetradic/tetradic.tokenscript +40 -0
- package/src/schemas/functions/tetradic/unit.test.ts +50 -0
- package/src/schemas/functions/tint_scale/schema.json +32 -0
- package/src/schemas/functions/tint_scale/tint-scale.tokenscript +71 -0
- package/src/schemas/functions/tint_scale/unit.test.ts +64 -0
- package/src/schemas/functions/to_gamut/schema.json +48 -0
- package/src/schemas/functions/to_gamut/to-gamut.tokenscript +96 -0
- package/src/schemas/functions/to_gamut/unit.test.ts +97 -0
- package/src/schemas/functions/triadic/schema.json +20 -0
- package/src/schemas/functions/triadic/triadic.tokenscript +33 -0
- package/src/schemas/functions/triadic/unit.test.ts +64 -0
- package/src/schemas/functions/vibrant/schema.json +48 -0
- package/src/schemas/functions/vibrant/unit.test.ts +55 -0
- package/src/schemas/functions/vibrant/vibrant.tokenscript +29 -0
- package/src/schemas/functions/warmer/schema.json +43 -0
- package/src/schemas/functions/warmer/unit.test.ts +69 -0
- package/src/schemas/functions/warmer/warmer.tokenscript +45 -0
- package/src/schemas/functions/wcag_level/schema.json +48 -0
- package/src/schemas/functions/wcag_level/unit.test.ts +75 -0
- package/src/schemas/functions/wcag_level/wcag-level.tokenscript +50 -0
- package/src/schemas/types/css-color/from-hsl-color.tokenscript +16 -0
- package/src/schemas/types/css-color/from-hwb-color.tokenscript +16 -0
- package/src/schemas/types/css-color/from-lab-color.tokenscript +16 -0
- package/src/schemas/types/css-color/from-lch-color.tokenscript +16 -0
- package/src/schemas/types/css-color/from-oklab-color.tokenscript +16 -0
- package/src/schemas/types/css-color/from-oklch-color.tokenscript +16 -0
- package/src/schemas/types/css-color/from-p3-color.tokenscript +16 -0
- package/src/schemas/types/css-color/from-rgb-color.tokenscript +15 -0
- package/src/schemas/types/css-color/from-srgb-color.tokenscript +16 -0
- package/src/schemas/types/css-color/from-srgb-linear-color.tokenscript +16 -0
- package/src/schemas/types/css-color/from-xyz-d50-color.tokenscript +16 -0
- package/src/schemas/types/css-color/from-xyz-d65-color.tokenscript +16 -0
- package/src/schemas/types/css-color/initializer.tokenscript +13 -0
- package/src/schemas/types/css-color/schema.json +148 -0
- package/src/schemas/types/css-color/unit.test.ts +402 -0
- package/src/schemas/types/hex-color/initializer.tokenscript +3 -0
- package/src/schemas/types/hex-color/schema.json +24 -0
- package/src/schemas/types/hex-color/unit.test.ts +123 -0
- package/src/schemas/types/hsl-color/from-srgb.tokenscript +87 -0
- package/src/schemas/types/hsl-color/initializer.tokenscript +16 -0
- package/src/schemas/types/hsl-color/schema.json +48 -0
- package/src/schemas/types/hsl-color/unit.test.ts +201 -0
- package/src/schemas/types/hsv-color/from-srgb.tokenscript +80 -0
- package/src/schemas/types/hsv-color/initializer.tokenscript +16 -0
- package/src/schemas/types/hsv-color/schema.json +48 -0
- package/src/schemas/types/hsv-color/unit.test.ts +162 -0
- package/src/schemas/types/hwb-color/from-hsv.tokenscript +31 -0
- package/src/schemas/types/hwb-color/initializer.tokenscript +16 -0
- package/src/schemas/types/hwb-color/schema.json +48 -0
- package/src/schemas/types/hwb-color/unit.test.ts +150 -0
- package/src/schemas/types/lab-color/from-xyz-d50.tokenscript +78 -0
- package/src/schemas/types/lab-color/initializer.tokenscript +16 -0
- package/src/schemas/types/lab-color/schema.json +48 -0
- package/src/schemas/types/lab-color/unit.test.ts +263 -0
- package/src/schemas/types/lch-color/from-lab.tokenscript +44 -0
- package/src/schemas/types/lch-color/initializer.tokenscript +16 -0
- package/src/schemas/types/lch-color/schema.json +48 -0
- package/src/schemas/types/lch-color/unit.test.ts +173 -0
- package/src/schemas/types/okhsl-color/from-oklab.tokenscript +410 -0
- package/src/schemas/types/okhsl-color/initializer.tokenscript +24 -0
- package/src/schemas/types/okhsl-color/schema.json +48 -0
- package/src/schemas/types/okhsl-color/unit.test.ts +514 -0
- package/src/schemas/types/okhsv-color/from-oklab.tokenscript +286 -0
- package/src/schemas/types/okhsv-color/initializer.tokenscript +24 -0
- package/src/schemas/types/okhsv-color/schema.json +48 -0
- package/src/schemas/types/okhsv-color/unit.test.ts +499 -0
- package/src/schemas/types/oklab-color/from-okhsl.tokenscript +195 -0
- package/src/schemas/types/oklab-color/from-okhsv.tokenscript +197 -0
- package/src/schemas/types/oklab-color/from-oklch.tokenscript +39 -0
- package/src/schemas/types/oklab-color/from-xyz-d65.tokenscript +43 -0
- package/src/schemas/types/oklab-color/initializer.tokenscript +16 -0
- package/src/schemas/types/oklab-color/schema.json +78 -0
- package/src/schemas/types/oklab-color/unit.test.ts +345 -0
- package/src/schemas/types/oklch-color/from-oklab.tokenscript +45 -0
- package/src/schemas/types/oklch-color/initializer.tokenscript +16 -0
- package/src/schemas/types/oklch-color/schema.json +48 -0
- package/src/schemas/types/oklch-color/unit.test.ts +267 -0
- package/src/schemas/types/p3-color/from-p3-linear.tokenscript +59 -0
- package/src/schemas/types/p3-color/initializer.tokenscript +16 -0
- package/src/schemas/types/p3-color/schema.json +48 -0
- package/src/schemas/types/p3-color/unit.test.ts +119 -0
- package/src/schemas/types/p3-linear-color/from-xyz-d65.tokenscript +47 -0
- package/src/schemas/types/p3-linear-color/initializer.tokenscript +16 -0
- package/src/schemas/types/p3-linear-color/schema.json +48 -0
- package/src/schemas/types/p3-linear-color/unit.test.ts +82 -0
- package/src/schemas/types/rgb-color/from-hex.tokenscript +43 -0
- package/src/schemas/types/rgb-color/initializer.tokenscript +16 -0
- package/src/schemas/types/rgb-color/schema.json +55 -0
- package/src/schemas/types/rgb-color/to-hex.tokenscript +42 -0
- package/src/schemas/types/rgb-color/unit.test.ts +302 -0
- package/src/schemas/types/srgb-color/from-hsl.tokenscript +106 -0
- package/src/schemas/types/srgb-color/from-linear.tokenscript +58 -0
- package/src/schemas/types/srgb-color/from-rgb.tokenscript +20 -0
- package/src/schemas/types/srgb-color/initializer.tokenscript +16 -0
- package/src/schemas/types/srgb-color/schema.json +68 -0
- package/src/schemas/types/srgb-color/unit.test.ts +303 -0
- package/src/schemas/types/srgb-linear-color/from-srgb.tokenscript +55 -0
- package/src/schemas/types/srgb-linear-color/from-xyz-d65.tokenscript +34 -0
- package/src/schemas/types/srgb-linear-color/initializer.tokenscript +13 -0
- package/src/schemas/types/srgb-linear-color/schema.json +58 -0
- package/src/schemas/types/srgb-linear-color/unit.test.ts +291 -0
- package/src/schemas/types/xyz-d50-color/from-xyz-d65.tokenscript +36 -0
- package/src/schemas/types/xyz-d50-color/initializer.tokenscript +16 -0
- package/src/schemas/types/xyz-d50-color/schema.json +48 -0
- package/src/schemas/types/xyz-d50-color/unit.test.ts +240 -0
- package/src/schemas/types/xyz-d65-color/from-linear-p3.tokenscript +47 -0
- package/src/schemas/types/xyz-d65-color/from-linear-srgb.tokenscript +38 -0
- package/src/schemas/types/xyz-d65-color/from-oklab.tokenscript +44 -0
- package/src/schemas/types/xyz-d65-color/initializer.tokenscript +16 -0
- package/src/schemas/types/xyz-d65-color/schema.json +68 -0
- package/src/schemas/types/xyz-d65-color/unit.test.ts +319 -0
- package/src/utils/schema-uri.ts +192 -0
- package/src/utils/type.ts +194 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HWB Color Schema Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for the HWB color space (Hue, Whiteness, Blackness)
|
|
5
|
+
* Validates against ColorJS for parity
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { log } from "@tests/helpers/logger";
|
|
9
|
+
import { executeWithSchema, getBundledSchema } from "@tests/helpers/schema-test-utils";
|
|
10
|
+
import Color from "colorjs.io";
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
import type { ColorSpecification } from "@/bundler/types";
|
|
13
|
+
|
|
14
|
+
// ColorJS reference tolerance
|
|
15
|
+
const TOLERANCE = 1e-9;
|
|
16
|
+
const HUE_TOLERANCE = 1e-6;
|
|
17
|
+
|
|
18
|
+
describe("HWB Color Schema", () => {
|
|
19
|
+
describe("Schema Definition", () => {
|
|
20
|
+
it("should have correct schema structure", async () => {
|
|
21
|
+
const schema = (await getBundledSchema("hwb-color")) as ColorSpecification;
|
|
22
|
+
|
|
23
|
+
expect(schema.name).toBe("HWB");
|
|
24
|
+
expect(schema.type).toBe("color");
|
|
25
|
+
expect(schema.schema).toBeDefined();
|
|
26
|
+
expect(schema.schema?.properties).toHaveProperty("h");
|
|
27
|
+
expect(schema.schema?.properties).toHaveProperty("w");
|
|
28
|
+
expect(schema.schema?.properties).toHaveProperty("b");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("ColorJS Parity", () => {
|
|
33
|
+
const testCases = [
|
|
34
|
+
{ name: "red", srgb: [1, 0, 0] },
|
|
35
|
+
{ name: "green", srgb: [0, 1, 0] },
|
|
36
|
+
{ name: "blue", srgb: [0, 0, 1] },
|
|
37
|
+
{ name: "cyan", srgb: [0, 1, 1] },
|
|
38
|
+
{ name: "magenta", srgb: [1, 0, 1] },
|
|
39
|
+
{ name: "yellow", srgb: [1, 1, 0] },
|
|
40
|
+
{ name: "coral", srgb: [1, 0.5, 0.314] },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
for (const { name, srgb } of testCases) {
|
|
44
|
+
it(`should match ColorJS for ${name}`, async () => {
|
|
45
|
+
const result = await executeWithSchema(
|
|
46
|
+
"hwb-color",
|
|
47
|
+
"type",
|
|
48
|
+
`
|
|
49
|
+
variable srgb: Color.SRGB;
|
|
50
|
+
srgb.r = ${srgb[0]};
|
|
51
|
+
srgb.g = ${srgb[1]};
|
|
52
|
+
srgb.b = ${srgb[2]};
|
|
53
|
+
|
|
54
|
+
variable hsv: Color.HSV = srgb.to.hsv();
|
|
55
|
+
hsv.to.hwb()
|
|
56
|
+
`,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// ColorJS reference (uses 0-100 for W and B, we use 0-1)
|
|
60
|
+
const colorJS = new Color("srgb", srgb as [number, number, number]).to("hwb");
|
|
61
|
+
const cjW = colorJS.coords[1] / 100;
|
|
62
|
+
const cjB = colorJS.coords[2] / 100;
|
|
63
|
+
|
|
64
|
+
const tsH = (result as any).value.h.value;
|
|
65
|
+
const tsW = (result as any).value.w.value;
|
|
66
|
+
const tsB = (result as any).value.b.value;
|
|
67
|
+
|
|
68
|
+
let diffH = Math.abs(tsH - colorJS.coords[0]);
|
|
69
|
+
if (diffH > 180) diffH = 360 - diffH;
|
|
70
|
+
const diffW = Math.abs(tsW - cjW);
|
|
71
|
+
const diffB = Math.abs(tsB - cjB);
|
|
72
|
+
const maxDiff = Math.max(diffW, diffB);
|
|
73
|
+
|
|
74
|
+
log.info(`\n=== ${name.toUpperCase()} ColorJS Parity ===`);
|
|
75
|
+
log.info(`Input sRGB: { r: ${srgb[0]}, g: ${srgb[1]}, b: ${srgb[2]} }`);
|
|
76
|
+
log.info(
|
|
77
|
+
`TokenScript: { h: ${tsH.toFixed(3)}, w: ${tsW.toFixed(6)}, b: ${tsB.toFixed(6)} }`,
|
|
78
|
+
);
|
|
79
|
+
log.info(
|
|
80
|
+
`ColorJS: { h: ${colorJS.coords[0].toFixed(3)}, w: ${cjW.toFixed(6)}, b: ${cjB.toFixed(6)} } (normalized)`,
|
|
81
|
+
);
|
|
82
|
+
log.info(`Max Diff (W,B): ${maxDiff.toExponential(2)}`);
|
|
83
|
+
log.info(`Status: ${maxDiff < TOLERANCE ? "✅ PASS" : "❌ FAIL"}`);
|
|
84
|
+
|
|
85
|
+
expect(maxDiff).toBeLessThan(TOLERANCE);
|
|
86
|
+
expect(diffH).toBeLessThan(HUE_TOLERANCE);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("Edge Cases", () => {
|
|
92
|
+
it("should handle white (W=1, B=0)", async () => {
|
|
93
|
+
const result = await executeWithSchema(
|
|
94
|
+
"hwb-color",
|
|
95
|
+
"type",
|
|
96
|
+
`
|
|
97
|
+
variable srgb: Color.SRGB;
|
|
98
|
+
srgb.r = 1;
|
|
99
|
+
srgb.g = 1;
|
|
100
|
+
srgb.b = 1;
|
|
101
|
+
|
|
102
|
+
variable hsv: Color.HSV = srgb.to.hsv();
|
|
103
|
+
hsv.to.hwb()
|
|
104
|
+
`,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
expect((result as any).value.w.value).toBeCloseTo(1, 9);
|
|
108
|
+
expect((result as any).value.b.value).toBeCloseTo(0, 9);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should handle black (W=0, B=1)", async () => {
|
|
112
|
+
const result = await executeWithSchema(
|
|
113
|
+
"hwb-color",
|
|
114
|
+
"type",
|
|
115
|
+
`
|
|
116
|
+
variable srgb: Color.SRGB;
|
|
117
|
+
srgb.r = 0;
|
|
118
|
+
srgb.g = 0;
|
|
119
|
+
srgb.b = 0;
|
|
120
|
+
|
|
121
|
+
variable hsv: Color.HSV = srgb.to.hsv();
|
|
122
|
+
hsv.to.hwb()
|
|
123
|
+
`,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
expect((result as any).value.w.value).toBeCloseTo(0, 9);
|
|
127
|
+
expect((result as any).value.b.value).toBeCloseTo(1, 9);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("should handle gray (W+B near 1)", async () => {
|
|
131
|
+
const result = await executeWithSchema(
|
|
132
|
+
"hwb-color",
|
|
133
|
+
"type",
|
|
134
|
+
`
|
|
135
|
+
variable srgb: Color.SRGB;
|
|
136
|
+
srgb.r = 0.5;
|
|
137
|
+
srgb.g = 0.5;
|
|
138
|
+
srgb.b = 0.5;
|
|
139
|
+
|
|
140
|
+
variable hsv: Color.HSV = srgb.to.hsv();
|
|
141
|
+
hsv.to.hwb()
|
|
142
|
+
`,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// For gray, W + B = 1
|
|
146
|
+
expect((result as any).value.w.value).toBeCloseTo(0.5, 9);
|
|
147
|
+
expect((result as any).value.b.value).toBeCloseTo(0.5, 9);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// XYZ-D50 to CIE Lab Conversion
|
|
2
|
+
// Reference: CIE 15.3:2004 section 8.2.1.1
|
|
3
|
+
// Reference: https://github.com/color-js/color.js/blob/main/src/spaces/lab.js
|
|
4
|
+
//
|
|
5
|
+
// Algorithm:
|
|
6
|
+
// 1. Scale XYZ by D50 white point reference
|
|
7
|
+
// 2. Apply f function: f(x) = x > ε ? cbrt(x) : (κ*x + 16)/116
|
|
8
|
+
// 3. L = 116 * f[Y] - 16
|
|
9
|
+
// 4. a = 500 * (f[X] - f[Y])
|
|
10
|
+
// 5. b = 200 * (f[Y] - f[Z])
|
|
11
|
+
//
|
|
12
|
+
// Input: Color.XYZD50 with x, y, z tristimulus
|
|
13
|
+
// Output: Color.Lab with l, a, b coordinates
|
|
14
|
+
|
|
15
|
+
// Get input XYZ-D50 values
|
|
16
|
+
variable x: Number = {input}.x;
|
|
17
|
+
variable y: Number = {input}.y;
|
|
18
|
+
variable z: Number = {input}.z;
|
|
19
|
+
|
|
20
|
+
// CIE constants (exact rational fractions)
|
|
21
|
+
// ε = 216/24389 = (6/29)^3
|
|
22
|
+
variable epsilon: Number = 0.008856451679035631;
|
|
23
|
+
// κ = 24389/27 = (29/3)^3
|
|
24
|
+
variable kappa: Number = 903.2962962962963;
|
|
25
|
+
|
|
26
|
+
// D50 white point reference (ColorJS exact values)
|
|
27
|
+
// D50: [0.3457/0.3585, 1.0, (1-0.3457-0.3585)/0.3585]
|
|
28
|
+
variable white_x: Number = 0.9642956764295677;
|
|
29
|
+
variable white_y: Number = 1.0;
|
|
30
|
+
variable white_z: Number = 0.8251046025104602;
|
|
31
|
+
|
|
32
|
+
// Scale XYZ by white point
|
|
33
|
+
variable xr: Number = x / white_x;
|
|
34
|
+
variable yr: Number = y / white_y;
|
|
35
|
+
variable zr: Number = z / white_z;
|
|
36
|
+
|
|
37
|
+
// Apply f function with cube root
|
|
38
|
+
// f(t) = t > ε ? t^(1/3) : (κt + 16) / 116
|
|
39
|
+
variable cube_root_exp: Number = 0.3333333333333333;
|
|
40
|
+
|
|
41
|
+
variable fx: Number = 0;
|
|
42
|
+
variable fy: Number = 0;
|
|
43
|
+
variable fz: Number = 0;
|
|
44
|
+
|
|
45
|
+
if (xr > epsilon) [
|
|
46
|
+
fx = pow(xr, cube_root_exp);
|
|
47
|
+
] else [
|
|
48
|
+
fx = (kappa * xr + 16) / 116;
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
if (yr > epsilon) [
|
|
52
|
+
fy = pow(yr, cube_root_exp);
|
|
53
|
+
] else [
|
|
54
|
+
fy = (kappa * yr + 16) / 116;
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
if (zr > epsilon) [
|
|
58
|
+
fz = pow(zr, cube_root_exp);
|
|
59
|
+
] else [
|
|
60
|
+
fz = (kappa * zr + 16) / 116;
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
// Calculate Lab values
|
|
64
|
+
variable lab_l: Number = 116 * fy - 16;
|
|
65
|
+
variable lab_a: Number = 500 * (fx - fy);
|
|
66
|
+
variable lab_b: Number = 200 * (fy - fz);
|
|
67
|
+
|
|
68
|
+
// Create output
|
|
69
|
+
variable output: Color.Lab;
|
|
70
|
+
output.l = lab_l;
|
|
71
|
+
output.a = lab_a;
|
|
72
|
+
output.b = lab_b;
|
|
73
|
+
|
|
74
|
+
return output;
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// CIE Lab Color Initializer
|
|
2
|
+
// Creates a Lab color from L, a, b values
|
|
3
|
+
// Input: List of [l, a, b] values
|
|
4
|
+
|
|
5
|
+
variable lab_values: List = {input};
|
|
6
|
+
variable output: Color.Lab;
|
|
7
|
+
|
|
8
|
+
output.l = lab_values.get(0);
|
|
9
|
+
output.a = lab_values.get(1);
|
|
10
|
+
output.b = lab_values.get(2);
|
|
11
|
+
|
|
12
|
+
return output;
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Lab",
|
|
3
|
+
"type": "color",
|
|
4
|
+
"description": "CIE Lab (L*a*b*) perceptually uniform color space. L is lightness (0-100), a is green-red axis, b is blue-yellow axis.",
|
|
5
|
+
"schema": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"l": {
|
|
9
|
+
"type": "number",
|
|
10
|
+
"description": "Lightness (0-100)"
|
|
11
|
+
},
|
|
12
|
+
"a": {
|
|
13
|
+
"type": "number",
|
|
14
|
+
"description": "Green-red axis (typically -125 to 125)"
|
|
15
|
+
},
|
|
16
|
+
"b": {
|
|
17
|
+
"type": "number",
|
|
18
|
+
"description": "Blue-yellow axis (typically -125 to 125)"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"required": ["l", "a", "b"],
|
|
22
|
+
"order": ["l", "a", "b"],
|
|
23
|
+
"additionalProperties": false
|
|
24
|
+
},
|
|
25
|
+
"initializers": [
|
|
26
|
+
{
|
|
27
|
+
"title": "Lab Color Initializer",
|
|
28
|
+
"keyword": "lab",
|
|
29
|
+
"description": "Creates a CIE Lab color from L, a, b values",
|
|
30
|
+
"script": {
|
|
31
|
+
"type": "/api/v1/core/tokenscript/0/",
|
|
32
|
+
"script": "./initializer.tokenscript"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"conversions": [
|
|
37
|
+
{
|
|
38
|
+
"source": "/api/v1/core/xyz-d50-color/0/",
|
|
39
|
+
"target": "$self",
|
|
40
|
+
"description": "Converts XYZ-D50 to CIE Lab using the CIE standard formula",
|
|
41
|
+
"lossless": true,
|
|
42
|
+
"script": {
|
|
43
|
+
"type": "/api/v1/core/tokenscript/0/",
|
|
44
|
+
"script": "./from-xyz-d50.tokenscript"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CIE Lab Color Schema Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for the CIE Lab perceptually uniform color space
|
|
5
|
+
* Validates against ColorJS for parity
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { log } from "@tests/helpers/logger";
|
|
9
|
+
import { executeWithSchema, getBundledSchema } from "@tests/helpers/schema-test-utils";
|
|
10
|
+
import Color from "colorjs.io";
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
import type { ColorSpecification } from "@/bundler/types";
|
|
13
|
+
|
|
14
|
+
// ColorJS reference tolerance
|
|
15
|
+
const TOLERANCE = 1e-7;
|
|
16
|
+
|
|
17
|
+
describe("CIE Lab Color Schema", () => {
|
|
18
|
+
describe("Schema Definition", () => {
|
|
19
|
+
it("should have correct schema structure", async () => {
|
|
20
|
+
const schema = (await getBundledSchema("lab-color")) as ColorSpecification;
|
|
21
|
+
|
|
22
|
+
expect(schema.name).toBe("Lab");
|
|
23
|
+
expect(schema.type).toBe("color");
|
|
24
|
+
expect(schema.schema).toBeDefined();
|
|
25
|
+
expect(schema.schema?.properties).toHaveProperty("l");
|
|
26
|
+
expect(schema.schema?.properties).toHaveProperty("a");
|
|
27
|
+
expect(schema.schema?.properties).toHaveProperty("b");
|
|
28
|
+
expect(schema.schema?.required).toEqual(["l", "a", "b"]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should have lab initializer", async () => {
|
|
32
|
+
const schema = (await getBundledSchema("lab-color")) as ColorSpecification;
|
|
33
|
+
|
|
34
|
+
expect(schema.initializers).toHaveLength(1);
|
|
35
|
+
expect(schema.initializers[0].keyword).toBe("lab");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should have conversion from XYZ-D50", async () => {
|
|
39
|
+
const schema = (await getBundledSchema("lab-color")) as ColorSpecification;
|
|
40
|
+
|
|
41
|
+
expect(schema.conversions).toHaveLength(1);
|
|
42
|
+
|
|
43
|
+
const xyzToLab = schema.conversions.find((c: { source: string }) =>
|
|
44
|
+
c.source.includes("xyz-d50-color"),
|
|
45
|
+
);
|
|
46
|
+
expect(xyzToLab).toBeDefined();
|
|
47
|
+
expect(xyzToLab?.lossless).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("Initialization", () => {
|
|
52
|
+
it("should create Lab color from values", async () => {
|
|
53
|
+
const result = await executeWithSchema(
|
|
54
|
+
"lab-color",
|
|
55
|
+
"type",
|
|
56
|
+
`
|
|
57
|
+
variable c: Color.Lab;
|
|
58
|
+
c.l = 50;
|
|
59
|
+
c.a = 25;
|
|
60
|
+
c.b = -10;
|
|
61
|
+
c
|
|
62
|
+
`,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
expect(result?.constructor.name).toBe("ColorSymbol");
|
|
66
|
+
expect((result as any).subType).toBe("Lab");
|
|
67
|
+
expect((result as any).value.l.value).toBeCloseTo(50, 10);
|
|
68
|
+
expect((result as any).value.a.value).toBeCloseTo(25, 10);
|
|
69
|
+
expect((result as any).value.b.value).toBeCloseTo(-10, 10);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("Conversion from XYZ-D50 to Lab", () => {
|
|
74
|
+
it("should convert D50 white to Lab (L=100)", async () => {
|
|
75
|
+
// D50 white point
|
|
76
|
+
const result = await executeWithSchema(
|
|
77
|
+
"lab-color",
|
|
78
|
+
"type",
|
|
79
|
+
`
|
|
80
|
+
variable xyz: Color.XYZD50;
|
|
81
|
+
xyz.x = 0.96429567;
|
|
82
|
+
xyz.y = 1.0;
|
|
83
|
+
xyz.z = 0.82510460;
|
|
84
|
+
xyz.to.lab()
|
|
85
|
+
`,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// ColorJS reference
|
|
89
|
+
const colorJS = new Color("xyz-d50", [0.96429567, 1.0, 0.8251046]).to("lab");
|
|
90
|
+
|
|
91
|
+
log.info(`\n=== D50 WHITE → Lab ===`);
|
|
92
|
+
log.info(
|
|
93
|
+
`TokenScript: { l: ${(result as any).value.l.value}, a: ${(result as any).value.a.value}, b: ${(result as any).value.b.value} }`,
|
|
94
|
+
);
|
|
95
|
+
log.info(
|
|
96
|
+
`ColorJS: { l: ${colorJS.coords[0]}, a: ${colorJS.coords[1]}, b: ${colorJS.coords[2]} }`,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// White should have L≈100 and a≈b≈0
|
|
100
|
+
expect((result as any).value.l.value).toBeCloseTo(colorJS.coords[0], 5);
|
|
101
|
+
expect((result as any).value.a.value).toBeCloseTo(colorJS.coords[1], 5);
|
|
102
|
+
expect((result as any).value.b.value).toBeCloseTo(colorJS.coords[2], 5);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("Full Conversion Chain: sRGB → ... → XYZ-D50 → Lab", () => {
|
|
107
|
+
it("should convert sRGB red through to Lab", async () => {
|
|
108
|
+
const result = await executeWithSchema(
|
|
109
|
+
"lab-color",
|
|
110
|
+
"type",
|
|
111
|
+
`
|
|
112
|
+
variable srgb: Color.SRGB;
|
|
113
|
+
srgb.r = 1;
|
|
114
|
+
srgb.g = 0;
|
|
115
|
+
srgb.b = 0;
|
|
116
|
+
|
|
117
|
+
variable linear: Color.LinearSRGB = srgb.to.linearsrgb();
|
|
118
|
+
variable xyz65: Color.XYZD65 = linear.to.xyzd65();
|
|
119
|
+
variable xyz50: Color.XYZD50 = xyz65.to.xyzd50();
|
|
120
|
+
xyz50.to.lab()
|
|
121
|
+
`,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// ColorJS reference
|
|
125
|
+
const colorJS = new Color("srgb", [1, 0, 0]).to("lab");
|
|
126
|
+
|
|
127
|
+
log.info(`\n=== sRGB RED → Lab (full chain) ===`);
|
|
128
|
+
log.info(
|
|
129
|
+
`TokenScript: { l: ${(result as any).value.l.value}, a: ${(result as any).value.a.value}, b: ${(result as any).value.b.value} }`,
|
|
130
|
+
);
|
|
131
|
+
log.info(
|
|
132
|
+
`ColorJS: { l: ${colorJS.coords[0]}, a: ${colorJS.coords[1]}, b: ${colorJS.coords[2]} }`,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
expect((result as any).value.l.value).toBeCloseTo(colorJS.coords[0], 5);
|
|
136
|
+
expect((result as any).value.a.value).toBeCloseTo(colorJS.coords[1], 5);
|
|
137
|
+
expect((result as any).value.b.value).toBeCloseTo(colorJS.coords[2], 5);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("ColorJS Parity", () => {
|
|
142
|
+
const testCases = [
|
|
143
|
+
{ name: "red", srgb: [1, 0, 0] },
|
|
144
|
+
{ name: "green", srgb: [0, 1, 0] },
|
|
145
|
+
{ name: "blue", srgb: [0, 0, 1] },
|
|
146
|
+
{ name: "white", srgb: [1, 1, 1] },
|
|
147
|
+
{ name: "black", srgb: [0, 0, 0] },
|
|
148
|
+
{ name: "gray-50%", srgb: [0.5, 0.5, 0.5] },
|
|
149
|
+
{ name: "cyan", srgb: [0, 1, 1] },
|
|
150
|
+
{ name: "magenta", srgb: [1, 0, 1] },
|
|
151
|
+
{ name: "yellow", srgb: [1, 1, 0] },
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
for (const { name, srgb } of testCases) {
|
|
155
|
+
it(`should match ColorJS for ${name}`, async () => {
|
|
156
|
+
const result = await executeWithSchema(
|
|
157
|
+
"lab-color",
|
|
158
|
+
"type",
|
|
159
|
+
`
|
|
160
|
+
variable srgb: Color.SRGB;
|
|
161
|
+
srgb.r = ${srgb[0]};
|
|
162
|
+
srgb.g = ${srgb[1]};
|
|
163
|
+
srgb.b = ${srgb[2]};
|
|
164
|
+
|
|
165
|
+
variable linear: Color.LinearSRGB = srgb.to.linearsrgb();
|
|
166
|
+
variable xyz65: Color.XYZD65 = linear.to.xyzd65();
|
|
167
|
+
variable xyz50: Color.XYZD50 = xyz65.to.xyzd50();
|
|
168
|
+
xyz50.to.lab()
|
|
169
|
+
`,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// ColorJS reference
|
|
173
|
+
const colorJS = new Color("srgb", srgb as [number, number, number]).to("lab");
|
|
174
|
+
|
|
175
|
+
const tsL = (result as any).value.l.value;
|
|
176
|
+
const tsA = (result as any).value.a.value;
|
|
177
|
+
const tsB = (result as any).value.b.value;
|
|
178
|
+
|
|
179
|
+
const diffL = Math.abs(tsL - colorJS.coords[0]);
|
|
180
|
+
const diffA = Math.abs(tsA - colorJS.coords[1]);
|
|
181
|
+
const diffB = Math.abs(tsB - colorJS.coords[2]);
|
|
182
|
+
const maxDiff = Math.max(diffL, diffA, diffB);
|
|
183
|
+
|
|
184
|
+
log.info(`\n=== ${name.toUpperCase()} ColorJS Parity ===`);
|
|
185
|
+
log.info(`Input sRGB: { r: ${srgb[0]}, g: ${srgb[1]}, b: ${srgb[2]} }`);
|
|
186
|
+
log.info(
|
|
187
|
+
`TokenScript: { l: ${tsL.toFixed(4)}, a: ${tsA.toFixed(4)}, b: ${tsB.toFixed(4)} }`,
|
|
188
|
+
);
|
|
189
|
+
log.info(
|
|
190
|
+
`ColorJS: { l: ${colorJS.coords[0].toFixed(4)}, a: ${colorJS.coords[1].toFixed(4)}, b: ${colorJS.coords[2].toFixed(4)} }`,
|
|
191
|
+
);
|
|
192
|
+
log.info(`Max Diff: ${maxDiff.toExponential(2)}`);
|
|
193
|
+
log.info(`Status: ${maxDiff < TOLERANCE ? "✅ PASS" : "❌ FAIL"}`);
|
|
194
|
+
|
|
195
|
+
expect(maxDiff).toBeLessThan(TOLERANCE);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("Edge Cases", () => {
|
|
201
|
+
it("should handle black (L=0)", async () => {
|
|
202
|
+
const result = await executeWithSchema(
|
|
203
|
+
"lab-color",
|
|
204
|
+
"type",
|
|
205
|
+
`
|
|
206
|
+
variable srgb: Color.SRGB;
|
|
207
|
+
srgb.r = 0;
|
|
208
|
+
srgb.g = 0;
|
|
209
|
+
srgb.b = 0;
|
|
210
|
+
|
|
211
|
+
variable linear: Color.LinearSRGB = srgb.to.linearsrgb();
|
|
212
|
+
variable xyz65: Color.XYZD65 = linear.to.xyzd65();
|
|
213
|
+
variable xyz50: Color.XYZD50 = xyz65.to.xyzd50();
|
|
214
|
+
xyz50.to.lab()
|
|
215
|
+
`,
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
expect((result as any).value.l.value).toBeCloseTo(0, 5);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should handle white (L=100)", async () => {
|
|
222
|
+
const result = await executeWithSchema(
|
|
223
|
+
"lab-color",
|
|
224
|
+
"type",
|
|
225
|
+
`
|
|
226
|
+
variable srgb: Color.SRGB;
|
|
227
|
+
srgb.r = 1;
|
|
228
|
+
srgb.g = 1;
|
|
229
|
+
srgb.b = 1;
|
|
230
|
+
|
|
231
|
+
variable linear: Color.LinearSRGB = srgb.to.linearsrgb();
|
|
232
|
+
variable xyz65: Color.XYZD65 = linear.to.xyzd65();
|
|
233
|
+
variable xyz50: Color.XYZD50 = xyz65.to.xyzd50();
|
|
234
|
+
xyz50.to.lab()
|
|
235
|
+
`,
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
expect((result as any).value.l.value).toBeCloseTo(100, 4);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("should handle neutral gray (a≈0, b≈0)", async () => {
|
|
242
|
+
const result = await executeWithSchema(
|
|
243
|
+
"lab-color",
|
|
244
|
+
"type",
|
|
245
|
+
`
|
|
246
|
+
variable srgb: Color.SRGB;
|
|
247
|
+
srgb.r = 0.5;
|
|
248
|
+
srgb.g = 0.5;
|
|
249
|
+
srgb.b = 0.5;
|
|
250
|
+
|
|
251
|
+
variable linear: Color.LinearSRGB = srgb.to.linearsrgb();
|
|
252
|
+
variable xyz65: Color.XYZD65 = linear.to.xyzd65();
|
|
253
|
+
variable xyz50: Color.XYZD50 = xyz65.to.xyzd50();
|
|
254
|
+
xyz50.to.lab()
|
|
255
|
+
`,
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
// Gray should have a≈0 and b≈0
|
|
259
|
+
expect(Math.abs((result as any).value.a.value)).toBeLessThan(0.01);
|
|
260
|
+
expect(Math.abs((result as any).value.b.value)).toBeLessThan(0.01);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// CIE Lab to LCH Conversion
|
|
2
|
+
// Converts Cartesian (a, b) to polar (C, H) coordinates
|
|
3
|
+
// Same algorithm as OKLab → OKLCH, but for CIE Lab
|
|
4
|
+
//
|
|
5
|
+
// Algorithm:
|
|
6
|
+
// L stays the same (lightness 0-100)
|
|
7
|
+
// C = sqrt(a² + b²) (chroma)
|
|
8
|
+
// H = atan2(b, a) * 180/π (hue angle in degrees)
|
|
9
|
+
//
|
|
10
|
+
// Input: Color.Lab with l, a, b coordinates
|
|
11
|
+
// Output: Color.LCH with l, c, h coordinates
|
|
12
|
+
|
|
13
|
+
// Get input Lab values
|
|
14
|
+
variable l: Number = {input}.l;
|
|
15
|
+
variable a: Number = {input}.a;
|
|
16
|
+
variable b: Number = {input}.b;
|
|
17
|
+
|
|
18
|
+
// Constants
|
|
19
|
+
variable pi: Number = pi();
|
|
20
|
+
variable rad_to_deg: Number = 180 / pi;
|
|
21
|
+
|
|
22
|
+
// Calculate chroma (distance from neutral axis)
|
|
23
|
+
variable c: Number = sqrt(a * a + b * b);
|
|
24
|
+
|
|
25
|
+
// Calculate hue angle using atan2
|
|
26
|
+
variable h_rad: Number = atan2(b, a);
|
|
27
|
+
variable h: Number = h_rad * rad_to_deg;
|
|
28
|
+
|
|
29
|
+
// Normalize hue to 0-360 range
|
|
30
|
+
if (h < 0) [
|
|
31
|
+
h = h + 360;
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
// Create output
|
|
35
|
+
variable output: Color.LCH;
|
|
36
|
+
output.l = l;
|
|
37
|
+
output.c = c;
|
|
38
|
+
output.h = h;
|
|
39
|
+
|
|
40
|
+
return output;
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// CIE LCH Color Initializer
|
|
2
|
+
// Creates a LCH color from L, C, H values
|
|
3
|
+
// Input: List of [l, c, h] values
|
|
4
|
+
|
|
5
|
+
variable lch_values: List = {input};
|
|
6
|
+
variable output: Color.LCH;
|
|
7
|
+
|
|
8
|
+
output.l = lch_values.get(0);
|
|
9
|
+
output.c = lch_values.get(1);
|
|
10
|
+
output.h = lch_values.get(2);
|
|
11
|
+
|
|
12
|
+
return output;
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "LCH",
|
|
3
|
+
"type": "color",
|
|
4
|
+
"description": "CIE LCH color space - the polar form of CIE Lab. L is lightness (0-100), C is chroma, H is hue angle (0-360).",
|
|
5
|
+
"schema": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"l": {
|
|
9
|
+
"type": "number",
|
|
10
|
+
"description": "Lightness (0-100)"
|
|
11
|
+
},
|
|
12
|
+
"c": {
|
|
13
|
+
"type": "number",
|
|
14
|
+
"description": "Chroma"
|
|
15
|
+
},
|
|
16
|
+
"h": {
|
|
17
|
+
"type": "number",
|
|
18
|
+
"description": "Hue angle (0-360 degrees)"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"required": ["l", "c", "h"],
|
|
22
|
+
"order": ["l", "c", "h"],
|
|
23
|
+
"additionalProperties": false
|
|
24
|
+
},
|
|
25
|
+
"initializers": [
|
|
26
|
+
{
|
|
27
|
+
"title": "LCH Color Initializer",
|
|
28
|
+
"keyword": "lch",
|
|
29
|
+
"description": "Creates a CIE LCH color from L, C, H values",
|
|
30
|
+
"script": {
|
|
31
|
+
"type": "/api/v1/core/tokenscript/0/",
|
|
32
|
+
"script": "./initializer.tokenscript"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"conversions": [
|
|
37
|
+
{
|
|
38
|
+
"source": "/api/v1/core/lab-color/0/",
|
|
39
|
+
"target": "$self",
|
|
40
|
+
"description": "Converts CIE Lab to LCH using Cartesian to polar transformation",
|
|
41
|
+
"lossless": true,
|
|
42
|
+
"script": {
|
|
43
|
+
"type": "/api/v1/core/tokenscript/0/",
|
|
44
|
+
"script": "./from-lab.tokenscript"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|