@blazediff/matcher 1.2.5 → 1.3.1

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
@@ -11,7 +11,7 @@ Core matcher logic for visual regression testing. Provides snapshot comparison w
11
11
 
12
12
  ## Features
13
13
 
14
- - **Multiple comparison methods**: `core`, `bin`, `ssim`, `msssim`, `hitchhikers-ssim`, `gmsd`
14
+ - **Multiple comparison methods**: `core`, `core-native`, `ssim`, `msssim`, `hitchhikers-ssim`, `gmsd`
15
15
  - **Flexible input types**: File paths or image buffers
16
16
  - **Snapshot state tracking**: Reports added/matched/updated/failed status
17
17
  - **Configurable thresholds**: Pixel count or percentage-based
@@ -127,13 +127,13 @@ Main function for snapshot comparison.
127
127
  <td><code>threshold</code></td>
128
128
  <td>number</td>
129
129
  <td>0.1</td>
130
- <td>Color difference threshold for core/bin methods (0-1)</td>
130
+ <td>Color difference threshold for core/core-native methods (0-1)</td>
131
131
  </tr>
132
132
  <tr>
133
133
  <td><code>antialiasing</code></td>
134
134
  <td>boolean</td>
135
135
  <td>false</td>
136
- <td>Enable anti-aliasing detection (bin method)</td>
136
+ <td>Enable anti-aliasing detection (core-native method)</td>
137
137
  </tr>
138
138
  <tr>
139
139
  <td><code>includeAA</code></td>
@@ -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
@@ -236,7 +242,7 @@ type ImageInput =
236
242
 
237
243
  ## Comparison Methods
238
244
 
239
- ### `bin`
245
+ ### `core-native`
240
246
  Rust-native comparison via N-API bindings. **Fastest method** with native performance.
241
247
  - **Input**: File paths only
242
248
  - **Algorithm**: YIQ color space with block-based optimization
@@ -281,7 +287,7 @@ Gradient Magnitude Similarity Deviation.
281
287
  ```typescript
282
288
  const result = await getOrCreateSnapshot(
283
289
  '/path/to/screenshot.png',
284
- { method: 'bin' },
290
+ { method: 'core-native' },
285
291
  { testPath: __filename, testName: 'test name' }
286
292
  );
287
293
  ```
@@ -320,7 +326,7 @@ const result = await getOrCreateSnapshot(
320
326
 
321
327
  ```typescript
322
328
  // Fastest - Rust native (file paths only)
323
- await getOrCreateSnapshot(imagePath, { method: 'bin' }, context);
329
+ await getOrCreateSnapshot(imagePath, { method: 'core-native' }, context);
324
330
 
325
331
  // Pure JavaScript
326
332
  await getOrCreateSnapshot(imageBuffer, { method: 'core' }, context);
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Comparison methods available in blazediff
3
3
  */
4
- type ComparisonMethod = "bin" | "core" | "ssim" | "msssim" | "hitchhikers-ssim" | "gmsd";
4
+ type ComparisonMethod = "core-native" | "core" | "ssim" | "msssim" | "hitchhikers-ssim" | "gmsd";
5
5
  /**
6
6
  * Status of a snapshot operation
7
7
  */
@@ -56,13 +56,13 @@ interface MatcherOptions {
56
56
  */
57
57
  updateCommand?: string;
58
58
  /**
59
- * Color difference threshold for core/bin methods (0-1)
59
+ * Color difference threshold for core/core-native methods (0-1)
60
60
  * Lower = more strict
61
61
  * @default 0.1
62
62
  */
63
63
  threshold?: number;
64
64
  /**
65
- * Enable anti-aliasing detection (bin method)
65
+ * Enable anti-aliasing detection (core-native method)
66
66
  * @default false
67
67
  */
68
68
  antialiasing?: boolean;
@@ -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'),codecPngjs=require('@blazediff/codec-pngjs'),core=require('@blazediff/core'),coreNative=require('@blazediff/core-native'),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 S=(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=S(()=>{});var me={};ie(me,{ensureDir:()=>ee,fileExists:()=>G,isFilePath:()=>I,isImageBuffer:()=>fe,isImageData:()=>C,isRawPngBuffer:()=>E,loadPNG:()=>B,normalizeImageInput:()=>D,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 codecPngjs.pngjsTransformer.read(e);return {data:new Uint8Array(t.data),width:t.width,height:t.height}}async function P(e,t,r,o){let n=path.dirname(e);fs.existsSync(n)||fs.mkdirSync(n,{recursive:true}),await codecPngjs.pngjsTransformer.write({data:t instanceof Uint8Array?t:new Uint8Array(t),width:r,height:o},e);}function R(e,t){let r=path.dirname(e);fs.existsSync(r)||fs.mkdirSync(r,{recursive:true}),fs.writeFileSync(e,t);}async function D(e){if(I(e))return B(e);if(E(e)){let t=Buffer.isBuffer(e)?e:Buffer.from(e),r=await codecPngjs.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 G(e){return fs.existsSync(e)}function ee(e){fs.existsSync(e)||fs.mkdirSync(e,{recursive:true});}var k=S(()=>{u();});function N(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,c=core.diff(e.data,t.data,f,n,a,{threshold:o.threshold??.1,includeAA:o.includeAA??false});return {diffCount:c,diffPercentage:c/i*100,diffOutput:f}}var te=S(()=>{u();});async function z(e,t,r,o){if(!I(e))throw new Error("Method 'core-native' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");if(!I(t))throw new Error("Method 'core-native' only supports file paths for baseline, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");let n=await coreNative.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();k();});function F(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.gmsd(e.data,t.data,f,n,a,{downsample:o.downsample}),diffOutput:f}}var ae=S(()=>{u();});function j(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 c=o?new Uint8Array(f*4):void 0,m={windowSize:n.windowSize,k1:n.k1,k2:n.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 _=S(()=>{u();});var ue={};ie(ue,{compareBin:()=>z,compareCore:()=>N,compareGmsd:()=>F,compareSsim:()=>j,isSsimMethod:()=>$,runComparison:()=>O,validateMethodSupportsInput:()=>q});function q(e,t){if(e==="core-native"&&!I(t))throw new Error("Method 'core-native' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.")}async function O(e,t,r,o,n){if(q(r,e),q(r,t),r==="core-native"){let m=await z(e,t,n,o);return {diffCount:m.diffCount,diffPercentage:m.diffPercentage}}let a=C(e)?e:await D(e),i=C(t)?t:await D(t),f=n!==void 0;if($(r)){let m=j(a,i,r,f,o);return {score:m.score,diffOutput:m.diffOutput}}if(r==="gmsd"){let m=F(a,i,f,o);return {score:m.score,diffOutput:m.diffOutput}}let c=N(a,i,f,o);return {diffCount:c.diffCount,diffPercentage:c.diffPercentage,diffOutput:c.diffOutput}}var L=S(()=>{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":"'-"},be=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:o,diffPath:n,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(o)}`),p.push(` ${s__default.default.dim("Diff".padEnd(g))}${s__default.default.dim(n)}`),p.push(""),be.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,b=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=b.get(t.id);r&&(b.delete(t.id),t.success?r.resolve(t.result):r.reject(new Error(t.error)));}),y.on("error",t=>{for(let r of b.values())r.reject(t);b.clear(),y=null;}),y.on("exit",t=>{if(t!==0){let r=new Error(`Worker exited with code ${t}`);for(let o of b.values())o.reject(r);b.clear();}y=null;});}return y}async function oe(e,t){let r=Ae();if(!r){let{runComparison:o}=await Promise.resolve().then(()=>(L(),ue)),{loadPNG:n,normalizeImageInput:a}=await Promise.resolve().then(()=>(k(),me));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=++Te;b.set(a,{resolve:o,reject:n});let i={id:a,type:e,payload:t};r.postMessage(i);})}function ne(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 oe("normalize",{input:e});return ne(t)}async function le(e){let t=await oe("loadPNG",{filePath:e});return ne(t)}function ge(e,t,r,o,n){let a=ne(e);return oe("compare",{received:a,baseline:t,method:r,options:o,diffOutputPath:n})}function Ee(){if(y){let e=y;return y=null,e.terminate()}return Promise.resolve(0)}function ve(e){return e.testName.replace(/[^a-zA-Z0-9-_\s]/g,"").replace(/\s+/g,"-").toLowerCase()||"snapshot"}function Ge(e,t){let r=path.dirname(e.testPath),o=t.snapshotsDir??"__snapshots__",n=path.isAbsolute(o)?o:path.join(r,o),a=t.snapshotIdentifier??ve(e);return {snapshotDir:n,baselinePath:path.join(n,`${a}.png`),receivedPath:path.join(n,`${a}.received.png`),diffPath:path.join(n,`${a}.diff.png`)}}function we(e,t,r){let o=r.failureThreshold??0,n=r.failureThresholdType??"pixel";if($(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 Ne(e,t,r){let o=Ge(r,t),{snapshotDir:n,baselinePath:a,receivedPath:i,diffPath:f}=o,c=E(e),m=c?e:null,x=t.runInWorker!==false,p=x?he:D,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(n);let U=G(a),v=t.updateSnapshots===true?"all":t.updateSnapshots===false||t.updateSnapshots===void 0?"new":t.updateSnapshots;if(!U&&v!=="none"&&(v==="new"||v==="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==="core-native"?(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(v==="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!=="core-native"){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 ze(e,t,r){let o=await O(e,t,r.method,r),n=we(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}}exports.compareImages=ze;exports.fileExists=G;exports.formatReport=T;exports.getOrCreateSnapshot=Ne;exports.isFilePath=I;exports.isImageBuffer=fe;exports.isImageData=C;exports.isRawPngBuffer=E;exports.loadPNG=B;exports.normalizeImageInput=D;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/codec-pngjs';import {diff}from'@blazediff/core';import {compare}from'@blazediff/core-native';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 D=(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=D(()=>{xe=()=>fileURLToPath(import.meta.url),Me=()=>Pe.dirname(xe()),I=Me();});var ue={};se(ue,{ensureDir:()=>te,fileExists:()=>N,isFilePath:()=>C,isImageBuffer:()=>me,isImageData:()=>P,isRawPngBuffer:()=>B,loadPNG:()=>W,normalizeImageInput:()=>b,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 b(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 N(e){return existsSync(e)}function te(e){existsSync(e)||mkdirSync(e,{recursive:true});}var $=D(()=>{u();});function z(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,c=diff(e.data,t.data,f,n,a,{threshold:o.threshold??.1,includeAA:o.includeAA??false});return {diffCount:c,diffPercentage:c/i*100,diffOutput:f}}var re=D(()=>{u();});async function F(e,t,r,o){if(!C(e))throw new Error("Method 'core-native' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");if(!C(t))throw new Error("Method 'core-native' 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 ae=D(()=>{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=D(()=>{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 c=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,c,a,i,m);break;case "msssim":M=msssim(e.data,t.data,c,a,i,m);break;case "hitchhikers-ssim":M=hitchhikersSSIM(e.data,t.data,c,a,i,m);break;default:throw new Error(`Unknown SSIM method: ${r}`)}return {score:M,diffOutput:c}}function O(e){return e==="ssim"||e==="msssim"||e==="hitchhikers-ssim"}var q=D(()=>{u();});var de={};se(de,{compareBin:()=>F,compareCore:()=>z,compareGmsd:()=>j,compareSsim:()=>_,isSsimMethod:()=>O,runComparison:()=>T,validateMethodSupportsInput:()=>L});function L(e,t){if(e==="core-native"&&!C(t))throw new Error("Method 'core-native' 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==="core-native"){let m=await F(e,t,n,o);return {diffCount:m.diffCount,diffPercentage:m.diffPercentage}}let a=P(e)?e:await b(e),i=P(t)?t:await b(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 c=z(a,i,f,o);return {diffCount:c.diffCount,diffPercentage:c.diffPercentage,diffOutput:c.diffOutput}}var H=D(()=>{u();$();re();ae();oe();q();re();ae();oe();q();});u();H();$();u();var v={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(v.success)} ${s.green("New snapshot created")}`,` ${s.dim(v.arrow)} ${s.dim(e)}`].join(`
2
+ `)}function Ue(){return `${s.green(v.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:c,thresholdType:m,updateCommand:M}=e,p=[`${s.red(v.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))}${c} ${K}`),p.push(""),p.push(` ${s.cyan(v.info)} ${s.cyan(`Run with ${M??"--update"} to update the snapshot`)}`),p.join(`
3
+ `)}u();H();q();$();u();var x=null,We=0,he=false,k=new Map;function ve(){let e=join(I,"worker.js");if(existsSync(e))return e;let t=join(I,"../dist/worker.js");return existsSync(t)?t:null}function Ge(){if(he)return null;if(!x){let e=ve();if(!e)return he=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=Ge();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 Ne(){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,c=B(e),m=c?e:null,M=t.runInWorker!==false,p=M?le:b,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=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(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 h,d;t.method==="core-native"?(h=await T(e,a,t.method,t,f),d=e):(d=c?await p(e):e,h=await K(P(d)?d:await p(d),a,f));let Q=Ie(t.method,h,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: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(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(h.diffOutput&&t.method!=="core-native"){let w=P(d)?d:await p(d);await y(f,h.diffOutput,w.width,w.height);}return {pass:false,message:A({pass:Q,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 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,N as fileExists,A as formatReport,qe as getOrCreateSnapshot,C as isFilePath,me as isImageBuffer,P as isImageData,B as isRawPngBuffer,W as loadPNG,b as normalizeImageInput,T as runComparison,y as savePNG,R as saveRawPNGBuffer,Ne 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 codecPngjs=require('@blazediff/codec-pngjs'),core=require('@blazediff/core'),coreNative=require('@blazediff/core-native'),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 e=await codecPngjs.pngjsTransformer.read(t);return {data:new Uint8Array(e.data),width:e.width,height:e.height}}async function d(t){if(p(t))return l(t);if(D(t)){let e=Buffer.isBuffer(t)?t:Buffer.from(t),o=await codecPngjs.pngjsTransformer.read(e);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}}function y(t,e,o,a){let{width:r,height:i}=t,n=r*i;if(t.width!==e.width||t.height!==e.height)return {diffCount:n,diffPercentage:100};let m=o?new Uint8Array(n*4):void 0,s=core.diff(t.data,e.data,m,r,i,{threshold:a.threshold??.1,includeAA:a.includeAA??false});return {diffCount:s,diffPercentage:s/n*100,diffOutput:m}}async function I(t,e,o,a){if(!p(t))throw new Error("Method 'core-native' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");if(!p(e))throw new Error("Method 'core-native' only supports file paths for baseline, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");let r=await coreNative.compare(t,e,o,{threshold:a.threshold,antialiasing:a.antialiasing});if(r.match)return {diffCount:0,diffPercentage:0};if(r.reason==="layout-diff")return {diffCount:Number.MAX_SAFE_INTEGER,diffPercentage:100};if(r.reason==="pixel-diff")return {diffCount:r.diffCount,diffPercentage:r.diffPercentage};if(r.reason==="file-not-exists")throw new Error(`Image file not found: ${r.file}`);return {diffCount:0,diffPercentage:0}}function w(t,e,o,a){let{width:r,height:i}=t,n=r*i;if(t.width!==e.width||t.height!==e.height)return {score:1};let m=o?new Uint8Array(n*4):void 0;return {score:gmsd.gmsd(t.data,e.data,m,r,i,{downsample:a.downsample}),diffOutput:m}}function P(t,e,o,a,r){let{width:i,height:n}=t,m=i*n;if(t.width!==e.width||t.height!==e.height)return {score:0};let s=a?new Uint8Array(m*4):void 0,f={windowSize:r.windowSize,k1:r.k1,k2:r.k2},c;switch(o){case "ssim":c=ssim.ssim(t.data,e.data,s,i,n,f);break;case "msssim":c=msssim.msssim(t.data,e.data,s,i,n,f);break;case "hitchhikers-ssim":c=hitchhikersSsim.hitchhikersSSIM(t.data,e.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,e){if(t==="core-native"&&!p(e))throw new Error("Method 'core-native' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.")}async function b(t,e,o,a,r){if(M(o,t),M(o,e),o==="core-native"){let f=await I(t,e,r,a);return {diffCount:f.diffCount,diffPercentage:f.diffPercentage}}let i=g(t)?t:await d(t),n=g(e)?e:await d(e),m=r!==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=y(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:e}=t.payload;return d(e)}case "loadPNG":{let{filePath:e}=t.payload;return l(e)}case "compare":{let{received:e,baseline:o,method:a,options:r,diffOutputPath:i}=t.payload;return b(G(e),o,a,r,i)}default:throw new Error(`Unknown request type: ${t.type}`)}}worker_threads.parentPort&&worker_threads.parentPort.on("message",async t=>{try{let e=await N(t);worker_threads.parentPort.postMessage({id:t.id,success:!0,result:e});}catch(e){worker_threads.parentPort.postMessage({id:t.id,success:false,error:e instanceof Error?e.message:String(e)});}});
@@ -0,0 +1 @@
1
+ import {parentPort}from'worker_threads';import {existsSync}from'fs';import'path';import {pngjsTransformer}from'@blazediff/codec-pngjs';import {diff}from'@blazediff/core';import {compare}from'@blazediff/core-native';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 e=await pngjsTransformer.read(t);return {data:new Uint8Array(e.data),width:e.width,height:e.height}}async function c(t){if(d(t))return y(t);if(O(t)){let e=Buffer.isBuffer(t)?t:Buffer.from(t),o=await pngjsTransformer.read(e);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}}function I(t,e,o,a){let{width:r,height:i}=t,n=r*i;if(t.width!==e.width||t.height!==e.height)return {diffCount:n,diffPercentage:100};let m=o?new Uint8Array(n*4):void 0,s=diff(t.data,e.data,m,r,i,{threshold:a.threshold??.1,includeAA:a.includeAA??false});return {diffCount:s,diffPercentage:s/n*100,diffOutput:m}}async function w(t,e,o,a){if(!d(t))throw new Error("Method 'core-native' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");if(!d(e))throw new Error("Method 'core-native' only supports file paths for baseline, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");let r=await compare(t,e,o,{threshold:a.threshold,antialiasing:a.antialiasing});if(r.match)return {diffCount:0,diffPercentage:0};if(r.reason==="layout-diff")return {diffCount:Number.MAX_SAFE_INTEGER,diffPercentage:100};if(r.reason==="pixel-diff")return {diffCount:r.diffCount,diffPercentage:r.diffPercentage};if(r.reason==="file-not-exists")throw new Error(`Image file not found: ${r.file}`);return {diffCount:0,diffPercentage:0}}function P(t,e,o,a){let{width:r,height:i}=t,n=r*i;if(t.width!==e.width||t.height!==e.height)return {score:1};let m=o?new Uint8Array(n*4):void 0;return {score:gmsd(t.data,e.data,m,r,i,{downsample:a.downsample}),diffOutput:m}}function C(t,e,o,a,r){let{width:i,height:n}=t,m=i*n;if(t.width!==e.width||t.height!==e.height)return {score:0};let s=a?new Uint8Array(m*4):void 0,f={windowSize:r.windowSize,k1:r.k1,k2:r.k2},h;switch(o){case "ssim":h=ssim(t.data,e.data,s,i,n,f);break;case "msssim":h=msssim(t.data,e.data,s,i,n,f);break;case "hitchhikers-ssim":h=hitchhikersSSIM(t.data,e.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,e){if(t==="core-native"&&!d(e))throw new Error("Method 'core-native' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.")}async function A(t,e,o,a,r){if(b(o,t),b(o,e),o==="core-native"){let f=await w(t,e,r,a);return {diffCount:f.diffCount,diffPercentage:f.diffPercentage}}let i=l(t)?t:await c(t),n=l(e)?e:await c(e),m=r!==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=I(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:e}=t.payload;return c(e)}case "loadPNG":{let{filePath:e}=t.payload;return y(e)}case "compare":{let{received:e,baseline:o,method:a,options:r,diffOutputPath:i}=t.payload;return A(N(e),o,a,r,i)}default:throw new Error(`Unknown request type: ${t.type}`)}}parentPort&&parentPort.on("message",async t=>{try{let e=await E(t);parentPort.postMessage({id:t.id,success:!0,result:e});}catch(e){parentPort.postMessage({id:t.id,success:false,error:e instanceof Error?e.message:String(e)});}});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blazediff/matcher",
3
- "version": "1.2.5",
3
+ "version": "1.3.1",
4
4
  "description": "Core matcher logic for visual regression testing with blazediff",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -33,11 +33,11 @@
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
35
  "picocolors": "^1.1.1",
36
- "@blazediff/core": "1.9.1",
37
- "@blazediff/bin": "3.5.0",
36
+ "@blazediff/core-native": "4.0.0",
38
37
  "@blazediff/gmsd": "1.7.1",
38
+ "@blazediff/codec-pngjs": "3.0.0",
39
39
  "@blazediff/ssim": "1.7.1",
40
- "@blazediff/pngjs-transformer": "2.1.1"
40
+ "@blazediff/core": "1.9.1"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^24.3.0",