webdriverio-automation
Version:
WebdriverIO-Automation android ios project
208 lines (165 loc) • 6.81 kB
text/typescript
/*
* Copyright (c) 2015, Leon Sorokin
* All rights reserved. (MIT Licensed)
*
* ColorHistogram.js - an image quantization lib
*/
/**
* @preserve TypeScript port:
* Copyright 2015-2016 Igor Bezkrovnyi
* All rights reserved. (MIT Licensed)
*
* colorHistogram.ts - part of Image Quantization Library
*/
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;
// 1 = by global population, 2 = subregion population threshold
private _method : number;
// HueStatistics instance
private _hueStats : HueStatistics;
private _histogram : {[color : string] : number};
// # of highest-frequency colors to start with for palette reduction
private _initColors : number;
// if > 0, enables hues stats and min-color retention per group
private _minHueCols : number;
constructor(method : number, colors : number) {
// 1 = by global population, 2 = subregion population threshold
this._method = method;
// if > 0, enables hues stats and min-color retention per group
this._minHueCols = colors << 2;//opts.minHueCols || 0;
// # of highest-frequency colors to start with for palette reduction
this._initColors = colors << 2;
// HueStatistics instance
this._hueStats = new HueStatistics(ColorHistogram._hueGroups, this._minHueCols);
this._histogram = Object.create(null);
}
sample(pointBuffer : PointContainer) : void {
switch (this._method) {
case 1:
this._colorStats1D(pointBuffer);
break;
case 2:
this._colorStats2D(pointBuffer);
break;
}
}
getImportanceSortedColorsIDXI32() {
// TODO: fix typing issue in stableSort func
const sorted = stableSort(Object.keys(this._histogram), (a, b) => this._histogram[ b ] - this._histogram[ a ]);
if (sorted.length === 0) {
return [];
}
let idxi32 : string[];
switch (this._method) {
case 1:
const initialColorsLimit = Math.min(sorted.length, this._initColors),
last = sorted[ initialColorsLimit - 1 ],
freq = this._histogram[ last ];
idxi32 = sorted.slice(0, initialColorsLimit);
// add any cut off colors with same freq as last
let pos = initialColorsLimit, len = sorted.length;
while (pos < len && this._histogram[ sorted[ pos ] ] == freq)
idxi32.push(sorted[ pos++ ]);
// inject min huegroup colors
this._hueStats.injectIntoArray(idxi32);
break;
case 2:
idxi32 = sorted;
break;
default:
// TODO: rethink errors
throw new Error("Incorrect method")
}
// int32-ify values
return (<string[]><any>idxi32).map(function (v : string) {
return +v;
});
}
// global top-population
private _colorStats1D(pointBuffer : PointContainer) {
const histG = this._histogram,
pointArray = pointBuffer.getPointArray(),
len = pointArray.length;
for (let i = 0; i < len; i++) {
const col = pointArray[ i ].uint32;
// collect hue stats
this._hueStats.check(col);
if (col in histG)
histG[ col ]++;
else
histG[ col ] = 1;
}
}
// population threshold within subregions
// FIXME: this can over-reduce (few/no colors same?), need a way to keep
// important colors that dont ever reach local thresholds (gradients?)
private _colorStats2D(pointBuffer : PointContainer) {
const width = pointBuffer.getWidth(),
height = pointBuffer.getHeight(),
pointArray = pointBuffer.getPointArray();
const boxW = ColorHistogram._boxSize[ 0 ],
boxH = ColorHistogram._boxSize[ 1 ],
area = boxW * boxH,
boxes = this._makeBoxes(width, height, boxW, boxH),
histG = this._histogram;
boxes.forEach(box => {
let effc = Math.round((box.w * box.h) / area) * ColorHistogram._boxPixels;
if (effc < 2) effc = 2;
const histL : { [key : string ] : number} = {};
this._iterateBox(box, width, (i : number) => {
const col = pointArray[ i ].uint32;
// collect hue stats
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;
});
});
// inject min huegroup colors
this._hueStats.injectIntoDictionary(histG);
}
// iterates @bbox within a parent rect of width @wid; calls @fn, passing index within parent
private _iterateBox(bbox : Box, wid : number, fn : Function) {
const b = bbox,
i0 = b.y * wid + b.x,
i1 = (b.y + b.h - 1) * wid + (b.x + b.w - 1),
incr = wid - b.w + 1;
let cnt = 0,
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 : number, height : number, stepX : number, stepY : number) {
const wrem = width % stepX,
hrem = height % stepY,
xend = width - wrem,
yend = height - hrem,
boxesArray : Box[] = [];
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;
}
}