import { clamp } from './gl-matrix-extensions' ; import { assert } from './auxiliaries' ; import { Color } from './color' ; import { fetchJsonAsync } from './fetch' ; export class ColorScale { /**
     * Color scale preset file schema.
     */ private static readonly SCHEMA : any = require ( './data/colorscalepresets.schema.json' ) ; see hint protected _hint : ColorScale . InterpolationHint = ColorScale . InterpolationHint . Linear ; ee colors protected _colors = new Array < Color > ( ) ; see invert protected _inverted = false ; param type protected static stride ( type ) { switch ( type ) { case ColorScale . ArrayType . RGBA : case ColorScale . ArrayType . RGBAf : return 4 ; case ColorScale . ArrayType . RGB : case ColorScale . ArrayType . RGBf : default : return 3 ; } } param url param preset param stepCount returns static fromPreset ( url , preset , stepCount ) { const transform = ( data ) : ColorScale | undefined => { let p ; for ( const item of data ) { if ( item . identifier !== preset ) { continue ; } p = item as ColorScale . Preset ; break ; } if ( p === undefined ) { return undefined ; } const type = p . format ; const stride = ColorScale . stride ( type ) ; const colorsByStepCount = p . colors ; let index = colorsByStepCount . length - 1 ; for ( let i = 0 ; i < colorsByStepCount . length ; ++ i ) { if ( colorsByStepCount [ i ] . length !== stepCount * stride ) { continue ; } index = i ; break ; } const colors = colorsByStepCount [ index ] ; const positionsByStepCount = p . positions ; if ( positionsByStepCount === undefined ) { return ColorScale . fromArray ( colors , type , stepCount , undefined ) ; } let positions ; for ( let i = 0 ; i < positionsByStepCount . length ; ++ i ) { if ( positionsByStepCount ! [ i ] . length !== colors . length ) { continue ; } positions = positionsByStepCount ! [ i ] ; } return ColorScale . fromArray ( colors , type , stepCount , positions ) ; } ; return fetchJsonAsync < ColorScale > ( url , transform , ColorScale . SCHEMA . properties ) ; } param interleavedColorComponents param type param stepCount param positions returns static fromArray ( interleavedColorComponents , type , stepCount , positions ? ) { if ( stepCount === 0 || interleavedColorComponents . length === 0 ) { return new ColorScale ( ) ; } const array = interleavedColorComponents ; const stride = ColorScale . stride ( type ) ; const size = array . length / stride ; const colors = new Array < Color > ( size ) ; for ( let i = 0 ; i < array . length ; i += stride ) { const color = new Color ( ) ; switch ( type ) { case ColorScale . ArrayType . RGB : color . fromUI8 ( array [ i + 0 ] , array [ i + 1 ] , array [ i + 2 ] ) ; break ; case ColorScale . ArrayType . RGBA : color . fromUI8 ( array [ i + 0 ] , array [ i + 1 ] , array [ i + 2 ] , array [ i + 3 ] ) ; break ; case ColorScale . ArrayType . RGBf : color . fromF32 ( array [ i + 0 ] , array [ i + 1 ] , array [ i + 2 ] ) ; break ; case ColorScale . ArrayType . RGBAf : color . fromF32 ( array [ i + 0 ] , array [ i + 1 ] , array [ i + 2 ] , array [ i + 3 ] ) ; break ; default : } colors [ i / stride ] = color ; } const scale = new ColorScale ( ) ; if ( positions === undefined && stepCount === size ) { scale . _colors = colors ; return scale ; } if ( stepCount === 1 ) { scale . _colors . push ( colors [ 0 ] ) ; } if ( positions === undefined ) { positions = new Array ( size ) ; positions [ 0 ] = 0.0 ; for ( let i = 1 ; i < size ; ++ i ) { positions [ i ] = i / ( size - 1 ) ; } } assert ( positions . length === colors . length , `expected number of positions (${ positions . length }) to match number of colors (${ colors . length })` ) ; let lower = 0 ; let upper = lower + 1 ; const last = size - 1 ; for ( let i = 0 ; i < stepCount ; ++ i ) { const position = i === 0 ? 0 : i / ( stepCount - 1 ) ; if ( position <= positions [ lower ] ) { scale . _colors . push ( colors [ lower ] ) ; continue ; } else if ( positions [ last ] <= position ) { scale . _colors . push ( colors [ last ] ) ; continue ; } for ( let u = lower + 1 ; u < size ; ++ u ) { if ( positions [ u ] < position ) { continue ; } upper = u ; lower = u - 1 ; break ; } const a = ( position - positions [ lower ] ) / ( positions [ upper ] - positions [ lower ] ) ; scale . _colors . push ( Color . lerp ( colors [ lower ] , colors [ upper ] , a , Color . Space . LAB ) ) ; } return scale ; } param position param space returns lerp ( position , space = Color . Space . LAB ) { if ( this . _colors . length === 0 ) { return undefined ; } if ( this . _colors . length === 1 ) { return this . _colors [ 0 ] ; } const clamped = clamp ( position , 0.0 , 1.0 ) ; if ( clamped <= 0.0 ) { return this . _colors [ 0 ] ; } else if ( clamped >= 1.0 ) { return this . _colors [ this . _colors . length - 1 ] ; } const posIndex = position * this . _colors . length ; const lower = Math . floor ( posIndex ) ; const upper = lower + 1 ; if ( upper >= this . _colors . length ) { return this . _colors [ this . _colors . length - 1 ] ; } assert ( upper < this . _colors . length , `expected upper not exceed maximum color index: ${ upper } < ${ this . _colors . length }` ) ; if ( this . _hint === ColorScale . InterpolationHint . Nearest ) { return this . _colors [ posIndex - lower <= upper - posIndex ? lower : upper ] ; } return Color . lerp ( this . _colors [ lower ] , this . _colors [ upper ] , posIndex - lower , space ) ; } /**
     * Returns the color with specified index. If the index is out of bounds, undefined is returned. Alternatively, the
     * color array can be used directly @see colors .
     *  param index color ( index ) { if ( index < 0 || index >= this . _colors . length ) { return undefined ; } return this . _colors [ index ] ; } /**
     * Returns the array containing the colors of the color scale.
     */ get colors ( ) : Array < Color > { return this . _colors ; } set colors ( colors ) { this . _colors = colors ; } /**
     * The interpolation hint used when accessing a color using interpolation, e.g., @see lerp .
      set hint ( hint ) { this . _hint = hint ; } get hint ( ) : ColorScale . InterpolationHint { return this . _hint ; } /**
     * Provides read access to the number of colors of this scale. This is a shortcut for this.colors.length.
     */ get length ( ) : number { return this . _colors . length ; } /**
     * Whether or not the scale was inverted based on its initial state.
     */ get inverted ( ) : boolean { return this . _inverted ; } /**
     * Inverts the color scale. Whether or not the scale is inverted can be checked using the
     * inverted read-only property (@link inverted).
     */ invert ( ) { this . _colors . reverse ( ) ; this . _inverted = ! this . _inverted ; } param space param alpha bitsUI8 ( space = Color . Space . RGB , alpha = true ) { const size = this . _colors . length ; const stride = alpha ? 4 : 3 ; const bits = new Uint8Array ( size * stride ) ; for ( let i = 0 ; i < size ; ++ i ) { const color = this . _colors [ i ] . tuple ( space , alpha ) ; bits [ i * stride + 0 ] = color [ 0 ] * 255 ; bits [ i * stride + 1 ] = color [ 1 ] * 255 ; bits [ i * stride + 2 ] = color [ 2 ] * 255 ; if ( alpha && color . length === 4 ) { bits [ i * stride + 3 ] = color [ 3 ] * 255 ; } } return bits ; } param space param alpha bitsF32 ( space = Color . Space . RGB , alpha = true ) { const size = this . _colors . length ; const stride = alpha ? 4 : 3 ; const bits = new Float32Array ( size * stride ) ; for ( let i = 0 ; i < size ; ++ i ) { const color = this . _colors [ i ] . tuple ( space , alpha ) ; bits [ i * stride + 0 ] = color [ 0 ] ; bits [ i * stride + 1 ] = color [ 1 ] ; bits [ i * stride + 2 ] = color [ 2 ] ; if ( alpha && color . length === 4 ) { bits [ i * stride + 3 ] = color [ 3 ] ; } } return bits ; } } export namespace ColorScale { /**
     * Color interpolation type for a color scale.
     */ export enum InterpolationHint { Linear = 'linear' , Nearest = 'nearest' } export enum ScaleType { sequential = 'sequential' , diverging = 'diverging' , qualitative = 'qualitative' } export enum ArrayType { RGB = 'rgb' , RGBf = 'rgbf' , RGBA = 'rgba' , RGBAf = 'rgbaf' } export interface Preset { identifier : string ; type : ScaleType | undefined ; format : ArrayType ; colors : Array < Array < number > > ; positions : Array < Array < number > > | undefined ; } }