import { vec4 } from 'gl-matrix' ; import { clamp , mix } from './gl-matrix-extensions' ; import { assert , log , LogLevel } from './auxiliaries' ; import { clampf , clampf3 , clampf4 , duplicate4 , equals4 , GLclampf3 , GLclampf4 , GLclampf5 } from './tuples' ; todo export type GLubyte = number ; /**
 * Color class that allows for specification and conversion of colors in various color spaces. Please not that most of
 * the color conversion math is based on   https . The internal color representation
 * is a 4-tuple of GLclampf components in RGB color space and additional alpha. All color conversion, e.g., getters is
 * computed on the fly, not cached, and is not optimized for, e.g., massive pixel processing.
  export class Color { protected static readonly DEFAULT_ALPHA : GLclampf = 1.0 ; protected static readonly HEX_FORMAT_REGEX = new RegExp ( /^(#|0x)?(([0-9a-f]{3}){1,2}|([0-9a-f]{4}){1,2})$/i ) ; protected _rgba : GLclampf4 = [ 0.0 , 0.0 , 0.0 , Color . DEFAULT_ALPHA ] ; see altered protected _altered = false ; /**
     * Converts a hue value into an rgb value.
     */ protected static hue2rgb ( p , q , t ) { assert ( t >= - 1.0 && t <= 2.0 , `t is expected to be between -1 and 2` ) ; if ( t < 0.0 ) { t += 1.0 ; } else if ( t > 1.0 ) { t -= 1.0 ; } if ( ( 6.0 * t ) < 1.0 ) { return p + ( q - p ) * 6.0 * t ; } if ( ( 2.0 * t ) < 1.0 ) { return q ; } if ( ( 3.0 * t ) < 2.0 ) { return p + ( q - p ) * 6.0 * ( 2.0 / 3.0 - t ) ; } return p ; } param value returns protected static to2CharHexCode ( value ) { return ( value < 15.5 / 255.0 ? '0' : '' ) + Math . round ( value * 255.0 ) . toString ( 16 ) ; } param hsl returns static hsl2rgb ( hsl ) { const hslF = clampf3 ( hsl , 'HSL input' ) ; if ( hslF [ 1 ] === 0.0 ) { return [ hslF [ 2 ] , hslF [ 2 ] , hslF [ 2 ] ] ; } const q = hslF [ 2 ] < 0.5 ? hslF [ 2 ] * ( 1.0 + hslF [ 1 ] ) : ( hslF [ 2 ] + hslF [ 1 ] ) - ( hslF [ 1 ] * hslF [ 2 ] ) ; const p = 2.0 * hslF [ 2 ] - q ; return [ Color . hue2rgb ( p , q , hslF [ 0 ] + ( 1.0 / 3.0 ) ) , Color . hue2rgb ( p , q , hslF [ 0 ] ) , Color . hue2rgb ( p , q , hslF [ 0 ] - ( 1.0 / 3.0 ) ) ] ; } param rgb returns static rgb2hsl ( rgb ) { const rgbF = clampf3 ( rgb , 'RGB input' ) ; const hsl = [ 0.0 , 0.0 , 0.0 ] ; const min = Math . min ( rgbF [ 0 ] , rgbF [ 1 ] , rgbF [ 2 ] ) ; const max = Math . max ( rgbF [ 0 ] , rgbF [ 1 ] , rgbF [ 2 ] ) ; const delta = max - min ; hsl [ 2 ] = ( max + min ) * 0.5 ; if ( delta === 0.0 ) { return hsl ; } hsl [ 1 ] = hsl [ 2 ] < 0.5 ? delta / ( max + min ) : delta / ( 2.0 - max - min ) ; const deltaR = ( ( ( max - rgbF [ 0 ] ) / 6.0 ) + ( delta / 2.0 ) ) / delta ; const deltaG = ( ( ( max - rgbF [ 1 ] ) / 6.0 ) + ( delta / 2.0 ) ) / delta ; const deltaB = ( ( ( max - rgbF [ 2 ] ) / 6.0 ) + ( delta / 2.0 ) ) / delta ; if ( rgbF [ 0 ] === max ) { hsl [ 0 ] = deltaB - deltaG ; } else if ( rgbF [ 1 ] === max ) { hsl [ 0 ] = deltaR - deltaB + ( 1.0 / 3.0 ) ; } else { hsl [ 0 ] = deltaG - deltaR + ( 2.0 / 3.0 ) ; } return hsl ; } param lab returns static lab2xyz ( lab ) { const labF = clampf3 ( lab , 'LAB input' ) ; /** The following computation assumes the value ranges:
         *  L: [0, 100], a: [-128, 127], b: [-128, 127]
         */ const yr = ( 100.0 * labF [ 0 ] + 16.0 ) / 116.0 ; const xr = ( 256.0 * labF [ 1 ] - 128.0 ) / 500.0 + yr ; const zr = yr - ( 256.0 * labF [ 2 ] - 128.0 ) / 200.0 ; const xr3 = Math . pow ( xr , 3.0 ) ; const yr3 = Math . pow ( yr , 3.0 ) ; const zr3 = Math . pow ( zr , 3.0 ) ; const x = 0.95047 * ( xr3 > 0.008856 ? xr3 : ( xr - 16.0 / 116.0 ) / 7.787 ) ; const y = 1.00000 * ( yr3 > 0.008856 ? yr3 : ( yr - 16.0 / 116.0 ) / 7.787 ) ; const z = 1.08883 * ( zr3 > 0.008856 ? zr3 : ( zr - 16.0 / 116.0 ) / 7.787 ) ; return [ x , y , z ] ; } param xyz returns static xyz2lab ( xyz ) { const xyzF = [ xyz [ 0 ] / 0.95047 , xyz [ 1 ] / 1.00000 , xyz [ 2 ] / 1.08883 ] ; const x = xyzF [ 0 ] > 0.008856 ? Math . cbrt ( xyzF [ 0 ] ) : ( 7.787 * xyzF [ 0 ] + ( 16.0 / 116.0 ) ) ; const y = xyzF [ 1 ] > 0.008856 ? Math . cbrt ( xyzF [ 1 ] ) : ( 7.787 * xyzF [ 1 ] + ( 16.0 / 116.0 ) ) ; const z = xyzF [ 2 ] > 0.008856 ? Math . cbrt ( xyzF [ 2 ] ) : ( 7.787 * xyzF [ 2 ] + ( 16.0 / 116.0 ) ) ; return clampf3 ( [ ( 116.0 * y - 16.0 ) / 100.0 , ( 500.0 * ( x - y ) + 128.0 ) / 256.0 , ( 200.0 * ( y - z ) + 128.0 ) / 256.0 ] ) ; } param xyz returns static xyz2rgb ( xyz ) { const r = xyz [ 0 ] * + 2.04137 + xyz [ 1 ] * - 0.56495 + xyz [ 2 ] * - 0.34469 ; const g = xyz [ 0 ] * - 0.96927 + xyz [ 1 ] * + 1.87601 + xyz [ 2 ] * + 0.04156 ; const b = xyz [ 0 ] * + 0.01345 + xyz [ 1 ] * - 0.11839 + xyz [ 2 ] * + 1.01541 ; return clampf3 ( [ r > 0.0 ? Math . pow ( r , 1.0 / 2.19921875 ) : 0 , g > 0.0 ? Math . pow ( g , 1.0 / 2.19921875 ) : 0 , b > 0.0 ? Math . pow ( b , 1.0 / 2.19921875 ) : 0 ] ) ; } param rgb returns static rgb2xyz ( rgb ) { const rgbF = clampf3 ( rgb , 'RGB input' ) ; const r = Math . pow ( rgbF [ 0 ] , 2.19921875 ) ; const g = Math . pow ( rgbF [ 1 ] , 2.19921875 ) ; const b = Math . pow ( rgbF [ 2 ] , 2.19921875 ) ; const x = r * 0.57667 + g * 0.18555 + b * 0.18819 ; const y = r * 0.29738 + g * 0.62735 + b * 0.07527 ; const z = r * 0.02703 + g * 0.07069 + b * 0.99110 ; return [ x , y , z ] ; } param lab returns static lab2rgb ( lab ) { return Color . xyz2rgb ( Color . lab2xyz ( lab ) ) ; } param lab returns static rgb2lab ( rgb ) { return Color . xyz2lab ( Color . rgb2xyz ( rgb ) ) ; } param cmyk returns static cmyk2rgb ( cmyk ) { const cmykF = clampf4 ( cmyk , 'CMYK input' ) ; const k = 1.0 - cmykF [ 3 ] ; return [ ( 1.0 - cmykF [ 0 ] ) * k , ( 1.0 - cmykF [ 1 ] ) * k , ( 1.0 - cmykF [ 2 ] ) * k ] ; } param rgb returns static rgb2cmyk ( rgb ) { const rgbF = clampf3 ( rgb , 'RGB input' ) ; const k1 = 1.0 - Math . max ( rgbF [ 0 ] , rgbF [ 1 ] , rgbF [ 2 ] ) ; const k2 = 1.0 - k1 ; const k3 = k2 === 0.0 ? 0.0 : 1.0 / k2 ; return [ ( k2 - rgbF [ 0 ] ) * k3 , ( k2 - rgbF [ 1 ] ) * k3 , ( k2 - rgbF [ 2 ] ) * k3 , k1 ] ; } param hex returns static hex2rgba ( hex ) { const rgba = [ 0.0 , 0.0 , 0.0 , Color . DEFAULT_ALPHA ] ; if ( ! Color . HEX_FORMAT_REGEX . test ( hex ) ) { log ( LogLevel . Warning , `hexadecimal RGBA color string must conform to either \
'0x0000', '#0000', '0000', '0x00000000', '#00000000', or '00000000', given '${ hex }'` ) ; return rgba ; } const offset = hex . startsWith ( '0x' ) ? 2 : hex . startsWith ( '#' ) ? 1 : 0 ; const length = Math . floor ( ( hex . length - offset ) / 3 ) ; const stride = length - 1 ; rgba [ 0 ] = parseInt ( hex [ offset + 0 * length ] + hex [ offset + 0 * length + stride ] , 16 ) / 255.0 ; rgba [ 1 ] = parseInt ( hex [ offset + 1 * length ] + hex [ offset + 1 * length + stride ] , 16 ) / 255.0 ; rgba [ 2 ] = parseInt ( hex [ offset + 2 * length ] + hex [ offset + 2 * length + stride ] , 16 ) / 255.0 ; if ( ( hex . length - offset ) === 4 || ( hex . length - offset ) === 8 ) { rgba [ 3 ] = parseInt ( hex [ offset + 3 * length ] + hex [ offset + 3 * length + stride ] , 16 ) / 255.0 ; } assert ( ! isNaN ( rgba [ 0 ] ) && ! isNaN ( rgba [ 1 ] ) && ! isNaN ( rgba [ 2 ] ) && ! isNaN ( rgba [ 3 ] ) , `expected well formated hexadecimal RGBA string, given '${ hex }'` ) ; return rgba ; } param rgb returns static rgb2hex ( rgb ) { const rgbF = clampf3 ( rgb , 'RGB input' ) ; const r = Color . to2CharHexCode ( rgbF [ 0 ] ) ; const g = Color . to2CharHexCode ( rgbF [ 1 ] ) ; const b = Color . to2CharHexCode ( rgbF [ 2 ] ) ; return '#' + r + g + b ; } param rgba returns static rgba2hex ( rgba ) { const rgbaF = clampf4 ( rgba , 'RGBA input' ) ; const r = Color . to2CharHexCode ( rgbaF [ 0 ] ) ; const g = Color . to2CharHexCode ( rgbaF [ 1 ] ) ; const b = Color . to2CharHexCode ( rgbaF [ 2 ] ) ; const a = Color . to2CharHexCode ( rgbaF [ 3 ] ) ; return '#' + r + g + b + a ; } param x param y param a param space static lerp ( x , y , a , space = Color . Space . LAB ) { if ( a <= 0.0 ) { return new Color ( x . rgba ) ; } else if ( a >= 1.0 ) { return new Color ( y . rgba ) ; } const result = vec4 . create ( ) ; switch ( space ) { case Color . Space . CMYK : vec4 . lerp ( result , x . cmyk , y . cmyk , a ) ; const alpha = mix ( x . a , y . a , a ) ; return new Color ( ) . fromCMYK ( result [ 0 ] , result [ 1 ] , result [ 2 ] , result [ 3 ] , alpha ) ; case Color . Space . LAB : vec4 . lerp ( result , x . laba , y . laba , a ) ; return new Color ( ) . fromLAB ( result [ 0 ] , result [ 1 ] , result [ 2 ] , result [ 3 ] ) ; case Color . Space . HSL : vec4 . lerp ( result , x . hsla , y . hsla , a ) ; return new Color ( ) . fromHSL ( result [ 0 ] , result [ 1 ] , result [ 2 ] , result [ 3 ] ) ; case Color . Space . RGB : vec4 . lerp ( result , x . rgba , y . rgba , a ) ; return new Color ( ) . fromRGB ( result [ 0 ] , result [ 1 ] , result [ 2 ] , result [ 3 ] ) ; } } param rgba param alpha constructor ( rgba ? , alpha ? ) { if ( rgba === undefined ) { return ; } if ( rgba . length === 3 && alpha !== undefined ) { this . fromF32 ( rgba [ 0 ] , rgba [ 1 ] , rgba [ 2 ] , alpha ) ; } else if ( rgba . length === 4 ) { this . fromF32 ( rgba [ 0 ] , rgba [ 1 ] , rgba [ 2 ] , rgba [ 3 ] ) ; assert ( alpha === undefined , `expected alpha to be undefined when given an 4-tuple in RGBA` ) ; } else { this . fromF32 ( rgba [ 0 ] , rgba [ 1 ] , rgba [ 2 ] ) ; } } param other returns equals ( other ) { return equals4 < GLclampf > ( this . _rgba , other . _rgba ) ; } param red param green param blue param alpha returns fromF32 ( red , green , blue , alpha = Color . DEFAULT_ALPHA ) { const previous = duplicate4 < GLclampf > ( this . _rgba ) ; this . _rgba [ 0 ] = clampf ( red , `red value` ) ; this . _rgba [ 1 ] = clampf ( green , `green value` ) ; this . _rgba [ 2 ] = clampf ( blue , `blue value` ) ; this . _rgba [ 3 ] = clampf ( alpha , `alpha value` ) ; this . _altered = ! equals4 < GLclampf > ( this . _rgba , previous ) ; return this ; } param red param green param blue param alpha returns fromUI8 ( red , green , blue , alpha = Math . floor ( Color . DEFAULT_ALPHA * 255 ) ) { const previous = duplicate4 < GLclampf > ( this . _rgba ) ; this . _rgba [ 0 ] = clamp ( red , 0 , 255 ) / 255.0 ; this . _rgba [ 1 ] = clamp ( green , 0 , 255 ) / 255.0 ; this . _rgba [ 2 ] = clamp ( blue , 0 , 255 ) / 255.0 ; this . _rgba [ 3 ] = clamp ( alpha , 0 , 255 ) / 255.0 ; this . _altered = ! equals4 < GLclampf > ( this . _rgba , previous ) ; return this ; } param red param green param blue param alpha returns fromRGB ( red , green , blue , alpha = Color . DEFAULT_ALPHA ) { const previous = duplicate4 < GLclampf > ( this . _rgba ) ; this . _rgba = clampf4 ( [ red , green , blue , alpha ] , 'RGBA input' ) ; this . _altered = ! equals4 < GLclampf > ( this . _rgba , previous ) ; return this ; } param hue param saturation param lightness param alpha returns fromHSL ( hue , saturation , lightness , alpha = Color . DEFAULT_ALPHA ) { const previous = duplicate4 < GLclampf > ( this . _rgba ) ; const rgb = Color . hsl2rgb ( [ hue , saturation , lightness ] ) ; const alphaf = clampf ( alpha , 'ALPHA input' ) ; this . _rgba = [ rgb [ 0 ] , rgb [ 1 ] , rgb [ 2 ] , alphaf ] ; this . _altered = ! equals4 < GLclampf > ( this . _rgba , previous ) ; return this ; } param lightness param greenRed param blueYellow param alpha returns fromLAB ( lightness , greenRed , blueYellow , alpha = Color . DEFAULT_ALPHA ) { const previous = duplicate4 < GLclampf > ( this . _rgba ) ; const rgb = Color . lab2rgb ( [ lightness , greenRed , blueYellow ] ) ; const alphaf = clampf ( alpha , 'ALPHA input' ) ; this . _rgba = [ rgb [ 0 ] , rgb [ 1 ] , rgb [ 2 ] , alphaf ] ; this . _altered = ! equals4 < GLclampf > ( this . _rgba , previous ) ; return this ; } param cyan param magenta param yellow param key param alpha returns fromCMYK ( cyan , magenta , yellow , key , alpha = Color . DEFAULT_ALPHA ) { const previous = duplicate4 < GLclampf > ( this . _rgba ) ; const rgb = Color . cmyk2rgb ( [ cyan , magenta , yellow , key ] ) ; const alphaf = clampf ( alpha , 'ALPHA input' ) ; this . _rgba = [ rgb [ 0 ] , rgb [ 1 ] , rgb [ 2 ] , alphaf ] ; this . _altered = ! equals4 < GLclampf > ( this . _rgba , previous ) ; return this ; } param hex returns fromHex ( hex ) { const previous = duplicate4 < GLclampf > ( this . _rgba ) ; this . _rgba = Color . hex2rgba ( hex ) ; this . _altered = ! equals4 < GLclampf > ( this . _rgba , previous ) ; return this ; } param algorithm gray ( algorithm = Color . GrayscaleAlgorithm . LinearLuminance ) { switch ( algorithm ) { case Color . GrayscaleAlgorithm . Average : return ( this . _rgba [ 0 ] + this . _rgba [ 1 ] + this . _rgba [ 2 ] ) / 3.0 ; case Color . GrayscaleAlgorithm . LeastSaturatedVariant : return ( Math . max ( this . _rgba [ 0 ] , this . _rgba [ 1 ] , this . _rgba [ 2 ] ) - Math . min ( this . _rgba [ 0 ] , this . _rgba [ 1 ] , this . _rgba [ 2 ] ) ) * 0.5 ; case Color . GrayscaleAlgorithm . MinimumDecomposition : return Math . min ( this . _rgba [ 0 ] , this . _rgba [ 1 ] , this . _rgba [ 2 ] ) ; case Color . GrayscaleAlgorithm . MaximumDecomposition : return Math . max ( this . _rgba [ 0 ] , this . _rgba [ 1 ] , this . _rgba [ 2 ] ) ; case Color . GrayscaleAlgorithm . LinearLuminance : return this . _rgba [ 0 ] * 0.2126 + this . _rgba [ 1 ] * 0.7152 + this . _rgba [ 2 ] * 0.0722 ; } } param space param alpha tuple ( space , alpha = true ) { switch ( space ) { case Color . Space . RGB : return alpha ? this . rgba : this . rgb ; case Color . Space . LAB : return alpha ? this . laba : this . lab ; case Color . Space . CMYK : return alpha ? this . cmyka : this . cmyk ; case Color . Space . HSL : return alpha ? this . hsla : this . hsl ; } } /**
     * Read access to the RGB components as floating point 3-tuple, each value in range [0.0, 1.0].
     */ get rgb ( ) : GLclampf3 { return [ this . _rgba [ 0 ] , this . _rgba [ 1 ] , this . _rgba [ 2 ] ] ; } /**
     * Read access to the RGB components as array of three bytes (8bit unsigned int), each in range [0, 255].
     */ get rgbUI8 ( ) : Uint8Array { const ui8Array = new Uint8Array ( 3 ) ; ui8Array [ 0 ] = Math . round ( this . _rgba [ 0 ] * 255.0 ) ; ui8Array [ 1 ] = Math . round ( this . _rgba [ 1 ] * 255.0 ) ; ui8Array [ 2 ] = Math . round ( this . _rgba [ 2 ] * 255.0 ) ; return ui8Array ; } /**
     * Read access to the RGB components as array of three 32bit floats, each in range [0.0, 1.0].
     */ get rgbF32 ( ) : Float32Array { const f32Array = new Float32Array ( 3 ) ; f32Array [ 0 ] = this . _rgba [ 0 ] ; f32Array [ 1 ] = this . _rgba [ 1 ] ; f32Array [ 2 ] = this . _rgba [ 2 ] ; return f32Array ; } /**
     * Read access to the RGBA components as floating point 4-tuple, each value in range [0.0, 1.0].
     */ get rgba ( ) : GLclampf4 { return this . _rgba ; } /**
     * Read access to the RGBA components as array of four bytes (8bit unsigned int), each in range [0, 255].
     */ get rgbaUI8 ( ) : Uint8Array { const ui8Array = new Uint8Array ( 4 ) ; ui8Array [ 0 ] = Math . round ( this . _rgba [ 0 ] * 255.0 ) ; ui8Array [ 1 ] = Math . round ( this . _rgba [ 1 ] * 255.0 ) ; ui8Array [ 2 ] = Math . round ( this . _rgba [ 2 ] * 255.0 ) ; ui8Array [ 3 ] = Math . round ( this . _rgba [ 3 ] * 255.0 ) ; return ui8Array ; } /**
     * Read access to the RGBA components as array of four 32bit floats, each in range [0.0, 1.0].
     */ get rgbaF32 ( ) : Float32Array { return new Float32Array ( this . _rgba ) ; } /**
     * Read access to the Red component as float value in range [0.0, 1.0].
     */ get r ( ) : GLclampf { return this . _rgba [ 0 ] ; } /**
     * Read access to the Green component as float value in range [0.0, 1.0].
     */ get g ( ) : GLclampf { return this . _rgba [ 1 ] ; } /**
     * Read access to the Blue component as float value in range [0.0, 1.0].
     */ get b ( ) : GLclampf { return this . _rgba [ 2 ] ; } /**
     * Read access to the Alpha component as float value in range [0.0, 1.0].
     */ get a ( ) : GLclampf { return this . _rgba [ 3 ] ; } /**
     * Read access to the RGB components as hexadecimal string.
     */ get hexRGB ( ) : string { return Color . rgb2hex ( this . rgb ) ; } /**
     * Read access to the RGBA components as hexadecimal string.
     */ get hexRGBA ( ) : string { return Color . rgba2hex ( this . _rgba ) ; } /**
     * Read access to the HSL components as floating point 3-tuple, each value in range [0.0, 1.0].
     */ get hsl ( ) : GLclampf3 { return Color . rgb2hsl ( this . rgb ) ; } /**
     * Read access to the HSLA components as floating point 4-tuple, each value in range [0.0, 1.0].
     */ get hsla ( ) : GLclampf4 { const hsl = Color . rgb2hsl ( this . rgb ) ; return [ hsl [ 0 ] , hsl [ 1 ] , hsl [ 2 ] , this . _rgba [ 3 ] ] ; } /**
     * Read access to the LAB components as floating point 3-tuple, each value in range [0.0, 1.0].
     */ get lab ( ) : GLclampf3 { return Color . rgb2lab ( this . rgb ) ; } /**
     * Read access to the LABA components as floating point 4-tuple, each value in range [0.0, 1.0].
     */ get laba ( ) : GLclampf4 { const lab = Color . rgb2lab ( this . rgb ) ; return [ lab [ 0 ] , lab [ 1 ] , lab [ 2 ] , this . _rgba [ 3 ] ] ; } /**
     * Read access to the CMYK components as floating point 4-tuple, each value in range [0.0, 1.0].
     */ get cmyk ( ) : GLclampf4 { return Color . rgb2cmyk ( this . rgb ) ; } /**
     * Read access to the CMYKA components as floating point 5-tuple, each value in range [0.0, 1.0].
     */ get cmyka ( ) : GLclampf5 { const cmyk = Color . rgb2cmyk ( this . rgb ) ; return [ cmyk [ 0 ] , cmyk [ 1 ] , cmyk [ 2 ] , cmyk [ 3 ] , this . _rgba [ 3 ] ] ; } /**
     * Whether or not color value has changed.
     */ get altered ( ) : boolean { return this . _altered ; } /**
     * Intended for resetting alteration status.
     */ set altered ( status ) { this . _altered = status ; } } export namespace Color { export enum GrayscaleAlgorithm { Average = 'average' , LinearLuminance = 'linear-luminance' , LeastSaturatedVariant = 'least-saturated-variant' , MinimumDecomposition = 'minimum-decomposition' , MaximumDecomposition = 'maximum-decomposition' } /**
     * Color spaces covered by this class.
     */ export enum Space { RGB = 'rgb' , HSL = 'hsl' , LAB = 'lab' , CMYK = 'cmyk' } }