preserve import { Palette } from '../../utils/palette' ; import { Point } from '../../utils/point' ; import { PointContainer } from '../../utils/pointContainer' ; import { AbstractDistanceCalculator } from '../../distance/distanceCalculator' ; import { AbstractPaletteQuantizer } from '../paletteQuantizer' ; import { PaletteQuantizerYieldValue } from '../paletteQuantizerYieldValue' ; import { ProgressTracker } from '../../utils' ; const networkBiasShift = 3 ; class Neuron { r : number ; g : number ; b : number ; a : number ; constructor ( defaultValue ) { this . r = this . g = this . b = this . a = defaultValue ; } example toPoint ( ) { return Point . createByRGBA ( this . r >> networkBiasShift , this . g >> networkBiasShift , this . b >> networkBiasShift , this . a >> networkBiasShift ) ; } subtract ( r , g , b , a ) { this . r -= r | 0 ; this . g -= g | 0 ; this . b -= b | 0 ; this . a -= a | 0 ; } } export class NeuQuant extends AbstractPaletteQuantizer { private static readonly _prime1 = 499 ; private static readonly _prime2 = 491 ; private static readonly _prime3 = 487 ; private static readonly _prime4 = 503 ; private static readonly _minpicturebytes = NeuQuant . _prime4 ; private static readonly _nCycles = 100 ; private static readonly _initialBiasShift = 16 ; private static readonly _initialBias = 1 << NeuQuant . _initialBiasShift ; private static readonly _gammaShift = 10 ; private static readonly _betaShift = 10 ; private static readonly _beta = NeuQuant . _initialBias >> NeuQuant . _betaShift ; private static readonly _betaGamma = NeuQuant . _initialBias << ( NeuQuant . _gammaShift - NeuQuant . _betaShift ) ; private static readonly _radiusBiasShift = 6 ; private static readonly _radiusBias = 1 << NeuQuant . _radiusBiasShift ; private static readonly _radiusDecrease = 30 ; private static readonly _alphaBiasShift = 10 ; private static readonly _initAlpha = 1 << NeuQuant . _alphaBiasShift ; private static readonly _radBiasShift = 8 ; private static readonly _radBias = 1 << NeuQuant . _radBiasShift ; private static readonly _alphaRadBiasShift = NeuQuant . _alphaBiasShift + NeuQuant . _radBiasShift ; private static readonly _alphaRadBias = 1 << NeuQuant . _alphaRadBiasShift ; private _pointArray : Point [ ] ; private readonly _networkSize : number ; private _network ! : Neuron [ ] ; /** sampling factor 1..30 */ private readonly _sampleFactor ! : number ; private _radPower ! : number [ ] ; private _freq ! : number [ ] ; private _bias ! : number [ ] ; private readonly _distance : AbstractDistanceCalculator ; constructor ( colorDistanceCalculator , colors = 256 ) { super ( ) ; this . _distance = colorDistanceCalculator ; this . _pointArray = [ ] ; this . _sampleFactor = 1 ; this . _networkSize = colors ; this . _distance . setWhitePoint ( 255 << networkBiasShift , 255 << networkBiasShift , 255 << networkBiasShift , 255 << networkBiasShift ) ; } sample ( pointContainer ) { this . _pointArray = this . _pointArray . concat ( pointContainer . getPointArray ( ) ) ; } * quantize ( ) { this . _init ( ) ; yield * this . _learn ( ) ; yield { palette : this . _buildPalette ( ) , progress : 100 , } ; } private _init ( ) { this . _freq = [ ] ; this . _bias = [ ] ; this . _radPower = [ ] ; this . _network = [ ] ; for ( let i = 0 ; i < this . _networkSize ; i ++ ) { this . _network [ i ] = new Neuron ( ( ( i << ( networkBiasShift + 8 ) ) / this . _networkSize ) | 0 ) ; this . _freq [ i ] = ( NeuQuant . _initialBias / this . _networkSize ) | 0 ; this . _bias [ i ] = 0 ; } } /**
     * Main Learning Loop
     */ private * _learn ( ) { let sampleFactor = this . _sampleFactor ; const pointsNumber = this . _pointArray . length ; if ( pointsNumber < NeuQuant . _minpicturebytes ) sampleFactor = 1 ; const alphadec = ( 30 + ( sampleFactor - 1 ) / 3 ) | 0 ; const pointsToSample = ( pointsNumber / sampleFactor ) | 0 ; let delta = ( pointsToSample / NeuQuant . _nCycles ) | 0 ; let alpha = NeuQuant . _initAlpha ; let radius = ( this . _networkSize >> 3 ) * NeuQuant . _radiusBias ; let rad = radius >> NeuQuant . _radiusBiasShift ; if ( rad <= 1 ) rad = 0 ; for ( let i = 0 ; i < rad ; i ++ ) { this . _radPower [ i ] = ( alpha * ( ( ( rad * rad - i * i ) * NeuQuant . _radBias ) / ( rad * rad ) ) ) >>> 0 ; } let step ; if ( pointsNumber < NeuQuant . _minpicturebytes ) { step = 1 ; } else if ( pointsNumber % NeuQuant . _prime1 !== 0 ) { step = NeuQuant . _prime1 ; } else if ( pointsNumber % NeuQuant . _prime2 !== 0 ) { step = NeuQuant . _prime2 ; } else if ( pointsNumber % NeuQuant . _prime3 !== 0 ) { step = NeuQuant . _prime3 ; } else { step = NeuQuant . _prime4 ; } const tracker = new ProgressTracker ( pointsToSample , 99 ) ; for ( let i = 0 , pointIndex = 0 ; i < pointsToSample ; ) { if ( tracker . shouldNotify ( i ) ) { yield { progress : tracker . progress , } ; } const point = this . _pointArray [ pointIndex ] ; const b = point . b << networkBiasShift ; const g = point . g << networkBiasShift ; const r = point . r << networkBiasShift ; const a = point . a << networkBiasShift ; const neuronIndex = this . _contest ( b , g , r , a ) ; this . _alterSingle ( alpha , neuronIndex , b , g , r , a ) ; if ( rad !== 0 ) this . _alterNeighbour ( rad , neuronIndex , b , g , r , a ) ; pointIndex += step ; if ( pointIndex >= pointsNumber ) pointIndex -= pointsNumber ; i ++ ; if ( delta === 0 ) delta = 1 ; if ( i % delta === 0 ) { alpha -= ( alpha / alphadec ) | 0 ; radius -= ( radius / NeuQuant . _radiusDecrease ) | 0 ; rad = radius >> NeuQuant . _radiusBiasShift ; if ( rad <= 1 ) rad = 0 ; for ( let j = 0 ; j < rad ; j ++ ) { this . _radPower [ j ] = ( alpha * ( ( ( rad * rad - j * j ) * NeuQuant . _radBias ) / ( rad * rad ) ) ) >>> 0 ; } } } } private _buildPalette ( ) { const palette = new Palette ( ) ; this . _network . forEach ( ( neuron ) => { palette . add ( neuron . toPoint ( ) ) ; } ) ; palette . sort ( ) ; return palette ; } /**
     * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|]
     */ private _alterNeighbour ( rad , i , b , g , r , al ) { let lo = i - rad ; if ( lo < - 1 ) lo = - 1 ; let hi = i + rad ; if ( hi > this . _networkSize ) hi = this . _networkSize ; let j = i + 1 ; let k = i - 1 ; let m = 1 ; while ( j < hi || k > lo ) { const a = this . _radPower [ m ++ ] / NeuQuant . _alphaRadBias ; if ( j < hi ) { const p = this . _network [ j ++ ] ; p . subtract ( a * ( p . r - r ) , a * ( p . g - g ) , a * ( p . b - b ) , a * ( p . a - al ) ) ; } if ( k > lo ) { const p = this . _network [ k -- ] ; p . subtract ( a * ( p . r - r ) , a * ( p . g - g ) , a * ( p . b - b ) , a * ( p . a - al ) ) ; } } } /**
     * Move neuron i towards biased (b,g,r) by factor alpha
     */ private _alterSingle ( alpha , i , b , g , r , a ) { alpha /= NeuQuant . _initAlpha ; const n = this . _network [ i ] ; n . subtract ( alpha * ( n . r - r ) , alpha * ( n . g - g ) , alpha * ( n . b - b ) , alpha * ( n . a - a ) ) ; } /**
     * Search for biased BGR values
     * description:
     *    finds closest neuron (min dist) and updates freq
     *    finds best neuron (min dist-bias) and returns position
     *    for frequently chosen neurons, freq[i] is high and bias[i] is negative
     *    bias[i] = _gamma*((1/this._networkSize)-freq[i])
     *
     * Original distance equation:
     *        dist = abs(dR) + abs(dG) + abs(dB)
     */ private _contest ( b , g , r , a ) { const multiplier = ( 255 * 4 ) << networkBiasShift ; let bestd = ~ ( 1 << 31 ) ; let bestbiasd = bestd ; let bestpos = - 1 ; let bestbiaspos = bestpos ; for ( let i = 0 ; i < this . _networkSize ; i ++ ) { const n = this . _network [ i ] ; const dist = ( this . _distance . calculateNormalized ( n , { r , g , b , a } ) * multiplier ) | 0 ; if ( dist < bestd ) { bestd = dist ; bestpos = i ; } const biasdist = dist - ( this . _bias [ i ] >> ( NeuQuant . _initialBiasShift - networkBiasShift ) ) ; if ( biasdist < bestbiasd ) { bestbiasd = biasdist ; bestbiaspos = i ; } const betafreq = this . _freq [ i ] >> NeuQuant . _betaShift ; this . _freq [ i ] -= betafreq ; this . _bias [ i ] += betafreq << NeuQuant . _gammaShift ; } this . _freq [ bestpos ] += NeuQuant . _beta ; this . _bias [ bestpos ] -= NeuQuant . _betaGamma ; return bestbiaspos ; } }