Baseline color rules working (#111)

Made the new rule the default, but its now easy to play with to adjust the exact color rule we are using. This new one pick 3 values (with a bit of randomness), picks a random hue (then picks 2 more with a small chance of taking the opposite hue), and picks a saturation level that tends to be high.
pull/115/head
mittimithai 2021-04-30 04:15:38 -07:00 zatwierdzone przez GitHub
rodzic bbc278d061
commit 7003daeebf
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
5 zmienionych plików z 170 dodań i 18 usunięć

Wyświetl plik

@ -1,13 +1,19 @@
import { Random } from "./random";
import { range } from "./util";
import { range, clamp } from "./util";
import * as colorspaces from "colorspaces";
import { ColorTuple, hsluvToHex } from "hsluv";
type RandomPaletteGenerator = (numEntries: number, rng: Random) => string[];
export type RandomPaletteAlgorithm = "RGB" | "CIELUV";
export type RandomPaletteAlgorithm =
| "RGB"
| "CIELUV"
| "threevals"
| "huecontrast"
| "randgrey";
export const DEFAULT_RANDOM_PALETTE_ALGORITHM: RandomPaletteAlgorithm =
"CIELUV";
"threevals";
/**
* Clamp the given number to be between 0 and 255, then
@ -33,8 +39,8 @@ function createRandomRGBColor(rng: Random): string {
function createRandomCIELUVColor(rng: Random): string {
const max_luv_samples = 100;
let luv_sample_failed = true;
let rand_color_hex: string = "#000000";
let luvSampleFailed = true;
let randColorHex: string = "#000000";
//See if we can pull out a sample inside the LUV solid
for (let i = 0; i < max_luv_samples; i++) {
@ -42,29 +48,114 @@ function createRandomCIELUVColor(rng: Random): string {
let L = rng.inInterval({ min: 0, max: 100 });
let u = rng.inInterval({ min: -134, max: 220 });
let v = rng.inInterval({ min: -140, max: 122 });
let rand_color = colorspaces.make_color("CIELUV", [L, u, v]);
let randColor = colorspaces.make_color("CIELUV", [L, u, v]);
//console.log(`L:${L},u${u},v${v}`);
if (rand_color.is_displayable() && !(L == 0.0 && (u != 0 || v != 0))) {
rand_color_hex = rand_color.as("hex");
//console.log(rand_color_hex);
luv_sample_failed = false;
if (randColor.is_displayable() && !(L == 0.0 && (u != 0 || v != 0))) {
randColorHex = randColor.as("hex");
luvSampleFailed = false;
break;
}
}
//just sample sRGB if I couldn't sample a random LUV color
if (luv_sample_failed) {
//console.log("Sampling sRGB");
if (luvSampleFailed) {
let rgb = [0, 0, 0].map(
() => rng.inRange({ min: 0, max: 255, step: 1 }) / 255.0
);
//console.log(rgb);
let rand_color = colorspaces.make_color("sRGB", rgb);
rand_color_hex = rand_color.as("hex");
let randColor = colorspaces.make_color("sRGB", rgb);
randColorHex = randColor.as("hex");
}
return rand_color_hex;
return randColorHex;
}
function createRandGrey(rng: Random): string[] {
let L1 = rng.inInterval({ min: 0, max: 100 });
let L2 = rng.inInterval({ min: 0, max: 100 });
let L3 = rng.inInterval({ min: 0, max: 100 });
let Ls = [L1, L2, L3];
let h = 0;
let Hs = [h, h, h];
let S = 0;
let Ss = [S, S, S];
//zip
let hsls = Ls.map((k, i) => [Hs[i], Ss[i], k]);
let hexcolors = hsls.map((x) => hsluvToHex(x as ColorTuple));
//scramble order
hexcolors = rng.uniqueChoices(hexcolors, hexcolors.length);
return hexcolors;
}
function create3HColor(rng: Random): string[] {
let L = rng.fromGaussian({ mean: 50, stddev: 20 });
let Ls = [L, L, L];
Ls = Ls.map((x) => clamp(x, 0, 100));
let h1 = rng.inInterval({ min: 0, max: 360 }),
h2 = 360 * (((h1 + 120) / 360) % 1),
h3 = 360 * (((h1 + 240) / 360) % 1);
let Hs = [h1, h2, h3];
let S = 100;
let Ss = [S, S, S];
Ss = Ss.map((x) => clamp(x, 0, 100));
//zip
let hsls = Ls.map((k, i) => [Hs[i], Ss[i], k]);
let hexcolors = hsls.map((x) => hsluvToHex(x as ColorTuple));
//scramble order
hexcolors = rng.uniqueChoices(hexcolors, hexcolors.length);
return hexcolors;
}
function create3VColor(rng: Random): string[] {
let lowL_Mean = 20.0,
medL_Mean = 40.0,
hiL_Mean = 70,
lowL_SD = 30.0,
medL_SD = lowL_SD,
hiL_SD = lowL_SD;
let Ls = [
rng.fromGaussian({ mean: lowL_Mean, stddev: lowL_SD }),
rng.fromGaussian({ mean: medL_Mean, stddev: medL_SD }),
rng.fromGaussian({ mean: hiL_Mean, stddev: hiL_SD }),
];
Ls = Ls.map((x) => clamp(x, 0, 100));
//Now we have 3 lightness values, pick a random hue and sat
let h1 = rng.inInterval({ min: 0, max: 360 }),
h2 = 360 * (((h1 + 60 * Number(rng.bool(0.5))) / 360) % 1),
h3 = 360 * (((h1 + 180 * Number(rng.bool(0.5))) / 360) % 1);
let Hs = [h1, h2, h3];
let Ss = [
rng.fromGaussian({ mean: 100, stddev: 40 }),
rng.fromGaussian({ mean: 100, stddev: 40 }),
rng.fromGaussian({ mean: 100, stddev: 40 }),
];
Ss = Ss.map((x) => clamp(x, 0, 100));
//zip
let hsls = Ls.map((k, i) => [Hs[i], Ss[i], k]);
let hexcolors = hsls.map((x) => hsluvToHex(x as ColorTuple));
//scramble order
hexcolors = rng.uniqueChoices(hexcolors, hexcolors.length);
return hexcolors;
}
/**
@ -79,11 +170,36 @@ function createSimplePaletteGenerator(
range(numEntries).map(() => createColor(rng));
}
/**
* Factory function to make a random palette generator for a triad generator
*/
function createTriadPaletteGenerator(
createTriad: (rng: Random) => string[]
): RandomPaletteGenerator {
return (numEntries: number, rng: Random): string[] => {
let colors: string[] = [];
let n = Math.floor(numEntries / 3) + 1;
if (numEntries == 3) {
colors = colors.concat(createTriad(rng));
} else {
for (let i = 0; i < n; i++) colors = colors.concat(createTriad(rng));
colors = colors.slice(0, numEntries);
}
return colors;
};
}
const PALETTE_GENERATORS: {
[key in RandomPaletteAlgorithm]: RandomPaletteGenerator;
} = {
RGB: createSimplePaletteGenerator(createRandomRGBColor),
CIELUV: createSimplePaletteGenerator(createRandomCIELUVColor),
threevals: createTriadPaletteGenerator(create3VColor),
huecontrast: createTriadPaletteGenerator(create3HColor),
randgrey: createTriadPaletteGenerator(createRandGrey),
};
export const RANDOM_PALETTE_ALGORITHMS = Object.keys(

Wyświetl plik

@ -1,4 +1,9 @@
import { inclusiveRange, NumericInterval, NumericRange } from "./util";
import {
inclusiveRange,
NumericInterval,
NumericRange,
GaussianDist,
} from "./util";
export type RandomParameters = {
modulus: number;
@ -65,6 +70,19 @@ export class Random {
return this.next() * (max - min) + min;
}
/**
* Return a number from the specified gaussian distribution
* from: https://stackoverflow.com/questions/25582882/javascript-math-random-normal-distribution-gaussian-bell-curve
*/
fromGaussian({ mean, stddev }: GaussianDist, nsamples = 6) {
let runtotal = 0;
for (var i = 0; i < nsamples; i++) {
runtotal += this.next();
}
return (stddev * (runtotal - nsamples / 2)) / (nsamples / 2) + mean;
}
/**
* Return a random item from the given array. If the array is
* empty, an exception is thrown.

Wyświetl plik

@ -37,6 +37,11 @@ export type NumericRange = NumericInterval & {
step: number;
};
export type GaussianDist = {
mean: number;
stddev: number;
};
/**
* Return numbers within the given range, inclusive.
*/
@ -50,6 +55,13 @@ export function inclusiveRange({ min, max, step }: NumericRange): number[] {
return result;
}
/**
* Clamp a number between min and max
*/
export function clamp(x: number, min: number, max: number) {
return Math.max(min, Math.min(x, max));
}
/**
* Return an array containing the numbers from 0 to one
* less than the given value, increasing.

5
package-lock.json wygenerowano
Wyświetl plik

@ -4620,6 +4620,11 @@
"resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz",
"integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg="
},
"hsluv": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/hsluv/-/hsluv-0.1.0.tgz",
"integrity": "sha512-ERcanKLAszD2XN3Vh5r5Szkrv9q0oSTudmP0rkiKAGM/3NMc9FLmMZBB7TSqTaXJfSDBOreYTfjezCOYbRKqlw=="
},
"html-comment-regex": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz",

Wyświetl plik

@ -41,6 +41,7 @@
"classnames": "^2.3.1",
"colorspaces": "^0.1.5",
"gh-pages": "^3.1.0",
"hsluv": "^0.1.0",
"jest": "^26.6.3",
"parcel-bundler": "^1.12.4",
"prettier": "^2.2.1",