New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

pam-diff

Package Overview
Dependencies
Maintainers
1
Versions
53
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

pam-diff - npm Package Compare versions

Comparing version 1.0.0 to 1.0.1

1071

index.js
'use strict';
const {Transform} = require('stream');
const { Transform } = require('stream');

@@ -10,588 +10,585 @@ const PP = require('polygon-points');

class PamDiff extends Transform {
/**
*
* @param [options] {Object}
* @param [options.debug=false] {Boolean} - If true, debug info will be logged to console
* @param [options.sync=false] {Boolean} - If true, pixel change detection will block the event loop instead of using a worker
* @param [options.difference=5] {Number} - Pixel difference value 1 to 255
* @param [options.percent=5] {Number} - Percent of pixels or blobs that exceed difference value
* @param [options.response=percent] {String} - Accepted values: percent or bounds or blobs
* @param [options.draw=false] {Boolean} - If true and response is 'bounds' or 'blobs', return a pixel buffer with drawn bounding box
* @param [options.regions] {Array} - Array of region objects
* @param options.regions[i].name {String} - Name of region
* @param [options.regions[i].difference=options.difference] {Number} - Difference value for region
* @param [options.regions[i].percent=options.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 [options.mask=false] {Boolean} - Indicate if regions should be used as masks of pixels to ignore
* @param [callback] {Function} - Function to be called when diff event occurs
*/
constructor(options, callback) {
super(options);
Transform.call(this, {objectMode: true});
this.debug = PamDiff._parseOptions('debug', options);// output debug info to console. defaults to false
this.response = PamDiff._parseOptions('response', options);//percent, bounds, blobs
this.draw = PamDiff._parseOptions('draw', options);// return pixels with bounding box if response is bounds or blobs
this.sync = PamDiff._parseOptions('sync', options);// should be processed before regions
this.difference = PamDiff._parseOptions('difference', options);// global value, can be overridden per region
this.percent = PamDiff._parseOptions('percent', options);// global value, can be overridden per region
this.mask = PamDiff._parseOptions('mask', options);// should be processed before regions
this.regions = PamDiff._parseOptions('regions', options);// can be zero regions, a single region, or multiple regions. if no regions, all pixels will be compared.
this.callback = callback;// callback function to be called when pixel difference is detected
this._parseChunk = this._parseFirstChunk;// first parsing will be reading settings and configuring internal pixel reading
}
/**
*
* @param [options] {Object}
* @param [options.debug=false] {Boolean} - If true, debug info will be logged to console
* @param [options.sync=false] {Boolean} - If true, pixel change detection will block the event loop instead of using a worker
* @param [options.difference=5] {Number} - Pixel difference value 1 to 255
* @param [options.percent=5] {Number} - Percent of pixels or blobs that exceed difference value
* @param [options.response=percent] {String} - Accepted values: percent or bounds or blobs
* @param [options.draw=false] {Boolean} - If true and response is 'bounds' or 'blobs', return a pixel buffer with drawn bounding box
* @param [options.regions] {Array} - Array of region objects
* @param options.regions[i].name {String} - Name of region
* @param [options.regions[i].difference=options.difference] {Number} - Difference value for region
* @param [options.regions[i].percent=options.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 [options.mask=false] {Boolean} - Indicate if regions should be used as masks of pixels to ignore
* @param [callback] {Function} - Function to be called when diff event occurs
*/
constructor(options, callback) {
super(options);
Transform.call(this, { objectMode: true });
this.debug = PamDiff._parseOptions('debug', options); // output debug info to console. defaults to false
this.response = PamDiff._parseOptions('response', options); // percent, bounds, blobs
this.draw = PamDiff._parseOptions('draw', options); // return pixels with bounding box if response is bounds or blobs
this.sync = PamDiff._parseOptions('sync', options); // should be processed before regions
this.difference = PamDiff._parseOptions('difference', options); // global value, can be overridden per region
this.percent = PamDiff._parseOptions('percent', options); // global value, can be overridden per region
this.mask = PamDiff._parseOptions('mask', options); // should be processed before regions
this.regions = PamDiff._parseOptions('regions', options); // can be zero regions, a single region, or multiple regions. if no regions, all pixels will be compared.
this.callback = callback; // callback function to be called when pixel difference is detected
this._parseChunk = this._parseFirstChunk; // first parsing will be reading settings and configuring internal pixel reading
}
/**
*
* @param option {String}
* @param options {Object}
* @return {*}
* @private
*/
static _parseOptions(option, options) {
if (options && options.hasOwnProperty(option)) {
return options[option];
}
return null;
/**
*
* @param option {String}
* @param options {Object}
* @return {*}
* @private
*/
static _parseOptions(option, options) {
if (options && options.hasOwnProperty(option)) {
return options[option];
}
return null;
}
/**
*
* @param number {Number}
* @param def {Number}
* @param low {Number}
* @param high {Number}
* @return {Number}
* @private
*/
static _validateNumber(number, def, low, high) {
if (isNaN(number)) {
return def;
} else if (number < low) {
return low;
} else if (number > high) {
return high;
} else {
return number;
}
/**
*
* @param number {Number}
* @param def {Number}
* @param low {Number}
* @param high {Number}
* @return {Number}
* @private
*/
static _validateNumber(number, def, low, high) {
if (isNaN(number)) {
return def;
} else if (number < low) {
return low;
} else if (number > high) {
return high;
} else {
return number;
}
}
/**
*
* @param bool
* @return {boolean}
* @private
*/
static _validateBoolean(bool) {
return (bool === true || bool === 'true' || bool === 1 || bool === '1');
}
/**
*
* @param bool
* @return {boolean}
* @private
*/
static _validateBoolean(bool) {
return bool === true || bool === 'true' || bool === 1 || bool === '1';
}
/**
*
* @param string {String}
* @param strings {Array}
* @return {String}
* @private
*/
static _validateString(string, strings) {
if (strings.includes(string)) {
return string;
}
return strings[0];
/**
*
* @param string {String}
* @param strings {Array}
* @return {String}
* @private
*/
static _validateString(string, strings) {
if (strings.includes(string)) {
return string;
}
return strings[0];
}
/**
*
* @param bool {Boolean}
*/
set debug(bool) {
this._debug = PamDiff._validateBoolean(bool);
this._processRegions();
this._configurePixelDiffEngine();
}
/**
*
* @param bool {Boolean}
*/
set debug(bool) {
this._debug = PamDiff._validateBoolean(bool);
this._processRegions();
this._configurePixelDiffEngine();
}
/**
*
* @return {Boolean}
*/
get debug() {
return this._debug;
}
/**
*
* @return {Boolean}
*/
get debug() {
return this._debug;
}
/**
*
* @param bool {Boolean}
* @return {PamDiff}
*/
setDebug(bool) {
this.debug = bool;
return this;
}
/**
*
* @param bool {Boolean}
* @return {PamDiff}
*/
setDebug(bool) {
this.debug = bool;
return this;
}
/**
*
* @param string {String}
*/
set response(string) {
this._response = PamDiff._validateString(string, ['percent', 'bounds', 'blobs']);
this._processRegions();
this._configurePixelDiffEngine();
}
/**
*
* @param string {String}
*/
set response(string) {
this._response = PamDiff._validateString(string, ['percent', 'bounds', 'blobs']);
this._processRegions();
this._configurePixelDiffEngine();
}
/**
*
* @return {String}
*/
get response() {
return this._response;
}
/**
*
* @return {String}
*/
get response() {
return this._response;
}
/**
*
* @param string {String}
* @return {PamDiff}
*/
setResponse(string) {
this.response = string;
return this;
}
/**
*
* @param string {String}
* @return {PamDiff}
*/
setResponse(string) {
this.response = string;
return this;
}
/**
*
* @param bool {Boolean}
*/
set sync(bool) {
this._sync = PamDiff._validateBoolean(bool);
this._processRegions();
this._configurePixelDiffEngine();
}
/**
*
* @param bool {Boolean}
*/
set sync(bool) {
this._sync = PamDiff._validateBoolean(bool);
this._processRegions();
this._configurePixelDiffEngine();
}
/**
*
* @return {Boolean}
*/
get sync() {
return this._sync;
}
/**
*
* @return {Boolean}
*/
get sync() {
return this._sync;
}
/**
*
* @param bool {Boolean}
* @return {PamDiff}
*/
setSync(bool) {
this.sync = bool;
return this;
}
/**
*
* @param bool {Boolean}
* @return {PamDiff}
*/
setSync(bool) {
this.sync = bool;
return this;
}
/**
*
* @param bool {Boolean}
*/
set draw(bool) {
this._draw = PamDiff._validateBoolean(bool);
this._processRegions();
this._configurePixelDiffEngine();
}
/**
*
* @param bool {Boolean}
*/
set draw(bool) {
this._draw = PamDiff._validateBoolean(bool);
this._processRegions();
this._configurePixelDiffEngine();
}
/**
*
* @return {Boolean}
*/
get draw() {
return this._draw;
}
/**
*
* @return {Boolean}
*/
get draw() {
return this._draw;
}
/**
*
* @param bool {Boolean}
* @return {PamDiff}
*/
setDraw(bool) {
this.draw = bool;
return this;
}
/**
*
* @param bool {Boolean}
* @return {PamDiff}
*/
setDraw(bool) {
this.draw = bool;
return this;
}
/**
*
* @param number {Number}
*/
set difference(number) {
this._difference = PamDiff._validateNumber(parseInt(number), 5, 1, 255);
this._configurePixelDiffEngine();
}
/**
*
* @param number {Number}
*/
set difference(number) {
this._difference = PamDiff._validateNumber(parseInt(number), 5, 1, 255);
this._configurePixelDiffEngine();
}
/**
*
* @return {Number}
*/
get difference() {
return this._difference;
}
/**
*
* @return {Number}
*/
get difference() {
return this._difference;
}
/**
*
* @param number {Number}
* @return {PamDiff}
*/
setDifference(number) {
this.difference = number;
return this;
}
/**
*
* @param number {Number}
* @return {PamDiff}
*/
setDifference(number) {
this.difference = number;
return this;
}
/**
*
* @param number {Number}
*/
set percent(number) {
this._percent = PamDiff._validateNumber(parseInt(number), 5, 1, 100);
this._configurePixelDiffEngine();
}
/**
*
* @param number {Number}
*/
set percent(number) {
this._percent = PamDiff._validateNumber(parseInt(number), 5, 1, 100);
this._configurePixelDiffEngine();
}
/**
*
* @return {Number}
*/
get percent() {
return this._percent;
}
/**
*
* @return {Number}
*/
get percent() {
return this._percent;
}
/**
*
* @param number {Number}
* @return {PamDiff}
*/
setPercent(number) {
this.percent = number;
return this;
}
/**
*
* @param number {Number}
* @return {PamDiff}
*/
setPercent(number) {
this.percent = number;
return this;
}
/**
*
* @param array {Array}
*/
set regions(array) {
if (!array) {
delete this._regions;
delete this._regionObj;
delete this._maskObj;
} else if (!Array.isArray(array) || array.length < 1) {
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]]}`);
} else {
this._regions = array;
this._processRegions();
}
this._configurePixelDiffEngine();
/**
*
* @param array {Array}
*/
set regions(array) {
if (!array) {
delete this._regions;
delete this._regionObj;
delete this._maskObj;
} else if (!Array.isArray(array) || array.length < 1) {
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]] }`);
} else {
this._regions = array;
this._processRegions();
}
this._configurePixelDiffEngine();
}
/**
*
* @return {Array}
*/
get regions() {
return this._regions;
}
/**
*
* @return {Array}
*/
get regions() {
return this._regions;
}
/**
*
* @param array {Array}
* @return {PamDiff}
*/
setRegions(array) {
this.regions = array;
return this;
}
/**
*
* @param array {Array}
* @return {PamDiff}
*/
setRegions(array) {
this.regions = array;
return this;
}
set mask(bool) {
this._mask = PamDiff._validateBoolean(bool);
this._processRegions();
this._configurePixelDiffEngine();
}
set mask(bool) {
this._mask = PamDiff._validateBoolean(bool);
this._processRegions();
this._configurePixelDiffEngine();
}
get mask() {
return this._mask;
get mask() {
return this._mask;
}
setMask(bool) {
this.mask = bool;
return this;
}
/**
*
* @param func {Function}
*/
set callback(func) {
if (!func) {
delete this._callback;
} else if (typeof func === 'function' && func.length === 1) {
this._callback = func;
} else {
throw new Error('Callback must be a function that accepts 1 argument.');
}
}
setMask(bool) {
this.mask = bool;
return this;
/**
*
* @return {Function}
*/
get callback() {
return this._callback;
}
/**
*
* @param func {Function}
* @return {PamDiff}
*/
setCallback(func) {
this.callback = func;
return this;
}
/**
*
* @return {PamDiff}
*/
resetCache() {
delete this._engine;
delete this._oldPix;
delete this._newPix;
delete this._width;
delete this._height;
delete this._depth;
delete this._tupltype;
delete this._regionObj;
this._parseChunk = this._parseFirstChunk;
return this;
}
/**
*
* @private
*/
_processRegions() {
if (!this._regions || !this._width || !this._height) {
return;
}
/**
*
* @param func {Function}
*/
set callback(func) {
if (!func) {
delete this._callback;
} else if (typeof func === 'function' && func.length === 1) {
this._callback = func;
} else {
throw new Error('Callback must be a function that accepts 1 argument.');
const regions = [];
if (this._mask === true) {
// combine all regions to form a single region of flipped 0's and 1's
let minX = this._width;
let maxX = 0;
let minY = this._height;
let maxY = 0;
const wxh = this._width * this._height;
const maskBitset = 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);
if (bitset.count === 0) {
throw new Error('Bitset count must be greater than 0.');
}
const bitsetBuffer = bitset.buffer;
for (let i = 0; i < wxh; ++i) {
if (bitsetBuffer[i] === 1) {
maskBitset[i] = 0;
}
}
}
let maskBitsetCount = 0;
for (let i = 0; i < wxh; ++i) {
if (maskBitset[i] === 1) {
const y = Math.floor(i / this._width);
const x = i % this._width;
minX = Math.min(minX, x);
maxX = Math.max(maxX, x);
minY = Math.min(minY, y);
maxY = Math.max(maxY, y);
maskBitsetCount++;
}
}
if (maskBitsetCount === 0) {
throw new Error('Bitset count must be greater than 0');
}
regions.push({
name: 'mask',
bitset: maskBitset,
bitsetCount: maskBitsetCount,
difference: this._difference,
percent: this._percent,
minX: minX,
maxX: maxX,
minY: minY,
maxY: maxY,
});
} else {
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);
if (bitset.count === 0) {
throw new Error('Bitset count must be greater than 0');
}
const difference = PamDiff._validateNumber(parseInt(region.difference), this._difference, 1, 255);
const percent = PamDiff._validateNumber(parseInt(region.percent), this._percent, 1, 100);
regions.push({
name: region.name,
bitset: bitset.buffer,
bitsetCount: bitset.count,
difference: difference,
percent: percent,
minX: bitset.minX,
maxX: bitset.maxX,
minY: bitset.minY,
maxY: bitset.maxY,
});
}
}
this._regionObj = { length: regions.length, regions: regions };
}
/**
*
* @return {Function}
*/
get callback() {
return this._callback;
/**
*
* @private
*/
_configurePixelDiffEngine() {
if (!this._tupltype || !this._width || !this._height) {
return;
}
/**
*
* @param func {Function}
* @return {PamDiff}
*/
setCallback(func) {
this.callback = func;
return this;
let engine = this._tupltype;
engine += `_${this._width}_x_${this._height}`;
const config = { width: this._width, height: this._height, depth: this._depth, response: this._response, sync: this._sync };
if (this._regionObj) {
engine += '_region';
// config.target = 'region';
config.regions = this._regionObj.regions;
if (this._regionObj.length > 1) {
engine += 's';
}
} else {
engine += '_all';
// config.target = 'all';
config.difference = this._difference;
config.percent = this._percent;
}
/**
*
* @return {PamDiff}
*/
resetCache() {
delete this._engine;
delete this._oldPix;
delete this._newPix;
delete this._width;
delete this._height;
delete this._depth;
delete this._tupltype;
delete this._regionObj;
this._parseChunk = this._parseFirstChunk;
return this;
engine += `_${this._response}`;
if ((this._response === 'bounds' || this._response === 'blobs') && this._draw) {
config.draw = this._draw;
engine += '_draw';
}
engine += this._sync ? '_sync' : '_async';
const pixelChange = PC(config);
this._engine = this._sync ? pixelChange.compareSync.bind(pixelChange) : pixelChange.compare.bind(pixelChange);
if (this._debug) {
console.dir(this, { showHidden: false, depth: 0, colors: true });
this._parseChunk = this._parsePixelsDebug;
this._debugEngine = engine;
this._debugCount = 0;
} else {
this._parseChunk = this._parsePixels;
}
}
/**
*
* @private
*/
_processRegions() {
if (!this._regions || !this._width || !this._height) {
return;
}
const regions = [];
if (this._mask === true) {// combine all regions to form a single region of flipped 0's and 1's
let minX = this._width;
let maxX = 0;
let minY = this._height;
let maxY = 0;
const wxh = this._width * this._height;
const maskBitset = 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);
if (bitset.count === 0) {
throw new Error('Bitset count must be greater than 0.');
}
const bitsetBuffer = bitset.buffer;
for (let i = 0; i < wxh; ++i) {
if (bitsetBuffer[i] === 1) {
maskBitset[i] = 0;
}
}
}
let maskBitsetCount = 0;
for (let i = 0; i < wxh; ++i) {
if (maskBitset[i] === 1) {
const y = Math.floor(i / this._width);
const x = i % this._width;
minX = Math.min(minX, x);
maxX = Math.max(maxX, x);
minY = Math.min(minY, y);
maxY = Math.max(maxY, y);
maskBitsetCount++;
}
}
if (maskBitsetCount === 0) {
throw new Error('Bitset count must be greater than 0');
}
regions.push(
{
name: 'mask',
bitset: maskBitset,
bitsetCount: maskBitsetCount,
difference: this._difference,
percent: this._percent,
minX: minX,
maxX: maxX,
minY: minY,
maxY: maxY
}
);
/**
*
* @param chunk
* @private
*/
_parsePixels(chunk) {
this._newPix = chunk.pixels;
this._engine(this._oldPix, this._newPix, (err, results, pixels) => {
if (results.length) {
const data = { trigger: results, pam: chunk.pam, headers: chunk.headers };
if (pixels) {
data.pixels = pixels;
} else {
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);
if (bitset.count === 0) {
throw new Error('Bitset count must be greater than 0');
}
const difference = PamDiff._validateNumber(parseInt(region.difference), this._difference, 1, 255);
const percent = PamDiff._validateNumber(parseInt(region.percent), this._percent, 1, 100);
regions.push(
{
name: region.name,
bitset: bitset.buffer,
bitsetCount: bitset.count,
difference: difference,
percent: percent,
minX: bitset.minX,
maxX: bitset.maxX,
minY: bitset.minY,
maxY: bitset.maxY
}
);
}
data.pixels = chunk.pixels;
}
this._regionObj = {length: regions.length, regions: regions};
}
/**
*
* @private
*/
_configurePixelDiffEngine() {
if (!this._tupltype || !this._width || !this._height) {
return;
if (this._callback) {
this._callback(data);
}
let engine = this._tupltype;
engine += `_${this._width}_x_${this._height}`;
const config = {width: this._width, height: this._height, depth: this._depth, response: this._response, sync: this._sync};
if (this._regionObj) {
engine += '_region';
//config.target = 'region';
config.regions = this._regionObj.regions;
if (this._regionObj.length > 1) {
engine += 's';
}
} else {
engine += '_all';
//config.target = 'all';
config.difference = this._difference;
config.percent = this._percent;
if (this._readableState.pipesCount > 0) {
this.push(data);
}
engine += `_${this._response}`;
if ((this._response === 'bounds' || this._response === 'blobs') && this._draw) {
config.draw = this._draw;
engine += '_draw';
if (this.listenerCount('diff') > 0) {
this.emit('diff', data);
}
engine += this._sync ? '_sync' : '_async';
const pixelChange = PC(config);
this._engine = this._sync ? pixelChange.compareSync.bind(pixelChange) : pixelChange.compare.bind(pixelChange);
if (this._debug) {
console.dir(this, {showHidden: false, depth: 0, colors: true});
this._parseChunk = this._parsePixelsDebug;
this._debugEngine = engine;
this._debugCount = 0;
}
});
this._oldPix = this._newPix;
}
/**
*
* @param chunk
* @private
*/
_parsePixelsDebug(chunk) {
const debugCount = this._debugCount++;
console.time(`${this._debugEngine}-${debugCount}`);
this._newPix = chunk.pixels;
this._engine(this._oldPix, this._newPix, (err, results, pixels) => {
console.timeEnd(`${this._debugEngine}-${debugCount}`);
if (results.length) {
const data = { trigger: results, pam: chunk.pam, headers: chunk.headers };
if (pixels) {
data.pixels = pixels;
} else {
this._parseChunk = this._parsePixels;
data.pixels = chunk.pixels;
}
}
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;
}
/**
*
* @param chunk
* @private
*/
_parsePixels(chunk) {
this._newPix = chunk.pixels;
this._engine(this._oldPix, this._newPix, (err, results, pixels) => {
if (results.length) {
const data = {trigger: results, pam: chunk.pam, headers: chunk.headers};
if (pixels) {
data.pixels = pixels;
} else {
data.pixels = chunk.pixels;
}
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;
}
/**
*
* @param chunk
* @private
*/
_parseFirstChunk(chunk) {
this._width = parseInt(chunk.width);
this._height = parseInt(chunk.height);
this._depth = parseInt(chunk.depth);
this._oldPix = chunk.pixels;
this._tupltype = chunk.tupltype;
this._processRegions();
this._configurePixelDiffEngine();
}
/**
*
* @param chunk
* @private
*/
_parsePixelsDebug(chunk) {
const debugCount = this._debugCount++;
console.time(`${this._debugEngine}-${debugCount}`);
this._newPix = chunk.pixels;
this._engine(this._oldPix, this._newPix, (err, results, pixels) => {
console.timeEnd(`${this._debugEngine}-${debugCount}`);
if (results.length) {
const data = {trigger: results, pam: chunk.pam, headers: chunk.headers};
if (pixels) {
data.pixels = pixels;
} else {
data.pixels = chunk.pixels;
}
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;
}
/**
*
* @param chunk
* @param encoding
* @param callback
* @private
*/
_transform(chunk, encoding, callback) {
this._parseChunk(chunk);
callback();
}
/**
*
* @param chunk
* @private
*/
_parseFirstChunk(chunk) {
this._width = parseInt(chunk.width);
this._height = parseInt(chunk.height);
this._depth = parseInt(chunk.depth);
this._oldPix = chunk.pixels;
this._tupltype = chunk.tupltype;
this._processRegions();
this._configurePixelDiffEngine();
}
/**
*
* @param chunk
* @param encoding
* @param callback
* @private
*/
_transform(chunk, encoding, callback) {
this._parseChunk(chunk);
callback();
}
/**
*
* @param callback
* @private
*/
_flush(callback) {
this.resetCache();
callback();
}
/**
*
* @param callback
* @private
*/
_flush(callback) {
this.resetCache();
callback();
}
}

@@ -603,2 +600,2 @@

*/
module.exports = PamDiff;
module.exports = PamDiff;
{
"name": "pam-diff",
"version": "1.0.0",
"version": "1.0.1",
"description": "Measure differences between pixel arrays extracted from pam images",
"main": "index.js",
"scripts": {
"pack": "npm --verbose pack",
"preversion": "npm install && npm test",
"postversion": "npm run doc",
"doc": "./node_modules/.bin/jsdoc index.js -d docs && git commit -m \"update docs\" -- docs",
"lint": "./node_modules/.bin/eslint --fix --ext js,md .",
"examples": "node examples/example && node examples/example2 && node examples/example3 && node examples/example4 && node examples/example5",
"doc": "jsdoc index.js -d docs && git commit -m \"update docs\" -- docs",
"test": "npm run gray && npm run rgb && npm run rgba",

@@ -101,5 +103,5 @@ "gray": "npm run gray:all && npm run gray:mask && npm run gray:region && npm run gray:regions",

"pam:async": "npm run pam:gray:async && npm run pam:rgb:async && npm run pam:rgba:async",
"pam:gray:async": "node --expose-gc examples/createJpegs --pixfmt gray --sync false --response bounds --draw false --target regions",
"pam:rgb:async": "node --expose-gc examples/createJpegs --pixfmt rgb24 --sync false --response bounds --draw false --target regions",
"pam:rgba:async": "node --expose-gc examples/createJpegs --pixfmt rgba --sync false --response bounds --draw false --target regions",
"pam:gray:async": "node --expose-gc examples/createPam --pixfmt gray --sync false --response bounds --draw false --target regions",
"pam:rgb:async": "node --expose-gc examples/createPam --pixfmt rgb24 --sync false --response bounds --draw false --target regions",
"pam:rgba:async": "node --expose-gc examples/createPam --pixfmt rgba --sync false --response bounds --draw false --target regions",
"pam:sync": "npm run pam:gray:sync && npm run pam:rgb:sync && npm run pam:rgba:sync",

@@ -142,11 +144,16 @@ "pam:gray:sync": "node --expose-gc examples/createJpegs --pixfmt gray --sync true --response bounds --draw false --target regions",

"dependencies": {
"pixel-change": "^1.0.0",
"pixel-change": "1.0.0",
"polygon-points": "^0.6.0"
},
"devDependencies": {
"@ffmpeg-installer/ffmpeg": "^1.1.0",
"dotenv": "^7.0.0",
"ffmpeg-static": "^2.4.0",
"jsdoc": "github:jsdoc3/jsdoc",
"minimist": "^1.2.0",
"pipe2pam": "^0.6.2"
"eslint": "^7.32.0",
"eslint-config-prettier": "^7.1.0",
"eslint-plugin-markdown": "^2.2.1",
"eslint-plugin-prettier": "^3.4.1",
"jsdoc": "^3.6.7",
"minimist": "^1.2.5",
"pipe2pam": "^0.6.2",
"prettier": "^2.5.0"
},

@@ -153,0 +160,0 @@ "private": false,

# pam-diff
###### [![dependencies Status](https://david-dm.org/kevinGodell/pam-diff/master/status.svg)](https://david-dm.org/kevinGodell/pam-diff/master) [![Build Status](https://travis-ci.org/kevinGodell/pam-diff.svg?branch=master)](https://travis-ci.org/kevinGodell/pam-diff) [![Build status](https://ci.appveyor.com/api/projects/status/hu6qw285sm6vfwtd/branch/master?svg=true)](https://ci.appveyor.com/project/kevinGodell/pam-diff/branch/master) [![GitHub issues](https://img.shields.io/github/issues/kevinGodell/pam-diff.svg)](https://github.com/kevinGodell/pam-diff/issues) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/kevinGodell/pam-diff/master/LICENSE) [![npm](https://img.shields.io/npm/dt/pam-diff.svg?style=flat-square)](https://www.npmjs.com/package/pam-diff)
Measure differences between pixel arrays extracted from pam images. Works well with node module [pipe2pam](https://www.npmjs.com/package/pipe2pam) to extract pam images from an ffmpeg pipe. Supported ***tupltypes*** are ***rgb***, ***rgb_alpha***, and ***grayscale***. It is currently being used for a video motion detection project.
###### [![Build Status](https://github.com/kevinGodell/pam-diff/workflows/build/badge.svg)](https://github.com/kevinGodell/pam-diff/actions?query=workflow%3Abuild) [![Build Status](https://ci.appveyor.com/api/projects/status/hu6qw285sm6vfwtd/branch/master?svg=true)](https://ci.appveyor.com/project/kevinGodell/pam-diff/branch/master) [![GitHub Issues](https://img.shields.io/github/issues/kevinGodell/pam-diff.svg)](https://github.com/kevinGodell/pam-diff/issues) [![GitHub License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/kevinGodell/pam-diff/master/LICENSE) [![npm](https://img.shields.io/npm/dt/pam-diff.svg?style=flat-square)](https://www.npmjs.com/package/pam-diff)
Measure differences between pixel arrays extracted from pam images. Works well with node module [pipe2pam](https://www.npmjs.com/package/pipe2pam) to extract pam images from an ffmpeg pipe. Supported **_tupltypes_** are **_rgb_**, **_rgb_alpha_**, and **_grayscale_**. It is currently being used for a video motion detection project.
### Installation:
```
```
npm install pam-diff@latest --save
```
#### *Important Note:* The js-only version will no longer receive any updates. All future work will be dedicated to the n-api version because it is much more efficient.
#### *New Feature:* Async made default in 0.13.6. ~~Starting with version 0.13.0, the option to use worker threads can be enabled by passing `{async: true}` to the pam-diff constructor.~~
#### *New Feature:* Starting with version 0.13.2, the option to get x y bounding box coordinates can be set by passing `{response: "bounds"}` to the pam-diff constructor.
#### *New Feature:* Starting with version 0.13.5, the option to get the pixel buffer containing the drawn x y bounding box can be set by passing `{draw: true}` to the pam-diff constructor.
#### *New Feature:* Starting with version 0.13.6, the option to filter results by connected component labelling can be set by passing `{response: "blobs"}` to the pam-diff constructor.
#### *New Feature:* Starting with version 0.13.6, async behavior will now be default. If you need the pixel difference measurements to block the event loop, use `{sync: true}`.
#### *New Feature:* Starting with version 1.0.0, pre-built binaries will be used. If binaries are not available, installation will fall back to node-gyp.
### Usage Options:
##### When comparing 2 equally sized buffers of grayscale, rgb, or rgba pixels, there are several options:
1. all (default)
#### _Important Note:_ The js-only version will no longer receive any updates. All future work will be dedicated to the n-api version because it is much more efficient.
#### _New Feature:_ Async made default in 0.13.6. ~~Starting with version 0.13.0, the option to use worker threads can be enabled by passing `{async: true}` to the pam-diff constructor.~~
#### _New Feature:_ Starting with version 0.13.2, the option to get x y bounding box coordinates can be set by passing `{response: "bounds"}` to the pam-diff constructor.
#### _New Feature:_ Starting with version 0.13.5, the option to get the pixel buffer containing the drawn x y bounding box can be set by passing `{draw: true}` to the pam-diff constructor.
#### _New Feature:_ Starting with version 0.13.6, the option to filter results by connected component labelling can be set by passing `{response: "blobs"}` to the pam-diff constructor.
#### _New Feature:_ Starting with version 0.13.6, async behavior will now be default. If you need the pixel difference measurements to block the event loop, use `{sync: true}`.
#### _New Feature:_ Starting with version 1.0.0, pre-built binaries will be used. If binaries are not available, installation will fall back to node-gyp.
## Usage Options:
###### When comparing 2 equally sized buffers of grayscale, rgb, or rgba pixels, there are several options:
### all (default)
- All pixels will be targeted when checking for differences.
- To use this option, set the configuration object without creating any regions.
```javascript
const pamDiff = new PamDiff({difference: 5, percent: 5});
const pamDiff = new PamDiff({ difference: 5, percent: 5 });
```
2. regions
### regions
- Specific regions of pixels will be targeted when checking for differences and the rest will be ignored.
- To use this option, create a regions array and pass it to the constructor.
```javascript
const region1 = {name: 'region1', difference: 12, percent: 22, polygon: [{x: 0, y: 0}, {x: 0, y: 225}, {x: 100, y: 225}, {x: 100, y: 0}]};
const region2 = {name: 'region2', difference: 14, percent: 10, polygon: [{x: 100, y: 0}, {x: 100, y: 225}, {x: 200, y: 225}, {x: 200, y: 0}]};
const region1 = {
name: 'region1',
difference: 12,
percent: 22,
polygon: [
{ x: 0, y: 0 },
{ x: 0, y: 225 },
{ x: 100, y: 225 },
{ x: 100, y: 0 },
],
};
const region2 = {
name: 'region2',
difference: 14,
percent: 10,
polygon: [
{ x: 100, y: 0 },
{ x: 100, y: 225 },
{ x: 200, y: 225 },
{ x: 200, y: 0 },
],
};
const regions = [region1, region2];
const pamDiff = new PamDiff({regions : regions});
const pamDiff = new PamDiff({ regions: regions });
```
3. mask
### mask
- Specific regions of pixels will be ignored when checking for differences.
- To use this option, create a regions array and set the mask option to true.
- `difference` and `percent` of the individual region is ignored. Set a global value.
```javascript
const region1 = {name: 'region1', polygon: [{x: 0, y: 0}, {x: 0, y: 225}, {x: 100, y: 225}, {x: 100, y: 0}]};
const region2 = {name: 'region2', polygon: [{x: 100, y: 0}, {x: 100, y: 225}, {x: 200, y: 225}, {x: 200, y: 0}]};
const region1 = {
name: 'region1',
polygon: [
{ x: 0, y: 0 },
{ x: 0, y: 225 },
{ x: 100, y: 225 },
{ x: 100, y: 0 },
],
};
const region2 = {
name: 'region2',
polygon: [
{ x: 100, y: 0 },
{ x: 100, y: 225 },
{ x: 200, y: 225 },
{ x: 200, y: 0 },
],
};
const regions = [region1, region2];
const pamDiff = new PamDiff({difference: 12, percent: 10, mask: true, regions : regions});
const pamDiff = new PamDiff({ difference: 12, percent: 10, mask: true, regions: regions });
```
##### Getting results back from the pixel difference detection:
1. event
- A *diff* event will be emitted with a data object passed as the only argument.
- A _diff_ event will be emitted with a data object passed as the only argument.
```javascript
pamDiff.on('diff', data => {
console.log(data);
});
console.log(data);
});
```
2. callback
- A *callback* function will be called with a data object passed as the only argument.
- The callback can be passed as the 2nd argument to the constructor or it can be added later.
- A _callback_ function will be called with a data object passed as the only argument.
- The callback can be passed as the 2nd argument to the constructor or it can be added later.
```javascript
/* callback function */
/* callback function */
function myCallback(data) {
console.log(data);
console.log(data);
}
/* via the constructor */
const pamDiff = new pamDiff({difference: 10, percent: 20}, myCallback);
const pamDiff = new pamDiff({ difference: 10, percent: 20 }, myCallback);

@@ -70,8 +134,11 @@ /* via the setter */

```
##### Expected results:
1. When targeting all pixels:
```
{
trigger: [
{name: 'all', percent: 13}
{ name: 'all', percent: 13 }
],

@@ -83,8 +150,10 @@ pam: <Buffer>,

```
2. When targeting regions of pixels:
```
{
trigger: [
{name: 'region1', percent: 13},
{name: 'region2', percent: 22}
{ name: 'region1', percent: 13 },
{ name: 'region2', percent: 22 }
],

@@ -96,7 +165,9 @@ pam: <Buffer>,

```
3. When targeting all pixels ignored by mask:
```
{
trigger: [
{name: 'mask', percent: 13}
{ name: 'mask', percent: 13 }
],

@@ -108,7 +179,9 @@ pam: <Buffer>,

```
4. When targeting all pixels and setting {response: "bounds"}:
```
{
trigger: [
{name: 'all', percent: 13, minX: 42, maxX: 399, minY: 113, maxY: 198}
{ name: 'all', percent: 13, minX: 42, maxX: 399, minY: 113, maxY: 198 }
],

@@ -120,3 +193,5 @@ pam: <Buffer>,

```
5. When targeting all pixels and setting {response: "blobs"}:
```

@@ -157,5 +232,9 @@ {

```
### Other Resources:
View the [docs](https://kevingodell.github.io/pam-diff/PamDiff.html), [tests](https://github.com/kevinGodell/pam-diff/tree/master/tests), or [examples](https://github.com/kevinGodell/pam-diff/tree/master/examples) for more implementations.
### Future Plans:
- [x] Make pre-built binaries available when using node-gyp is not an option.

@@ -166,2 +245,2 @@ - [x] Include option to return coordinates for bounding box of changed pixels.

- [x] Include option to return pixel buffer containing bounding boxes around blobs.
- [x] Make async worker threads the default. Can be overridden with {sync: true}.
- [x] Make async worker threads the default. Can be overridden with {sync: true}.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc