UNPKG

webdriverio-automation

Version:

WebdriverIO-Automation android ios project

281 lines (243 loc) 10.5 kB
/** * @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); } } }