????
Current Path : C:/inetpub/vhost/invest.gdtsolutions.vn/api/node_modules/image-q/src/palette/wu/ |
Current File : C:/inetpub/vhost/invest.gdtsolutions.vn/api/node_modules/image-q/src/palette/wu/wuQuant.ts |
/** * @preserve * Copyright 2015-2018 Igor Bezkrovnyi * All rights reserved. (MIT Licensed) * * wuQuant.ts - part of Image Quantization Library */ 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'; function createArray1D(dimension1: number) { const a = []; for (let k = 0; k < dimension1; k++) { a[k] = 0; } return a; } function createArray4D( dimension1: number, dimension2: number, dimension3: number, dimension4: number, ): number[][][][] { const a = new Array(dimension1); for (let i = 0; i < dimension1; i++) { a[i] = new Array(dimension2); for (let j = 0; j < dimension2; j++) { a[i][j] = new Array(dimension3); for (let k = 0; k < dimension3; k++) { a[i][j][k] = new Array(dimension4); for (let l = 0; l < dimension4; l++) { a[i][j][k][l] = 0; } } } } return a; } function createArray3D( dimension1: number, dimension2: number, dimension3: number, ): number[][][] { const a = new Array(dimension1); for (let i = 0; i < dimension1; i++) { a[i] = new Array(dimension2); for (let j = 0; j < dimension2; j++) { a[i][j] = new Array(dimension3); for (let k = 0; k < dimension3; k++) { a[i][j][k] = 0; } } } return a; } function fillArray3D<T>( a: T[][][], dimension1: number, dimension2: number, dimension3: number, value: T, ) { for (let i = 0; i < dimension1; i++) { a[i] = []; for (let j = 0; j < dimension2; j++) { a[i][j] = []; for (let k = 0; k < dimension3; k++) { a[i][j][k] = value; } } } } function fillArray1D<T>(a: T[], dimension1: number, value: T) { for (let i = 0; i < dimension1; i++) { a[i] = value; } } export class WuColorCube { redMinimum!: number; redMaximum!: number; greenMinimum!: number; greenMaximum!: number; blueMinimum!: number; blueMaximum!: number; volume!: number; alphaMinimum!: number; alphaMaximum!: number; } export class WuQuant extends AbstractPaletteQuantizer { private static readonly _alpha = 3; private static readonly _red = 2; private static readonly _green = 1; private static readonly _blue = 0; private _reds!: number[]; private _greens!: number[]; private _blues!: number[]; private _alphas!: number[]; private _sums!: number[]; private _weights!: number[][][][]; private _momentsRed!: number[][][][]; private _momentsGreen!: number[][][][]; private _momentsBlue!: number[][][][]; private _momentsAlpha!: number[][][][]; private _moments!: number[][][][]; private _table!: number[]; private _pixels!: Point[]; private _cubes!: WuColorCube[]; private _colors!: number; private _significantBitsPerChannel!: number; private _maxSideIndex!: number; private _alphaMaxSideIndex!: number; private _sideSize!: number; private _alphaSideSize!: number; private readonly _distance: AbstractDistanceCalculator; constructor( colorDistanceCalculator: AbstractDistanceCalculator, colors = 256, significantBitsPerChannel = 5, ) { super(); this._distance = colorDistanceCalculator; this._setQuality(significantBitsPerChannel); this._initialize(colors); } sample(image: PointContainer) { const pointArray = image.getPointArray(); for (let i = 0, l = pointArray.length; i < l; i++) { this._addColor(pointArray[i]); } this._pixels = this._pixels.concat(pointArray); } *quantize() { yield* this._preparePalette(); const palette = new Palette(); // generates palette for (let paletteIndex = 0; paletteIndex < this._colors; paletteIndex++) { if (this._sums[paletteIndex] > 0) { const sum = this._sums[paletteIndex]; const r = this._reds[paletteIndex] / sum; const g = this._greens[paletteIndex] / sum; const b = this._blues[paletteIndex] / sum; const a = this._alphas[paletteIndex] / sum; const color = Point.createByRGBA(r | 0, g | 0, b | 0, a | 0); palette.add(color); } } palette.sort(); yield { palette, progress: 100, }; } private *_preparePalette() { // preprocess the colors yield* this._calculateMoments(); let next = 0; const volumeVariance = createArray1D(this._colors); // processes the cubes for (let cubeIndex = 1; cubeIndex < this._colors; ++cubeIndex) { // if cut is possible; make it if (this._cut(this._cubes[next], this._cubes[cubeIndex])) { volumeVariance[next] = this._cubes[next].volume > 1 ? this._calculateVariance(this._cubes[next]) : 0.0; volumeVariance[cubeIndex] = this._cubes[cubeIndex].volume > 1 ? this._calculateVariance(this._cubes[cubeIndex]) : 0.0; } else { // the cut was not possible, revert the index volumeVariance[next] = 0.0; cubeIndex--; } next = 0; let temp = volumeVariance[0]; for (let index = 1; index <= cubeIndex; ++index) { if (volumeVariance[index] > temp) { temp = volumeVariance[index]; next = index; } } if (temp <= 0.0) { this._colors = cubeIndex + 1; break; } } const lookupRed = []; const lookupGreen = []; const lookupBlue = []; const lookupAlpha = []; // precalculates lookup tables for (let k = 0; k < this._colors; ++k) { const weight = WuQuant._volume(this._cubes[k], this._weights); if (weight > 0) { lookupRed[k] = (WuQuant._volume(this._cubes[k], this._momentsRed) / weight) | 0; lookupGreen[k] = (WuQuant._volume(this._cubes[k], this._momentsGreen) / weight) | 0; lookupBlue[k] = (WuQuant._volume(this._cubes[k], this._momentsBlue) / weight) | 0; lookupAlpha[k] = (WuQuant._volume(this._cubes[k], this._momentsAlpha) / weight) | 0; } else { lookupRed[k] = 0; lookupGreen[k] = 0; lookupBlue[k] = 0; lookupAlpha[k] = 0; } } this._reds = createArray1D(this._colors + 1); this._greens = createArray1D(this._colors + 1); this._blues = createArray1D(this._colors + 1); this._alphas = createArray1D(this._colors + 1); this._sums = createArray1D(this._colors + 1); // scans and adds colors for (let index = 0, l = this._pixels.length; index < l; index++) { const color = this._pixels[index]; const match = -1; let bestMatch = match; let bestDistance = Number.MAX_VALUE; for (let lookup = 0; lookup < this._colors; lookup++) { const foundRed = lookupRed[lookup]; const foundGreen = lookupGreen[lookup]; const foundBlue = lookupBlue[lookup]; const foundAlpha = lookupAlpha[lookup]; const distance = this._distance.calculateRaw( foundRed, foundGreen, foundBlue, foundAlpha, color.r, color.g, color.b, color.a, ); if (distance < bestDistance) { bestDistance = distance; bestMatch = lookup; } } this._reds[bestMatch] += color.r; this._greens[bestMatch] += color.g; this._blues[bestMatch] += color.b; this._alphas[bestMatch] += color.a; this._sums[bestMatch]++; } } private _addColor(color: Point) { const bitsToRemove = 8 - this._significantBitsPerChannel; const indexRed = (color.r >> bitsToRemove) + 1; const indexGreen = (color.g >> bitsToRemove) + 1; const indexBlue = (color.b >> bitsToRemove) + 1; const indexAlpha = (color.a >> bitsToRemove) + 1; // if(color.a > 10) { this._weights[indexAlpha][indexRed][indexGreen][indexBlue]++; this._momentsRed[indexAlpha][indexRed][indexGreen][indexBlue] += color.r; this._momentsGreen[indexAlpha][indexRed][indexGreen][indexBlue] += color.g; this._momentsBlue[indexAlpha][indexRed][indexGreen][indexBlue] += color.b; this._momentsAlpha[indexAlpha][indexRed][indexGreen][indexBlue] += color.a; this._moments[indexAlpha][indexRed][indexGreen][indexBlue] += this._table[color.r] + this._table[color.g] + this._table[color.b] + this._table[color.a]; // } } /** * Converts the histogram to a series of _moments. */ private *_calculateMoments(): IterableIterator<PaletteQuantizerYieldValue> { const area: number[] = []; const areaRed: number[] = []; const areaGreen: number[] = []; const areaBlue: number[] = []; const areaAlpha: number[] = []; const area2: number[] = []; const xarea = createArray3D(this._sideSize, this._sideSize, this._sideSize); const xareaRed = createArray3D( this._sideSize, this._sideSize, this._sideSize, ); const xareaGreen = createArray3D( this._sideSize, this._sideSize, this._sideSize, ); const xareaBlue = createArray3D( this._sideSize, this._sideSize, this._sideSize, ); const xareaAlpha = createArray3D( this._sideSize, this._sideSize, this._sideSize, ); const xarea2 = createArray3D( this._sideSize, this._sideSize, this._sideSize, ); let trackerProgress = 0; const tracker = new ProgressTracker( this._alphaMaxSideIndex * this._maxSideIndex, 99, ); for ( let alphaIndex = 1; alphaIndex <= this._alphaMaxSideIndex; ++alphaIndex ) { fillArray3D<number>( xarea, this._sideSize, this._sideSize, this._sideSize, 0, ); fillArray3D<number>( xareaRed, this._sideSize, this._sideSize, this._sideSize, 0, ); fillArray3D<number>( xareaGreen, this._sideSize, this._sideSize, this._sideSize, 0, ); fillArray3D<number>( xareaBlue, this._sideSize, this._sideSize, this._sideSize, 0, ); fillArray3D<number>( xareaAlpha, this._sideSize, this._sideSize, this._sideSize, 0, ); fillArray3D<number>( xarea2, this._sideSize, this._sideSize, this._sideSize, 0, ); for ( let redIndex = 1; redIndex <= this._maxSideIndex; ++redIndex, ++trackerProgress ) { if (tracker.shouldNotify(trackerProgress)) { yield { progress: tracker.progress, }; } fillArray1D<number>(area, this._sideSize, 0); fillArray1D<number>(areaRed, this._sideSize, 0); fillArray1D<number>(areaGreen, this._sideSize, 0); fillArray1D<number>(areaBlue, this._sideSize, 0); fillArray1D<number>(areaAlpha, this._sideSize, 0); fillArray1D<number>(area2, this._sideSize, 0); for ( let greenIndex = 1; greenIndex <= this._maxSideIndex; ++greenIndex ) { let line = 0; let lineRed = 0; let lineGreen = 0; let lineBlue = 0; let lineAlpha = 0; let line2 = 0.0; for ( let blueIndex = 1; blueIndex <= this._maxSideIndex; ++blueIndex ) { line += this._weights[alphaIndex][redIndex][greenIndex][blueIndex]; lineRed += this._momentsRed[alphaIndex][redIndex][greenIndex][blueIndex]; lineGreen += this._momentsGreen[alphaIndex][redIndex][greenIndex][blueIndex]; lineBlue += this._momentsBlue[alphaIndex][redIndex][greenIndex][blueIndex]; lineAlpha += this._momentsAlpha[alphaIndex][redIndex][greenIndex][blueIndex]; line2 += this._moments[alphaIndex][redIndex][greenIndex][blueIndex]; area[blueIndex] += line; areaRed[blueIndex] += lineRed; areaGreen[blueIndex] += lineGreen; areaBlue[blueIndex] += lineBlue; areaAlpha[blueIndex] += lineAlpha; area2[blueIndex] += line2; xarea[redIndex][greenIndex][blueIndex] = xarea[redIndex - 1][greenIndex][blueIndex] + area[blueIndex]; xareaRed[redIndex][greenIndex][blueIndex] = xareaRed[redIndex - 1][greenIndex][blueIndex] + areaRed[blueIndex]; xareaGreen[redIndex][greenIndex][blueIndex] = xareaGreen[redIndex - 1][greenIndex][blueIndex] + areaGreen[blueIndex]; xareaBlue[redIndex][greenIndex][blueIndex] = xareaBlue[redIndex - 1][greenIndex][blueIndex] + areaBlue[blueIndex]; xareaAlpha[redIndex][greenIndex][blueIndex] = xareaAlpha[redIndex - 1][greenIndex][blueIndex] + areaAlpha[blueIndex]; xarea2[redIndex][greenIndex][blueIndex] = xarea2[redIndex - 1][greenIndex][blueIndex] + area2[blueIndex]; this._weights[alphaIndex][redIndex][greenIndex][blueIndex] = this._weights[alphaIndex - 1][redIndex][greenIndex][blueIndex] + xarea[redIndex][greenIndex][blueIndex]; this._momentsRed[alphaIndex][redIndex][greenIndex][blueIndex] = this._momentsRed[alphaIndex - 1][redIndex][greenIndex][ blueIndex ] + xareaRed[redIndex][greenIndex][blueIndex]; this._momentsGreen[alphaIndex][redIndex][greenIndex][blueIndex] = this._momentsGreen[alphaIndex - 1][redIndex][greenIndex][ blueIndex ] + xareaGreen[redIndex][greenIndex][blueIndex]; this._momentsBlue[alphaIndex][redIndex][greenIndex][blueIndex] = this._momentsBlue[alphaIndex - 1][redIndex][greenIndex][ blueIndex ] + xareaBlue[redIndex][greenIndex][blueIndex]; this._momentsAlpha[alphaIndex][redIndex][greenIndex][blueIndex] = this._momentsAlpha[alphaIndex - 1][redIndex][greenIndex][ blueIndex ] + xareaAlpha[redIndex][greenIndex][blueIndex]; this._moments[alphaIndex][redIndex][greenIndex][blueIndex] = this._moments[alphaIndex - 1][redIndex][greenIndex][blueIndex] + xarea2[redIndex][greenIndex][blueIndex]; } } } } } /** * Computes the volume of the cube in a specific moment. */ private static _volumeFloat(cube: WuColorCube, moment: number[][][][]) { return ( moment[cube.alphaMaximum][cube.redMaximum][cube.greenMaximum][ cube.blueMaximum ] - moment[cube.alphaMaximum][cube.redMaximum][cube.greenMinimum][ cube.blueMaximum ] - moment[cube.alphaMaximum][cube.redMinimum][cube.greenMaximum][ cube.blueMaximum ] + moment[cube.alphaMaximum][cube.redMinimum][cube.greenMinimum][ cube.blueMaximum ] - moment[cube.alphaMinimum][cube.redMaximum][cube.greenMaximum][ cube.blueMaximum ] + moment[cube.alphaMinimum][cube.redMaximum][cube.greenMinimum][ cube.blueMaximum ] + moment[cube.alphaMinimum][cube.redMinimum][cube.greenMaximum][ cube.blueMaximum ] - moment[cube.alphaMinimum][cube.redMinimum][cube.greenMinimum][ cube.blueMaximum ] - (moment[cube.alphaMaximum][cube.redMaximum][cube.greenMaximum][ cube.blueMinimum ] - moment[cube.alphaMinimum][cube.redMaximum][cube.greenMaximum][ cube.blueMinimum ] - moment[cube.alphaMaximum][cube.redMaximum][cube.greenMinimum][ cube.blueMinimum ] + moment[cube.alphaMinimum][cube.redMaximum][cube.greenMinimum][ cube.blueMinimum ] - moment[cube.alphaMaximum][cube.redMinimum][cube.greenMaximum][ cube.blueMinimum ] + moment[cube.alphaMinimum][cube.redMinimum][cube.greenMaximum][ cube.blueMinimum ] + moment[cube.alphaMaximum][cube.redMinimum][cube.greenMinimum][ cube.blueMinimum ] - moment[cube.alphaMinimum][cube.redMinimum][cube.greenMinimum][ cube.blueMinimum ]) ); } /** * Computes the volume of the cube in a specific moment. */ private static _volume(cube: WuColorCube, moment: number[][][][]) { return WuQuant._volumeFloat(cube, moment) | 0; } /** * Splits the cube in given position][and color direction. */ private static _top( cube: WuColorCube, direction: number, position: number, moment: number[][][][], ) { let result; switch (direction) { case WuQuant._alpha: result = moment[position][cube.redMaximum][cube.greenMaximum][ cube.blueMaximum ] - moment[position][cube.redMaximum][cube.greenMinimum][ cube.blueMaximum ] - moment[position][cube.redMinimum][cube.greenMaximum][ cube.blueMaximum ] + moment[position][cube.redMinimum][cube.greenMinimum][ cube.blueMaximum ] - (moment[position][cube.redMaximum][cube.greenMaximum][ cube.blueMinimum ] - moment[position][cube.redMaximum][cube.greenMinimum][ cube.blueMinimum ] - moment[position][cube.redMinimum][cube.greenMaximum][ cube.blueMinimum ] + moment[position][cube.redMinimum][cube.greenMinimum][ cube.blueMinimum ]); break; case WuQuant._red: result = moment[cube.alphaMaximum][position][cube.greenMaximum][ cube.blueMaximum ] - moment[cube.alphaMaximum][position][cube.greenMinimum][ cube.blueMaximum ] - moment[cube.alphaMinimum][position][cube.greenMaximum][ cube.blueMaximum ] + moment[cube.alphaMinimum][position][cube.greenMinimum][ cube.blueMaximum ] - (moment[cube.alphaMaximum][position][cube.greenMaximum][ cube.blueMinimum ] - moment[cube.alphaMaximum][position][cube.greenMinimum][ cube.blueMinimum ] - moment[cube.alphaMinimum][position][cube.greenMaximum][ cube.blueMinimum ] + moment[cube.alphaMinimum][position][cube.greenMinimum][ cube.blueMinimum ]); break; case WuQuant._green: result = moment[cube.alphaMaximum][cube.redMaximum][position][ cube.blueMaximum ] - moment[cube.alphaMaximum][cube.redMinimum][position][ cube.blueMaximum ] - moment[cube.alphaMinimum][cube.redMaximum][position][ cube.blueMaximum ] + moment[cube.alphaMinimum][cube.redMinimum][position][ cube.blueMaximum ] - (moment[cube.alphaMaximum][cube.redMaximum][position][ cube.blueMinimum ] - moment[cube.alphaMaximum][cube.redMinimum][position][ cube.blueMinimum ] - moment[cube.alphaMinimum][cube.redMaximum][position][ cube.blueMinimum ] + moment[cube.alphaMinimum][cube.redMinimum][position][ cube.blueMinimum ]); break; case WuQuant._blue: result = moment[cube.alphaMaximum][cube.redMaximum][cube.greenMaximum][ position ] - moment[cube.alphaMaximum][cube.redMaximum][cube.greenMinimum][ position ] - moment[cube.alphaMaximum][cube.redMinimum][cube.greenMaximum][ position ] + moment[cube.alphaMaximum][cube.redMinimum][cube.greenMinimum][ position ] - (moment[cube.alphaMinimum][cube.redMaximum][cube.greenMaximum][ position ] - moment[cube.alphaMinimum][cube.redMaximum][cube.greenMinimum][ position ] - moment[cube.alphaMinimum][cube.redMinimum][cube.greenMaximum][ position ] + moment[cube.alphaMinimum][cube.redMinimum][cube.greenMinimum][ position ]); break; default: throw new Error('impossible'); } return result | 0; } /** * Splits the cube in a given color direction at its minimum. */ private static _bottom( cube: WuColorCube, direction: number, moment: number[][][][], ) { switch (direction) { case WuQuant._alpha: return ( -moment[cube.alphaMinimum][cube.redMaximum][cube.greenMaximum][ cube.blueMaximum ] + moment[cube.alphaMinimum][cube.redMaximum][cube.greenMinimum][ cube.blueMaximum ] + moment[cube.alphaMinimum][cube.redMinimum][cube.greenMaximum][ cube.blueMaximum ] - moment[cube.alphaMinimum][cube.redMinimum][cube.greenMinimum][ cube.blueMaximum ] - (-moment[cube.alphaMinimum][cube.redMaximum][cube.greenMaximum][ cube.blueMinimum ] + moment[cube.alphaMinimum][cube.redMaximum][cube.greenMinimum][ cube.blueMinimum ] + moment[cube.alphaMinimum][cube.redMinimum][cube.greenMaximum][ cube.blueMinimum ] - moment[cube.alphaMinimum][cube.redMinimum][cube.greenMinimum][ cube.blueMinimum ]) ); case WuQuant._red: return ( -moment[cube.alphaMaximum][cube.redMinimum][cube.greenMaximum][ cube.blueMaximum ] + moment[cube.alphaMaximum][cube.redMinimum][cube.greenMinimum][ cube.blueMaximum ] + moment[cube.alphaMinimum][cube.redMinimum][cube.greenMaximum][ cube.blueMaximum ] - moment[cube.alphaMinimum][cube.redMinimum][cube.greenMinimum][ cube.blueMaximum ] - (-moment[cube.alphaMaximum][cube.redMinimum][cube.greenMaximum][ cube.blueMinimum ] + moment[cube.alphaMaximum][cube.redMinimum][cube.greenMinimum][ cube.blueMinimum ] + moment[cube.alphaMinimum][cube.redMinimum][cube.greenMaximum][ cube.blueMinimum ] - moment[cube.alphaMinimum][cube.redMinimum][cube.greenMinimum][ cube.blueMinimum ]) ); case WuQuant._green: return ( -moment[cube.alphaMaximum][cube.redMaximum][cube.greenMinimum][ cube.blueMaximum ] + moment[cube.alphaMaximum][cube.redMinimum][cube.greenMinimum][ cube.blueMaximum ] + moment[cube.alphaMinimum][cube.redMaximum][cube.greenMinimum][ cube.blueMaximum ] - moment[cube.alphaMinimum][cube.redMinimum][cube.greenMinimum][ cube.blueMaximum ] - (-moment[cube.alphaMaximum][cube.redMaximum][cube.greenMinimum][ cube.blueMinimum ] + moment[cube.alphaMaximum][cube.redMinimum][cube.greenMinimum][ cube.blueMinimum ] + moment[cube.alphaMinimum][cube.redMaximum][cube.greenMinimum][ cube.blueMinimum ] - moment[cube.alphaMinimum][cube.redMinimum][cube.greenMinimum][ cube.blueMinimum ]) ); case WuQuant._blue: return ( -moment[cube.alphaMaximum][cube.redMaximum][cube.greenMaximum][ cube.blueMinimum ] + moment[cube.alphaMaximum][cube.redMaximum][cube.greenMinimum][ cube.blueMinimum ] + moment[cube.alphaMaximum][cube.redMinimum][cube.greenMaximum][ cube.blueMinimum ] - moment[cube.alphaMaximum][cube.redMinimum][cube.greenMinimum][ cube.blueMinimum ] - (-moment[cube.alphaMinimum][cube.redMaximum][cube.greenMaximum][ cube.blueMinimum ] + moment[cube.alphaMinimum][cube.redMaximum][cube.greenMinimum][ cube.blueMinimum ] + moment[cube.alphaMinimum][cube.redMinimum][cube.greenMaximum][ cube.blueMinimum ] - moment[cube.alphaMinimum][cube.redMinimum][cube.greenMinimum][ cube.blueMinimum ]) ); default: // TODO: why here is return 0, and in this._top there is no default at all (now it is throw error)? return 0; } } /** * Calculates statistical variance for a given cube. */ private _calculateVariance(cube: WuColorCube) { const volumeRed = WuQuant._volume(cube, this._momentsRed); const volumeGreen = WuQuant._volume(cube, this._momentsGreen); const volumeBlue = WuQuant._volume(cube, this._momentsBlue); const volumeAlpha = WuQuant._volume(cube, this._momentsAlpha); const volumeMoment = WuQuant._volumeFloat(cube, this._moments); const volumeWeight = WuQuant._volume(cube, this._weights); const distance = volumeRed * volumeRed + volumeGreen * volumeGreen + volumeBlue * volumeBlue + volumeAlpha * volumeAlpha; return volumeMoment - distance / volumeWeight; } /** * Finds the optimal (maximal) position for the cut. */ private _maximize( cube: WuColorCube, direction: number, first: number, last: number, wholeRed: number, wholeGreen: number, wholeBlue: number, wholeAlpha: number, wholeWeight: number, ) { const bottomRed = WuQuant._bottom(cube, direction, this._momentsRed) | 0; const bottomGreen = WuQuant._bottom(cube, direction, this._momentsGreen) | 0; const bottomBlue = WuQuant._bottom(cube, direction, this._momentsBlue) | 0; const bottomAlpha = WuQuant._bottom(cube, direction, this._momentsAlpha) | 0; const bottomWeight = WuQuant._bottom(cube, direction, this._weights) | 0; let result = 0.0; let cutPosition = -1; for (let position = first; position < last; ++position) { // determines the cube cut at a certain position let halfRed = bottomRed + WuQuant._top(cube, direction, position, this._momentsRed); let halfGreen = bottomGreen + WuQuant._top(cube, direction, position, this._momentsGreen); let halfBlue = bottomBlue + WuQuant._top(cube, direction, position, this._momentsBlue); let halfAlpha = bottomAlpha + WuQuant._top(cube, direction, position, this._momentsAlpha); let halfWeight = bottomWeight + WuQuant._top(cube, direction, position, this._weights); // the cube cannot be cut at bottom (this would lead to empty cube) if (halfWeight !== 0) { let halfDistance = halfRed * halfRed + halfGreen * halfGreen + halfBlue * halfBlue + halfAlpha * halfAlpha; let temp = halfDistance / halfWeight; halfRed = wholeRed - halfRed; halfGreen = wholeGreen - halfGreen; halfBlue = wholeBlue - halfBlue; halfAlpha = wholeAlpha - halfAlpha; halfWeight = wholeWeight - halfWeight; if (halfWeight !== 0) { halfDistance = halfRed * halfRed + halfGreen * halfGreen + halfBlue * halfBlue + halfAlpha * halfAlpha; temp += halfDistance / halfWeight; if (temp > result) { result = temp; cutPosition = position; } } } } return { max: result, position: cutPosition }; } // Cuts a cube with another one. private _cut(first: WuColorCube, second: WuColorCube) { let direction; const wholeRed = WuQuant._volume(first, this._momentsRed); const wholeGreen = WuQuant._volume(first, this._momentsGreen); const wholeBlue = WuQuant._volume(first, this._momentsBlue); const wholeAlpha = WuQuant._volume(first, this._momentsAlpha); const wholeWeight = WuQuant._volume(first, this._weights); const red = this._maximize( first, WuQuant._red, first.redMinimum + 1, first.redMaximum, wholeRed, wholeGreen, wholeBlue, wholeAlpha, wholeWeight, ); const green = this._maximize( first, WuQuant._green, first.greenMinimum + 1, first.greenMaximum, wholeRed, wholeGreen, wholeBlue, wholeAlpha, wholeWeight, ); const blue = this._maximize( first, WuQuant._blue, first.blueMinimum + 1, first.blueMaximum, wholeRed, wholeGreen, wholeBlue, wholeAlpha, wholeWeight, ); const alpha = this._maximize( first, WuQuant._alpha, first.alphaMinimum + 1, first.alphaMaximum, wholeRed, wholeGreen, wholeBlue, wholeAlpha, wholeWeight, ); if ( alpha.max >= red.max && alpha.max >= green.max && alpha.max >= blue.max ) { direction = WuQuant._alpha; // cannot split empty cube if (alpha.position < 0) return false; } else if ( red.max >= alpha.max && red.max >= green.max && red.max >= blue.max ) { direction = WuQuant._red; } else if ( green.max >= alpha.max && green.max >= red.max && green.max >= blue.max ) { direction = WuQuant._green; } else { direction = WuQuant._blue; } second.redMaximum = first.redMaximum; second.greenMaximum = first.greenMaximum; second.blueMaximum = first.blueMaximum; second.alphaMaximum = first.alphaMaximum; // cuts in a certain direction switch (direction) { case WuQuant._red: second.redMinimum = first.redMaximum = red.position; second.greenMinimum = first.greenMinimum; second.blueMinimum = first.blueMinimum; second.alphaMinimum = first.alphaMinimum; break; case WuQuant._green: second.greenMinimum = first.greenMaximum = green.position; second.redMinimum = first.redMinimum; second.blueMinimum = first.blueMinimum; second.alphaMinimum = first.alphaMinimum; break; case WuQuant._blue: second.blueMinimum = first.blueMaximum = blue.position; second.redMinimum = first.redMinimum; second.greenMinimum = first.greenMinimum; second.alphaMinimum = first.alphaMinimum; break; case WuQuant._alpha: second.alphaMinimum = first.alphaMaximum = alpha.position; second.blueMinimum = first.blueMinimum; second.redMinimum = first.redMinimum; second.greenMinimum = first.greenMinimum; break; } // determines the volumes after cut first.volume = (first.redMaximum - first.redMinimum) * (first.greenMaximum - first.greenMinimum) * (first.blueMaximum - first.blueMinimum) * (first.alphaMaximum - first.alphaMinimum); second.volume = (second.redMaximum - second.redMinimum) * (second.greenMaximum - second.greenMinimum) * (second.blueMaximum - second.blueMinimum) * (second.alphaMaximum - second.alphaMinimum); // the cut was successful return true; } private _initialize(colors: number) { this._colors = colors; // creates all the _cubes this._cubes = []; // initializes all the _cubes for (let cubeIndex = 0; cubeIndex < colors; cubeIndex++) { this._cubes[cubeIndex] = new WuColorCube(); } // resets the reference minimums this._cubes[0].redMinimum = 0; this._cubes[0].greenMinimum = 0; this._cubes[0].blueMinimum = 0; this._cubes[0].alphaMinimum = 0; // resets the reference maximums this._cubes[0].redMaximum = this._maxSideIndex; this._cubes[0].greenMaximum = this._maxSideIndex; this._cubes[0].blueMaximum = this._maxSideIndex; this._cubes[0].alphaMaximum = this._alphaMaxSideIndex; this._weights = createArray4D( this._alphaSideSize, this._sideSize, this._sideSize, this._sideSize, ); this._momentsRed = createArray4D( this._alphaSideSize, this._sideSize, this._sideSize, this._sideSize, ); this._momentsGreen = createArray4D( this._alphaSideSize, this._sideSize, this._sideSize, this._sideSize, ); this._momentsBlue = createArray4D( this._alphaSideSize, this._sideSize, this._sideSize, this._sideSize, ); this._momentsAlpha = createArray4D( this._alphaSideSize, this._sideSize, this._sideSize, this._sideSize, ); this._moments = createArray4D( this._alphaSideSize, this._sideSize, this._sideSize, this._sideSize, ); this._table = []; for (let tableIndex = 0; tableIndex < 256; ++tableIndex) { this._table[tableIndex] = tableIndex * tableIndex; } this._pixels = []; } private _setQuality(significantBitsPerChannel = 5) { this._significantBitsPerChannel = significantBitsPerChannel; this._maxSideIndex = 1 << this._significantBitsPerChannel; this._alphaMaxSideIndex = this._maxSideIndex; this._sideSize = this._maxSideIndex + 1; this._alphaSideSize = this._alphaMaxSideIndex + 1; } }