Skip to content

Commit

Permalink
refactor: Optimize Color class and update dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
FelixLuciano committed Jul 11, 2024
1 parent 7cc52af commit 128905f
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 87 deletions.
52 changes: 31 additions & 21 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
</script> -->
<script type="importmap">
{ "imports": {
"alpinejs": "https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.11.1/module.esm.min.js"
"alpinejs": "https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.11.1/module.esm.min.js",
"colorthief": "https://unpkg.com/[email protected]/dist/color-thief.mjs"
} }
</script>
<script type="module" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn/beer.min.js"></script>
Expand Down Expand Up @@ -101,7 +102,7 @@ <h1>picker</h1>
<div class="max"></div>
<button class="transparent small-round vertical">
<i>image</i>
<span>image</span>
<span>extract</span>
<menu id="menu" class="no-wrap min left small-round">
<a @click="getImageFromFIle" data-ui="#menu" class="row">
<i>upload_file</i>
Expand All @@ -126,11 +127,20 @@ <h1>picker</h1>
</nav>
</header>
<template x-if="image !== null">
<div class="">
<canvas id="canvas" x-effect="displayImage($el)" @click="setColorFromCanvas($el, $event)"
class="round w-full h-full"></canvas>
<span class="helper font-mono">image (click do pick)</span>
</div>
<nav class="grid">
<div class="s12">
<canvas id="canvas" x-effect="displayImage($el)" @click="setColorFromCanvas($el, $event)"
class="round w-full h-full"></canvas>
<span class="helper font-mono">image (click do pick)</span>
</div>
<template x-for="[name, imageColor] in Object.entries(imagePalette)" :key="imageColor.hex">
<div class="s4 m2 field fill round ratio-2-1">
<span class="helper font-mono" x-text="name"></span>
<button @click="setColor(imageColor)" x-bind="getDisplayBind('imageColor')" x-text="imageColor.hex" class="responsive h-full m-0 font-mono text-medium smooth-color">#FFFFFF</button>
</div>
</template>
<div class="s12"></div>
</nav>
</template>
<nav class="grid">
<div x-bind="getDisplayBind('color')" class="s12 l6 field round border ratio-3-1 smooth-color" style="background-color: #FFFFFF;">
Expand Down Expand Up @@ -175,23 +185,23 @@ <h1>picker</h1>
</div>
<div class="s m s12"></div>
<div class="s4 l2 field fill round ratio-square">
<span class="helper font-mono">hue:<b x-text="Math.round(hue) + '°'">180°</b></span>
<span class="helper font-mono">hue:<b x-text="Math.round(hue * 360) + '°'">180°</b></span>
<label class="slider max vertical" :style="{color: hueDisplay.hex}">
<input x-model.number="hue" type="range" min="0" max="360" step="1" value="180">
<input x-model.number="hue" type="range" min="0" max="1" :step="1/360">
<span class="smooth-color"></span>
</label>
</div>
<div class="s4 l2 field fill round ratio-square">
<span class="helper font-mono">chroma:<b x-text="Math.round(chroma * 100)">0</b></span>
<label class="slider max vertical" :style="{color: chromaDisplay.hex}">
<input x-model.number="chroma" type="range" min="0" max="1" :step="1/100" value="0">
<input x-model.number="chroma" type="range" min="0" max="1" :step="1/100">
<span class="smooth-color"></span>
</label>
</div>
<div class="s4 l2 field fill round ratio-square">
<span class="helper font-mono">light:<b x-text="Math.round(light * 100)">100</b></span>
<span class="helper font-mono">light:<b x-text="Math.round(color.light * 100)">100</b></span>
<label class="slider max vertical" style="color: currentcolor;">
<input x-model.number="light" type="range" min="0" max="1" :step="1/100">
<input x-model.number="light" type="range" :min="0" max="1" :step="1/100">
<span class="smooth-color"></span>
</label>
</div>
Expand Down Expand Up @@ -259,17 +269,17 @@ <h2>accessibility</h2>
<div class="s m s12"></div>
<nav class="s8 m4 field ratio-2-1 no-space">
<span class="helper font-mono">subject</span>
<button @click="setBackgroundColor()" x-bind="getDisplayBind('background')" class="responsive w-half h-full p-0 left-round text-medium smooth-color" style="position: relative;">background</button>
<button @click="setForegroundColor()" x-bind="getDisplayBind('foreground')" class="responsive w-half h-full p-0 right-round text-medium smooth-color">foreground</button>
<button @click="setColor(background)" x-bind="getDisplayBind('background')" class="circle absolute bottom left transparent margin smooth-color" style="z-index: 2;">
<button @click="setForegroundColor()" x-bind="getDisplayBind('foreground')" class="responsive w-half h-full p-0 left-round text-medium smooth-color">foreground</button>
<button @click="setBackgroundColor()" x-bind="getDisplayBind('background')" class="responsive w-half h-full p-0 right-round text-medium smooth-color" style="position: relative;">background</button>
<button @click="setColor(foreground)" x-bind="getDisplayBind('foreground')" class="circle absolute bottom left transparent margin smooth-color" style="z-index: 2;">
<i>colorize</i>
</button>
<button @click="setColor(foreground)" x-bind="getDisplayBind('foreground')" class="circle absolute bottom right transparent margin smooth-color" style="z-index: 2;">
<button @click="setColor(background)" x-bind="getDisplayBind('background')" class="circle absolute bottom right transparent margin smooth-color" style="z-index: 2;">
<i>colorize</i>
</button>
</nav>
<div class="s4 m2 field fill round ratio-square">
<span class="helper font-mono">grade <strong x-text="contrastDisplay">65:8</strong></span>
<span class="helper font-mono">grade <strong x-text="contrastDisplay">100:1</strong></span>
<button x-bind="getDisplayBind('contrastGradeDisplay')" x-text="contrastGrade" class="responsive h-full m-0 font-mono text-extra center-text smooth-color">AAA</button>
</div>
</nav>
Expand Down Expand Up @@ -303,9 +313,9 @@ <h2>fades</h2>
</div>
<div class="s m s12"></div>
<div class="s4 l2 field fill round ratio-square">
<span class="helper font-mono">darken hue:<b x-text="darkHue">0</b></span>
<span class="helper font-mono">darken hue:<b x-text="Math.round(darkHue * 360)">0</b></span>
<label class="slider max vertical" :style="{color: darkHueDisplay.hex}">
<input x-model.number="darkHue" type="range" min="-360" max="360">
<input x-model.number="darkHue" type="range" min="-1" max="1" :step="1/360">
<span></span>
</label>
</div>
Expand Down Expand Up @@ -333,9 +343,9 @@ <h2>fades</h2>
</div>
<div class="s m s12"></div>
<div class="s4 l2 field fill round ratio-square">
<span class="helper font-mono">lighten hue:<b x-text="lightHue">0</b></span>
<span class="helper font-mono">lighten hue:<b x-text="Math.round(lightHue * 360)">0</b></span>
<label class="slider max vertical" :style="{color: lightHueDisplay.hex}">
<input x-model.number="lightHue" type="range" min="-360" max="360">
<input x-model.number="lightHue" type="range" min="-1" max="1" :step="1/360">
<span class="smooth-color"></span>
</label>
</div>
Expand Down
114 changes: 48 additions & 66 deletions public/picker.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import Alpine from 'alpinejs'
import ColorThief from 'colorthief'

import Color from '/color.mjs'
import swatches from '/swatches/swatches.mjs'


export function picker() {
const BLACK = new Color(0, 0, 0)
const WHITE = new Color(1, 1, 1)

return {
swatches,
createColor: Color,
Expand Down Expand Up @@ -75,7 +73,7 @@ export function picker() {
this.hexInput = this.color.hex
},
get hueDisplay() {
return Color.fromHsv(this.hue, .8, .8)
return Color.fromHsv(this.hue, 0.8, 0.8)
},

get chroma() {
Expand All @@ -86,7 +84,7 @@ export function picker() {
this.hexInput = this.color.hex
},
get chromaDisplay() {
return Color.fromHsv(this.hue, this.chroma * .8, this.chroma * .5 + .3)
return Color.fromHsv(this.hue, this.chroma * .8, this.chroma * 0.2 + 0.6)
},

get light() {
Expand All @@ -98,12 +96,12 @@ export function picker() {
},

get complementary1() {
const hue = (this.color.hue + 180 + 43) % 360
const hue = (this.color.hue + (180 + 43) / 360) % 1

return Color.fromHsv(hue, this.color.chroma, this.color.light)
},
get complementary2() {
const hue = (this.color.hue + 180 - 43) % 360
const hue = (this.color.hue + (180 - 43) / 360) % 1

return Color.fromHsv(hue, this.color.chroma, this.color.light)
},
Expand Down Expand Up @@ -168,7 +166,7 @@ export function picker() {

if (this.contrast >= target) target = 4.5
if (this.contrast >= target) target = 7
if (this.background.getContrast(WHITE) < this.background.getContrast(BLACK)) step = -0.01
if (this.background.luminance > 0.5) step = -0.01

for (let light = this.foreground.light; this.contrast < target && light >= 0.0 && light <= 1.0; light += step) {
this.foreground = Color.fromHsv(this.foreground.hue, this.foreground.chroma, light)
Expand All @@ -182,10 +180,10 @@ export function picker() {
return this.background.getContrast(this.foreground)
},
get contrastDisplay() {
const max = Math.round((Math.max(this.background.luminance, this.foreground.luminance) + 0.05) * 100)
const min = Math.round((Math.min(this.background.luminance, this.foreground.luminance) + 0.05) * 100)
const b = Math.round(this.foreground.luminance * 100)
const a = Math.round(this.background.luminance * 100)

return `${max}:${min}`
return `${b}:${a}`
},
get contrastGrade() {
if (this.contrast >= 7) return 'AAA'
Expand All @@ -201,19 +199,15 @@ export function picker() {
},

darkAmount: 6,
darkHue: 0.0,
darkHue: -15/360,
darkChroma: 1.0,
darkLevel: 0.8,
get darkFade() {
let hue = this.hue + this.darkHue
const saturation = this.darkChroma < 0 ? this.chroma * (this.darkChroma + 1) : this.chroma + (1 - this.chroma) * this.darkChroma
const value = this.light * (1 - this.darkLevel)

// if (hue < 0) {
// hue += 360
// }
const h = this.hue + this.darkHue
const s = this.darkChroma <= 0 ? this.chroma * (this.darkChroma + 1) : this.chroma + (1 - this.chroma) * this.darkChroma
const v = this.light * (1 - this.darkLevel)

return Color.fromHsv(hue, saturation, value)
return Color.fromHsv(h, s, v)
},
get darkHueDisplay() {
return Color.fromHsv(this.darkFade.hue, .8, .8)
Expand All @@ -222,20 +216,15 @@ export function picker() {
return Color.fromHsv(this.darkFade.hue, this.darkFade.chroma * .8, this.darkFade.chroma * .5 + .3)
},
lightAmount: 6,
lightHue: 0.0,
lightHue: 15/360,
lightChroma: -0.8,
lightLevel: 1.0,
get lightFade() {
let hue = this.hue + this.lightHue
// let hue = (this.hue + this.lightHue) % 361
const saturation = this.lightChroma < 0 ? this.chroma * (this.lightChroma + 1) : this.chroma + (1 - this.chroma) * this.lightChroma
const value = this.light + (1 - this.light) * this.lightLevel
const h = this.hue + this.lightHue
const s = this.lightChroma < 0 ? this.chroma * (this.lightChroma + 1) : this.chroma + (1 - this.chroma) * this.lightChroma
const v = this.light + (1 - this.light) * this.lightLevel

// if (hue < 0) {
// hue += 360
// }

return Color.fromHsv(hue, saturation, value)
return Color.fromHsv(h, s, v)
},
get lightHueDisplay() {
return Color.fromHsv(this.lightFade.hue, .8, .8)
Expand All @@ -252,7 +241,7 @@ export function picker() {
const dLight = c2.light - c1.light

return function (x) {
const hue = (x * dHue + c1.hue) % 361
const hue = x * dHue + c1.hue
const chroma = x * dChroma + c1.chroma
const light = x * dLight + c1.light

Expand Down Expand Up @@ -283,11 +272,11 @@ export function picker() {
}
},
getDisplayClass(color) {
const inverse = color.getContrast(WHITE) < color.getContrast(BLACK)
const inverse = color.luminance > 0.5

return {
'text-surface-dim': !inverse,
'text-surface-inverse': inverse,
'white-text': !inverse,
'black-text': inverse,
}
},
getDisplayStyle(color) {
Expand All @@ -296,36 +285,7 @@ export function picker() {
}
},
image: null,
get imageAverage() {
if (this.image === null) {
return WHITE
}

const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')

canvas.width = this.image.width
canvas.height = this.image.height
context.drawImage(this.image, 0, 0, canvas.width, canvas.height)

const { data } = context.getImageData(0, 0, canvas.width, canvas.height)
const area = canvas.width * canvas.height
let r = 0
let g = 0
let b = 0

for (let i = 0; i < data.length; i += 4) {
r += data[i]
g += data[i+1]
b += data[i+2]
}

r = ~~(r / area) / 255
g = ~~(g / area) / 255
b = ~~(b / area) / 255

return new Color(r, g, b)
},
imagePalette: [],
async getImageFromFIle() {
const input = document.createElement('input')

Expand All @@ -340,6 +300,9 @@ export function picker() {

try {
this.image = await createImageBitmap(blob)
this.image.naturalWidth = this.image.width
this.image.naturalHeight = this.image.height
this.imagePalette = this.getImagePalette()
}
catch {
ui('#snackbar-error-file')
Expand Down Expand Up @@ -367,6 +330,25 @@ export function picker() {
const imageBlob = await image.getType(image_type)

this.image = await createImageBitmap(imageBlob)
this.image.naturalWidth = this.image.width
this.image.naturalHeight = this.image.height
this.imagePalette = this.getImagePalette()
},
getImagePalette() {
const palette = new ColorThief().getPalette(this.image, 6, 100).map((color) => {
const [r, g, b] = color.map((n) => n / 256)

return new Color(r, g, b)
})

return {
'predominant': palette[0],
'2nd': palette[1],
'3rd': palette[2],
'4th': palette[3],
'5th': palette[4],
'least': palette[5],
}
},
async displayImage(canvas) {
const ctx = canvas.getContext('2d')
Expand All @@ -378,7 +360,7 @@ export function picker() {

canvas.width = canvas.offsetWidth
canvas.height = height
ctx.imageSmoothingEnabled = false
// ctx.imageSmoothingEnabled = false

ctx.drawImage(this.image, x, y, width, height)
},
Expand All @@ -405,7 +387,7 @@ export function picker() {
},

init() {
this.color.hue = 300
this.color.hue = 1
this.randomColor = getRandomColor()
this.foreground = this.color
},
Expand Down

0 comments on commit 128905f

Please sign in to comment.