Skip to content

Commit

Permalink
パレットをserialize可能にする (#47)
Browse files Browse the repository at this point in the history
* コンストラクタで不正な値を丸める

* chroma-jsのserialize実装

* vitestのconfigにaliasがついてなかったのを修正

* superclassのコンストラクタで代入しているのになぜか子クラスでも代入してた

* 循環参照が発生するのでdeserializer.tsに移動

* d3-chromaticのserialize/deserialize処理追加

* 色計算時にp5に依存しないようにしたいので名前を変える

* p5jsの実装を排除できた

* p5Instanceを渡す必要はなくなった

* 特にexportする必要なかった

* OthersPaletteにserialize/deserialize実装

* copilot君に任せたらsafeParseIntを使ってくれていなかった

* テストの実装
  • Loading branch information
k-chop authored May 12, 2024
1 parent b842108 commit 2cc6366
Show file tree
Hide file tree
Showing 12 changed files with 403 additions and 85 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
"preview": "vite preview",
"test": "vitest"
},
"devDependencies": {
"@radix-ui/colors": "2.1.0",
Expand Down
53 changes: 47 additions & 6 deletions src/color/color-chromajs.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import chroma from "chroma-js";
import { BasePalette, Palette, RGB } from ".";
import { BasePalette, Palette, RGB, clampedPaletteParams } from ".";
import { safeParseInt } from "@/math";

class ChromaJsPalette extends BasePalette {
colorConstructor: (string | chroma.Color)[];
export class ChromaJsPalette extends BasePalette {
colorConstructor: string[];
colors: chroma.Color[] = [];

constructor(colorConstructor: (string | chroma.Color)[], length: number) {
super(length);
constructor(
colorConstructor: string[],
length: number,
mirrored = true,
offset = 0,
) {
const { colorLength, offsetIndex } = clampedPaletteParams(length, offset);

this.colorConstructor = colorConstructor;
super(colorLength, mirrored, offsetIndex);

if (colorConstructor.length === 0) {
this.colorConstructor = ["black", "white"];
} else if (colorConstructor.length > 16) {
this.colorConstructor = colorConstructor.slice(0, 16);
} else {
this.colorConstructor = colorConstructor;
}

this.buildColors();
}
Expand All @@ -22,6 +36,33 @@ class ChromaJsPalette extends BasePalette {
.scale(this.colorConstructor)
.colors(this.colorLength, null);
}

serialize(): string {
const result = ["chroma-js"];
result.push(`${this.colorConstructor.length}`);
result.push(...this.colorConstructor);
result.push(`${this.mirrored ? 1 : 0}`);
result.push(`${this.colorLength}`);
result.push(`${this.offsetIndex}`);

return result.join(",");
}

static deserialize(serialized: string): ChromaJsPalette {
const parts = serialized.split(",");
const colorNum = safeParseInt(parts[1], 0);

const colorConstructor = parts.slice(2, colorNum + 2);
const mirrored = parts[2 + colorNum] === "1";
const colorLength = safeParseInt(parts[2 + colorNum + 1], 16);
const offset = safeParseInt(parts[2 + colorNum + 2], 0);

return new ChromaJsPalette(colorConstructor, colorLength, mirrored, offset);
}

static defaultPalette(): ChromaJsPalette {
return new ChromaJsPalette(["lightblue", "navy", "white"], 128);
}
}

export const chromaJsPalettes = [
Expand Down
68 changes: 64 additions & 4 deletions src/color/color-d3-chromatic.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BasePalette, Palette, RGB, buildRGB } from ".";
import { BasePalette, Palette, RGB, buildRGB, clampedPaletteParams } from ".";
import { samples } from "culori";
import {
interpolateInferno,
Expand All @@ -7,16 +7,53 @@ import {
interpolateTurbo,
} from "d3-scale-chromatic";
import { color } from "d3-color";
import { safeParseInt } from "@/math";

type D3Interpolator = (t: number) => string;
type D3Color = ReturnType<typeof color>;

class D3ChromaticPalette extends BasePalette {
const getInterpolatorFromName = (name: string): D3Interpolator => {
switch (name) {
case "Inferno":
return interpolateInferno;
case "RdYlBlu":
return interpolateRdYlBu;
case "Turbo":
return interpolateTurbo;
case "Sinebow":
return interpolateSinebow;
default:
return interpolateRdYlBu;
}
};

const getInterpolatorName = (interpolator: D3Interpolator): string => {
if (interpolator === interpolateInferno) {
return "Inferno";
} else if (interpolator === interpolateRdYlBu) {
return "RdYlBlu";
} else if (interpolator === interpolateTurbo) {
return "Turbo";
} else if (interpolator === interpolateSinebow) {
return "Sinebow";
} else {
return "RdYlBlu";
}
};

export class D3ChromaticPalette extends BasePalette {
interpolator: D3Interpolator;
colors: D3Color[] = [];

constructor(interpolator: D3Interpolator, length: number) {
super(length);
constructor(
interpolator: D3Interpolator,
length: number,
mirrored = true,
offset = 0,
) {
const { colorLength, offsetIndex } = clampedPaletteParams(length, offset);

super(colorLength, mirrored, offsetIndex);

this.interpolator = interpolator;

Expand All @@ -32,6 +69,29 @@ class D3ChromaticPalette extends BasePalette {
.map((t) => color(this.interpolator(t)))
.filter((v): v is NonNullable<typeof v> => v != null);
}

serialize(): string {
const result = ["d3-chromatic"];
result.push(getInterpolatorName(this.interpolator));
result.push(this.mirrored ? "1" : "0");
result.push(`${this.colorLength}`);
result.push(`${this.offsetIndex}`);

return result.join(",");
}

static deserialize(serialized: string): D3ChromaticPalette {
const [, rawInterpolate, rawMirrored, rawLength, rawOffset] =
serialized.split(",");

const length = safeParseInt(rawLength);
const offset = safeParseInt(rawOffset);
const mirrored = rawMirrored === "1";

const interpolator = getInterpolatorFromName(rawInterpolate);

return new D3ChromaticPalette(interpolator, length, mirrored, offset);
}
}

export const d3ChromaticPalettes = [
Expand Down
103 changes: 103 additions & 0 deletions src/color/color-others.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Hsv, convertHsvToRgb, samples } from "culori";
import {
BasePalette,
Palette,
RGB,
buildRGB32Byte,
clampedPaletteParams,
} from ".";
import { safeParseInt } from "@/math";

type OthersInterpolator = (t: number) => Hsv;

const interpolators: Record<string, OthersInterpolator> = {
hue360: (t) => {
// hue 0~360
const hue = Math.floor(t * 360);
return { mode: "hsv", h: hue, s: 0.75, v: 1 };
},
monochrome: (t) => {
// monochrome
const brightness = t * 0.8 + 0.2;
return { mode: "hsv", s: 0, v: brightness };
},
fire: (t) => {
// fire
const brightness = t * 0.7 + 0.3;
const hue = Math.floor(t * 90) - 30;
return { mode: "hsv", h: hue, s: 0.9, v: brightness };
},
};

const getInterpolatorFromName = (name: string): ((t: number) => Hsv) => {
const interpolator = interpolators[name];
return interpolator ?? interpolators.hue360;
};

const getInterpolatorName = (interpolator: OthersInterpolator): string => {
for (const [name, func] of Object.entries(interpolators)) {
if (func === interpolator) {
return name;
}
}
return "hue360";
};

export class OthersPalette extends BasePalette {
private interpolator: (t: number) => Hsv;
colors: Hsv[] = [];

constructor(
length: number,
interpolator: (t: number) => Hsv,
mirrored = true,
offset = 0,
) {
const { colorLength, offsetIndex } = clampedPaletteParams(length, offset);

super(colorLength, mirrored, offsetIndex);

this.interpolator = interpolator;

this.buildColors();
}

buildColors(): void {
this.colors = samples(this.colorLength)
.map((t) => this.interpolator(t))
.filter((v): v is NonNullable<typeof v> => v != null);
}

getRGBFromColorIndex(index: number): RGB {
return buildRGB32Byte(convertHsvToRgb(this.colors[index]));
}

serialize(): string {
const result = ["others"];
result.push(getInterpolatorName(this.interpolator));
result.push(`${this.mirrored ? 1 : 0}`);
result.push(`${this.colorLength}`);
result.push(`${this.offsetIndex}`);

return result.join(",");
}

static deserialize(serialized: string): OthersPalette {
const [, rawInterpolate, rawMirrored, rawLength, rawOffset] =
serialized.split(",");

const length = safeParseInt(rawLength);
const offset = safeParseInt(rawOffset);
const mirrored = rawMirrored === "1";

const interpolator = getInterpolatorFromName(rawInterpolate);

return new OthersPalette(length, interpolator, mirrored, offset);
}
}

export const othersPalettes = [
new OthersPalette(128, interpolators.hue360),
new OthersPalette(128, interpolators.monochrome),
new OthersPalette(128, interpolators.fire),
] satisfies Palette[];
70 changes: 0 additions & 70 deletions src/color/color-p5js.ts

This file was deleted.

19 changes: 19 additions & 0 deletions src/color/deserializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Palette } from ".";
import { ChromaJsPalette } from "./color-chromajs";
import { D3ChromaticPalette } from "./color-d3-chromatic";
import { OthersPalette } from "./color-others";

export const deserializePalette = (serialized: string): Palette => {
const [type] = serialized.split(",");

switch (type) {
case "chroma-js":
return ChromaJsPalette.deserialize(serialized);
case "d3-chromatic":
return D3ChromaticPalette.deserialize(serialized);
case "others":
return OthersPalette.deserialize(serialized);
default:
throw new Error(`Unknown palette type: ${type}`);
}
};
Loading

0 comments on commit 2cc6366

Please sign in to comment.