rodzic
9de487fac0
commit
949d8d72dd
|
@ -4,8 +4,13 @@ import { CreatureContext, CreatureContextType } from "../creature-symbol";
|
||||||
import { createCreatureSymbolFactory } from "../creature-symbol-factory";
|
import { createCreatureSymbolFactory } from "../creature-symbol-factory";
|
||||||
import { HoverDebugHelper } from "../hover-debug-helper";
|
import { HoverDebugHelper } from "../hover-debug-helper";
|
||||||
import { Page } from "../page";
|
import { Page } from "../page";
|
||||||
|
import { PaletteAlgorithmWidget } from "../palette-algorithm-widget";
|
||||||
import { Random } from "../random";
|
import { Random } from "../random";
|
||||||
import { createRandomColorPalette } from "../random-colors";
|
import {
|
||||||
|
createRandomColorPalette,
|
||||||
|
DEFAULT_RANDOM_PALETTE_ALGORITHM,
|
||||||
|
RandomPaletteAlgorithm,
|
||||||
|
} from "../random-colors";
|
||||||
import { createSvgSymbolContext } from "../svg-symbol";
|
import { createSvgSymbolContext } from "../svg-symbol";
|
||||||
import { svgScale, SvgTransform } from "../svg-transform";
|
import { svgScale, SvgTransform } from "../svg-transform";
|
||||||
import { SvgVocabulary } from "../svg-vocabulary";
|
import { SvgVocabulary } from "../svg-vocabulary";
|
||||||
|
@ -55,17 +60,22 @@ const EYE_CREATURE = (
|
||||||
);
|
);
|
||||||
|
|
||||||
const RandomColorSampling: React.FC<{}> = () => {
|
const RandomColorSampling: React.FC<{}> = () => {
|
||||||
|
const [paletteAlg, setPaletteAlg] = useState<RandomPaletteAlgorithm>(
|
||||||
|
DEFAULT_RANDOM_PALETTE_ALGORITHM
|
||||||
|
);
|
||||||
const [seed, setSeed] = useState(Date.now());
|
const [seed, setSeed] = useState(Date.now());
|
||||||
const NUM_COLORS = 100;
|
const NUM_COLORS = 100;
|
||||||
const rng = new Random(seed);
|
const rng = new Random(seed);
|
||||||
const palette = createRandomColorPalette(NUM_COLORS, rng);
|
const palette = createRandomColorPalette(NUM_COLORS, rng, paletteAlg);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<PaletteAlgorithmWidget value={paletteAlg} onChange={setPaletteAlg} />
|
||||||
<div className="thingy">
|
<div className="thingy">
|
||||||
<div style={{ fontSize: 0 }}>
|
<div style={{ fontSize: 0 }}>
|
||||||
{range(NUM_COLORS).map((i) => (
|
{range(NUM_COLORS).map((i) => (
|
||||||
<div
|
<div
|
||||||
|
key={i}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: palette[i],
|
backgroundColor: palette[i],
|
||||||
width: "1rem",
|
width: "1rem",
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
RandomPaletteAlgorithm,
|
||||||
|
RANDOM_PALETTE_ALGORITHMS,
|
||||||
|
} from "./random-colors";
|
||||||
|
|
||||||
|
export type PaletteAlgorithmWidgetProps = {
|
||||||
|
value: RandomPaletteAlgorithm;
|
||||||
|
onChange: (value: RandomPaletteAlgorithm) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PaletteAlgorithmWidget: React.FC<PaletteAlgorithmWidgetProps> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
const id = "algorithm";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex-widget thingy">
|
||||||
|
<label htmlFor={id}>Palette algorithm: </label>
|
||||||
|
<select
|
||||||
|
id={id}
|
||||||
|
onChange={(e) => onChange(e.target.value as RandomPaletteAlgorithm)}
|
||||||
|
value={value}
|
||||||
|
>
|
||||||
|
{RANDOM_PALETTE_ALGORITHMS.map((choice) => (
|
||||||
|
<option key={choice} value={choice}>
|
||||||
|
{choice}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,4 +1,8 @@
|
||||||
import { clampedByteToHex, createRandomColorPalette } from "./random-colors";
|
import {
|
||||||
|
clampedByteToHex,
|
||||||
|
createRandomColorPalette,
|
||||||
|
RANDOM_PALETTE_ALGORITHMS,
|
||||||
|
} from "./random-colors";
|
||||||
|
|
||||||
describe("clampedByteToHex", () => {
|
describe("clampedByteToHex", () => {
|
||||||
it("clamps values over 255 to 255", () => {
|
it("clamps values over 255 to 255", () => {
|
||||||
|
@ -18,10 +22,14 @@ describe("clampedByteToHex", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("createRandomColorPalette() works", () => {
|
describe("createRandomColorPalette()", () => {
|
||||||
const palette = createRandomColorPalette(3);
|
for (let alg of RANDOM_PALETTE_ALGORITHMS) {
|
||||||
|
it(`works using the '${alg}' algorithm`, () => {
|
||||||
|
const palette = createRandomColorPalette(3, undefined, alg);
|
||||||
expect(palette).toHaveLength(3);
|
expect(palette).toHaveLength(3);
|
||||||
for (let color of palette) {
|
for (let color of palette) {
|
||||||
expect(color).toMatch(/^\#[0-9a-f]{6}$/);
|
expect(color).toMatch(/^\#[0-9a-f]{6}$/);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -2,6 +2,13 @@ import { Random } from "./random";
|
||||||
import { range } from "./util";
|
import { range } from "./util";
|
||||||
import * as colorspaces from "colorspaces";
|
import * as colorspaces from "colorspaces";
|
||||||
|
|
||||||
|
type RandomPaletteGenerator = (numEntries: number, rng: Random) => string[];
|
||||||
|
|
||||||
|
export type RandomPaletteAlgorithm = "RGB" | "CIELUV";
|
||||||
|
|
||||||
|
export const DEFAULT_RANDOM_PALETTE_ALGORITHM: RandomPaletteAlgorithm =
|
||||||
|
"CIELUV";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clamp the given number to be between 0 and 255, then
|
* Clamp the given number to be between 0 and 255, then
|
||||||
* convert it to hexadecimal.
|
* convert it to hexadecimal.
|
||||||
|
@ -19,7 +26,12 @@ export function clampedByteToHex(value: number): string {
|
||||||
return hex;
|
return hex;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRandomColor(rng: Random): string {
|
function createRandomRGBColor(rng: Random): string {
|
||||||
|
const rgb = range(3).map(() => rng.inRange({ min: 0, max: 255, step: 1 }));
|
||||||
|
return "#" + rgb.map(clampedByteToHex).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRandomCIELUVColor(rng: Random): string {
|
||||||
const max_luv_samples = 100;
|
const max_luv_samples = 100;
|
||||||
let luv_sample_failed = true;
|
let luv_sample_failed = true;
|
||||||
let rand_color_hex: string = "#000000";
|
let rand_color_hex: string = "#000000";
|
||||||
|
@ -56,15 +68,40 @@ function createRandomColor(rng: Random): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a random color palette with the given number of
|
* Factory function to take a function that generates a random color
|
||||||
* entries, optionally using the given random number generator.
|
* and return a palette generator that just calls it once for every
|
||||||
|
* color in the palette.
|
||||||
|
*/
|
||||||
|
function createSimplePaletteGenerator(
|
||||||
|
createColor: (rng: Random) => string
|
||||||
|
): RandomPaletteGenerator {
|
||||||
|
return (numEntries: number, rng: Random) =>
|
||||||
|
range(numEntries).map(() => createColor(rng));
|
||||||
|
}
|
||||||
|
|
||||||
|
const PALETTE_GENERATORS: {
|
||||||
|
[key in RandomPaletteAlgorithm]: RandomPaletteGenerator;
|
||||||
|
} = {
|
||||||
|
RGB: createSimplePaletteGenerator(createRandomRGBColor),
|
||||||
|
CIELUV: createSimplePaletteGenerator(createRandomCIELUVColor),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RANDOM_PALETTE_ALGORITHMS = Object.keys(
|
||||||
|
PALETTE_GENERATORS
|
||||||
|
) as RandomPaletteAlgorithm[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a random color palette with the given number of entries,
|
||||||
|
* optionally using the given random number generator and the
|
||||||
|
* given algorithm.
|
||||||
*
|
*
|
||||||
* The return value is an Array of strings, where each string is
|
* The return value is an Array of strings, where each string is
|
||||||
* a color hex hash (e.g. `#ff0000`).
|
* a color hex hash (e.g. `#ff0000`).
|
||||||
*/
|
*/
|
||||||
export function createRandomColorPalette(
|
export function createRandomColorPalette(
|
||||||
numEntries: number,
|
numEntries: number,
|
||||||
rng: Random = new Random()
|
rng: Random = new Random(),
|
||||||
|
algorithm: RandomPaletteAlgorithm = DEFAULT_RANDOM_PALETTE_ALGORITHM
|
||||||
): string[] {
|
): string[] {
|
||||||
return range(numEntries).map(() => createRandomColor(rng));
|
return PALETTE_GENERATORS[algorithm](numEntries, rng);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
import { PaletteAlgorithmWidget } from "./palette-algorithm-widget";
|
||||||
import { Random } from "./random";
|
import { Random } from "./random";
|
||||||
import { createRandomColorPalette } from "./random-colors";
|
import {
|
||||||
|
createRandomColorPalette,
|
||||||
|
DEFAULT_RANDOM_PALETTE_ALGORITHM,
|
||||||
|
RandomPaletteAlgorithm,
|
||||||
|
} from "./random-colors";
|
||||||
import { SvgCompositionContext } from "./svg-composition-context";
|
import { SvgCompositionContext } from "./svg-composition-context";
|
||||||
|
|
||||||
type SvgCompositionColors = Pick<
|
type SvgCompositionColors = Pick<
|
||||||
|
@ -8,8 +13,14 @@ type SvgCompositionColors = Pick<
|
||||||
"background" | "fill" | "stroke"
|
"background" | "fill" | "stroke"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
function createRandomCompositionColors(): SvgCompositionColors {
|
function createRandomCompositionColors(
|
||||||
const [background, stroke, fill] = createRandomColorPalette(3);
|
alg: RandomPaletteAlgorithm
|
||||||
|
): SvgCompositionColors {
|
||||||
|
const [background, stroke, fill] = createRandomColorPalette(
|
||||||
|
3,
|
||||||
|
undefined,
|
||||||
|
alg
|
||||||
|
);
|
||||||
return { background, stroke, fill };
|
return { background, stroke, fill };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,10 +31,13 @@ export type RandomizerWidgetProps = {
|
||||||
|
|
||||||
export const RandomizerWidget: React.FC<RandomizerWidgetProps> = (props) => {
|
export const RandomizerWidget: React.FC<RandomizerWidgetProps> = (props) => {
|
||||||
type RandType = "colors" | "symbols" | "colors and symbols";
|
type RandType = "colors" | "symbols" | "colors and symbols";
|
||||||
|
const [paletteAlg, setPaletteAlg] = useState<RandomPaletteAlgorithm>(
|
||||||
|
DEFAULT_RANDOM_PALETTE_ALGORITHM
|
||||||
|
);
|
||||||
const [randType, setRandType] = useState<RandType>("colors and symbols");
|
const [randType, setRandType] = useState<RandType>("colors and symbols");
|
||||||
const randomize = () => {
|
const randomize = () => {
|
||||||
if (randType === "colors" || randType === "colors and symbols") {
|
if (randType === "colors" || randType === "colors and symbols") {
|
||||||
props.onColorsChange(createRandomCompositionColors());
|
props.onColorsChange(createRandomCompositionColors(paletteAlg));
|
||||||
}
|
}
|
||||||
if (randType === "symbols" || randType === "colors and symbols") {
|
if (randType === "symbols" || randType === "colors and symbols") {
|
||||||
props.onSymbolsChange(new Random(Date.now()));
|
props.onSymbolsChange(new Random(Date.now()));
|
||||||
|
@ -48,6 +62,9 @@ export const RandomizerWidget: React.FC<RandomizerWidgetProps> = (props) => {
|
||||||
{makeRadio("colors")}
|
{makeRadio("colors")}
|
||||||
{makeRadio("symbols")}
|
{makeRadio("symbols")}
|
||||||
{makeRadio("colors and symbols")}
|
{makeRadio("colors and symbols")}
|
||||||
|
{randType !== "symbols" && (
|
||||||
|
<PaletteAlgorithmWidget value={paletteAlg} onChange={setPaletteAlg} />
|
||||||
|
)}
|
||||||
<button accessKey="r" onClick={randomize}>
|
<button accessKey="r" onClick={randomize}>
|
||||||
<u>R</u>andomize!
|
<u>R</u>andomize!
|
||||||
</button>
|
</button>
|
||||||
|
|
Ładowanie…
Reference in New Issue