webdriverio-automation
Version:
WebdriverIO-Automation android ios project
281 lines (243 loc) • 10.5 kB
text/typescript
/**
* @preserve
* Copyright 2015-2016 Igor Bezkrovnyi
* All rights reserved. (MIT Licensed)
*
* ditherErrorDiffusionArray.ts - part of Image Quantization Library
*/
import { IImageDitherer } from "./common"
import { AbstractDistanceCalculator } from "../distance/abstractDistanceCalculator"
import { PointContainer } from "../utils/pointContainer"
import { Palette } from "../utils/palette"
import { Point } from "../utils/point"
import { inRange0to255Rounded } from "../utils/arithmetic"
// TODO: is it the best name for this enum "kernel"?
export enum ErrorDiffusionArrayKernel {
FloydSteinberg = 0,
FalseFloydSteinberg,
Stucki,
Atkinson,
Jarvis,
Burkes,
Sierra,
TwoSierra,
SierraLite
}
// http://www.tannerhelland.com/4660/dithering-eleven-algorithms-source-code/
export class ErrorDiffusionArray implements IImageDitherer {
private _minColorDistance : number;
private _serpentine : boolean;
private _kernel : number[][];
/** true = GIMP, false = XNVIEW */
private _calculateErrorLikeGIMP : boolean;
private _distance : AbstractDistanceCalculator;
constructor(colorDistanceCalculator : AbstractDistanceCalculator, kernel : ErrorDiffusionArrayKernel, serpentine : boolean = true, minimumColorDistanceToDither : number = 0, calculateErrorLikeGIMP : boolean = false) {
this._setKernel(kernel);
this._distance = colorDistanceCalculator;
this._minColorDistance = minimumColorDistanceToDither;
this._serpentine = serpentine;
this._calculateErrorLikeGIMP = calculateErrorLikeGIMP;
}
// adapted from http://jsbin.com/iXofIji/2/edit by PAEz
// fixed version. it doesn't use image pixels as error storage, also it doesn't have 0.3 + 0.3 + 0.3 + 0.3 = 0 error
quantize(pointBuffer : PointContainer, palette : Palette) : PointContainer {
const pointArray = pointBuffer.getPointArray(),
originalPoint = new Point(),
width = pointBuffer.getWidth(),
height = pointBuffer.getHeight(),
errorLines : number[][][] = [];
let dir = 1,
maxErrorLines = 1;
// initial error lines (number is taken from dithering kernel)
for (let i = 0; i < this._kernel.length; i++) {
const kernelErrorLines = this._kernel[ i ][ 2 ] + 1;
if (maxErrorLines < kernelErrorLines) maxErrorLines = kernelErrorLines;
}
for (let i = 0; i < maxErrorLines; i++) {
this._fillErrorLine(errorLines[ i ] = [], width);
}
for (let y = 0; y < height; y++) {
// always serpentine
if (this._serpentine) dir = dir * -1;
const lni = y * width,
xStart = dir == 1 ? 0 : width - 1,
xEnd = dir == 1 ? width : -1;
// cyclic shift with erasing
this._fillErrorLine(errorLines[ 0 ], width);
// TODO: why it is needed to cast types here?
errorLines.push(<number[][]>errorLines.shift());
const errorLine = errorLines[ 0 ];
for (let x = xStart, idx = lni + xStart; x !== xEnd; x += dir, idx += dir) {
// Image pixel
const point = pointArray[ idx ],
//originalPoint = new Utils.Point(),
error = errorLine[ x ];
originalPoint.from(point);
const correctedPoint = Point.createByRGBA(
inRange0to255Rounded(point.r + error[ 0 ]),
inRange0to255Rounded(point.g + error[ 1 ]),
inRange0to255Rounded(point.b + error[ 2 ]),
inRange0to255Rounded(point.a + error[ 3 ])
);
// Reduced pixel
const palettePoint = palette.getNearestColor(this._distance, correctedPoint);
point.from(palettePoint);
// dithering strength
if (this._minColorDistance) {
const dist = this._distance.calculateNormalized(point, palettePoint);
if (dist < this._minColorDistance) continue;
}
// Component distance
let er : number, eg : number, eb : number, ea : number;
if (this._calculateErrorLikeGIMP) {
er = correctedPoint.r - palettePoint.r;
eg = correctedPoint.g - palettePoint.g;
eb = correctedPoint.b - palettePoint.b;
ea = correctedPoint.a - palettePoint.a;
} else {
er = originalPoint.r - palettePoint.r;
eg = originalPoint.g - palettePoint.g;
eb = originalPoint.b - palettePoint.b;
ea = originalPoint.a - palettePoint.a;
}
const dStart = dir == 1 ? 0 : this._kernel.length - 1,
dEnd = dir == 1 ? this._kernel.length : -1;
for (let i = dStart; i !== dEnd; i += dir) {
const x1 = this._kernel[ i ][ 1 ] * dir,
y1 = this._kernel[ i ][ 2 ];
if (x1 + x >= 0 && x1 + x < width && y1 + y >= 0 && y1 + y < height) {
const d = this._kernel[ i ][ 0 ],
e = errorLines[ y1 ][ x1 + x ];
e[ 0 ] = e[ 0 ] + er * d;
e[ 1 ] = e[ 1 ] + eg * d;
e[ 2 ] = e[ 2 ] + eb * d;
e[ 3 ] = e[ 3 ] + ea * d;
}
}
}
}
return pointBuffer;
}
private _fillErrorLine(errorLine : number[][], width : number) : void {
// shrink
if (errorLine.length > width) {
errorLine.length = width;
}
// reuse existing arrays
const l = errorLine.length;
for (let i = 0; i < l; i++) {
const error = errorLine[ i ];
error[ 0 ] = error[ 1 ] = error[ 2 ] = error[ 3 ] = 0;
}
// create missing arrays
for (let i = l; i < width; i++) {
errorLine[ i ] = [ 0.0, 0.0, 0.0, 0.0 ];
}
}
private _setKernel(kernel : ErrorDiffusionArrayKernel) {
switch (kernel) {
case ErrorDiffusionArrayKernel.FloydSteinberg:
this._kernel = [
[ 7 / 16, 1, 0 ],
[ 3 / 16, -1, 1 ],
[ 5 / 16, 0, 1 ],
[ 1 / 16, 1, 1 ]
];
break;
case ErrorDiffusionArrayKernel.FalseFloydSteinberg:
this._kernel = [
[ 3 / 8, 1, 0 ],
[ 3 / 8, 0, 1 ],
[ 2 / 8, 1, 1 ]
];
break;
case ErrorDiffusionArrayKernel.Stucki:
this._kernel = [
[ 8 / 42, 1, 0 ],
[ 4 / 42, 2, 0 ],
[ 2 / 42, -2, 1 ],
[ 4 / 42, -1, 1 ],
[ 8 / 42, 0, 1 ],
[ 4 / 42, 1, 1 ],
[ 2 / 42, 2, 1 ],
[ 1 / 42, -2, 2 ],
[ 2 / 42, -1, 2 ],
[ 4 / 42, 0, 2 ],
[ 2 / 42, 1, 2 ],
[ 1 / 42, 2, 2 ]
];
break;
case ErrorDiffusionArrayKernel.Atkinson:
this._kernel = [
[ 1 / 8, 1, 0 ],
[ 1 / 8, 2, 0 ],
[ 1 / 8, -1, 1 ],
[ 1 / 8, 0, 1 ],
[ 1 / 8, 1, 1 ],
[ 1 / 8, 0, 2 ]
];
break;
case ErrorDiffusionArrayKernel.Jarvis:
this._kernel = [ // Jarvis, Judice, and Ninke / JJN?
[ 7 / 48, 1, 0 ],
[ 5 / 48, 2, 0 ],
[ 3 / 48, -2, 1 ],
[ 5 / 48, -1, 1 ],
[ 7 / 48, 0, 1 ],
[ 5 / 48, 1, 1 ],
[ 3 / 48, 2, 1 ],
[ 1 / 48, -2, 2 ],
[ 3 / 48, -1, 2 ],
[ 5 / 48, 0, 2 ],
[ 3 / 48, 1, 2 ],
[ 1 / 48, 2, 2 ]
];
break;
case ErrorDiffusionArrayKernel.Burkes:
this._kernel = [
[ 8 / 32, 1, 0 ],
[ 4 / 32, 2, 0 ],
[ 2 / 32, -2, 1 ],
[ 4 / 32, -1, 1 ],
[ 8 / 32, 0, 1 ],
[ 4 / 32, 1, 1 ],
[ 2 / 32, 2, 1 ],
];
break;
case ErrorDiffusionArrayKernel.Sierra:
this._kernel = [
[ 5 / 32, 1, 0 ],
[ 3 / 32, 2, 0 ],
[ 2 / 32, -2, 1 ],
[ 4 / 32, -1, 1 ],
[ 5 / 32, 0, 1 ],
[ 4 / 32, 1, 1 ],
[ 2 / 32, 2, 1 ],
[ 2 / 32, -1, 2 ],
[ 3 / 32, 0, 2 ],
[ 2 / 32, 1, 2 ]
];
break;
case ErrorDiffusionArrayKernel.TwoSierra:
this._kernel = [
[ 4 / 16, 1, 0 ],
[ 3 / 16, 2, 0 ],
[ 1 / 16, -2, 1 ],
[ 2 / 16, -1, 1 ],
[ 3 / 16, 0, 1 ],
[ 2 / 16, 1, 1 ],
[ 1 / 16, 2, 1 ]
];
break;
case ErrorDiffusionArrayKernel.SierraLite:
this._kernel = [
[ 2 / 4, 1, 0 ],
[ 1 / 4, -1, 1 ],
[ 1 / 4, 0, 1 ]
];
break;
default:
throw new Error("ErrorDiffusionArray: unknown kernel = " + kernel);
}
}
}