webdriverio-automation
Version:
WebdriverIO-Automation android ios project
180 lines (146 loc) • 6.46 kB
text/typescript
/*
* Copyright (c) 2015, Leon Sorokin
* All rights reserved. (MIT Licensed)
*
* RgbQuant.js - an image quantization lib
*/
/**
* @preserve TypeScript port:
* Copyright 2015-2016 Igor Bezkrovnyi
* All rights reserved. (MIT Licensed)
*
* rgbquant.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/abstractDistanceCalculator"
import { ColorHistogram } from "./colorHistogram"
import { IPaletteQuantizer } from "../common"
import { stableSort } from "../../utils/arithmetic"
class RemovedColor {
readonly index : number;
readonly color : Point;
readonly distance : number;
constructor(index : number, color : Point, distance : number) {
this.index = index;
this.color = color;
this.distance = distance;
}
}
// TODO: make input/output image and input/output palettes with instances of class Point only!
export class RGBQuant implements IPaletteQuantizer {
// desired final palette size
private readonly _colors : number;
// color-distance threshold for initial reduction pass
private readonly _initialDistance : number;
// subsequent passes threshold
private readonly _distanceIncrement : number;
// accumulated histogram
private readonly _histogram : ColorHistogram;
private readonly _distance : AbstractDistanceCalculator;
constructor(colorDistanceCalculator : AbstractDistanceCalculator, colors : number = 256, method : number = 2) {
this._distance = colorDistanceCalculator;
// desired final palette size
this._colors = colors;
// histogram to accumulate
this._histogram = new ColorHistogram(method, colors);
this._initialDistance = 0.01;
this._distanceIncrement = 0.005;
}
// gathers histogram info
sample(image : PointContainer) : void {
/*
var pointArray = image.getPointArray(), max = [0, 0, 0, 0], min = [255, 255, 255, 255];
for (var i = 0, l = pointArray.length; i < l; i++) {
var color = pointArray[i];
for (var componentIndex = 0; componentIndex < 4; componentIndex++) {
if (max[componentIndex] < color.rgba[componentIndex]) max[componentIndex] = color.rgba[componentIndex];
if (min[componentIndex] > color.rgba[componentIndex]) min[componentIndex] = color.rgba[componentIndex];
}
}
var rd = max[0] - min[0], gd = max[1] - min[1], bd = max[2] - min[2], ad = max[3] - min[3];
this._distance.setWhitePoint(rd, gd, bd, ad);
this._initialDistance = (Math.sqrt(rd * rd + gd * gd + bd * bd + ad * ad) / Math.sqrt(255 * 255 + 255 * 255 + 255 * 255)) * 0.01;
*/
this._histogram.sample(image);
}
// reduces histogram to palette, remaps & memoizes reduced colors
quantize() : Palette {
const idxi32 = this._histogram.getImportanceSortedColorsIDXI32()
if (idxi32.length === 0) {
throw new Error("No colors in image")
}
const palette = this._buildPalette(idxi32);
palette.sort();
return palette;
}
// reduces similar colors from an importance-sorted Uint32 rgba array
private _buildPalette(idxi32 : number[]) : Palette {
// reduce histogram to create initial palette
// build full rgb palette
const palette = new Palette(),
colorArray = palette.getPointContainer().getPointArray(),
usageArray = new Array(idxi32.length);
for (let i = 0; i < idxi32.length; i++) {
colorArray.push(Point.createByUint32(idxi32[ i ]));
usageArray[ i ] = 1;
}
const len = colorArray.length,
memDist : RemovedColor[] = [];
let palLen = len,
thold = this._initialDistance;
// palette already at or below desired length
while (palLen > this._colors) {
memDist.length = 0;
// iterate palette
for (let i = 0; i < len; i++) {
if (usageArray[ i ] === 0) continue;
const pxi = colorArray[ i ];
//if (!pxi) continue;
for (let j = i + 1; j < len; j++) {
if (usageArray[ j ] === 0) continue;
const pxj = colorArray[ j ];
//if (!pxj) continue;
const dist = this._distance.calculateNormalized(pxi, pxj);
if (dist < thold) {
// store index,rgb,dist
memDist.push(new RemovedColor(j, pxj, dist));
usageArray[ j ] = 0;
palLen--;
}
}
}
// palette reduction pass
// console.log("palette length: " + palLen);
// if palette is still much larger than target, increment by larger initDist
thold += (palLen > this._colors * 3) ? this._initialDistance : this._distanceIncrement;
}
// if palette is over-reduced, re-add removed colors with largest distances from last round
if (palLen < this._colors) {
// sort descending
stableSort(memDist, function (a : RemovedColor, b : RemovedColor) {
return b.distance - a.distance;
});
let k = 0;
while (palLen < this._colors && k < memDist.length) {
const removedColor = memDist[ k ];
// re-inject rgb into final palette
usageArray[ removedColor.index ] = 1;
palLen++;
k++;
}
}
let colors = colorArray.length;
for (let colorIndex = colors - 1; colorIndex >= 0; colorIndex--) {
if (usageArray[ colorIndex ] === 0) {
if (colorIndex !== colors - 1) {
colorArray[ colorIndex ] = colorArray[ colors - 1 ];
}
--colors;
}
}
colorArray.length = colors;
return palette;
}
}