From 0fc1527cf0966bfc6a9f2e043a7cb27ed5f6542d Mon Sep 17 00:00:00 2001 From: "Kalle (Sendou)" <38327916+Sendouc@users.noreply.github.com> Date: Sun, 27 Jun 2021 19:01:37 +0300 Subject: [PATCH] Demo page furthened --- .../layout/BeautifulDrawingByBorzoic.tsx | 407 ++++++++++++++++++ package-lock.json | 15 +- pages/demo.tsx | 396 +---------------- 3 files changed, 427 insertions(+), 391 deletions(-) create mode 100644 components/layout/BeautifulDrawingByBorzoic.tsx diff --git a/components/layout/BeautifulDrawingByBorzoic.tsx b/components/layout/BeautifulDrawingByBorzoic.tsx new file mode 100644 index 000000000..34b1bdf54 --- /dev/null +++ b/components/layout/BeautifulDrawingByBorzoic.tsx @@ -0,0 +1,407 @@ +import { Box, Image } from "@chakra-ui/react"; +import { useState } from "react"; + +interface HSL { + h: number; + s: number; + l: number; +} + +class Color { + public r: number; + public g: number; + public b: number; + constructor(r: number, g: number, b: number) { + this.r = this.clamp(r); + this.g = this.clamp(g); + this.b = this.clamp(b); + } + + toString() { + return `rgb(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round( + this.b + )})`; + } + + set(r: number, g: number, b: number) { + this.r = this.clamp(r); + this.g = this.clamp(g); + this.b = this.clamp(b); + } + + hueRotate(angle = 0) { + angle = (angle / 180) * Math.PI; + const sin = Math.sin(angle); + const cos = Math.cos(angle); + + this.multiply([ + 0.213 + cos * 0.787 - sin * 0.213, + 0.715 - cos * 0.715 - sin * 0.715, + 0.072 - cos * 0.072 + sin * 0.928, + 0.213 - cos * 0.213 + sin * 0.143, + 0.715 + cos * 0.285 + sin * 0.14, + 0.072 - cos * 0.072 - sin * 0.283, + 0.213 - cos * 0.213 - sin * 0.787, + 0.715 - cos * 0.715 + sin * 0.715, + 0.072 + cos * 0.928 + sin * 0.072, + ]); + } + + grayscale(value = 1) { + this.multiply([ + 0.2126 + 0.7874 * (1 - value), + 0.7152 - 0.7152 * (1 - value), + 0.0722 - 0.0722 * (1 - value), + 0.2126 - 0.2126 * (1 - value), + 0.7152 + 0.2848 * (1 - value), + 0.0722 - 0.0722 * (1 - value), + 0.2126 - 0.2126 * (1 - value), + 0.7152 - 0.7152 * (1 - value), + 0.0722 + 0.9278 * (1 - value), + ]); + } + + sepia(value = 1) { + this.multiply([ + 0.393 + 0.607 * (1 - value), + 0.769 - 0.769 * (1 - value), + 0.189 - 0.189 * (1 - value), + 0.349 - 0.349 * (1 - value), + 0.686 + 0.314 * (1 - value), + 0.168 - 0.168 * (1 - value), + 0.272 - 0.272 * (1 - value), + 0.534 - 0.534 * (1 - value), + 0.131 + 0.869 * (1 - value), + ]); + } + + saturate(value = 1) { + this.multiply([ + 0.213 + 0.787 * value, + 0.715 - 0.715 * value, + 0.072 - 0.072 * value, + 0.213 - 0.213 * value, + 0.715 + 0.285 * value, + 0.072 - 0.072 * value, + 0.213 - 0.213 * value, + 0.715 - 0.715 * value, + 0.072 + 0.928 * value, + ]); + } + + multiply(matrix: any) { + const newR = this.clamp( + this.r * matrix[0] + this.g * matrix[1] + this.b * matrix[2] + ); + const newG = this.clamp( + this.r * matrix[3] + this.g * matrix[4] + this.b * matrix[5] + ); + const newB = this.clamp( + this.r * matrix[6] + this.g * matrix[7] + this.b * matrix[8] + ); + this.r = newR; + this.g = newG; + this.b = newB; + } + + brightness(value = 1) { + this.linear(value); + } + contrast(value = 1) { + this.linear(value, -(0.5 * value) + 0.5); + } + + linear(slope = 1, intercept = 0) { + this.r = this.clamp(this.r * slope + intercept * 255); + this.g = this.clamp(this.g * slope + intercept * 255); + this.b = this.clamp(this.b * slope + intercept * 255); + } + + invert(value = 1) { + this.r = this.clamp((value + (this.r / 255) * (1 - 2 * value)) * 255); + this.g = this.clamp((value + (this.g / 255) * (1 - 2 * value)) * 255); + this.b = this.clamp((value + (this.b / 255) * (1 - 2 * value)) * 255); + } + + hsl(): HSL { + // Code taken from https://stackoverflow.com/a/9493060/2688027, licensed under CC BY-SA. + const r = this.r / 255; + const g = this.g / 255; + const b = this.b / 255; + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + + let h = 0; + let s = 0; + let l = (max + min) / 2; + + if (max === min) { + h = s = 0; + } else { + const d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + + case g: + h = (b - r) / d + 2; + break; + + case b: + h = (r - g) / d + 4; + break; + } + h /= 6; + } + + return { + h: h * 100, + s: s * 100, + l: l * 100, + }; + } + + clamp(value: number): number { + if (value > 255) { + value = 255; + } else if (value < 0) { + value = 0; + } + return value; + } +} +interface Solution { + loss: number; + values: number[]; +} +class Solver { + private target: Color; + private targetHSL: HSL; + private reusedColor: Color; + constructor(target: Color) { + this.target = target; + this.targetHSL = target.hsl(); + this.reusedColor = new Color(0, 0, 0); + } + + solve() { + const result = this.solveNarrow(this.solveWide()); + return { + values: result.values, + loss: result.loss, + filter: this.css(result.values), + }; + } + + solveWide(): Solution { + const A = 5; + const c = 15; + const a = [60, 180, 18000, 600, 1.2, 1.2]; + + let best = { loss: Infinity, values: [] as number[] }; + for (let i = 0; best.loss > 25 && i < 3; i++) { + const initial = [50, 20, 3750, 50, 100, 100]; + const result = this.spsa(A, a, c, initial, 1000); + if (result.loss < best.loss) { + best = result; + } + } + return best; + } + + solveNarrow(wide: Solution) { + const A = wide.loss; + const c = 2; + const A1 = A + 1; + const a = [0.25 * A1, 0.25 * A1, A1, 0.25 * A1, 0.2 * A1, 0.2 * A1]; + return this.spsa(A, a, c, wide.values, 500); + } + + spsa( + A: number, + a: number[], + c: number, + values: number[], + iters: number + ): Solution { + const alpha = 1; + const gamma = 0.16666666666666666; + + let best = [] as number[]; + let bestLoss = Infinity; + const deltas = new Array(6); + const highArgs = new Array(6); + const lowArgs = new Array(6); + + for (let k = 0; k < iters; k++) { + const ck = c / Math.pow(k + 1, gamma); + for (let i = 0; i < 6; i++) { + deltas[i] = Math.random() > 0.5 ? 1 : -1; + highArgs[i] = values[i] + ck * deltas[i]; + lowArgs[i] = values[i] - ck * deltas[i]; + } + + const lossDiff = this.loss(highArgs) - this.loss(lowArgs); + for (let i = 0; i < 6; i++) { + const g = (lossDiff / (2 * ck)) * deltas[i]; + const ak = a[i] / Math.pow(A + k + 1, alpha); + values[i] = fix(values[i] - ak * g, i); + } + + const loss = this.loss(values); + if (loss < bestLoss) { + best = values.slice(0); + bestLoss = loss; + } + } + return { values: best, loss: bestLoss }; + + function fix(value: number, idx: number): number { + let max = 100; + if (idx === 2 /* saturate */) { + max = 7500; + } else if (idx === 4 /* brightness */ || idx === 5 /* contrast */) { + max = 200; + } + + if (idx === 3 /* hue-rotate */) { + if (value > max) { + value %= max; + } else if (value < 0) { + value = max + (value % max); + } + } else if (value < 0) { + value = 0; + } else if (value > max) { + value = max; + } + return value; + } + } + + loss(filters: number[]) { + // Argument is array of percentages. + const color = this.reusedColor; + color.set(0, 0, 0); + + color.invert(filters[0] / 100); + color.sepia(filters[1] / 100); + color.saturate(filters[2] / 100); + color.hueRotate(filters[3] * 3.6); + color.brightness(filters[4] / 100); + color.contrast(filters[5] / 100); + + const colorHSL = color.hsl(); + return ( + Math.abs(color.r - this.target.r) + + Math.abs(color.g - this.target.g) + + Math.abs(color.b - this.target.b) + + Math.abs(colorHSL.h - this.targetHSL.h) + + Math.abs(colorHSL.s - this.targetHSL.s) + + Math.abs(colorHSL.l - this.targetHSL.l) + ); + } + + css(filters: number[]) { + function fmt(idx: number, multiplier = 1) { + return Math.round(filters[idx] * multiplier); + } + return `invert(${fmt(0)}%) sepia(${fmt(1)}%) saturate(${fmt( + 2 + )}%) hue-rotate(${fmt(3, 3.6)}deg) brightness(${fmt(4)}%) contrast(${fmt( + 5 + )}%);`; + } +} + +type RGB = [number, number, number]; +function hexToRgb(hex: string): RGB { + // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") + const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + hex = hex.replace(shorthandRegex, (m, r, g, b) => { + return r + r + g + g + b + b; + }); + + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + if (result) { + return [ + parseInt(result[1], 16), + parseInt(result[2], 16), + parseInt(result[3], 16), + ]; + } + + throw new Error("Error parsing hex: " + hex); +} + +function getFilters(hex: string) { + let rgb = [255, 255, 255]; + try { + rgb = hexToRgb(hex); + } catch (e) {} + const color = new Color(rgb[0], rgb[1], rgb[2]); + const solver = new Solver(color); + const result = solver.solve(); + return result.filter; +} + +// https://leanny.github.io/colors +const COLOR_OPTIONS: [alphaColorHex: string, bravoColorHex: string][] = [ + ["#2922b5", "#5eb604"], + ["#3b362", "#b1008d"], + ["#25b100", "#571db1"], + ["#7b0393", "#43ba05"], + ["#cae6e", "#f75900"], + ["#d9c100", "#7ac9"], + ["#ce8003", "#9208b2"], + ["#dea801", "#4717a9"], + ["#bbc905", "#830b9c"], + ["#7edc", "#e1a307"], + ["#d60e6e", "#311aa8"], + ["#cf0466", "#17a80d"], + ["#cb0856", "#199b8"], + ["#de0b64", "#bfd002"], + ["#4a14aa", "#fb5c03"], + ["#5f0fb4", "#8b672"], + ["#dea801", "#4717a9"], +]; + +const BeautifulDrawingOfBorzoic = ({ + type, + colorIndex, + setColorIndex, +}: { + type: "girl" | "boy"; + colorIndex: number; + setColorIndex: (newIndex: number) => void; +}) => { + return ( + + setColorIndex( + colorIndex === COLOR_OPTIONS.length - 1 ? 0 : colorIndex + 1 + ) + } + onMouseEnter={() => + setColorIndex( + colorIndex === COLOR_OPTIONS.length - 1 ? 0 : colorIndex + 1 + ) + } + > + + + + ); +}; + +export default BeautifulDrawingOfBorzoic; diff --git a/package-lock.json b/package-lock.json index a78a628f6..ae6cd66e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9684,14 +9684,17 @@ } }, "node_modules/jose": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/jose/-/jose-1.28.0.tgz", - "integrity": "sha512-JmfDRzt/HSj8ipd9TsDtEHoLUnLYavG+7e8F6s1mx2jfVSfXOTaFQsJUydbjJpTnTDHP1+yKL9Ke7ktS/a0Eiw==", + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/jose/-/jose-1.28.1.tgz", + "integrity": "sha512-6JK28rFu5ENp/yxMwM+iN7YeaInnY9B9Bggjkz5fuwLiJhbVrl2O4SJr65bdNBPl9y27fdC3Mymh+FVCvozLIg==", "dependencies": { "@panva/asn1.js": "^1.0.0" }, "engines": { "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/sponsors/panva" } }, "node_modules/js-sha3": { @@ -22673,9 +22676,9 @@ } }, "jose": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/jose/-/jose-1.28.0.tgz", - "integrity": "sha512-JmfDRzt/HSj8ipd9TsDtEHoLUnLYavG+7e8F6s1mx2jfVSfXOTaFQsJUydbjJpTnTDHP1+yKL9Ke7ktS/a0Eiw==", + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/jose/-/jose-1.28.1.tgz", + "integrity": "sha512-6JK28rFu5ENp/yxMwM+iN7YeaInnY9B9Bggjkz5fuwLiJhbVrl2O4SJr65bdNBPl9y27fdC3Mymh+FVCvozLIg==", "requires": { "@panva/asn1.js": "^1.0.0" } diff --git a/pages/demo.tsx b/pages/demo.tsx index 353568c51..b1eb1d1bf 100644 --- a/pages/demo.tsx +++ b/pages/demo.tsx @@ -1,399 +1,25 @@ /* eslint-disable */ -import { Image } from "@chakra-ui/image"; -import { Input } from "@chakra-ui/input"; import { Box } from "@chakra-ui/layout"; -import { useColorModeValue } from "@chakra-ui/react"; -import { useMyTheme } from "hooks/common"; +import BeautifulDrawingOfBorzoic from "components/layout/BeautifulDrawingByBorzoic"; import { useState } from "react"; -interface HSL { - h: number; - s: number; - l: number; -} - -class Color { - public r: number; - public g: number; - public b: number; - constructor(r: number, g: number, b: number) { - this.r = this.clamp(r); - this.g = this.clamp(g); - this.b = this.clamp(b); - } - - toString() { - return `rgb(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round( - this.b - )})`; - } - - set(r: number, g: number, b: number) { - this.r = this.clamp(r); - this.g = this.clamp(g); - this.b = this.clamp(b); - } - - hueRotate(angle = 0) { - angle = (angle / 180) * Math.PI; - const sin = Math.sin(angle); - const cos = Math.cos(angle); - - this.multiply([ - 0.213 + cos * 0.787 - sin * 0.213, - 0.715 - cos * 0.715 - sin * 0.715, - 0.072 - cos * 0.072 + sin * 0.928, - 0.213 - cos * 0.213 + sin * 0.143, - 0.715 + cos * 0.285 + sin * 0.14, - 0.072 - cos * 0.072 - sin * 0.283, - 0.213 - cos * 0.213 - sin * 0.787, - 0.715 - cos * 0.715 + sin * 0.715, - 0.072 + cos * 0.928 + sin * 0.072, - ]); - } - - grayscale(value = 1) { - this.multiply([ - 0.2126 + 0.7874 * (1 - value), - 0.7152 - 0.7152 * (1 - value), - 0.0722 - 0.0722 * (1 - value), - 0.2126 - 0.2126 * (1 - value), - 0.7152 + 0.2848 * (1 - value), - 0.0722 - 0.0722 * (1 - value), - 0.2126 - 0.2126 * (1 - value), - 0.7152 - 0.7152 * (1 - value), - 0.0722 + 0.9278 * (1 - value), - ]); - } - - sepia(value = 1) { - this.multiply([ - 0.393 + 0.607 * (1 - value), - 0.769 - 0.769 * (1 - value), - 0.189 - 0.189 * (1 - value), - 0.349 - 0.349 * (1 - value), - 0.686 + 0.314 * (1 - value), - 0.168 - 0.168 * (1 - value), - 0.272 - 0.272 * (1 - value), - 0.534 - 0.534 * (1 - value), - 0.131 + 0.869 * (1 - value), - ]); - } - - saturate(value = 1) { - this.multiply([ - 0.213 + 0.787 * value, - 0.715 - 0.715 * value, - 0.072 - 0.072 * value, - 0.213 - 0.213 * value, - 0.715 + 0.285 * value, - 0.072 - 0.072 * value, - 0.213 - 0.213 * value, - 0.715 - 0.715 * value, - 0.072 + 0.928 * value, - ]); - } - - multiply(matrix: any) { - const newR = this.clamp( - this.r * matrix[0] + this.g * matrix[1] + this.b * matrix[2] - ); - const newG = this.clamp( - this.r * matrix[3] + this.g * matrix[4] + this.b * matrix[5] - ); - const newB = this.clamp( - this.r * matrix[6] + this.g * matrix[7] + this.b * matrix[8] - ); - this.r = newR; - this.g = newG; - this.b = newB; - } - - brightness(value = 1) { - this.linear(value); - } - contrast(value = 1) { - this.linear(value, -(0.5 * value) + 0.5); - } - - linear(slope = 1, intercept = 0) { - this.r = this.clamp(this.r * slope + intercept * 255); - this.g = this.clamp(this.g * slope + intercept * 255); - this.b = this.clamp(this.b * slope + intercept * 255); - } - - invert(value = 1) { - this.r = this.clamp((value + (this.r / 255) * (1 - 2 * value)) * 255); - this.g = this.clamp((value + (this.g / 255) * (1 - 2 * value)) * 255); - this.b = this.clamp((value + (this.b / 255) * (1 - 2 * value)) * 255); - } - - hsl(): HSL { - // Code taken from https://stackoverflow.com/a/9493060/2688027, licensed under CC BY-SA. - const r = this.r / 255; - const g = this.g / 255; - const b = this.b / 255; - const max = Math.max(r, g, b); - const min = Math.min(r, g, b); - - let h = 0; - let s = 0; - let l = (max + min) / 2; - - if (max === min) { - h = s = 0; - } else { - const d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - switch (max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0); - break; - - case g: - h = (b - r) / d + 2; - break; - - case b: - h = (r - g) / d + 4; - break; - } - h /= 6; - } - - return { - h: h * 100, - s: s * 100, - l: l * 100, - }; - } - - clamp(value: number): number { - if (value > 255) { - value = 255; - } else if (value < 0) { - value = 0; - } - return value; - } -} -interface Solution { - loss: number; - values: number[]; -} -class Solver { - private target: Color; - private targetHSL: HSL; - private reusedColor: Color; - constructor(target: Color) { - this.target = target; - this.targetHSL = target.hsl(); - this.reusedColor = new Color(0, 0, 0); - } - - solve() { - const result = this.solveNarrow(this.solveWide()); - return { - values: result.values, - loss: result.loss, - filter: this.css(result.values), - }; - } - - solveWide(): Solution { - const A = 5; - const c = 15; - const a = [60, 180, 18000, 600, 1.2, 1.2]; - - let best = { loss: Infinity, values: [] as number[] }; - for (let i = 0; best.loss > 25 && i < 3; i++) { - const initial = [50, 20, 3750, 50, 100, 100]; - const result = this.spsa(A, a, c, initial, 1000); - if (result.loss < best.loss) { - best = result; - } - } - return best; - } - - solveNarrow(wide: Solution) { - const A = wide.loss; - const c = 2; - const A1 = A + 1; - const a = [0.25 * A1, 0.25 * A1, A1, 0.25 * A1, 0.2 * A1, 0.2 * A1]; - return this.spsa(A, a, c, wide.values, 500); - } - - spsa( - A: number, - a: number[], - c: number, - values: number[], - iters: number - ): Solution { - const alpha = 1; - const gamma = 0.16666666666666666; - - let best = [] as number[]; - let bestLoss = Infinity; - const deltas = new Array(6); - const highArgs = new Array(6); - const lowArgs = new Array(6); - - for (let k = 0; k < iters; k++) { - const ck = c / Math.pow(k + 1, gamma); - for (let i = 0; i < 6; i++) { - deltas[i] = Math.random() > 0.5 ? 1 : -1; - highArgs[i] = values[i] + ck * deltas[i]; - lowArgs[i] = values[i] - ck * deltas[i]; - } - - const lossDiff = this.loss(highArgs) - this.loss(lowArgs); - for (let i = 0; i < 6; i++) { - const g = (lossDiff / (2 * ck)) * deltas[i]; - const ak = a[i] / Math.pow(A + k + 1, alpha); - values[i] = fix(values[i] - ak * g, i); - } - - const loss = this.loss(values); - if (loss < bestLoss) { - best = values.slice(0); - bestLoss = loss; - } - } - return { values: best, loss: bestLoss }; - - function fix(value: number, idx: number): number { - let max = 100; - if (idx === 2 /* saturate */) { - max = 7500; - } else if (idx === 4 /* brightness */ || idx === 5 /* contrast */) { - max = 200; - } - - if (idx === 3 /* hue-rotate */) { - if (value > max) { - value %= max; - } else if (value < 0) { - value = max + (value % max); - } - } else if (value < 0) { - value = 0; - } else if (value > max) { - value = max; - } - return value; - } - } - - loss(filters: number[]) { - // Argument is array of percentages. - const color = this.reusedColor; - color.set(0, 0, 0); - - color.invert(filters[0] / 100); - color.sepia(filters[1] / 100); - color.saturate(filters[2] / 100); - color.hueRotate(filters[3] * 3.6); - color.brightness(filters[4] / 100); - color.contrast(filters[5] / 100); - - const colorHSL = color.hsl(); - return ( - Math.abs(color.r - this.target.r) + - Math.abs(color.g - this.target.g) + - Math.abs(color.b - this.target.b) + - Math.abs(colorHSL.h - this.targetHSL.h) + - Math.abs(colorHSL.s - this.targetHSL.s) + - Math.abs(colorHSL.l - this.targetHSL.l) - ); - } - - css(filters: number[]) { - function fmt(idx: number, multiplier = 1) { - return Math.round(filters[idx] * multiplier); - } - return `invert(${fmt(0)}%) sepia(${fmt(1)}%) saturate(${fmt( - 2 - )}%) hue-rotate(${fmt(3, 3.6)}deg) brightness(${fmt(4)}%) contrast(${fmt( - 5 - )}%);`; - } -} - -type RGB = [number, number, number]; -function hexToRgb(hex: string): RGB { - // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") - const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; - hex = hex.replace(shorthandRegex, (m, r, g, b) => { - return r + r + g + g + b + b; - }); - - const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); - if (result) { - return [ - parseInt(result[1], 16), - parseInt(result[2], 16), - parseInt(result[3], 16), - ]; - } - - throw new Error("Error parsing hex: " + hex); -} - -export function getFilters(hex: string) { - let rgb = [255, 255, 255]; - try { - rgb = hexToRgb(hex); - } catch (e) {} - const color = new Color(rgb[0], rgb[1], rgb[2]); - const solver = new Solver(color); - const result = solver.solve(); - console.log(result); - return result.filter; -} - const DemoPage = () => { - const borderColor = useColorModeValue("black", "white"); - const [hex, setHex] = useState("#FFC0CB"); - const [hex2, setHex2] = useState("#FFEA61"); + const [colorIndex, setColorIndex] = useState(0); return ( <> - - - - - setHex(e.target.value)} + - - - - - - setHex2(e.target.value)} + +