Comparing version 0.11.0 to 0.12.0
258
index.js
@@ -14,10 +14,11 @@ 'use strict'; | ||
* @param [options] {Object} | ||
* @param [options.difference] {Number} | ||
* @param [options.percent] {Number} | ||
* @param [options.regions] {Array} | ||
* @param [options.regions[i].name] {String} | ||
* @param [options.regions[i].difference] {Number} | ||
* @param [options.regions[i].percent] {Number} | ||
* @param [options.difference] {Number} - Pixel difference value 1 to 255 | ||
* @param [options.percent] {Number} - Percent of pixels that exceed difference value. | ||
* @param [options.regions] {Array} - Array of regions. | ||
* @param [options.regions[i].name] {String} - Name of region. | ||
* @param [options.regions[i].difference] {Number} - Difference value for region. | ||
* @param [options.regions[i].percent] {Number} - Percent value for region. | ||
* @param [options.regions[i].polygon] {Array} - Array of x y coordinates [{x:0,y:0},{x:0,y:360},{x:160,y:360},{x:160,y:0}] | ||
* @param [callback] {Function} | ||
* @param [mask] {Boolean} - Indicate if regions should be used as masks of pixels to ignore instead of areas of interest. | ||
* @param [callback] {Function} - Function to be called when diff event occurs. | ||
*/ | ||
@@ -29,2 +30,3 @@ constructor(options, callback) { | ||
this.percent = PamDiff._parseOptions('percent', options);//global option, can be overridden per region | ||
this.mask = PamDiff._parseOptions('mask', options);//should be processed before regions | ||
this.regions = PamDiff._parseOptions('regions', options);//can be no regions or a single region or multiple regions. if no regions, all pixels will be compared. | ||
@@ -72,7 +74,8 @@ this.callback = callback;//callback function to be called when pixel difference is detected | ||
* | ||
* @deprecated | ||
* @param string {String} | ||
* @param bool | ||
* @return {boolean} | ||
* @private | ||
*/ | ||
setGrayscale(string) { | ||
console.warn('grayscale option has been removed, "average" has proven to most accurate and is the default'); | ||
static _validateBoolean(bool) { | ||
return (bool === true || bool === 'true' || bool === 1 || bool === '1'); | ||
} | ||
@@ -86,2 +89,3 @@ | ||
this._difference = PamDiff._validateNumber(parseInt(number), 5, 1, 255); | ||
this._configurePixelDiffEngine(); | ||
} | ||
@@ -113,2 +117,3 @@ | ||
this._percent = PamDiff._validateNumber(parseInt(number), 5, 1, 100); | ||
this._configurePixelDiffEngine(); | ||
} | ||
@@ -141,5 +146,4 @@ | ||
delete this._regions; | ||
delete this._regionsArr; | ||
delete this._regionsLen; | ||
delete this._minDiff; | ||
delete this._regionObj; | ||
delete this._maskObj; | ||
} else if (!Array.isArray(array) || array.length < 1) { | ||
@@ -151,2 +155,3 @@ throw new Error(`Regions must be an array of at least 1 region object {name: 'region1', difference: 10, percent: 10, polygon: [[0, 0], [0, 50], [50, 50], [50, 0]]}`); | ||
} | ||
this._configurePixelDiffEngine(); | ||
} | ||
@@ -172,2 +177,17 @@ | ||
set mask(bool) { | ||
this._mask = PamDiff._validateBoolean(bool); | ||
this._processRegions(); | ||
this._configurePixelDiffEngine(); | ||
} | ||
get mask() { | ||
return this._mask; | ||
} | ||
setMask(bool) { | ||
this.mask = bool; | ||
return this; | ||
} | ||
/** | ||
@@ -214,7 +234,6 @@ * | ||
delete this._height; | ||
delete this._wxh; | ||
delete this._bufLen; | ||
delete this._regionsArr; | ||
delete this._regionsLen; | ||
delete this._minDiff; | ||
delete this._tupltype; | ||
delete this._regionObj; | ||
delete this._maskObj; | ||
delete this._pixelDiffEngine; | ||
this._parseChunk = this._parseFirstChunk; | ||
@@ -226,7 +245,56 @@ return this; | ||
* | ||
* @param chunk | ||
* @private | ||
*/ | ||
_bwPixelDiff(chunk) { | ||
throw new Error("black and white pixel diff not available, yet"); | ||
_processRegions() { | ||
if (!this._regions || !this._width || !this._height) { | ||
return; | ||
} | ||
if (this._mask) { | ||
const wxh = this._width * this._height; | ||
const buffer = Buffer.alloc(wxh, 1); | ||
for (const region of this._regions) { | ||
if (!region.hasOwnProperty('polygon')) { | ||
throw new Error('Region must include a polygon property'); | ||
} | ||
const pp = new PP(region.polygon); | ||
const bitset = pp.getBitset(this._width, this._height); | ||
const bitsetBuffer = bitset.buffer; | ||
for (let i = 0; i < wxh; i++) { | ||
if (bitsetBuffer[i]) { | ||
buffer[i] = 0; | ||
} | ||
} | ||
} | ||
let count = 0; | ||
for (let i = 0; i < wxh; i++) { | ||
if (buffer[i]) { | ||
count++; | ||
} | ||
} | ||
this._maskObj = {count: count, bitset: buffer}; | ||
} else { | ||
const regions = []; | ||
let minDiff = 255; | ||
for (const region of this._regions) { | ||
if (!region.hasOwnProperty('name') || !region.hasOwnProperty('polygon')) { | ||
throw new Error('Region must include a name and a polygon property'); | ||
} | ||
const pp = new PP(region.polygon); | ||
const bitset = pp.getBitset(this._width, this._height); | ||
const difference = PamDiff._validateNumber(parseInt(region.difference), this._difference, 1, 255); | ||
const percent = PamDiff._validateNumber(parseInt(region.percent), this._percent, 1, 100); | ||
minDiff = Math.min(minDiff, difference); | ||
regions.push( | ||
{ | ||
name: region.name, | ||
diff: difference, | ||
percent: percent, | ||
count: bitset.count, | ||
bitset: bitset.buffer | ||
} | ||
); | ||
} | ||
this._regionObj = {minDiff: minDiff, length: regions.length, regions: regions}; | ||
} | ||
} | ||
@@ -236,36 +304,55 @@ | ||
* | ||
* @param chunk | ||
* @private | ||
*/ | ||
_grayPixelDiff(chunk) { | ||
this._newPix = chunk.pixels; | ||
let results = []; | ||
if (this._regions) { | ||
results = PC.compareGrayRegions(this._minDiff, this._regionsLen, this._regionsArr, this._bufLen, this._oldPix, this._newPix); | ||
_configurePixelDiffEngine() { | ||
if (!this._tupltype || !this._width || ! this._height) { | ||
return; | ||
} | ||
const wxh = this._width * this._height; | ||
switch (this._tupltype) { | ||
case 'grayscale': | ||
if (this._regionObj) { | ||
this._pixelDiffEngine = PC.compareGrayRegions.bind(this, this._regionObj.minDiff, this._regionObj.length, this._regionObj.regions, wxh); | ||
} else if (this._maskObj) { | ||
this._pixelDiffEngine = PC.compareGrayMask.bind(this, this._difference, this._percent, this._maskObj.count, this._maskObj.bitset, wxh); | ||
} else { | ||
this._pixelDiffEngine = PC.compareGrayPixels.bind(this, this._difference, this._percent, wxh, wxh); | ||
} | ||
break; | ||
case 'rgb': | ||
if (this._regionObj) { | ||
this._pixelDiffEngine = PC.compareRgbRegions.bind(this, this._regionObj.minDiff, this._regionObj.length, this._regionObj.regions, wxh * 3); | ||
} else if (this._maskObj) { | ||
this._pixelDiffEngine = PC.compareRgbMask.bind(this, this._difference, this._percent, this._maskObj.count, this._maskObj.bitset, wxh * 3); | ||
} else { | ||
this._pixelDiffEngine = PC.compareRgbPixels.bind(this, this._difference, this._percent, wxh, wxh * 3); | ||
} | ||
break; | ||
case 'rgb_alpha': | ||
if (this._regionObj) { | ||
this._pixelDiffEngine = PC.compareRgbaRegions.bind(this, this._regionObj.minDiff, this._regionObj.length, this._regionObj.regions, wxh * 4); | ||
} else if (this._maskObj) { | ||
this._pixelDiffEngine = PC.compareRgbaMask.bind(this, this._difference, this._percent, this._maskObj.count, this._maskObj.bitset, wxh * 4); | ||
} else { | ||
this._pixelDiffEngine = PC.compareRgbaPixels.bind(this, this._difference, this._percent, wxh, wxh * 4); | ||
} | ||
break; | ||
default: | ||
throw new Error('Did not find a matching tupltype'); | ||
} | ||
if (process.env.NODE_ENV === 'development') { | ||
this._parseChunk = this._parsePixelsDebug; | ||
} else { | ||
results = PC.compareGrayPixels(this._difference, this._percent, this._wxh, this._bufLen, this._oldPix, this._newPix); | ||
this._parseChunk = this._parsePixels; | ||
} | ||
if (results.length) { | ||
const data = {trigger: results, pam:chunk.pam}; | ||
if (this._callback) { | ||
this._callback(data); | ||
} | ||
if (this._readableState.pipesCount > 0) { | ||
this.push(data); | ||
} | ||
if (this.listenerCount('diff') > 0) { | ||
this.emit('diff', data); | ||
} | ||
} | ||
this._oldPix = this._newPix; | ||
} | ||
_rgbPixelDiff(chunk) { | ||
/** | ||
* | ||
* @param chunk | ||
* @private | ||
*/ | ||
_parsePixels(chunk) { | ||
this._newPix = chunk.pixels; | ||
let results = []; | ||
if (this._regions) { | ||
results = PC.compareRgbRegions(this._minDiff, this._regionsLen, this._regionsArr, this._bufLen, this._oldPix, this._newPix); | ||
} else { | ||
results = PC.compareRgbPixels(this._difference, this._percent, this._wxh, this._bufLen, this._oldPix, this._newPix); | ||
} | ||
const results = this._pixelDiffEngine(this._oldPix, this._newPix); | ||
if (results.length) { | ||
@@ -291,10 +378,6 @@ const data = {trigger: results, pam:chunk.pam}; | ||
*/ | ||
_rgbaPixelDiff(chunk) { | ||
_parsePixelsDebug(chunk) { | ||
console.time(this._pixelDiffEngine.name); | ||
this._newPix = chunk.pixels; | ||
let results; | ||
if (this._regions) { | ||
results = PC.compareRgbaRegions(this._minDiff, this._regionsLen, this._regionsArr, this._bufLen, this._oldPix, this._newPix); | ||
} else { | ||
results = PC.compareRgbaPixels(this._difference, this._percent, this._wxh, this._bufLen, this._oldPix, this._newPix); | ||
} | ||
const results = this._pixelDiffEngine(this._oldPix, this._newPix); | ||
if (results.length) { | ||
@@ -313,31 +396,5 @@ const data = {trigger: results, pam:chunk.pam}; | ||
this._oldPix = this._newPix; | ||
console.timeEnd(this._pixelDiffEngine.name); | ||
} | ||
_processRegions() { | ||
if (this._regions && this._width && this._height) { | ||
this._regionsArr = []; | ||
this._minDiff = 255; | ||
for (const region of this._regions) { | ||
if (!region.hasOwnProperty('name') || !region.hasOwnProperty('polygon')) { | ||
throw new Error('Region must include a name and a polygon property'); | ||
} | ||
const polygonPoints = new PP(region.polygon); | ||
const bitset = polygonPoints.getBitset(this._width, this._height); | ||
const difference = PamDiff._validateNumber(parseInt(region.difference), this._difference, 1, 255); | ||
const percent = PamDiff._validateNumber(parseInt(region.percent), this._percent, 1, 100); | ||
this._minDiff = Math.min(this._minDiff, difference); | ||
this._regionsArr.push( | ||
{ | ||
name: region.name, | ||
diff: difference, | ||
percent: percent, | ||
count: bitset.count, | ||
bitset: bitset.buffer | ||
} | ||
); | ||
} | ||
this._regionsLen = this._regions.length; | ||
} | ||
} | ||
/** | ||
@@ -352,36 +409,5 @@ * | ||
this._oldPix = chunk.pixels; | ||
this._wxh = this._width * this._height; | ||
this._tupltype = chunk.tupltype; | ||
this._processRegions(); | ||
switch (chunk.tupltype) { | ||
case 'blackandwhite' : | ||
this._bufLen = this._wxh; | ||
if (this._bufLen !== this._oldPix.length) { | ||
throw new Error("Pixel count does not match width * height"); | ||
} | ||
this._parseChunk = this._bwPixelDiff; | ||
break; | ||
case 'grayscale' : | ||
this._bufLen = this._wxh; | ||
if (this._bufLen !== this._oldPix.length) { | ||
throw new Error("Pixel count does not match width * height"); | ||
} | ||
this._parseChunk = this._grayPixelDiff; | ||
break; | ||
case 'rgb' : | ||
this._bufLen = this._wxh * 3; | ||
if (this._bufLen !== this._oldPix.length) { | ||
throw new Error("Pixel count does not match width * height * 3"); | ||
} | ||
this._parseChunk = this._rgbPixelDiff; | ||
break; | ||
case 'rgb_alpha' : | ||
this._bufLen = this._wxh * 4; | ||
if (this._bufLen !== this._oldPix.length) { | ||
throw new Error("Pixel count does not match width * height * 4"); | ||
} | ||
this._parseChunk = this._rgbaPixelDiff; | ||
break; | ||
default : | ||
throw Error(`Unsupported tupltype: ${chunk.tupltype}. Supported tupltypes include grayscale(gray), blackandwhite(monob), rgb(rgb24), and rgb_alpha(rgba).`); | ||
} | ||
this._configurePixelDiffEngine(); | ||
} | ||
@@ -388,0 +414,0 @@ |
@@ -9,3 +9,3 @@ 'use strict'; | ||
const perc = Math.floor(100 * diffs / wxh); | ||
if (perc >= percent) {return [{name: "percent", percent: perc}];} | ||
if (perc >= percent) {return [{name: "all", percent: perc}];} | ||
return []; | ||
@@ -18,3 +18,3 @@ } | ||
const perc = Math.floor(100 * diffs / wxh); | ||
if (perc >= percent) {return [{name: "percent", percent: perc}];} | ||
if (perc >= percent) {return [{name: "all", percent: perc}];} | ||
return []; | ||
@@ -27,3 +27,3 @@ } | ||
const perc = Math.floor(100 * diffs / wxh); | ||
if (perc >= percent) {return [{name: "percent", percent: perc}];} | ||
if (perc >= percent) {return [{name: "all", percent: perc}];} | ||
return []; | ||
@@ -91,2 +91,27 @@ } | ||
} | ||
static compareGrayMask(diff, percent, count, bitset, bufLen, buf0, buf1) { | ||
let diffs = 0; | ||
for (let i = 0; i < bufLen; i++) {if(bitset[i] && Math.abs(buf0[i] - buf1[i]) >= diff) {diffs++;}} | ||
const perc = Math.floor(100 * diffs / count); | ||
if (perc >= percent) {return [{name: "mask", percent: perc}];} | ||
return []; | ||
} | ||
static compareRgbMask(diff, percent, count, bitset, bufLen, buf0, buf1) { | ||
let diffs = 0; | ||
for (let i = 0, p = 0; i < bufLen; i+=3, p++) {if(bitset[p] && Math.abs(buf0[i] + buf0[i+1] + buf0[i+2] - buf1[i] - buf1[i+1] - buf1[i+2])/3 >= diff) {diffs++;}} | ||
const perc = Math.floor(100 * diffs / count); | ||
if (perc >= percent) {return [{name: "mask", percent: perc}];} | ||
return []; | ||
} | ||
static compareRgbaMask(diff, percent, count, bitset, bufLen, buf0, buf1) { | ||
let diffs = 0; | ||
for (let i = 0, p = 0; i < bufLen; i+=4, p++) {if(bitset[p] && Math.abs(buf0[i] + buf0[i+1] + buf0[i+2] - buf1[i] - buf1[i+1] - buf1[i+2])/3 >= diff) {diffs++;}} | ||
const perc = Math.floor(100 * diffs / count); | ||
if (perc >= percent) {return [{name: "mask", percent: perc}];} | ||
return []; | ||
} | ||
} | ||
@@ -93,0 +118,0 @@ |
{ | ||
"name": "pam-diff", | ||
"version": "0.11.0", | ||
"version": "0.12.0", | ||
"description": "Measure differences between pixel arrays extracted from pam images", | ||
@@ -14,5 +14,5 @@ "main": "index.js", | ||
"doc": "jsdoc index.js -d docs && git commit -m 'update docs' -- docs", | ||
"gray": "node tests/test_gray && node tests/test_gray2 && node tests/test_gray3", | ||
"rgb": "node tests/test_rgb && node tests/test_rgb2 && node tests/test_rgb3", | ||
"rgba": "node tests/test_rgba && node tests/test_rgba2 && node tests/test_rgba3" | ||
"gray": "node tests/test_gray && node tests/test_gray2 && node tests/test_gray3 && node tests/test_gray4", | ||
"rgb": "node tests/test_rgb && node tests/test_rgb2 && node tests/test_rgb3 && node tests/test_rgb4", | ||
"rgba": "node tests/test_rgba && node tests/test_rgba2 && node tests/test_rgba3 && node tests/test_rgba4" | ||
}, | ||
@@ -19,0 +19,0 @@ "repository": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
24957
501
1