Add the color scheme generator

This commit is contained in:
Feufochmar 2019-11-08 13:29:43 +01:00
parent c2bbbfb103
commit ebeb1c5d9e
5 changed files with 501 additions and 14 deletions

View File

@ -10,18 +10,25 @@
"src/webcontainer/webcontainer.rkt"
"src/webcontainer/website.rkt"
"src/pages/sitemap.rkt"
"src/pages/home.rkt")
"src/pages/home.rkt"
"src/pages/other-generators.rkt")
; Website
(define *website*
(website
"" weblet pages:home
("AboutMe" weblet pages:about-me)
("Fonts" weblet pages:fonts)))
("Fonts" weblet pages:fonts)
("Generators" weblet pages:other-generators
("ColorScheme" weblet pages:color-scheme-generator))
))
; Sitemap
(sitemap
("Home" "/" #f
("About Me" "/AboutMe" #f)
("Fonts I made" "/Fonts" #f)))
("Fonts I made" "/Fonts" #f))
("Miscellaneous Generators" "/Generators" #f
("Color Scheme" "/Generators/ColorScheme" #f))
)
; Webcontainer
(define *webcontainer* (make-webcontainer))
(webcontainer-add-website! *webcontainer* *website*)
@ -29,12 +36,3 @@
(display "Starting server...")(newline)
(webcontainer-start *webcontainer*))
;(website
; "" weblet home-page
; ("ToyCatCreator" redirection "http://beleth.pink")
; ("About" weblet about-me-page)
; ("Fonts" weblet fonts-page)
; ("FlagGenerator" weblet flag-generator-page
; ("RawFlag" weblet flag-generator-raw-page)
; ("About" weblet about-flag-generator-page))
; )

View File

@ -0,0 +1,63 @@
#lang racket/base
; Pages with minor generators, often written in javascript
(require
"templates.rkt"
racket/string)
(provide
pages:other-generators
pages:color-scheme-generator)
; About page on the other generators
(define pages:other-generators
(pages:template
#:title "Miscellaneous Generators"
#:author "Feufochmar"
#:date "2019-11-08"
#:content
'(article
(p "One of the thing I like to code as a hobby are procedural generators. "
"This section regroups little other generators I wrote, but that are not important enough to have their own section. "
"Some where made during the " (a ((href "http://www.procjam.com/")) "PROCJAM") " sessions, a recurrent coding jam about procedural generation. "
"So they may also be present on my " (a ((href "https://feufochmar.itch.io/")) " itch.io page") ". ")
)))
; Color Scheme Generator
(define (color-scheme-block name type)
`(div ((class "palette-block"))
(p ((id ,(string-append "wheel." type)) (class "palette-wheel")) "")
(p ((class "palette-name")) ,name)
,@(build-list 5
(lambda (i)
(let ((j (+ 1 i)))
`(p ((id ,(string-append "color-" (number->string j) "-" type))
(class "palette-color"))
,(string-append "Color." (number->string j) "." name)))))))
(define pages:color-scheme-generator
(pages:template
#:title "Color Scheme Generator"
#:author "Feufochmar"
#:date "2019-11-08"
#:stylesheets '("/css/feuforeve.css" "/css/palette-generator.css")
#:scripts '("/scripts/palette-generator.js")
#:on-load "generateScheme();"
#:content
`(article
(section
(p (button ((onclick "generateScheme();")) "New color scheme")))
(section
,(color-scheme-block "HSL" "hsl")
,(color-scheme-block "HSV" "hsv")
,(color-scheme-block "LCHab" "lchab")
,(color-scheme-block "LCHuv" "lchuv") )
(section
(p (h3 "About this generator")
"This generator creates a small color palette using a cylindrical representation. "
"This representation is mapped to four different color spaces to generate four color schemes. " (br)
"The color wheel displayed shows for each colorspace the colors with the hues from 0° to 360°, "
"a lightness or value of 50%, and a saturation or chroma of 100%. " (br)
"The colors are displayed with their RGB values. "
))
)))

View File

@ -26,9 +26,9 @@
(define (get-avatar author)
(case author
(("Feufochmar")
"images/feufochmar.png")
"/images/feufochmar.png")
(("feuforeve.fr")
("images/feuforeve.png"))
("/images/feuforeve.png"))
(else
#f)))

View File

@ -0,0 +1,24 @@
.palette-block {
display: inline-block;
width: 180;
margin: 1ex;
border: solid;
border-color: hsla(230, 10%, 15%, 1.0);
border-width: thin;
}
.palette-wheel {
padding: 0px;
margin: 0px;
}
.palette-name {
text-align: center;
padding: 1ex;
margin: 0px;
}
.palette-color {
padding: 1ex;
margin: 0px;
}

View File

@ -0,0 +1,402 @@
// Color
function Color() {
this.hue = 0;
this.saturation = 0;
this.luminosity = 0;
}
// Palette
function ColorScheme() {
this.color1 = new Color();
this.color2 = new Color();
this.color3 = new Color();
this.color4 = new Color();
this.color5 = new Color();
};
//
function displayRgb(r, g, b) {
var hexVal = 0x1000000 + r * 0x010000 + g * 0x000100 + b * 0x000001;
return '#' + hexVal.toString(16).substring(1);
}
// Pick an element from an array at random
function pickFrom(arr) {
var idx = Math.floor(arr.length * Math.random());
return arr[idx];
}
function pickBoolean() {
return Math.random() < 0.5;
}
function clamp(val, mn, mx) {
return Math.min(mx, Math.max(mn, val));
}
function hToRgb(v1, v2, h) {
var vH = h;
if (h < 0) {
vH = h + 1;
}
if (h > 1) {
vH = h - 1;
}
var res;
if ((6 * vH) < 1) {
res = v1 + ((v2 - v1) * 6 * vH);
} else if ((2 * vH) < 1) {
res = v2;
} else if ((3 * vH) < 2) {
res = v1 + ((v2 - v1) * 6 * ((2 / 3) - vH));
} else {
res = v1;
}
return res;
}
function hslToRgb(color) {
var h = color.hue; // 0..360
var s = color.saturation; // 0..100
var l = color.luminosity; // 0..100
//
var r = 0;
var g = 0;
var b = 0;
//
if (s == 0) {
r = l / 100;
g = l / 100;
b = l / 100;
} else {
var hr = h / 360;
var lr = l / 100;
var sr = s / 100;
var v2;
if (lr < 0.5) {
v2 = lr * (1 + sr);
} else {
v2 = (lr + sr) - (lr * sr);
}
var v1 = 2 * lr - v2;
r = hToRgb(v1, v2, hr + (1 / 3));
g = hToRgb(v1, v2, hr);
b = hToRgb(v1, v2, hr - (1 / 3));
}
//
r = Math.floor(clamp(255 * r, 0, 255));
g = Math.floor(clamp(255 * g, 0, 255));
b = Math.floor(clamp(255 * b, 0, 255));
return displayRgb(r, g, b);
}
function hsvToRgb(color) {
var h = color.hue; // 0..360
var s = color.saturation; // 0..100
var v = color.luminosity; // 0..100
//
if (s == 0) {
r = v / 100;
g = v / 100;
b = v / 100;
} else {
var hr = h / 360;
var vr = v / 100;
var sr = s / 100;
//
var vH = hr * 6;
if (vH == 6) {
vH = 0;
}
var vI = Math.floor(vH);
var v1 = vr * (1 - sr);
var v2 = vr * (1 - sr * (vH - vI));
var v3 = vr * (1 - sr * (1 - vH + vI));
//
if (vI == 0) {
r = vr;
g = v3;
b = v1;
} else if (vI == 1) {
r = v2;
g = vr;
b = v1;
} else if (vI == 2) {
r = v1;
g = vr;
b = v3;
} else if (vI == 3) {
r = v1;
g = v2;
b = vr;
} else if (vI == 4) {
r = v3;
g = v1;
b = vr;
} else {
r = vr;
g = v1;
b = v2;
}
}
//
r = Math.floor(clamp(255 * r, 0, 255));
g = Math.floor(clamp(255 * g, 0, 255));
b = Math.floor(clamp(255 * b, 0, 255));
return displayRgb(r, g, b);
}
function xyzToRgb(x, y, z) {
var vX = x / 100; // Note: x from 0 to 95.047 (Observer = 2°, Illuminant = D65)
var vY = y / 100; // Note: y from 0 to 100
var vZ = z / 100; // Note: z from 0 to 108.883
//
var vR = (3.2406 * vX) + (-1.5372 * vY) + (-0.4986 * vZ);
var vG = (-0.9689 * vX) + (1.8758 * vY) + (0.0415 * vZ);
var vB = (0.0557 * vX) + (-0.2040 * vY) + (1.0570 * vZ);
//
var transform = function(v) {
var res;
if (v > 0.0031308) {
res = (1.055 * Math.pow(v, 1 / 2.4)) - 0.055;
} else {
res = 12.92 * v;
}
return res;
}
//
var r = Math.floor(clamp(255 * transform(vR), 0, 255));
var g = Math.floor(clamp(255 * transform(vG), 0, 255));
var b = Math.floor(clamp(255 * transform(vB), 0, 255));
return displayRgb(r, g, b);
}
function LabToRgb(l, a, b) {
var vY = (l + 16) / 116;
var vX = vY + (a / 500);
var vZ = vY - (b / 200);
//
var transform = function(v) {
var res;
var v3 = v * v * v;
if (v3 > 0.008856) {
res = v3;
} else {
res = (v - (16 / 116)) / 7.787;
}
return res;
}
// Observer= 2°, Illuminant= D65
var x = 95.047 * transform(vX);
var y = 100.000 * transform(vY);
var z = 108.883 * transform(vZ);
return xyzToRgb(x, y, z);
}
function LCHabToRgb(color) {
var l = color.luminosity; // 0..100
var c = color.saturation; // 0..100
var h = color.hue; // 0..360
//
var a = c * Math.cos(h * Math.PI / 180);
var b = c * Math.sin(h * Math.PI / 180);
return LabToRgb(l, a, b);
}
function LuvToRgb(l, u, v) {
var vY;
if (l > 8) {
vY = Math.pow((l + 16) / 116, 3);
} else {
vY = l * Math.pow(3 / 29, 3);
}
// Observer= 2°, Illuminant= D65
var rX = 95.047;
var rY = 100.000;
var rZ = 108.883;
var rU = (4 * rX) / (rX + (15 * rY) + (3 * rZ));
var rV = (9 * rY) / (rX + (15 * rY) + (3 * rZ));
var vU = rU + (u / (13 * l));
var vV = rV + (v / (13 * l));
var y = 100 * vY;
var x = y * ((9 * vU) / (4 * vV));
var z = y * ((12 - (3 * vU) - (20 * vV)) / (4 * vV));
return xyzToRgb(x, y, z);
}
function LCHuvToRgb(color) {
var l = color.luminosity; // 0..100
var c = color.saturation; // 0..100
var h = color.hue; // 0..360
//
var u = c * Math.cos(h * Math.PI / 180);
var v = c * Math.sin(h * Math.PI / 180);
return LuvToRgb(l, u, v);
}
// Choose the first color
function pickInitialColor() {
var result = new Color();
result.hue = 360 * Math.random();
result.saturation = 60 + (30 * Math.random());
result.luminosity = 60 + (30 * Math.random());
return result;
}
function lerp(a, b, t) {
return a + t * (b - a);
}
function onWheel(val) {
if (val < 0) {
return onWheel(val + 360);
} else if (val >= 360) {
return onWheel(val - 360);
} else {
return val;
}
}
// Gradient between complementaries colors
function algorithmGradient(initialColor) {
var scheme = new ColorScheme();
scheme.color1 = initialColor;
scheme.color5.hue = onWheel(scheme.color1.hue + 180);
scheme.color5.saturation = 100 - scheme.color1.saturation;
scheme.color5.luminosity = 100 - scheme.color1.luminosity;
var directHue = pickBoolean();
var minHue = scheme.color1.hue;
var maxHue = scheme.color5.hue;
var minSat = scheme.color1.saturation;
var maxSat = scheme.color5.saturation;
var minLum = scheme.color1.luminosity;
var maxLum = scheme.color5.luminosity;
var computeColor = function(color, step) {
if (directHue) {
color.hue = onWheel(lerp(minHue, maxHue, step));
} else {
color.hue = onWheel(lerp(maxHue, minHue, 1 - step));
}
color.saturation = lerp(minSat, maxSat, step);
color.luminosity = lerp(minLum, maxLum, step);
}
computeColor(scheme.color2, 0.25);
computeColor(scheme.color3, 0.5);
computeColor(scheme.color4, 0.75);
return scheme;
}
function pickValueAnalog(x) {
var a = 50 + x / 2;
var b = 3 * x / 2 - 50;
var mn = Math.max(50, Math.min(a, b));
var mx = Math.min(90, Math.max(a, b));
return lerp(mn, mx, Math.random());
}
function pickValueComplementary(x) {
var a = x / 4;
var b = 3 * x / 4;
var mn = Math.max(a, 25);
var mx = Math.min(b, 50);
return lerp(mn, mx, Math.random());
}
// initial, 2 analogs, 2 complementaries
function algorithmFiveHues(initialColor) {
var scheme = new ColorScheme();
scheme.color2 = initialColor;
var analogDistance = 25 + Math.floor(25 * Math.random());
scheme.color1.hue = onWheel(scheme.color2.hue + analogDistance);
scheme.color3.hue = onWheel(scheme.color2.hue - analogDistance);
scheme.color4.hue = onWheel(scheme.color2.hue + (analogDistance / 2) + 180);
scheme.color5.hue = onWheel(scheme.color2.hue - (analogDistance / 2) + 180);
/**/
scheme.color1.saturation = pickValueAnalog(scheme.color2.saturation);
scheme.color3.saturation = pickValueAnalog(scheme.color2.saturation);
scheme.color1.luminosity = pickValueAnalog(scheme.color2.luminosity);
scheme.color3.luminosity = pickValueAnalog(scheme.color2.luminosity);
if (pickBoolean()) {
scheme.color4.saturation = pickValueComplementary(scheme.color2.saturation);
scheme.color4.luminosity = pickValueComplementary(scheme.color2.luminosity);
} else {
scheme.color4.saturation = pickValueAnalog(scheme.color2.saturation);
scheme.color4.luminosity = pickValueAnalog(scheme.color2.luminosity);
}
if (pickBoolean()) {
scheme.color5.saturation = pickValueComplementary(scheme.color2.saturation);
scheme.color5.luminosity = pickValueComplementary(scheme.color2.luminosity);
} else {
scheme.color5.saturation = pickValueAnalog(scheme.color2.saturation);
scheme.color5.luminosity = pickValueAnalog(scheme.color2.luminosity);
}
return scheme;
}
// Create a new color scheme
function newScheme() {
// Pick a color
var firstColor = pickInitialColor();
// choose a scheme generator algorithm
var algorithm = pickFrom([algorithmGradient, algorithmFiveHues, algorithmFiveHues, algorithmFiveHues, algorithmFiveHues]);
//
return algorithm(firstColor);
}
// Entry point
function generateScheme() {
var scheme = newScheme();
updateBlock('hsl', scheme, hslToRgb);
updateBlock('hsv', scheme, hsvToRgb);
updateBlock('lchab', scheme, LCHabToRgb);
updateBlock('lchuv', scheme, LCHuvToRgb);
}
// Update a block
function updateBlock(blockId, scheme, colorFun) {
updateColor('color-1-' + blockId, scheme.color1, colorFun);
updateColor('color-2-' + blockId, scheme.color2, colorFun);
updateColor('color-3-' + blockId, scheme.color3, colorFun);
updateColor('color-4-' + blockId, scheme.color4, colorFun);
updateColor('color-5-' + blockId, scheme.color5, colorFun);
displayWheel('wheel.' + blockId, colorFun, 50);
}
// Update the background (and foreground) color of element
function updateColor(id, color, func) {
var elem = document.getElementById(id);
var value = func(color);
elem.style.background = value;
elem.innerHTML = value;
if (color.luminosity < 45) {
elem.style.color = 'white';
} else {
elem.style.color = 'black';
}
}
// Display the wheel at sat = 100
function displayWheel(id, colorFun, lum) {
var elem = document.getElementById(id);
var contents = '';
var col = new Color();
col.saturation = 100;
col.luminosity = lum;
for (i = 0; i < 359; i += 2) {
col.hue = i;
contents += '<span style="display:inline-block;width:1px;height:20px;background:';
contents += colorFun(col);
contents += ';"></span>';
}
elem.innerHTML = contents;
}