preserve import { HueStatistics } from '../../utils/hueStatistics' ; import { PointContainer } from '../../utils/pointContainer' ; import { stableSort } from '../../utils/arithmetic' ; interface Box { x : number ; y : number ; h : number ; w : number ; } export class ColorHistogram { private static _boxSize = [ 64 , 64 ] ; private static _boxPixels = 2 ; private static _hueGroups = 10 ; private _method : number ; private _hueStats : HueStatistics ; private _histogram : { [ color ] : number ; } ; private _initColors : number ; private _minHueCols : number ; constructor ( method , colors ) { this . _method = method ; this . _minHueCols = colors << 2 ; this . _initColors = colors << 2 ; this . _hueStats = new HueStatistics ( ColorHistogram . _hueGroups , this . _minHueCols ) ; this . _histogram = Object . create ( null ) ; } sample ( pointContainer ) { switch ( this . _method ) { case 1 : this . _colorStats1D ( pointContainer ) ; break ; case 2 : this . _colorStats2D ( pointContainer ) ; break ; } } getImportanceSortedColorsIDXI32 ( ) { const sorted = stableSort ( Object . keys ( this . _histogram ) , ( a , b ) => this . _histogram [ b ] - this . _histogram [ a ] ) ; if ( sorted . length === 0 ) { return [ ] ; } let idxi32 ; switch ( this . _method ) { case 1 : const initialColorsLimit = Math . min ( sorted . length , this . _initColors ) ; const last = sorted [ initialColorsLimit - 1 ] ; const freq = this . _histogram [ last ] ; idxi32 = sorted . slice ( 0 , initialColorsLimit ) ; let pos = initialColorsLimit ; const len = sorted . length ; while ( pos < len && this . _histogram [ sorted [ pos ] ] === freq ) { idxi32 . push ( sorted [ pos ++ ] ) ; } this . _hueStats . injectIntoArray ( idxi32 ) ; break ; case 2 : idxi32 = sorted ; break ; default : throw new Error ( 'Incorrect method' ) ; } return idxi32 . map ( ( v ) => + v ) ; } private _colorStats1D ( pointContainer ) { const histG = this . _histogram ; const pointArray = pointContainer . getPointArray ( ) ; const len = pointArray . length ; for ( let i = 0 ; i < len ; i ++ ) { const col = pointArray [ i ] . uint32 ; this . _hueStats . check ( col ) ; if ( col in histG ) { histG [ col ] ++ ; } else { histG [ col ] = 1 ; } } } private _colorStats2D ( pointContainer ) { const width = pointContainer . getWidth ( ) ; const height = pointContainer . getHeight ( ) ; const pointArray = pointContainer . getPointArray ( ) ; const boxW = ColorHistogram . _boxSize [ 0 ] ; const boxH = ColorHistogram . _boxSize [ 1 ] ; const area = boxW * boxH ; const boxes = this . _makeBoxes ( width , height , boxW , boxH ) ; const histG = this . _histogram ; boxes . forEach ( ( box ) => { let effc = Math . round ( ( box . w * box . h ) / area ) * ColorHistogram . _boxPixels ; if ( effc < 2 ) effc = 2 ; const histL = { } ; this . _iterateBox ( box , width , ( i ) => { const col = pointArray [ i ] . uint32 ; this . _hueStats . check ( col ) ; if ( col in histG ) { histG [ col ] ++ ; } else if ( col in histL ) { if ( ++ histL [ col ] >= effc ) { histG [ col ] = histL [ col ] ; } } else { histL [ col ] = 1 ; } } ) ; } ) ; this . _hueStats . injectIntoDictionary ( histG ) ; } private _iterateBox ( bbox , wid , fn ) { const b = bbox ; const i0 = b . y * wid + b . x ; const i1 = ( b . y + b . h - 1 ) * wid + ( b . x + b . w - 1 ) ; const incr = wid - b . w + 1 ; let cnt = 0 ; let i = i0 ; do { fn . call ( this , i ) ; i += ++ cnt % b . w === 0 ? incr : 1 ; } while ( i <= i1 ) ; } /**
     *    partitions a rectangle of width x height into
     *    array of boxes stepX x stepY (or less)
     */ private _makeBoxes ( width , height , stepX , stepY ) { const wrem = width % stepX ; const hrem = height % stepY ; const xend = width - wrem ; const yend = height - hrem ; const boxesArray = [ ] ; for ( let y = 0 ; y < height ; y += stepY ) { for ( let x = 0 ; x < width ; x += stepX ) { boxesArray . push ( { x , y , w : x === xend ? wrem : stepX , h : y === yend ? hrem : stepY , } ) ; } } return boxesArray ; } }