Skip to content
Snippets Groups Projects
Commit 421bb9a8 authored by Alexander Rose's avatar Alexander Rose
Browse files

added lab and hcl color spaces

parent 4a788d2d
No related branches found
No related tags found
No related merge requests found
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { NumberArray } from 'mol-util/type-helpers';
import { Vec3 } from 'mol-math/linear-algebra';
import { Hcl } from './spaces/hcl';
import { Lab } from './spaces/lab';
/** RGB color triplet expressed as a single number */
export type Color = { readonly '@type': 'color' } & number
......@@ -81,7 +83,7 @@ export namespace Color {
return out
}
/** Linear interpolation between two colors */
/** Linear interpolation between two colors in rgb space */
export function interpolate(c1: Color, c2: Color, t: number): Color {
const r1 = c1 >> 16 & 255
const g1 = c1 >> 8 & 255
......@@ -96,6 +98,26 @@ export namespace Color {
return ((r << 16) | (g << 8) | b) as Color
}
const tmpSaturateHcl = [0, 0, 0] as Hcl
export function saturate(c: Color, amount: number): Color {
Hcl.fromColor(tmpSaturateHcl, c)
return Hcl.toColor(Hcl.saturate(tmpSaturateHcl, tmpSaturateHcl, amount))
}
export function desaturate(c: Color, amount: number): Color {
return saturate(c, -amount)
}
const tmpDarkenLab = [0, 0, 0] as Lab
export function darken(c: Color, amount: number): Color {
Lab.fromColor(tmpDarkenLab, c)
return Lab.toColor(Lab.darken(tmpDarkenLab, tmpDarkenLab, amount))
}
export function lighten(c: Color, amount: number): Color {
return darken(c, -amount)
}
}
export type ColorTable<T extends { [k: string]: number[] }> = { [k in keyof T]: Color[] }
......
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
* Color conversion code adapted from chroma.js (https://github.com/gka/chroma.js)
* Copyright (c) 2011-2018, Gregor Aisch, BSD license
*/
import { Color } from '../color';
import { degToRad } from 'mol-math/misc';
import { Lab } from './lab';
export { Hcl }
interface Hcl extends Array<number> { [d: number]: number, '@type': 'hcl', length: 3 }
/**
* CIE HCL (Hue-Chroma-Luminance) color
*
* - H [0..360]
* - C [0..100]
* - L [0..100]
*
* Cylindrical representation of CIELUV (see https://en.wikipedia.org/wiki/CIELUV)
*/
function Hcl() {
return Hcl.zero()
}
namespace Hcl {
export function zero(): Hcl {
const out = [0.1, 0.0, 0.0]
out[0] = 0
return out as Hcl;
}
export function create(h: number, c: number, l: number): Hcl {
const out = zero()
out[0] = h
out[1] = c
out[2] = l
return out
}
const tmpFromColorLab = [0, 0, 0] as Lab
export function fromColor(out: Hcl, color: Color): Hcl {
return Lab.toHcl(out, Lab.fromColor(tmpFromColorLab, color))
}
export function fromLab(hcl: Hcl, lab: Lab): Hcl {
return Lab.toHcl(hcl, lab)
}
const tmpToColorLab = [0, 0, 0] as Lab
export function toColor(hcl: Hcl): Color {
return Lab.toColor(toLab(tmpToColorLab, hcl))
}
/**
* Convert from a qualitative parameter h and a quantitative parameter l to a 24-bit pixel.
*
* These formulas were invented by David Dalrymple to obtain maximum contrast without going
* out of gamut if the parameters are in the range 0-1.
* A saturation multiplier was added by Gregor Aisch
*/
export function toLab(out: Lab, hcl: Hcl): Lab {
let [h, c, l] = hcl
if (isNaN(h)) h = 0
h = degToRad(h)
out[0] = l
out[1] = Math.cos(h) * c
out[2] = Math.sin(h) * c
return out
}
export function copy(out: Hcl, c: Hcl): Hcl {
out[0] = c[0]
out[1] = c[1]
out[2] = c[2]
return out
}
export function saturate(out: Hcl, c: Hcl, amount: number): Hcl {
out[0] = c[0]
out[1] = Math.max(0, c[1] + Kn * amount)
out[2] = c[2]
return out
}
export function desaturate(out: Hcl, c: Hcl, amount: number): Hcl {
return saturate(out, c, -amount)
}
const tmpDarkenLab = [0, 0, 0] as Lab
export function darken(out: Hcl, c: Hcl, amount: number): Hcl {
toLab(tmpDarkenLab, c)
return Lab.toHcl(out, Lab.darken(tmpDarkenLab, tmpDarkenLab, amount))
}
export function lighten(out: Hcl, c: Hcl, amount: number): Hcl {
return darken(out, c, -amount)
}
// Corresponds roughly to RGB brighter/darker
const Kn = 18
}
\ No newline at end of file
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
* Color conversion code adapted from chroma.js (https://github.com/gka/chroma.js)
* Copyright (c) 2011-2018, Gregor Aisch, BSD license
*/
import { Color } from '../color';
import { Hcl } from './hcl';
import { radToDeg } from 'mol-math/misc';
import { clamp } from 'mol-math/interpolate';
export { Lab }
interface Lab extends Array<number> { [d: number]: number, '@type': 'lab', length: 3 }
/**
* CIE LAB color
*
* - L* [0..100] - lightness from black to white
* - a [-100..100] - green (-) to red (+)
* - b [-100..100] - blue (-) to yellow (+)
*
* see https://en.wikipedia.org/wiki/CIELAB_color_space
*/
function Lab() {
return Lab.zero()
}
namespace Lab {
export function zero(): Lab {
const out = [0.1, 0.0, 0.0]
out[0] = 0
return out as Lab;
}
export function create(l: number, a: number, b: number): Lab {
const out = zero()
out[0] = l
out[1] = a
out[2] = b
return out
}
export function fromColor(out: Lab, color: Color): Lab {
const [r, g, b] = Color.toRgb(color)
const [x, y, z] = rgbToXyz(r, g, b)
const l = 116 * y - 16
out[0] = l < 0 ? 0 : l
out[1] = 500 * (x - y)
out[2] = 200 * (y - z)
return out
}
export function fromHcl(out: Lab, hcl: Hcl): Lab {
return Hcl.toLab(out, hcl)
}
export function toColor(lab: Lab): Color {
let y = (lab[0] + 16) / 116
let x = isNaN(lab[1]) ? y : y + lab[1] / 500
let z = isNaN(lab[2]) ? y : y - lab[2] / 200
y = Yn * lab_xyz(y)
x = Xn * lab_xyz(x)
z = Zn * lab_xyz(z)
const r = xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z) // D65 -> sRGB
const g = xyz_rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z)
const b = xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z)
return Color.fromRgb(
Math.round(clamp(r, 0, 255)),
Math.round(clamp(g, 0, 255)),
Math.round(clamp(b, 0, 255))
)
}
export function toHcl(out: Hcl, lab: Lab): Hcl {
const [l, a, b] = lab
const c = Math.sqrt(a * a + b * b)
let h = (radToDeg(Math.atan2(b, a)) + 360) % 360
if (Math.round(c * 10000) === 0) h = Number.NaN
out[0] = h
out[1] = c
out[2] = l
return out
}
export function copy(out: Lab, c: Lab): Lab {
out[0] = c[0]
out[1] = c[1]
out[2] = c[2]
return out
}
export function darken(out: Lab, c: Lab, amount: number): Lab {
out[0] = c[0] - Kn * amount
out[1] = c[1]
out[2] = c[2]
return out
}
export function lighten(out: Lab, c: Lab, amount: number): Lab {
return darken(out, c, -amount)
}
const tmpSaturateHcl = [0, 0, 0] as Hcl
export function saturate(out: Lab, c: Lab, amount: number): Lab {
toHcl(tmpSaturateHcl, c)
return Hcl.toLab(out, Hcl.saturate(tmpSaturateHcl, tmpSaturateHcl, amount))
}
export function desaturate(out: Lab, c: Lab, amount: number): Lab {
return saturate(out, c, -amount)
}
// Corresponds roughly to RGB brighter/darker
const Kn = 18
/** D65 standard referent */
const Xn = 0.950470
const Yn = 1
const Zn = 1.088830
const T0 = 0.137931034 // 4 / 29
const T1 = 0.206896552 // 6 / 29
const T2 = 0.12841855 // 3 * t1 * t1
const T3 = 0.008856452 // t1 * t1 * t1
/** convert component from xyz to rgb */
function xyz_rgb(c: number) {
return 255 * (c <= 0.00304 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055)
}
/** convert component from lab to xyz */
function lab_xyz(t: number) {
return t > T1 ? t * t * t : T2 * (t - T0)
}
/** convert component from rgb to xyz */
function rgb_xyz(c: number) {
if ((c /= 255) <= 0.04045) return c / 12.92
return Math.pow((c + 0.055) / 1.055, 2.4)
}
/** convert component from xyz to lab */
function xyz_lab(t: number) {
if (t > T3) return Math.pow(t, 1 / 3)
return t / T2 + T0
}
function rgbToXyz(r: number, g: number, b: number) {
r = rgb_xyz(r)
g = rgb_xyz(g)
b = rgb_xyz(b)
const x = xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / Xn)
const y = xyz_lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / Yn)
const z = xyz_lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / Zn)
return [x, y, z]
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment