@blazediff/matcher 1.2.4 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -165,6 +165,12 @@ Main function for snapshot comparison.
165
165
  <td>0</td>
166
166
  <td>Downsample factor for GMSD</td>
167
167
  </tr>
168
+ <tr>
169
+ <td><code>runInWorker</code></td>
170
+ <td>boolean</td>
171
+ <td>true</td>
172
+ <td>Run image I/O and comparison in a worker thread for better performance</td>
173
+ </tr>
168
174
  </table>
169
175
 
170
176
  ### ComparisonResult
package/dist/index.d.ts CHANGED
@@ -91,6 +91,12 @@ interface MatcherOptions {
91
91
  * @default 0
92
92
  */
93
93
  downsample?: 0 | 1;
94
+ /**
95
+ * Run image I/O and comparison in a worker thread
96
+ * Worker threads provide better performance by avoiding V8 heap contention
97
+ * @default true
98
+ */
99
+ runInWorker?: boolean;
94
100
  }
95
101
  /**
96
102
  * Result of a comparison operation
@@ -168,6 +174,10 @@ declare function isImageBuffer(input: ImageInput): input is {
168
174
  width: number;
169
175
  height: number;
170
176
  };
177
+ /**
178
+ * Type guard for ImageData
179
+ */
180
+ declare function isImageData(input: ImageInput): input is ImageData;
171
181
  /**
172
182
  * Load a PNG image from file path
173
183
  */
@@ -176,11 +186,15 @@ declare function loadPNG(filePath: string): Promise<ImageData>;
176
186
  * Save image data to a PNG file
177
187
  */
178
188
  declare function savePNG(filePath: string, data: Uint8Array | Uint8ClampedArray | Buffer, width: number, height: number): Promise<void>;
189
+ /**
190
+ * Save a raw PNG buffer directly to file (no decode/encode cycle)
191
+ */
192
+ declare function saveRawPNGBuffer(filePath: string, buffer: Buffer | Uint8Array): void;
179
193
  /**
180
194
  * Normalize image input to ImageData
181
195
  * - File path: loads the PNG
182
196
  * - Raw PNG buffer: decodes to get dimensions
183
- * - Buffer with dimensions: returns as-is with normalized Uint8Array
197
+ * - Buffer with dimensions: returns as-is (avoids unnecessary copy if already Uint8Array)
184
198
  */
185
199
  declare function normalizeImageInput(input: ImageInput): Promise<ImageData>;
186
200
  /**
@@ -213,4 +227,6 @@ declare function getOrCreateSnapshot(received: ImageInput, options: MatcherOptio
213
227
  */
214
228
  declare function compareImages(received: ImageInput, baseline: ImageInput, options: MatcherOptions): Promise<ComparisonResult>;
215
229
 
216
- export { type ComparisonMethod, type ComparisonResult, type FormatOptions, type ImageData, type ImageInput, type MatcherOptions, type TestContext, compareImages, fileExists, formatMessage as formatReport, getOrCreateSnapshot, isFilePath, isImageBuffer, isRawPngBuffer, loadPNG, normalizeImageInput, runComparison, savePNG, validateMethodSupportsInput };
230
+ declare function terminateWorker(): Promise<number>;
231
+
232
+ export { type ComparisonMethod, type ComparisonResult, type FormatOptions, type ImageData, type ImageInput, type MatcherOptions, type TestContext, compareImages, fileExists, formatMessage as formatReport, getOrCreateSnapshot, isFilePath, isImageBuffer, isImageData, isRawPngBuffer, loadPNG, normalizeImageInput, runComparison, savePNG, saveRawPNGBuffer, terminateWorker, validateMethodSupportsInput };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- 'use strict';var fs=require('fs'),path=require('path'),pngjsTransformer=require('@blazediff/pngjs-transformer'),bin=require('@blazediff/bin'),core=require('@blazediff/core'),gmsd=require('@blazediff/gmsd'),ssim=require('@blazediff/ssim'),hitchhikersSsim=require('@blazediff/ssim/hitchhikers-ssim'),msssim=require('@blazediff/ssim/msssim'),s=require('picocolors');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var s__default=/*#__PURE__*/_interopDefault(s);function c(e){return typeof e=="string"}function $(e){return (Buffer.isBuffer(e)||e instanceof Uint8Array)&&!("width"in e)}function j(e){return typeof e=="object"&&e!==null&&"data"in e&&"width"in e&&"height"in e}async function I(e){if(!fs.existsSync(e))throw new Error(`Image file not found: ${e}`);let t=await pngjsTransformer.pngjsTransformer.read(e);return {data:new Uint8Array(t.data),width:t.width,height:t.height}}async function l(e,t,o,a){let i=path.dirname(e);fs.existsSync(i)||fs.mkdirSync(i,{recursive:true}),await pngjsTransformer.pngjsTransformer.write({data:t,width:o,height:a},e);}async function w(e){if(c(e))return I(e);if($(e)){let t=Buffer.isBuffer(e)?e:Buffer.from(e),o=await pngjsTransformer.pngjsTransformer.read(t);return {data:new Uint8Array(o.data),width:o.width,height:o.height}}return {data:new Uint8Array(e.data),width:e.width,height:e.height}}function E(e){return fs.existsSync(e)}function z(e){fs.existsSync(e)||fs.mkdirSync(e,{recursive:true});}async function U(e,t,o,a){if(!c(e))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");if(!c(t))throw new Error("Method 'bin' only supports file paths for baseline, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");let i=await bin.compare(e,t,o,{threshold:a.threshold,antialiasing:a.antialiasing});if(i.match)return {diffCount:0,diffPercentage:0};if(i.reason==="layout-diff")return {diffCount:Number.MAX_SAFE_INTEGER,diffPercentage:100};if(i.reason==="pixel-diff")return {diffCount:i.diffCount,diffPercentage:i.diffPercentage};if(i.reason==="file-not-exists")throw new Error(`Image file not found: ${i.file}`);return {diffCount:0,diffPercentage:0}}function B(e,t,o,a){let{width:i,height:r}=e,f=i*r;if(e.width!==t.width||e.height!==t.height)return {diffCount:f,diffPercentage:100};let d=o?new Uint8Array(f*4):void 0,n=core.diff(e.data,t.data,d,i,r,{threshold:a.threshold??.1,includeAA:a.includeAA??false});return {diffCount:n,diffPercentage:n/f*100,diffOutput:d}}function k(e,t,o,a){let{width:i,height:r}=e,f=i*r;if(e.width!==t.width||e.height!==t.height)return {score:1};let d=o?new Uint8Array(f*4):void 0;return {score:gmsd.gmsd(e.data,t.data,d,i,r,{downsample:a.downsample}),diffOutput:d}}function F(e,t,o,a,i){let{width:r,height:f}=e,d=r*f;if(e.width!==t.width||e.height!==t.height)return {score:0};let n=a?new Uint8Array(d*4):void 0,u={windowSize:i.windowSize,k1:i.k1,k2:i.k2},p;switch(o){case "ssim":p=ssim.ssim(e.data,t.data,n,r,f,u);break;case "msssim":p=msssim.msssim(e.data,t.data,n,r,f,u);break;case "hitchhikers-ssim":p=hitchhikersSsim.hitchhikersSSIM(e.data,t.data,n,r,f,u);break;default:throw new Error(`Unknown SSIM method: ${o}`)}return {score:p,diffOutput:n}}function x(e){return e==="ssim"||e==="msssim"||e==="hitchhikers-ssim"}function G(e,t){if(e==="bin"&&!c(t))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.")}async function O(e,t,o,a,i){if(G(o,e),G(o,t),o==="bin"){let u=await U(e,t,i,a);return {diffCount:u.diffCount,diffPercentage:u.diffPercentage}}let r=await w(e),f=await w(t),d=i!==void 0;if(x(o)){let u=F(r,f,o,d,a);return {score:u.score,diffOutput:u.diffOutput}}if(o==="gmsd"){let u=k(r,f,d,a);return {score:u.score,diffOutput:u.diffOutput}}let n=B(r,f,d,a);return {diffCount:n.diffCount,diffPercentage:n.diffPercentage,diffOutput:n.diffOutput}}var S={success:s__default.default.isColorSupported?"\u2714":"\u221A",error:s__default.default.isColorSupported?"\u2716":"\xD7",info:s__default.default.isColorSupported?"\u2139":"i",arrow:s__default.default.isColorSupported?"\u2514\u2500":"'-"},J=new Set(["ssim","msssim","hitchhikers-ssim"]);function y(e){return e.snapshotCreated?K(e.baselinePath):e.pass?Q():V(e)}function K(e){return [`${s__default.default.green(S.success)} ${s__default.default.green("New snapshot created")}`,` ${s__default.default.dim(S.arrow)} ${s__default.default.dim(e)}`].join(`
2
- `)}function Q(){return `${s__default.default.green(S.success)} ${s__default.default.green("Image matches snapshot")}`}function V(e){let{method:t,baselinePath:o,receivedPath:a,diffPath:i,diffCount:r=0,diffPercentage:f,score:d=0,threshold:n,thresholdType:u,updateCommand:p}=e,h=[`${s__default.default.red(S.error)} ${s__default.default.red(s__default.default.bold("Image snapshot mismatch"))}`,""],m=12;if(h.push(` ${s__default.default.dim("Method".padEnd(m))}${t}`),h.push(` ${s__default.default.dim("Baseline".padEnd(m))}${s__default.default.dim(o)}`),h.push(` ${s__default.default.dim("Received".padEnd(m))}${s__default.default.dim(a)}`),h.push(` ${s__default.default.dim("Diff".padEnd(m))}${s__default.default.dim(i)}`),h.push(""),J.has(t)){let b=((1-d)*100).toFixed(2);h.push(` ${s__default.default.dim("SSIM Score".padEnd(m))}${s__default.default.yellow(d.toFixed(4))} ${s__default.default.dim("(1.0 = identical)")}`),h.push(` ${s__default.default.dim("Difference".padEnd(m))}${s__default.default.yellow(`${b}%`)}`);}else if(t==="gmsd")h.push(` ${s__default.default.dim("GMSD Score".padEnd(m))}${s__default.default.yellow(d.toFixed(4))} ${s__default.default.dim("(0.0 = identical)")}`);else {let b=f?.toFixed(2)??"0.00";h.push(` ${s__default.default.dim("Difference".padEnd(m))}${s__default.default.yellow(r.toLocaleString())} pixels ${s__default.default.dim(`(${b}%)`)}`);}let P=u==="percent"?"%":"pixels";return h.push(` ${s__default.default.dim("Threshold".padEnd(m))}${n} ${P}`),h.push(""),h.push(` ${s__default.default.cyan(S.info)} ${s__default.default.cyan(`Run with ${p??"--update"} to update the snapshot`)}`),h.join(`
3
- `)}function te(e){return e.testName.replace(/[^a-zA-Z0-9-_\s]/g,"").replace(/\s+/g,"-").toLowerCase()||"snapshot"}function re(e,t){let o=path.dirname(e.testPath),a=t.snapshotsDir??"__snapshots__",i=path.isAbsolute(a)?a:path.join(o,a),r=t.snapshotIdentifier??te(e);return {snapshotDir:i,baselinePath:path.join(i,`${r}.png`),receivedPath:path.join(i,`${r}.received.png`),diffPath:path.join(i,`${r}.diff.png`)}}function _(e,t,o){let a=o.failureThreshold??0,i=o.failureThresholdType??"pixel";if(x(e)){let r=t.score??0;return i==="percent"?(1-r)*100<=a:r>=1-a/100}if(e==="gmsd"){let r=t.score??0;return i==="percent"?r*100<=a:r<=a/100}return i==="percent"?(t.diffPercentage??0)<=a:(t.diffCount??0)<=a}async function ae(e,t,o){let a=re(o,t),{snapshotDir:i,baselinePath:r,receivedPath:f,diffPath:d}=a,n=$(e)?await w(e):e;z(i);let u=E(r),p=t.updateSnapshots===true?"all":t.updateSnapshots===false||t.updateSnapshots===void 0?"new":t.updateSnapshots;if(!u&&p!=="none"&&(p==="new"||p==="all")){if(c(n)){let C=await I(n);await l(r,C.data,C.width,C.height);}else await l(r,n.data,n.width,n.height);return {pass:true,message:y({pass:true,method:t.method,snapshotCreated:true,baselinePath:r,receivedPath:f,diffPath:d,diffCount:0,diffPercentage:0,score:0,threshold:t.failureThreshold??0,thresholdType:t.failureThresholdType??"pixel",updateCommand:t.updateCommand}),baselinePath:r,snapshotStatus:"added"}}let m=await O(n,r,t.method,t,d),P=_(t.method,m,t);if(P)return fs.existsSync(f)&&fs.unlinkSync(f),fs.existsSync(d)&&fs.unlinkSync(d),{pass:true,message:y({pass:P,method:t.method,snapshotCreated:false,baselinePath:r,receivedPath:f,diffPath:d,diffCount:m.diffCount,diffPercentage:m.diffPercentage,score:m.score,threshold:t.failureThreshold??0,thresholdType:t.failureThresholdType??"pixel",updateCommand:t.updateCommand}),diffCount:m.diffCount,diffPercentage:m.diffPercentage,score:m.score,baselinePath:r,snapshotStatus:"matched"};if(p==="all"){if(c(n)){let C=await I(n);await l(r,C.data,C.width,C.height);}else await l(r,n.data,n.width,n.height);return fs.existsSync(f)&&fs.unlinkSync(f),fs.existsSync(d)&&fs.unlinkSync(d),{pass:true,message:y({pass:true,method:t.method,snapshotCreated:true,baselinePath:r,receivedPath:f,diffPath:d,diffCount:0,diffPercentage:0,score:0,threshold:t.failureThreshold??0,thresholdType:t.failureThresholdType??"pixel",updateCommand:t.updateCommand}),baselinePath:r,snapshotStatus:"updated"}}if(c(n)){let g=await I(n);await l(f,g.data,g.width,g.height);}else await l(f,n.data,n.width,n.height);if(m.diffOutput){let g=await w(n);await l(d,m.diffOutput,g.width,g.height);}return {pass:false,message:y({pass:P,method:t.method,snapshotCreated:false,baselinePath:r,receivedPath:f,diffPath:d,diffCount:m.diffCount,diffPercentage:m.diffPercentage,score:m.score,threshold:t.failureThreshold??0,thresholdType:t.failureThresholdType??"pixel",updateCommand:t.updateCommand}),diffCount:m.diffCount,diffPercentage:m.diffPercentage,score:m.score,baselinePath:r,receivedPath:f,diffPath:d,snapshotStatus:"failed"}}async function ie(e,t,o){let a=await O(e,t,o.method,o),i=_(o.method,a,o);return {pass:i,message:i?"Images match.":`Images differ: ${a.diffCount??a.score} ${a.diffCount!==void 0?"pixels":"score"}`,diffCount:a.diffCount,diffPercentage:a.diffPercentage,score:a.score}}exports.compareImages=ie;exports.fileExists=E;exports.formatReport=y;exports.getOrCreateSnapshot=ae;exports.isFilePath=c;exports.isImageBuffer=j;exports.isRawPngBuffer=$;exports.loadPNG=I;exports.normalizeImageInput=w;exports.runComparison=O;exports.savePNG=l;exports.validateMethodSupportsInput=G;
1
+ 'use strict';var fs=require('fs'),path=require('path'),pngjsTransformer=require('@blazediff/pngjs-transformer'),bin=require('@blazediff/bin'),core=require('@blazediff/core'),gmsd=require('@blazediff/gmsd'),ssim=require('@blazediff/ssim'),hitchhikersSsim=require('@blazediff/ssim/hitchhikers-ssim'),msssim=require('@blazediff/ssim/msssim'),s=require('picocolors'),worker_threads=require('worker_threads');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var s__default=/*#__PURE__*/_interopDefault(s);var Ie=Object.defineProperty;var b=(e,t)=>()=>(e&&(t=e(e=0)),t);var ie=(e,t)=>{for(var r in t)Ie(e,r,{get:t[r],enumerable:true});};var u=b(()=>{});var me={};ie(me,{ensureDir:()=>ee,fileExists:()=>N,isFilePath:()=>I,isImageBuffer:()=>fe,isImageData:()=>C,isRawPngBuffer:()=>E,loadPNG:()=>B,normalizeImageInput:()=>S,savePNG:()=>P,saveRawPNGBuffer:()=>R});function I(e){return typeof e=="string"}function E(e){return (Buffer.isBuffer(e)||e instanceof Uint8Array)&&!("width"in e)}function fe(e){return typeof e=="object"&&e!==null&&"data"in e&&"width"in e&&"height"in e}function C(e){return typeof e=="object"&&e!==null&&"data"in e&&"width"in e&&"height"in e}async function B(e){if(!fs.existsSync(e))throw new Error(`Image file not found: ${e}`);let t=await pngjsTransformer.pngjsTransformer.read(e);return {data:new Uint8Array(t.data),width:t.width,height:t.height}}async function P(e,t,r,n){let o=path.dirname(e);fs.existsSync(o)||fs.mkdirSync(o,{recursive:true}),await pngjsTransformer.pngjsTransformer.write({data:t instanceof Uint8Array?t:new Uint8Array(t),width:r,height:n},e);}function R(e,t){let r=path.dirname(e);fs.existsSync(r)||fs.mkdirSync(r,{recursive:true}),fs.writeFileSync(e,t);}async function S(e){if(I(e))return B(e);if(E(e)){let t=Buffer.isBuffer(e)?e:Buffer.from(e),r=await pngjsTransformer.pngjsTransformer.read(t);return {data:new Uint8Array(r.data),width:r.width,height:r.height}}return e.data instanceof Uint8Array?e:{data:new Uint8Array(e.data),width:e.width,height:e.height}}function N(e){return fs.existsSync(e)}function ee(e){fs.existsSync(e)||fs.mkdirSync(e,{recursive:true});}var k=b(()=>{u();});async function z(e,t,r,n){if(!I(e))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");if(!I(t))throw new Error("Method 'bin' only supports file paths for baseline, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");let o=await bin.compare(e,t,r,{threshold:n.threshold,antialiasing:n.antialiasing});if(o.match)return {diffCount:0,diffPercentage:0};if(o.reason==="layout-diff")return {diffCount:Number.MAX_SAFE_INTEGER,diffPercentage:100};if(o.reason==="pixel-diff")return {diffCount:o.diffCount,diffPercentage:o.diffPercentage};if(o.reason==="file-not-exists")throw new Error(`Image file not found: ${o.file}`);return {diffCount:0,diffPercentage:0}}var te=b(()=>{u();k();});function F(e,t,r,n){let{width:o,height:a}=e,i=o*a;if(e.width!==t.width||e.height!==t.height)return {diffCount:i,diffPercentage:100};let f=r?new Uint8Array(i*4):void 0,c=core.diff(e.data,t.data,f,o,a,{threshold:n.threshold??.1,includeAA:n.includeAA??false});return {diffCount:c,diffPercentage:c/i*100,diffOutput:f}}var re=b(()=>{u();});function j(e,t,r,n){let{width:o,height:a}=e,i=o*a;if(e.width!==t.width||e.height!==t.height)return {score:1};let f=r?new Uint8Array(i*4):void 0;return {score:gmsd.gmsd(e.data,t.data,f,o,a,{downsample:n.downsample}),diffOutput:f}}var ae=b(()=>{u();});function v(e,t,r,n,o){let{width:a,height:i}=e,f=a*i;if(e.width!==t.width||e.height!==t.height)return {score:0};let c=n?new Uint8Array(f*4):void 0,m={windowSize:o.windowSize,k1:o.k1,k2:o.k2},x;switch(r){case "ssim":x=ssim.ssim(e.data,t.data,c,a,i,m);break;case "msssim":x=msssim.msssim(e.data,t.data,c,a,i,m);break;case "hitchhikers-ssim":x=hitchhikersSsim.hitchhikersSSIM(e.data,t.data,c,a,i,m);break;default:throw new Error(`Unknown SSIM method: ${r}`)}return {score:x,diffOutput:c}}function $(e){return e==="ssim"||e==="msssim"||e==="hitchhikers-ssim"}var _=b(()=>{u();});var ue={};ie(ue,{compareBin:()=>z,compareCore:()=>F,compareGmsd:()=>j,compareSsim:()=>v,isSsimMethod:()=>$,runComparison:()=>O,validateMethodSupportsInput:()=>q});function q(e,t){if(e==="bin"&&!I(t))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.")}async function O(e,t,r,n,o){if(q(r,e),q(r,t),r==="bin"){let m=await z(e,t,o,n);return {diffCount:m.diffCount,diffPercentage:m.diffPercentage}}let a=C(e)?e:await S(e),i=C(t)?t:await S(t),f=o!==void 0;if($(r)){let m=v(a,i,r,f,n);return {score:m.score,diffOutput:m.diffOutput}}if(r==="gmsd"){let m=j(a,i,f,n);return {score:m.score,diffOutput:m.diffOutput}}let c=F(a,i,f,n);return {diffCount:c.diffCount,diffPercentage:c.diffPercentage,diffOutput:c.diffOutput}}var L=b(()=>{u();k();te();re();ae();_();te();re();ae();_();});u();L();k();u();var W={success:s__default.default.isColorSupported?"\u2714":"\u221A",error:s__default.default.isColorSupported?"\u2716":"\xD7",info:s__default.default.isColorSupported?"\u2139":"i",arrow:s__default.default.isColorSupported?"\u2514\u2500":"'-"},De=new Set(["ssim","msssim","hitchhikers-ssim"]);function T(e){return e.snapshotCreated?Re(e.baselinePath):e.pass?ke():$e(e)}function Re(e){return [`${s__default.default.green(W.success)} ${s__default.default.green("New snapshot created")}`,` ${s__default.default.dim(W.arrow)} ${s__default.default.dim(e)}`].join(`
2
+ `)}function ke(){return `${s__default.default.green(W.success)} ${s__default.default.green("Image matches snapshot")}`}function $e(e){let{method:t,baselinePath:r,receivedPath:n,diffPath:o,diffCount:a=0,diffPercentage:i,score:f=0,threshold:c,thresholdType:m,updateCommand:x}=e,p=[`${s__default.default.red(W.error)} ${s__default.default.red(s__default.default.bold("Image snapshot mismatch"))}`,""],g=12;if(p.push(` ${s__default.default.dim("Method".padEnd(g))}${t}`),p.push(` ${s__default.default.dim("Baseline".padEnd(g))}${s__default.default.dim(r)}`),p.push(` ${s__default.default.dim("Received".padEnd(g))}${s__default.default.dim(n)}`),p.push(` ${s__default.default.dim("Diff".padEnd(g))}${s__default.default.dim(o)}`),p.push(""),De.has(t)){let U=((1-f)*100).toFixed(2);p.push(` ${s__default.default.dim("SSIM Score".padEnd(g))}${s__default.default.yellow(f.toFixed(4))} ${s__default.default.dim("(1.0 = identical)")}`),p.push(` ${s__default.default.dim("Difference".padEnd(g))}${s__default.default.yellow(`${U}%`)}`);}else if(t==="gmsd")p.push(` ${s__default.default.dim("GMSD Score".padEnd(g))}${s__default.default.yellow(f.toFixed(4))} ${s__default.default.dim("(0.0 = identical)")}`);else {let U=i?.toFixed(2)??"0.00";p.push(` ${s__default.default.dim("Difference".padEnd(g))}${s__default.default.yellow(a.toLocaleString())} pixels ${s__default.default.dim(`(${U}%)`)}`);}let J=m==="percent"?"%":"pixels";return p.push(` ${s__default.default.dim("Threshold".padEnd(g))}${c} ${J}`),p.push(""),p.push(` ${s__default.default.cyan(W.info)} ${s__default.default.cyan(`Run with ${x??"--update"} to update the snapshot`)}`),p.join(`
3
+ `)}u();L();_();k();u();var y=null,Te=0,ce=false,D=new Map;function Ue(){let e=path.join(__dirname,"worker.js");if(fs.existsSync(e))return e;let t=path.join(__dirname,"../dist/worker.js");return fs.existsSync(t)?t:null}function Ae(){if(ce)return null;if(!y){let e=Ue();if(!e)return ce=true,null;y=new worker_threads.Worker(e),y.on("message",t=>{let r=D.get(t.id);r&&(D.delete(t.id),t.success?r.resolve(t.result):r.reject(new Error(t.error)));}),y.on("error",t=>{for(let r of D.values())r.reject(t);D.clear(),y=null;}),y.on("exit",t=>{if(t!==0){let r=new Error(`Worker exited with code ${t}`);for(let n of D.values())n.reject(r);D.clear();}y=null;});}return y}async function ne(e,t){let r=Ae();if(!r){let{runComparison:n}=await Promise.resolve().then(()=>(L(),ue)),{loadPNG:o,normalizeImageInput:a}=await Promise.resolve().then(()=>(k(),me));switch(e){case "normalize":return a(t.input);case "loadPNG":return o(t.filePath);case "compare":{let i=t;return n(i.received,i.baseline,i.method,i.options,i.diffOutputPath)}default:throw new Error(`Unknown request type: ${e}`)}}return new Promise((n,o)=>{let a=++Te;D.set(a,{resolve:n,reject:o});let i={id:a,type:e,payload:t};r.postMessage(i);})}function oe(e){return e.data instanceof Uint8Array?e:{data:new Uint8Array(e.data),width:e.width,height:e.height}}async function he(e){let t=await ne("normalize",{input:e});return oe(t)}async function le(e){let t=await ne("loadPNG",{filePath:e});return oe(t)}function ge(e,t,r,n,o){let a=oe(e);return ne("compare",{received:a,baseline:t,method:r,options:n,diffOutputPath:o})}function Ee(){if(y){let e=y;return y=null,e.terminate()}return Promise.resolve(0)}function Ge(e){return e.testName.replace(/[^a-zA-Z0-9-_\s]/g,"").replace(/\s+/g,"-").toLowerCase()||"snapshot"}function Ne(e,t){let r=path.dirname(e.testPath),n=t.snapshotsDir??"__snapshots__",o=path.isAbsolute(n)?n:path.join(r,n),a=t.snapshotIdentifier??Ge(e);return {snapshotDir:o,baselinePath:path.join(o,`${a}.png`),receivedPath:path.join(o,`${a}.received.png`),diffPath:path.join(o,`${a}.diff.png`)}}function we(e,t,r){let n=r.failureThreshold??0,o=r.failureThresholdType??"pixel";if($(e)){let a=t.score??0;return o==="percent"?(1-a)*100<=n:a>=1-n/100}if(e==="gmsd"){let a=t.score??0;return o==="percent"?a*100<=n:a<=n/100}return o==="percent"?(t.diffPercentage??0)<=n:(t.diffCount??0)<=n}async function ze(e,t,r){let n=Ne(r,t),{snapshotDir:o,baselinePath:a,receivedPath:i,diffPath:f}=n,c=E(e),m=c?e:null,x=t.runInWorker!==false,p=x?he:S,g=x?le:B,J=x?(w,l,Q)=>ge(w,l,t.method,t,Q):(w,l,Q)=>O(w,l,t.method,t,Q);ee(o);let U=N(a),G=t.updateSnapshots===true?"all":t.updateSnapshots===false||t.updateSnapshots===void 0?"new":t.updateSnapshots;if(!U&&G!=="none"&&(G==="new"||G==="all")){if(m)R(a,m);else if(I(e)){let l=await g(e);await P(a,l.data,l.width,l.height);}else if(C(e))await P(a,e.data,e.width,e.height);else {let l=await p(e);await P(a,l.data,l.width,l.height);}return {pass:true,message:T({pass:true,method:t.method,snapshotCreated:true,baselinePath:a,receivedPath:i,diffPath:f,diffCount:0,diffPercentage:0,score:0,threshold:t.failureThreshold??0,thresholdType:t.failureThresholdType??"pixel",updateCommand:t.updateCommand}),baselinePath:a,snapshotStatus:"added"}}let h,d;t.method==="bin"?(h=await O(e,a,t.method,t,f),d=e):(d=c?await p(e):e,h=await J(C(d)?d:await p(d),a,f));let K=we(t.method,h,t);if(K)return fs.existsSync(i)&&fs.unlinkSync(i),fs.existsSync(f)&&fs.unlinkSync(f),{pass:true,message:T({pass:K,method:t.method,snapshotCreated:false,baselinePath:a,receivedPath:i,diffPath:f,diffCount:h.diffCount,diffPercentage:h.diffPercentage,score:h.score,threshold:t.failureThreshold??0,thresholdType:t.failureThresholdType??"pixel",updateCommand:t.updateCommand}),diffCount:h.diffCount,diffPercentage:h.diffPercentage,score:h.score,baselinePath:a,snapshotStatus:"matched"};if(G==="all"){if(m)R(a,m);else if(I(d)){let l=await g(d);await P(a,l.data,l.width,l.height);}else C(d)&&await P(a,d.data,d.width,d.height);return fs.existsSync(i)&&fs.unlinkSync(i),fs.existsSync(f)&&fs.unlinkSync(f),{pass:true,message:T({pass:true,method:t.method,snapshotCreated:true,baselinePath:a,receivedPath:i,diffPath:f,diffCount:0,diffPercentage:0,score:0,threshold:t.failureThreshold??0,thresholdType:t.failureThresholdType??"pixel",updateCommand:t.updateCommand}),baselinePath:a,snapshotStatus:"updated"}}if(m)R(i,m);else if(I(d)){let w=await g(d);await P(i,w.data,w.width,w.height);}else C(d)&&await P(i,d.data,d.width,d.height);if(h.diffOutput&&t.method!=="bin"){let w=C(d)?d:await p(d);await P(f,h.diffOutput,w.width,w.height);}return {pass:false,message:T({pass:K,method:t.method,snapshotCreated:false,baselinePath:a,receivedPath:i,diffPath:f,diffCount:h.diffCount,diffPercentage:h.diffPercentage,score:h.score,threshold:t.failureThreshold??0,thresholdType:t.failureThresholdType??"pixel",updateCommand:t.updateCommand}),diffCount:h.diffCount,diffPercentage:h.diffPercentage,score:h.score,baselinePath:a,receivedPath:i,diffPath:f,snapshotStatus:"failed"}}async function Fe(e,t,r){let n=await O(e,t,r.method,r),o=we(r.method,n,r);return {pass:o,message:o?"Images match.":`Images differ: ${n.diffCount??n.score} ${n.diffCount!==void 0?"pixels":"score"}`,diffCount:n.diffCount,diffPercentage:n.diffPercentage,score:n.score}}exports.compareImages=Fe;exports.fileExists=N;exports.formatReport=T;exports.getOrCreateSnapshot=ze;exports.isFilePath=I;exports.isImageBuffer=fe;exports.isImageData=C;exports.isRawPngBuffer=E;exports.loadPNG=B;exports.normalizeImageInput=S;exports.runComparison=O;exports.savePNG=P;exports.saveRawPNGBuffer=R;exports.terminateWorker=Ee;exports.validateMethodSupportsInput=q;
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import {existsSync,mkdirSync,unlinkSync}from'fs';import {dirname,isAbsolute,join}from'path';import {pngjsTransformer}from'@blazediff/pngjs-transformer';import {compare}from'@blazediff/bin';import {diff}from'@blazediff/core';import {gmsd}from'@blazediff/gmsd';import {ssim}from'@blazediff/ssim';import {hitchhikersSSIM}from'@blazediff/ssim/hitchhikers-ssim';import {msssim}from'@blazediff/ssim/msssim';import s from'picocolors';function c(e){return typeof e=="string"}function $(e){return (Buffer.isBuffer(e)||e instanceof Uint8Array)&&!("width"in e)}function j(e){return typeof e=="object"&&e!==null&&"data"in e&&"width"in e&&"height"in e}async function I(e){if(!existsSync(e))throw new Error(`Image file not found: ${e}`);let t=await pngjsTransformer.read(e);return {data:new Uint8Array(t.data),width:t.width,height:t.height}}async function l(e,t,o,a){let i=dirname(e);existsSync(i)||mkdirSync(i,{recursive:true}),await pngjsTransformer.write({data:t,width:o,height:a},e);}async function w(e){if(c(e))return I(e);if($(e)){let t=Buffer.isBuffer(e)?e:Buffer.from(e),o=await pngjsTransformer.read(t);return {data:new Uint8Array(o.data),width:o.width,height:o.height}}return {data:new Uint8Array(e.data),width:e.width,height:e.height}}function E(e){return existsSync(e)}function z(e){existsSync(e)||mkdirSync(e,{recursive:true});}async function U(e,t,o,a){if(!c(e))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");if(!c(t))throw new Error("Method 'bin' only supports file paths for baseline, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");let i=await compare(e,t,o,{threshold:a.threshold,antialiasing:a.antialiasing});if(i.match)return {diffCount:0,diffPercentage:0};if(i.reason==="layout-diff")return {diffCount:Number.MAX_SAFE_INTEGER,diffPercentage:100};if(i.reason==="pixel-diff")return {diffCount:i.diffCount,diffPercentage:i.diffPercentage};if(i.reason==="file-not-exists")throw new Error(`Image file not found: ${i.file}`);return {diffCount:0,diffPercentage:0}}function B(e,t,o,a){let{width:i,height:r}=e,f=i*r;if(e.width!==t.width||e.height!==t.height)return {diffCount:f,diffPercentage:100};let d=o?new Uint8Array(f*4):void 0,n=diff(e.data,t.data,d,i,r,{threshold:a.threshold??.1,includeAA:a.includeAA??false});return {diffCount:n,diffPercentage:n/f*100,diffOutput:d}}function k(e,t,o,a){let{width:i,height:r}=e,f=i*r;if(e.width!==t.width||e.height!==t.height)return {score:1};let d=o?new Uint8Array(f*4):void 0;return {score:gmsd(e.data,t.data,d,i,r,{downsample:a.downsample}),diffOutput:d}}function F(e,t,o,a,i){let{width:r,height:f}=e,d=r*f;if(e.width!==t.width||e.height!==t.height)return {score:0};let n=a?new Uint8Array(d*4):void 0,u={windowSize:i.windowSize,k1:i.k1,k2:i.k2},p;switch(o){case "ssim":p=ssim(e.data,t.data,n,r,f,u);break;case "msssim":p=msssim(e.data,t.data,n,r,f,u);break;case "hitchhikers-ssim":p=hitchhikersSSIM(e.data,t.data,n,r,f,u);break;default:throw new Error(`Unknown SSIM method: ${o}`)}return {score:p,diffOutput:n}}function x(e){return e==="ssim"||e==="msssim"||e==="hitchhikers-ssim"}function G(e,t){if(e==="bin"&&!c(t))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.")}async function O(e,t,o,a,i){if(G(o,e),G(o,t),o==="bin"){let u=await U(e,t,i,a);return {diffCount:u.diffCount,diffPercentage:u.diffPercentage}}let r=await w(e),f=await w(t),d=i!==void 0;if(x(o)){let u=F(r,f,o,d,a);return {score:u.score,diffOutput:u.diffOutput}}if(o==="gmsd"){let u=k(r,f,d,a);return {score:u.score,diffOutput:u.diffOutput}}let n=B(r,f,d,a);return {diffCount:n.diffCount,diffPercentage:n.diffPercentage,diffOutput:n.diffOutput}}var S={success:s.isColorSupported?"\u2714":"\u221A",error:s.isColorSupported?"\u2716":"\xD7",info:s.isColorSupported?"\u2139":"i",arrow:s.isColorSupported?"\u2514\u2500":"'-"},J=new Set(["ssim","msssim","hitchhikers-ssim"]);function y(e){return e.snapshotCreated?K(e.baselinePath):e.pass?Q():V(e)}function K(e){return [`${s.green(S.success)} ${s.green("New snapshot created")}`,` ${s.dim(S.arrow)} ${s.dim(e)}`].join(`
2
- `)}function Q(){return `${s.green(S.success)} ${s.green("Image matches snapshot")}`}function V(e){let{method:t,baselinePath:o,receivedPath:a,diffPath:i,diffCount:r=0,diffPercentage:f,score:d=0,threshold:n,thresholdType:u,updateCommand:p}=e,h=[`${s.red(S.error)} ${s.red(s.bold("Image snapshot mismatch"))}`,""],m=12;if(h.push(` ${s.dim("Method".padEnd(m))}${t}`),h.push(` ${s.dim("Baseline".padEnd(m))}${s.dim(o)}`),h.push(` ${s.dim("Received".padEnd(m))}${s.dim(a)}`),h.push(` ${s.dim("Diff".padEnd(m))}${s.dim(i)}`),h.push(""),J.has(t)){let b=((1-d)*100).toFixed(2);h.push(` ${s.dim("SSIM Score".padEnd(m))}${s.yellow(d.toFixed(4))} ${s.dim("(1.0 = identical)")}`),h.push(` ${s.dim("Difference".padEnd(m))}${s.yellow(`${b}%`)}`);}else if(t==="gmsd")h.push(` ${s.dim("GMSD Score".padEnd(m))}${s.yellow(d.toFixed(4))} ${s.dim("(0.0 = identical)")}`);else {let b=f?.toFixed(2)??"0.00";h.push(` ${s.dim("Difference".padEnd(m))}${s.yellow(r.toLocaleString())} pixels ${s.dim(`(${b}%)`)}`);}let P=u==="percent"?"%":"pixels";return h.push(` ${s.dim("Threshold".padEnd(m))}${n} ${P}`),h.push(""),h.push(` ${s.cyan(S.info)} ${s.cyan(`Run with ${p??"--update"} to update the snapshot`)}`),h.join(`
3
- `)}function te(e){return e.testName.replace(/[^a-zA-Z0-9-_\s]/g,"").replace(/\s+/g,"-").toLowerCase()||"snapshot"}function re(e,t){let o=dirname(e.testPath),a=t.snapshotsDir??"__snapshots__",i=isAbsolute(a)?a:join(o,a),r=t.snapshotIdentifier??te(e);return {snapshotDir:i,baselinePath:join(i,`${r}.png`),receivedPath:join(i,`${r}.received.png`),diffPath:join(i,`${r}.diff.png`)}}function _(e,t,o){let a=o.failureThreshold??0,i=o.failureThresholdType??"pixel";if(x(e)){let r=t.score??0;return i==="percent"?(1-r)*100<=a:r>=1-a/100}if(e==="gmsd"){let r=t.score??0;return i==="percent"?r*100<=a:r<=a/100}return i==="percent"?(t.diffPercentage??0)<=a:(t.diffCount??0)<=a}async function ae(e,t,o){let a=re(o,t),{snapshotDir:i,baselinePath:r,receivedPath:f,diffPath:d}=a,n=$(e)?await w(e):e;z(i);let u=E(r),p=t.updateSnapshots===true?"all":t.updateSnapshots===false||t.updateSnapshots===void 0?"new":t.updateSnapshots;if(!u&&p!=="none"&&(p==="new"||p==="all")){if(c(n)){let C=await I(n);await l(r,C.data,C.width,C.height);}else await l(r,n.data,n.width,n.height);return {pass:true,message:y({pass:true,method:t.method,snapshotCreated:true,baselinePath:r,receivedPath:f,diffPath:d,diffCount:0,diffPercentage:0,score:0,threshold:t.failureThreshold??0,thresholdType:t.failureThresholdType??"pixel",updateCommand:t.updateCommand}),baselinePath:r,snapshotStatus:"added"}}let m=await O(n,r,t.method,t,d),P=_(t.method,m,t);if(P)return existsSync(f)&&unlinkSync(f),existsSync(d)&&unlinkSync(d),{pass:true,message:y({pass:P,method:t.method,snapshotCreated:false,baselinePath:r,receivedPath:f,diffPath:d,diffCount:m.diffCount,diffPercentage:m.diffPercentage,score:m.score,threshold:t.failureThreshold??0,thresholdType:t.failureThresholdType??"pixel",updateCommand:t.updateCommand}),diffCount:m.diffCount,diffPercentage:m.diffPercentage,score:m.score,baselinePath:r,snapshotStatus:"matched"};if(p==="all"){if(c(n)){let C=await I(n);await l(r,C.data,C.width,C.height);}else await l(r,n.data,n.width,n.height);return existsSync(f)&&unlinkSync(f),existsSync(d)&&unlinkSync(d),{pass:true,message:y({pass:true,method:t.method,snapshotCreated:true,baselinePath:r,receivedPath:f,diffPath:d,diffCount:0,diffPercentage:0,score:0,threshold:t.failureThreshold??0,thresholdType:t.failureThresholdType??"pixel",updateCommand:t.updateCommand}),baselinePath:r,snapshotStatus:"updated"}}if(c(n)){let g=await I(n);await l(f,g.data,g.width,g.height);}else await l(f,n.data,n.width,n.height);if(m.diffOutput){let g=await w(n);await l(d,m.diffOutput,g.width,g.height);}return {pass:false,message:y({pass:P,method:t.method,snapshotCreated:false,baselinePath:r,receivedPath:f,diffPath:d,diffCount:m.diffCount,diffPercentage:m.diffPercentage,score:m.score,threshold:t.failureThreshold??0,thresholdType:t.failureThresholdType??"pixel",updateCommand:t.updateCommand}),diffCount:m.diffCount,diffPercentage:m.diffPercentage,score:m.score,baselinePath:r,receivedPath:f,diffPath:d,snapshotStatus:"failed"}}async function ie(e,t,o){let a=await O(e,t,o.method,o),i=_(o.method,a,o);return {pass:i,message:i?"Images match.":`Images differ: ${a.diffCount??a.score} ${a.diffCount!==void 0?"pixels":"score"}`,diffCount:a.diffCount,diffPercentage:a.diffPercentage,score:a.score}}export{ie as compareImages,E as fileExists,y as formatReport,ae as getOrCreateSnapshot,c as isFilePath,j as isImageBuffer,$ as isRawPngBuffer,I as loadPNG,w as normalizeImageInput,O as runComparison,l as savePNG,G as validateMethodSupportsInput};
1
+ import Pe,{dirname,isAbsolute,join}from'path';import {fileURLToPath}from'url';import {existsSync,mkdirSync,writeFileSync,unlinkSync}from'fs';import {pngjsTransformer}from'@blazediff/pngjs-transformer';import {compare}from'@blazediff/bin';import {diff}from'@blazediff/core';import {gmsd}from'@blazediff/gmsd';import {ssim}from'@blazediff/ssim';import {hitchhikersSSIM}from'@blazediff/ssim/hitchhikers-ssim';import {msssim}from'@blazediff/ssim/msssim';import s from'picocolors';import {Worker}from'worker_threads';var Ce=Object.defineProperty;var S=(e,t)=>()=>(e&&(t=e(e=0)),t);var se=(e,t)=>{for(var r in t)Ce(e,r,{get:t[r],enumerable:true});};var xe,Me,I,u=S(()=>{xe=()=>fileURLToPath(import.meta.url),Me=()=>Pe.dirname(xe()),I=Me();});var ue={};se(ue,{ensureDir:()=>te,fileExists:()=>z,isFilePath:()=>C,isImageBuffer:()=>me,isImageData:()=>P,isRawPngBuffer:()=>B,loadPNG:()=>W,normalizeImageInput:()=>D,savePNG:()=>y,saveRawPNGBuffer:()=>R});function C(e){return typeof e=="string"}function B(e){return (Buffer.isBuffer(e)||e instanceof Uint8Array)&&!("width"in e)}function me(e){return typeof e=="object"&&e!==null&&"data"in e&&"width"in e&&"height"in e}function P(e){return typeof e=="object"&&e!==null&&"data"in e&&"width"in e&&"height"in e}async function W(e){if(!existsSync(e))throw new Error(`Image file not found: ${e}`);let t=await pngjsTransformer.read(e);return {data:new Uint8Array(t.data),width:t.width,height:t.height}}async function y(e,t,r,o){let n=dirname(e);existsSync(n)||mkdirSync(n,{recursive:true}),await pngjsTransformer.write({data:t instanceof Uint8Array?t:new Uint8Array(t),width:r,height:o},e);}function R(e,t){let r=dirname(e);existsSync(r)||mkdirSync(r,{recursive:true}),writeFileSync(e,t);}async function D(e){if(C(e))return W(e);if(B(e)){let t=Buffer.isBuffer(e)?e:Buffer.from(e),r=await pngjsTransformer.read(t);return {data:new Uint8Array(r.data),width:r.width,height:r.height}}return e.data instanceof Uint8Array?e:{data:new Uint8Array(e.data),width:e.width,height:e.height}}function z(e){return existsSync(e)}function te(e){existsSync(e)||mkdirSync(e,{recursive:true});}var $=S(()=>{u();});async function F(e,t,r,o){if(!C(e))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");if(!C(t))throw new Error("Method 'bin' only supports file paths for baseline, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");let n=await compare(e,t,r,{threshold:o.threshold,antialiasing:o.antialiasing});if(n.match)return {diffCount:0,diffPercentage:0};if(n.reason==="layout-diff")return {diffCount:Number.MAX_SAFE_INTEGER,diffPercentage:100};if(n.reason==="pixel-diff")return {diffCount:n.diffCount,diffPercentage:n.diffPercentage};if(n.reason==="file-not-exists")throw new Error(`Image file not found: ${n.file}`);return {diffCount:0,diffPercentage:0}}var re=S(()=>{u();$();});function v(e,t,r,o){let{width:n,height:a}=e,i=n*a;if(e.width!==t.width||e.height!==t.height)return {diffCount:i,diffPercentage:100};let f=r?new Uint8Array(i*4):void 0,h=diff(e.data,t.data,f,n,a,{threshold:o.threshold??.1,includeAA:o.includeAA??false});return {diffCount:h,diffPercentage:h/i*100,diffOutput:f}}var ae=S(()=>{u();});function j(e,t,r,o){let{width:n,height:a}=e,i=n*a;if(e.width!==t.width||e.height!==t.height)return {score:1};let f=r?new Uint8Array(i*4):void 0;return {score:gmsd(e.data,t.data,f,n,a,{downsample:o.downsample}),diffOutput:f}}var oe=S(()=>{u();});function _(e,t,r,o,n){let{width:a,height:i}=e,f=a*i;if(e.width!==t.width||e.height!==t.height)return {score:0};let h=o?new Uint8Array(f*4):void 0,m={windowSize:n.windowSize,k1:n.k1,k2:n.k2},M;switch(r){case "ssim":M=ssim(e.data,t.data,h,a,i,m);break;case "msssim":M=msssim(e.data,t.data,h,a,i,m);break;case "hitchhikers-ssim":M=hitchhikersSSIM(e.data,t.data,h,a,i,m);break;default:throw new Error(`Unknown SSIM method: ${r}`)}return {score:M,diffOutput:h}}function O(e){return e==="ssim"||e==="msssim"||e==="hitchhikers-ssim"}var q=S(()=>{u();});var de={};se(de,{compareBin:()=>F,compareCore:()=>v,compareGmsd:()=>j,compareSsim:()=>_,isSsimMethod:()=>O,runComparison:()=>T,validateMethodSupportsInput:()=>L});function L(e,t){if(e==="bin"&&!C(t))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.")}async function T(e,t,r,o,n){if(L(r,e),L(r,t),r==="bin"){let m=await F(e,t,n,o);return {diffCount:m.diffCount,diffPercentage:m.diffPercentage}}let a=P(e)?e:await D(e),i=P(t)?t:await D(t),f=n!==void 0;if(O(r)){let m=_(a,i,r,f,o);return {score:m.score,diffOutput:m.diffOutput}}if(r==="gmsd"){let m=j(a,i,f,o);return {score:m.score,diffOutput:m.diffOutput}}let h=v(a,i,f,o);return {diffCount:h.diffCount,diffPercentage:h.diffPercentage,diffOutput:h.diffOutput}}var H=S(()=>{u();$();re();ae();oe();q();re();ae();oe();q();});u();H();$();u();var G={success:s.isColorSupported?"\u2714":"\u221A",error:s.isColorSupported?"\u2716":"\xD7",info:s.isColorSupported?"\u2139":"i",arrow:s.isColorSupported?"\u2514\u2500":"'-"},Te=new Set(["ssim","msssim","hitchhikers-ssim"]);function A(e){return e.snapshotCreated?Ae(e.baselinePath):e.pass?Ue():Ee(e)}function Ae(e){return [`${s.green(G.success)} ${s.green("New snapshot created")}`,` ${s.dim(G.arrow)} ${s.dim(e)}`].join(`
2
+ `)}function Ue(){return `${s.green(G.success)} ${s.green("Image matches snapshot")}`}function Ee(e){let{method:t,baselinePath:r,receivedPath:o,diffPath:n,diffCount:a=0,diffPercentage:i,score:f=0,threshold:h,thresholdType:m,updateCommand:M}=e,p=[`${s.red(G.error)} ${s.red(s.bold("Image snapshot mismatch"))}`,""],g=12;if(p.push(` ${s.dim("Method".padEnd(g))}${t}`),p.push(` ${s.dim("Baseline".padEnd(g))}${s.dim(r)}`),p.push(` ${s.dim("Received".padEnd(g))}${s.dim(o)}`),p.push(` ${s.dim("Diff".padEnd(g))}${s.dim(n)}`),p.push(""),Te.has(t)){let U=((1-f)*100).toFixed(2);p.push(` ${s.dim("SSIM Score".padEnd(g))}${s.yellow(f.toFixed(4))} ${s.dim("(1.0 = identical)")}`),p.push(` ${s.dim("Difference".padEnd(g))}${s.yellow(`${U}%`)}`);}else if(t==="gmsd")p.push(` ${s.dim("GMSD Score".padEnd(g))}${s.yellow(f.toFixed(4))} ${s.dim("(0.0 = identical)")}`);else {let U=i?.toFixed(2)??"0.00";p.push(` ${s.dim("Difference".padEnd(g))}${s.yellow(a.toLocaleString())} pixels ${s.dim(`(${U}%)`)}`);}let K=m==="percent"?"%":"pixels";return p.push(` ${s.dim("Threshold".padEnd(g))}${h} ${K}`),p.push(""),p.push(` ${s.cyan(G.info)} ${s.cyan(`Run with ${M??"--update"} to update the snapshot`)}`),p.join(`
3
+ `)}u();H();q();$();u();var x=null,We=0,ce=false,k=new Map;function Ge(){let e=join(I,"worker.js");if(existsSync(e))return e;let t=join(I,"../dist/worker.js");return existsSync(t)?t:null}function Ne(){if(ce)return null;if(!x){let e=Ge();if(!e)return ce=true,null;x=new Worker(e),x.on("message",t=>{let r=k.get(t.id);r&&(k.delete(t.id),t.success?r.resolve(t.result):r.reject(new Error(t.error)));}),x.on("error",t=>{for(let r of k.values())r.reject(t);k.clear(),x=null;}),x.on("exit",t=>{if(t!==0){let r=new Error(`Worker exited with code ${t}`);for(let o of k.values())o.reject(r);k.clear();}x=null;});}return x}async function ne(e,t){let r=Ne();if(!r){let{runComparison:o}=await Promise.resolve().then(()=>(H(),de)),{loadPNG:n,normalizeImageInput:a}=await Promise.resolve().then(()=>($(),ue));switch(e){case "normalize":return a(t.input);case "loadPNG":return n(t.filePath);case "compare":{let i=t;return o(i.received,i.baseline,i.method,i.options,i.diffOutputPath)}default:throw new Error(`Unknown request type: ${e}`)}}return new Promise((o,n)=>{let a=++We;k.set(a,{resolve:o,reject:n});let i={id:a,type:e,payload:t};r.postMessage(i);})}function ie(e){return e.data instanceof Uint8Array?e:{data:new Uint8Array(e.data),width:e.width,height:e.height}}async function le(e){let t=await ne("normalize",{input:e});return ie(t)}async function ge(e){let t=await ne("loadPNG",{filePath:e});return ie(t)}function we(e,t,r,o,n){let a=ie(e);return ne("compare",{received:a,baseline:t,method:r,options:o,diffOutputPath:n})}function ze(){if(x){let e=x;return x=null,e.terminate()}return Promise.resolve(0)}function je(e){return e.testName.replace(/[^a-zA-Z0-9-_\s]/g,"").replace(/\s+/g,"-").toLowerCase()||"snapshot"}function _e(e,t){let r=dirname(e.testPath),o=t.snapshotsDir??"__snapshots__",n=isAbsolute(o)?o:join(r,o),a=t.snapshotIdentifier??je(e);return {snapshotDir:n,baselinePath:join(n,`${a}.png`),receivedPath:join(n,`${a}.received.png`),diffPath:join(n,`${a}.diff.png`)}}function Ie(e,t,r){let o=r.failureThreshold??0,n=r.failureThresholdType??"pixel";if(O(e)){let a=t.score??0;return n==="percent"?(1-a)*100<=o:a>=1-o/100}if(e==="gmsd"){let a=t.score??0;return n==="percent"?a*100<=o:a<=o/100}return n==="percent"?(t.diffPercentage??0)<=o:(t.diffCount??0)<=o}async function qe(e,t,r){let o=_e(r,t),{snapshotDir:n,baselinePath:a,receivedPath:i,diffPath:f}=o,h=B(e),m=h?e:null,M=t.runInWorker!==false,p=M?le:D,g=M?ge:W,K=M?(w,l,V)=>we(w,l,t.method,t,V):(w,l,V)=>T(w,l,t.method,t,V);te(n);let U=z(a),N=t.updateSnapshots===true?"all":t.updateSnapshots===false||t.updateSnapshots===void 0?"new":t.updateSnapshots;if(!U&&N!=="none"&&(N==="new"||N==="all")){if(m)R(a,m);else if(C(e)){let l=await g(e);await y(a,l.data,l.width,l.height);}else if(P(e))await y(a,e.data,e.width,e.height);else {let l=await p(e);await y(a,l.data,l.width,l.height);}return {pass:true,message:A({pass:true,method:t.method,snapshotCreated:true,baselinePath:a,receivedPath:i,diffPath:f,diffCount:0,diffPercentage:0,score:0,threshold:t.failureThreshold??0,thresholdType:t.failureThresholdType??"pixel",updateCommand:t.updateCommand}),baselinePath:a,snapshotStatus:"added"}}let c,d;t.method==="bin"?(c=await T(e,a,t.method,t,f),d=e):(d=h?await p(e):e,c=await K(P(d)?d:await p(d),a,f));let Q=Ie(t.method,c,t);if(Q)return existsSync(i)&&unlinkSync(i),existsSync(f)&&unlinkSync(f),{pass:true,message:A({pass:Q,method:t.method,snapshotCreated:false,baselinePath:a,receivedPath:i,diffPath:f,diffCount:c.diffCount,diffPercentage:c.diffPercentage,score:c.score,threshold:t.failureThreshold??0,thresholdType:t.failureThresholdType??"pixel",updateCommand:t.updateCommand}),diffCount:c.diffCount,diffPercentage:c.diffPercentage,score:c.score,baselinePath:a,snapshotStatus:"matched"};if(N==="all"){if(m)R(a,m);else if(C(d)){let l=await g(d);await y(a,l.data,l.width,l.height);}else P(d)&&await y(a,d.data,d.width,d.height);return existsSync(i)&&unlinkSync(i),existsSync(f)&&unlinkSync(f),{pass:true,message:A({pass:true,method:t.method,snapshotCreated:true,baselinePath:a,receivedPath:i,diffPath:f,diffCount:0,diffPercentage:0,score:0,threshold:t.failureThreshold??0,thresholdType:t.failureThresholdType??"pixel",updateCommand:t.updateCommand}),baselinePath:a,snapshotStatus:"updated"}}if(m)R(i,m);else if(C(d)){let w=await g(d);await y(i,w.data,w.width,w.height);}else P(d)&&await y(i,d.data,d.width,d.height);if(c.diffOutput&&t.method!=="bin"){let w=P(d)?d:await p(d);await y(f,c.diffOutput,w.width,w.height);}return {pass:false,message:A({pass:Q,method:t.method,snapshotCreated:false,baselinePath:a,receivedPath:i,diffPath:f,diffCount:c.diffCount,diffPercentage:c.diffPercentage,score:c.score,threshold:t.failureThreshold??0,thresholdType:t.failureThresholdType??"pixel",updateCommand:t.updateCommand}),diffCount:c.diffCount,diffPercentage:c.diffPercentage,score:c.score,baselinePath:a,receivedPath:i,diffPath:f,snapshotStatus:"failed"}}async function Le(e,t,r){let o=await T(e,t,r.method,r),n=Ie(r.method,o,r);return {pass:n,message:n?"Images match.":`Images differ: ${o.diffCount??o.score} ${o.diffCount!==void 0?"pixels":"score"}`,diffCount:o.diffCount,diffPercentage:o.diffPercentage,score:o.score}}export{Le as compareImages,z as fileExists,A as formatReport,qe as getOrCreateSnapshot,C as isFilePath,me as isImageBuffer,P as isImageData,B as isRawPngBuffer,W as loadPNG,D as normalizeImageInput,T as runComparison,y as savePNG,R as saveRawPNGBuffer,ze as terminateWorker,L as validateMethodSupportsInput};
package/dist/worker.js ADDED
@@ -0,0 +1 @@
1
+ 'use strict';var worker_threads=require('worker_threads'),fs=require('fs');require('path');var pngjsTransformer=require('@blazediff/pngjs-transformer'),bin=require('@blazediff/bin'),core=require('@blazediff/core'),gmsd=require('@blazediff/gmsd'),ssim=require('@blazediff/ssim'),hitchhikersSsim=require('@blazediff/ssim/hitchhikers-ssim'),msssim=require('@blazediff/ssim/msssim');function p(t){return typeof t=="string"}function D(t){return (Buffer.isBuffer(t)||t instanceof Uint8Array)&&!("width"in t)}function g(t){return typeof t=="object"&&t!==null&&"data"in t&&"width"in t&&"height"in t}async function l(t){if(!fs.existsSync(t))throw new Error(`Image file not found: ${t}`);let r=await pngjsTransformer.pngjsTransformer.read(t);return {data:new Uint8Array(r.data),width:r.width,height:r.height}}async function d(t){if(p(t))return l(t);if(D(t)){let r=Buffer.isBuffer(t)?t:Buffer.from(t),o=await pngjsTransformer.pngjsTransformer.read(r);return {data:new Uint8Array(o.data),width:o.width,height:o.height}}return t.data instanceof Uint8Array?t:{data:new Uint8Array(t.data),width:t.width,height:t.height}}async function y(t,r,o,a){if(!p(t))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");if(!p(r))throw new Error("Method 'bin' only supports file paths for baseline, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");let e=await bin.compare(t,r,o,{threshold:a.threshold,antialiasing:a.antialiasing});if(e.match)return {diffCount:0,diffPercentage:0};if(e.reason==="layout-diff")return {diffCount:Number.MAX_SAFE_INTEGER,diffPercentage:100};if(e.reason==="pixel-diff")return {diffCount:e.diffCount,diffPercentage:e.diffPercentage};if(e.reason==="file-not-exists")throw new Error(`Image file not found: ${e.file}`);return {diffCount:0,diffPercentage:0}}function I(t,r,o,a){let{width:e,height:i}=t,n=e*i;if(t.width!==r.width||t.height!==r.height)return {diffCount:n,diffPercentage:100};let m=o?new Uint8Array(n*4):void 0,s=core.diff(t.data,r.data,m,e,i,{threshold:a.threshold??.1,includeAA:a.includeAA??false});return {diffCount:s,diffPercentage:s/n*100,diffOutput:m}}function w(t,r,o,a){let{width:e,height:i}=t,n=e*i;if(t.width!==r.width||t.height!==r.height)return {score:1};let m=o?new Uint8Array(n*4):void 0;return {score:gmsd.gmsd(t.data,r.data,m,e,i,{downsample:a.downsample}),diffOutput:m}}function P(t,r,o,a,e){let{width:i,height:n}=t,m=i*n;if(t.width!==r.width||t.height!==r.height)return {score:0};let s=a?new Uint8Array(m*4):void 0,f={windowSize:e.windowSize,k1:e.k1,k2:e.k2},c;switch(o){case "ssim":c=ssim.ssim(t.data,r.data,s,i,n,f);break;case "msssim":c=msssim.msssim(t.data,r.data,s,i,n,f);break;case "hitchhikers-ssim":c=hitchhikersSsim.hitchhikersSSIM(t.data,r.data,s,i,n,f);break;default:throw new Error(`Unknown SSIM method: ${o}`)}return {score:c,diffOutput:s}}function C(t){return t==="ssim"||t==="msssim"||t==="hitchhikers-ssim"}function M(t,r){if(t==="bin"&&!p(r))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.")}async function b(t,r,o,a,e){if(M(o,t),M(o,r),o==="bin"){let f=await y(t,r,e,a);return {diffCount:f.diffCount,diffPercentage:f.diffPercentage}}let i=g(t)?t:await d(t),n=g(r)?r:await d(r),m=e!==void 0;if(C(o)){let f=P(i,n,o,m,a);return {score:f.score,diffOutput:f.diffOutput}}if(o==="gmsd"){let f=w(i,n,m,a);return {score:f.score,diffOutput:f.diffOutput}}let s=I(i,n,m,a);return {diffCount:s.diffCount,diffPercentage:s.diffPercentage,diffOutput:s.diffOutput}}function G(t){return t.data instanceof Uint8Array?t:{data:new Uint8Array(t.data),width:t.width,height:t.height}}async function N(t){switch(t.type){case "normalize":{let{input:r}=t.payload;return d(r)}case "loadPNG":{let{filePath:r}=t.payload;return l(r)}case "compare":{let{received:r,baseline:o,method:a,options:e,diffOutputPath:i}=t.payload;return b(G(r),o,a,e,i)}default:throw new Error(`Unknown request type: ${t.type}`)}}worker_threads.parentPort&&worker_threads.parentPort.on("message",async t=>{try{let r=await N(t);worker_threads.parentPort.postMessage({id:t.id,success:!0,result:r});}catch(r){worker_threads.parentPort.postMessage({id:t.id,success:false,error:r instanceof Error?r.message:String(r)});}});
@@ -0,0 +1 @@
1
+ import {parentPort}from'worker_threads';import {existsSync}from'fs';import'path';import {pngjsTransformer}from'@blazediff/pngjs-transformer';import {compare}from'@blazediff/bin';import {diff}from'@blazediff/core';import {gmsd}from'@blazediff/gmsd';import {ssim}from'@blazediff/ssim';import {hitchhikersSSIM}from'@blazediff/ssim/hitchhikers-ssim';import {msssim}from'@blazediff/ssim/msssim';function d(t){return typeof t=="string"}function O(t){return (Buffer.isBuffer(t)||t instanceof Uint8Array)&&!("width"in t)}function l(t){return typeof t=="object"&&t!==null&&"data"in t&&"width"in t&&"height"in t}async function y(t){if(!existsSync(t))throw new Error(`Image file not found: ${t}`);let r=await pngjsTransformer.read(t);return {data:new Uint8Array(r.data),width:r.width,height:r.height}}async function c(t){if(d(t))return y(t);if(O(t)){let r=Buffer.isBuffer(t)?t:Buffer.from(t),o=await pngjsTransformer.read(r);return {data:new Uint8Array(o.data),width:o.width,height:o.height}}return t.data instanceof Uint8Array?t:{data:new Uint8Array(t.data),width:t.width,height:t.height}}async function I(t,r,o,a){if(!d(t))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");if(!d(r))throw new Error("Method 'bin' only supports file paths for baseline, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");let e=await compare(t,r,o,{threshold:a.threshold,antialiasing:a.antialiasing});if(e.match)return {diffCount:0,diffPercentage:0};if(e.reason==="layout-diff")return {diffCount:Number.MAX_SAFE_INTEGER,diffPercentage:100};if(e.reason==="pixel-diff")return {diffCount:e.diffCount,diffPercentage:e.diffPercentage};if(e.reason==="file-not-exists")throw new Error(`Image file not found: ${e.file}`);return {diffCount:0,diffPercentage:0}}function w(t,r,o,a){let{width:e,height:i}=t,n=e*i;if(t.width!==r.width||t.height!==r.height)return {diffCount:n,diffPercentage:100};let m=o?new Uint8Array(n*4):void 0,s=diff(t.data,r.data,m,e,i,{threshold:a.threshold??.1,includeAA:a.includeAA??false});return {diffCount:s,diffPercentage:s/n*100,diffOutput:m}}function P(t,r,o,a){let{width:e,height:i}=t,n=e*i;if(t.width!==r.width||t.height!==r.height)return {score:1};let m=o?new Uint8Array(n*4):void 0;return {score:gmsd(t.data,r.data,m,e,i,{downsample:a.downsample}),diffOutput:m}}function C(t,r,o,a,e){let{width:i,height:n}=t,m=i*n;if(t.width!==r.width||t.height!==r.height)return {score:0};let s=a?new Uint8Array(m*4):void 0,f={windowSize:e.windowSize,k1:e.k1,k2:e.k2},h;switch(o){case "ssim":h=ssim(t.data,r.data,s,i,n,f);break;case "msssim":h=msssim(t.data,r.data,s,i,n,f);break;case "hitchhikers-ssim":h=hitchhikersSSIM(t.data,r.data,s,i,n,f);break;default:throw new Error(`Unknown SSIM method: ${o}`)}return {score:h,diffOutput:s}}function x(t){return t==="ssim"||t==="msssim"||t==="hitchhikers-ssim"}function b(t,r){if(t==="bin"&&!d(r))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.")}async function A(t,r,o,a,e){if(b(o,t),b(o,r),o==="bin"){let f=await I(t,r,e,a);return {diffCount:f.diffCount,diffPercentage:f.diffPercentage}}let i=l(t)?t:await c(t),n=l(r)?r:await c(r),m=e!==void 0;if(x(o)){let f=C(i,n,o,m,a);return {score:f.score,diffOutput:f.diffOutput}}if(o==="gmsd"){let f=P(i,n,m,a);return {score:f.score,diffOutput:f.diffOutput}}let s=w(i,n,m,a);return {diffCount:s.diffCount,diffPercentage:s.diffPercentage,diffOutput:s.diffOutput}}function N(t){return t.data instanceof Uint8Array?t:{data:new Uint8Array(t.data),width:t.width,height:t.height}}async function E(t){switch(t.type){case "normalize":{let{input:r}=t.payload;return c(r)}case "loadPNG":{let{filePath:r}=t.payload;return y(r)}case "compare":{let{received:r,baseline:o,method:a,options:e,diffOutputPath:i}=t.payload;return A(N(r),o,a,e,i)}default:throw new Error(`Unknown request type: ${t.type}`)}}parentPort&&parentPort.on("message",async t=>{try{let r=await E(t);parentPort.postMessage({id:t.id,success:!0,result:r});}catch(r){parentPort.postMessage({id:t.id,success:false,error:r instanceof Error?r.message:String(r)});}});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blazediff/matcher",
3
- "version": "1.2.4",
3
+ "version": "1.3.0",
4
4
  "description": "Core matcher logic for visual regression testing with blazediff",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -33,7 +33,7 @@
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
35
  "picocolors": "^1.1.1",
36
- "@blazediff/bin": "3.4.0",
36
+ "@blazediff/bin": "3.5.0",
37
37
  "@blazediff/core": "1.9.1",
38
38
  "@blazediff/gmsd": "1.7.1",
39
39
  "@blazediff/pngjs-transformer": "2.1.1",