@blazediff/bin 1.4.1 → 1.6.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/dist/cli.js CHANGED
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
9
16
  var __copyProps = (to, from, except, desc) => {
10
17
  if (from && typeof from === "object" || typeof from === "function") {
11
18
  for (let key of __getOwnPropNames(from))
@@ -23,55 +30,23 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
30
  mod
24
31
  ));
25
32
 
26
- // src/index.ts
27
- var import_core = __toESM(require("@blazediff/core"));
28
- async function blazeDiffBin(image1Path, image2Path, options) {
29
- const [image1, image2] = await Promise.all([
30
- options.transformer.transform(image1Path),
31
- options.transformer.transform(image2Path)
32
- ]);
33
- if (image1.width !== image2.width || image1.height !== image2.height) {
33
+ // src/commands/diff.ts
34
+ var diff_exports = {};
35
+ __export(diff_exports, {
36
+ default: () => main
37
+ });
38
+ function parseRGB(colorStr) {
39
+ const parts = colorStr.split(",").map((s) => parseInt(s.trim(), 10));
40
+ if (parts.length !== 3 || parts.some((p) => Number.isNaN(p) || p < 0 || p > 255)) {
34
41
  throw new Error(
35
- `Image dimensions do not match: ${image1.width}x${image1.height} vs ${image2.width}x${image2.height}`
36
- );
37
- }
38
- let outputData;
39
- if (options.outputPath) {
40
- outputData = new Uint8Array(image1.data.length);
41
- }
42
- const startTime = performance.now();
43
- const diffCount = (0, import_core.default)(
44
- image1.data,
45
- image2.data,
46
- outputData,
47
- image1.width,
48
- image1.height,
49
- options.coreOptions
50
- );
51
- const duration = performance.now() - startTime;
52
- if (diffCount > 0 && options.outputPath && outputData) {
53
- await options.transformer.write(
54
- {
55
- data: outputData,
56
- width: image1.width,
57
- height: image1.height
58
- },
59
- options.outputPath
42
+ `Invalid RGB color format: ${colorStr}. Expected format: r,g,b (e.g., 255,0,0)`
60
43
  );
61
44
  }
62
- return {
63
- diffCount,
64
- width: image1.width,
65
- height: image1.height,
66
- outputData,
67
- duration
68
- };
45
+ return [parts[0], parts[1], parts[2]];
69
46
  }
70
-
71
- // src/cli.ts
72
47
  function printUsage() {
73
48
  console.log(`
74
- Usage: blazediff <image1> <image2> [options]
49
+ Usage: blazediff diff <image1> <image2> [options]
75
50
 
76
51
  Arguments:
77
52
  image1 Path to the first image
@@ -86,161 +61,822 @@ Options:
86
61
  --diff-color-alt <r,g,b> Alternative color for dark differences (default: same as diff-color)
87
62
  --include-aa Include anti-aliasing detection
88
63
  --diff-mask Draw diff over transparent background
89
- --transformer <name> Specify transformer to use (e.g. pngjs, sharp)
90
64
  --color-space <name> Specify color space to use (e.g. yiq, ycbcr)
65
+ --transformer <name> Specify transformer to use (e.g. pngjs, sharp)
91
66
  -h, --help Show this help message
92
67
 
93
68
  Examples:
94
- blazediff image1.png image2.png
95
- blazediff image1.png image2.png -o diff.png -t 0.05
96
- blazediff image1.png image2.png --threshold 0.2 --alpha 0.3
69
+ blazediff diff image1.png image2.png
70
+ blazediff diff image1.png image2.png -o diff.png -t 0.05
71
+ blazediff diff image1.png image2.png --threshold 0.2 --alpha 0.3
97
72
  `);
98
73
  }
99
- function parseRGB(colorStr) {
100
- const parts = colorStr.split(",").map((s) => parseInt(s.trim(), 10));
101
- if (parts.length !== 3 || parts.some((p) => Number.isNaN(p) || p < 0 || p > 255)) {
102
- throw new Error(
103
- `Invalid RGB color format: ${colorStr}. Expected format: r,g,b (e.g., 255,0,0)`
74
+ async function main() {
75
+ try {
76
+ const args = process.argv.slice(2);
77
+ if (args.length === 0 || args.includes("-h") || args.includes("--help")) {
78
+ printUsage();
79
+ process.exit(0);
80
+ }
81
+ if (args.length < 2) {
82
+ console.error("Error: Two image paths are required");
83
+ printUsage();
84
+ process.exit(1);
85
+ }
86
+ const image1 = args[0];
87
+ const image2 = args[1];
88
+ const options = {};
89
+ for (let i = 2; i < args.length; i++) {
90
+ const arg = args[i];
91
+ const nextArg = args[i + 1];
92
+ switch (arg) {
93
+ case "-o":
94
+ case "--output":
95
+ if (nextArg) {
96
+ options.outputPath = nextArg;
97
+ i++;
98
+ }
99
+ break;
100
+ case "-t":
101
+ case "--threshold":
102
+ if (nextArg) {
103
+ const threshold = parseFloat(nextArg);
104
+ if (Number.isNaN(threshold) || threshold < 0 || threshold > 1) {
105
+ throw new Error(
106
+ `Invalid threshold: ${nextArg}. Must be between 0 and 1`
107
+ );
108
+ }
109
+ options.threshold = threshold;
110
+ i++;
111
+ }
112
+ break;
113
+ case "-a":
114
+ case "--alpha":
115
+ if (nextArg) {
116
+ const alpha = parseFloat(nextArg);
117
+ if (Number.isNaN(alpha) || alpha < 0 || alpha > 1) {
118
+ throw new Error(
119
+ `Invalid alpha: ${nextArg}. Must be between 0 and 1`
120
+ );
121
+ }
122
+ options.alpha = alpha;
123
+ i++;
124
+ }
125
+ break;
126
+ case "--aa-color":
127
+ if (nextArg) {
128
+ options.aaColor = parseRGB(nextArg);
129
+ i++;
130
+ }
131
+ break;
132
+ case "--diff-color":
133
+ if (nextArg) {
134
+ options.diffColor = parseRGB(nextArg);
135
+ i++;
136
+ }
137
+ break;
138
+ case "--diff-color-alt":
139
+ if (nextArg) {
140
+ options.diffColorAlt = parseRGB(nextArg);
141
+ i++;
142
+ }
143
+ break;
144
+ case "--include-aa":
145
+ options.includeAA = true;
146
+ break;
147
+ case "--diff-mask":
148
+ options.diffMask = true;
149
+ break;
150
+ case "--transformer":
151
+ if (nextArg) {
152
+ options.transformer = nextArg;
153
+ i++;
154
+ }
155
+ break;
156
+ case "--color-space":
157
+ if (nextArg) {
158
+ options.colorSpace = nextArg;
159
+ i++;
160
+ }
161
+ break;
162
+ default:
163
+ console.error(`Unknown option: ${arg}`);
164
+ printUsage();
165
+ process.exit(1);
166
+ }
167
+ }
168
+ const transformer = await getTransformer(
169
+ options.transformer
170
+ );
171
+ const [img1, img2] = await Promise.all([
172
+ transformer.transform(image1),
173
+ transformer.transform(image2)
174
+ ]);
175
+ if (img1.width !== img2.width || img1.height !== img2.height) {
176
+ throw new Error(
177
+ `Image dimensions do not match: ${img1.width}x${img1.height} vs ${img2.width}x${img2.height}`
178
+ );
179
+ }
180
+ let outputData;
181
+ if (options.outputPath) {
182
+ outputData = new Uint8Array(img1.data.length);
183
+ }
184
+ const coreOptions = {
185
+ threshold: options.threshold,
186
+ alpha: options.alpha,
187
+ aaColor: options.aaColor,
188
+ diffColor: options.diffColor,
189
+ diffColorAlt: options.diffColorAlt,
190
+ includeAA: options.includeAA,
191
+ diffMask: options.diffMask
192
+ };
193
+ const startTime = performance.now();
194
+ const diffCount = (0, import_core.default)(
195
+ img1.data,
196
+ img2.data,
197
+ outputData,
198
+ img1.width,
199
+ img1.height,
200
+ coreOptions
104
201
  );
202
+ const duration = performance.now() - startTime;
203
+ if (diffCount > 0 && options.outputPath && outputData) {
204
+ await transformer.write(
205
+ {
206
+ data: outputData,
207
+ width: img1.width,
208
+ height: img1.height
209
+ },
210
+ options.outputPath
211
+ );
212
+ }
213
+ const result = {
214
+ diffCount,
215
+ width: img1.width,
216
+ height: img1.height,
217
+ duration
218
+ };
219
+ console.log(`completed in: ${result.duration.toFixed(2)}ms`);
220
+ console.log(`dimensions: ${result.width}x${result.height}`);
221
+ console.log(`different pixels: ${result.diffCount}`);
222
+ console.log(
223
+ `error: ${(result.diffCount / (result.width * result.height) * 100).toFixed(2)}%`
224
+ );
225
+ if (result.diffCount > 0 && outputData && options.outputPath) {
226
+ console.log(`diff image: ${options.outputPath}`);
227
+ }
228
+ if (result.diffCount > 0) {
229
+ process.exit(1);
230
+ } else {
231
+ console.log(`Images are identical!`);
232
+ process.exit(0);
233
+ }
234
+ } catch (error) {
235
+ console.error(
236
+ "Error:",
237
+ error instanceof Error ? error.message : String(error)
238
+ );
239
+ process.exit(1);
105
240
  }
106
- return [parts[0], parts[1], parts[2]];
107
241
  }
108
- function parseArgs() {
109
- const args = process.argv.slice(2);
110
- if (args.length === 0 || args.includes("-h") || args.includes("--help")) {
111
- printUsage();
112
- process.exit(0);
242
+ var import_core, getTransformer;
243
+ var init_diff = __esm({
244
+ "src/commands/diff.ts"() {
245
+ "use strict";
246
+ import_core = __toESM(require("@blazediff/core"));
247
+ getTransformer = async (transformer) => {
248
+ if (!transformer || transformer === "pngjs") {
249
+ const { default: transformer2 } = await import("@blazediff/pngjs-transformer");
250
+ return transformer2;
251
+ }
252
+ if (transformer === "sharp") {
253
+ const { default: transformer2 } = await import("@blazediff/sharp-transformer");
254
+ return transformer2;
255
+ }
256
+ throw new Error(`Unknown transformer: ${transformer}`);
257
+ };
258
+ if (typeof require !== "undefined" && require.main === module) {
259
+ main();
260
+ }
113
261
  }
114
- if (args.length < 2) {
115
- console.error("Error: Two image paths are required");
116
- printUsage();
262
+ });
263
+
264
+ // src/commands/gmsd.ts
265
+ var gmsd_exports = {};
266
+ __export(gmsd_exports, {
267
+ default: () => main2
268
+ });
269
+ function printUsage2() {
270
+ console.log(`
271
+ Usage: blazediff gmsd <image1> <image2> [options]
272
+
273
+ Arguments:
274
+ image1 Path to the first image
275
+ image2 Path to the second image
276
+
277
+ Options:
278
+ -o, --output <path> Output path for GMS similarity map (grayscale visualization)
279
+ --downsample <0|1> Downsample factor: 0=full-res, 1=2x downsample (default: 0)
280
+ --gmsd-c <num> Stability constant for GMSD (default: 170)
281
+ --transformer <name> Specify transformer to use (e.g. pngjs, sharp)
282
+ -h, --help Show this help message
283
+
284
+ Examples:
285
+ blazediff gmsd image1.png image2.png
286
+ blazediff gmsd image1.png image2.png --downsample 1
287
+ blazediff gmsd image1.png image2.png -o gms-map.png
288
+ `);
289
+ }
290
+ async function main2() {
291
+ try {
292
+ const args = process.argv.slice(2);
293
+ if (args.length === 0 || args.includes("-h") || args.includes("--help")) {
294
+ printUsage2();
295
+ process.exit(0);
296
+ }
297
+ if (args.length < 2) {
298
+ console.error("Error: Two image paths are required");
299
+ printUsage2();
300
+ process.exit(1);
301
+ }
302
+ const image1 = args[0];
303
+ const image2 = args[1];
304
+ const options = {};
305
+ for (let i = 2; i < args.length; i++) {
306
+ const arg = args[i];
307
+ const nextArg = args[i + 1];
308
+ switch (arg) {
309
+ case "-o":
310
+ case "--output":
311
+ if (nextArg) {
312
+ options.outputPath = nextArg;
313
+ i++;
314
+ }
315
+ break;
316
+ case "--downsample":
317
+ if (nextArg) {
318
+ const downsample = parseInt(nextArg, 10);
319
+ if (downsample !== 0 && downsample !== 1) {
320
+ throw new Error(`Invalid downsample: ${nextArg}. Must be 0 or 1`);
321
+ }
322
+ options.downsample = downsample;
323
+ i++;
324
+ }
325
+ break;
326
+ case "--gmsd-c":
327
+ if (nextArg) {
328
+ const gmsdC = parseFloat(nextArg);
329
+ if (Number.isNaN(gmsdC) || gmsdC <= 0) {
330
+ throw new Error(
331
+ `Invalid gmsd-c: ${nextArg}. Must be a positive number`
332
+ );
333
+ }
334
+ options.gmsdC = gmsdC;
335
+ i++;
336
+ }
337
+ break;
338
+ case "--transformer":
339
+ if (nextArg) {
340
+ options.transformer = nextArg;
341
+ i++;
342
+ }
343
+ break;
344
+ default:
345
+ console.error(`Unknown option: ${arg}`);
346
+ printUsage2();
347
+ process.exit(1);
348
+ }
349
+ }
350
+ const transformer = await getTransformer2(
351
+ options.transformer
352
+ );
353
+ const [img1, img2] = await Promise.all([
354
+ transformer.transform(image1),
355
+ transformer.transform(image2)
356
+ ]);
357
+ if (img1.width !== img2.width || img1.height !== img2.height) {
358
+ throw new Error(
359
+ `Image dimensions do not match: ${img1.width}x${img1.height} vs ${img2.width}x${img2.height}`
360
+ );
361
+ }
362
+ let outputData;
363
+ if (options.outputPath) {
364
+ outputData = new Uint8Array(img1.data.length);
365
+ }
366
+ const gmsdOptions = {
367
+ downsample: options.downsample || 0,
368
+ c: options.gmsdC
369
+ };
370
+ const startTime = performance.now();
371
+ const score = (0, import_gmsd.default)(
372
+ img1.data,
373
+ img2.data,
374
+ outputData,
375
+ img1.width,
376
+ img1.height,
377
+ gmsdOptions
378
+ );
379
+ const duration = performance.now() - startTime;
380
+ if (options.outputPath && outputData) {
381
+ await transformer.write(
382
+ {
383
+ data: outputData,
384
+ width: img1.width,
385
+ height: img1.height
386
+ },
387
+ options.outputPath
388
+ );
389
+ }
390
+ const result = {
391
+ score,
392
+ width: img1.width,
393
+ height: img1.height,
394
+ duration
395
+ };
396
+ console.log(`completed in: ${result.duration.toFixed(2)}ms`);
397
+ console.log(`dimensions: ${result.width}x${result.height}`);
398
+ console.log(
399
+ `similarity score: ${result.score.toFixed(6)} (0=different, 1=identical)`
400
+ );
401
+ console.log(`similarity: ${(result.score * 100).toFixed(2)}%`);
402
+ if (options.outputPath && outputData) {
403
+ console.log(`GMS map saved to: ${options.outputPath}`);
404
+ }
405
+ if (result.score < 0.95) {
406
+ process.exit(1);
407
+ } else {
408
+ console.log(`Images are highly similar!`);
409
+ process.exit(0);
410
+ }
411
+ } catch (error) {
412
+ console.error(
413
+ "Error:",
414
+ error instanceof Error ? error.message : String(error)
415
+ );
117
416
  process.exit(1);
118
417
  }
119
- const image1 = args[0];
120
- const image2 = args[1];
121
- const options = {};
122
- for (let i = 2; i < args.length; i++) {
123
- const arg = args[i];
124
- const nextArg = args[i + 1];
125
- switch (arg) {
126
- case "-o":
127
- case "--output":
128
- if (nextArg) {
129
- options.outputPath = nextArg;
130
- i++;
131
- }
132
- break;
133
- case "-t":
134
- case "--threshold":
135
- if (nextArg) {
136
- const threshold = parseFloat(nextArg);
137
- if (Number.isNaN(threshold) || threshold < 0 || threshold > 1) {
138
- throw new Error(
139
- `Invalid threshold: ${nextArg}. Must be between 0 and 1`
140
- );
418
+ }
419
+ var import_gmsd, getTransformer2;
420
+ var init_gmsd = __esm({
421
+ "src/commands/gmsd.ts"() {
422
+ "use strict";
423
+ import_gmsd = __toESM(require("@blazediff/gmsd"));
424
+ getTransformer2 = async (transformer) => {
425
+ if (!transformer || transformer === "pngjs") {
426
+ const { default: transformer2 } = await import("@blazediff/pngjs-transformer");
427
+ return transformer2;
428
+ }
429
+ if (transformer === "sharp") {
430
+ const { default: transformer2 } = await import("@blazediff/sharp-transformer");
431
+ return transformer2;
432
+ }
433
+ throw new Error(`Unknown transformer: ${transformer}`);
434
+ };
435
+ if (typeof require !== "undefined" && require.main === module) {
436
+ main2();
437
+ }
438
+ }
439
+ });
440
+
441
+ // src/commands/ssim.ts
442
+ var ssim_exports = {};
443
+ __export(ssim_exports, {
444
+ default: () => main3
445
+ });
446
+ function printUsage3() {
447
+ console.log(`
448
+ Usage: blazediff ssim <image1> <image2> [options]
449
+
450
+ Arguments:
451
+ image1 Path to the first image
452
+ image2 Path to the second image
453
+
454
+ Options:
455
+ -o, --output <path> Output path for SSIM map visualization
456
+ --transformer <name> Specify transformer to use (e.g. pngjs, sharp)
457
+ -h, --help Show this help message
458
+
459
+ Examples:
460
+ blazediff ssim image1.png image2.png
461
+ blazediff ssim image1.png image2.png -o ssim-map.png
462
+ `);
463
+ }
464
+ async function main3() {
465
+ try {
466
+ const args = process.argv.slice(2);
467
+ if (args.length === 0 || args.includes("-h") || args.includes("--help")) {
468
+ printUsage3();
469
+ process.exit(0);
470
+ }
471
+ if (args.length < 2) {
472
+ console.error("Error: Two image paths are required");
473
+ printUsage3();
474
+ process.exit(1);
475
+ }
476
+ const image1 = args[0];
477
+ const image2 = args[1];
478
+ const options = {};
479
+ for (let i = 2; i < args.length; i++) {
480
+ const arg = args[i];
481
+ const nextArg = args[i + 1];
482
+ switch (arg) {
483
+ case "-o":
484
+ case "--output":
485
+ if (nextArg) {
486
+ options.outputPath = nextArg;
487
+ i++;
141
488
  }
142
- options.threshold = threshold;
143
- i++;
144
- }
145
- break;
146
- case "-a":
147
- case "--alpha":
148
- if (nextArg) {
149
- const alpha = parseFloat(nextArg);
150
- if (Number.isNaN(alpha) || alpha < 0 || alpha > 1) {
151
- throw new Error(
152
- `Invalid alpha: ${nextArg}. Must be between 0 and 1`
153
- );
489
+ break;
490
+ case "--transformer":
491
+ if (nextArg) {
492
+ options.transformer = nextArg;
493
+ i++;
154
494
  }
155
- options.alpha = alpha;
156
- i++;
157
- }
158
- break;
159
- case "--aa-color":
160
- if (nextArg) {
161
- options.aaColor = parseRGB(nextArg);
162
- i++;
163
- }
164
- break;
165
- case "--diff-color":
166
- if (nextArg) {
167
- options.diffColor = parseRGB(nextArg);
168
- i++;
169
- }
170
- break;
171
- case "--diff-color-alt":
172
- if (nextArg) {
173
- options.diffColorAlt = parseRGB(nextArg);
174
- i++;
175
- }
176
- break;
177
- case "--include-aa":
178
- options.includeAA = true;
179
- break;
180
- case "--diff-mask":
181
- options.diffMask = true;
182
- break;
183
- case "--transformer":
184
- if (nextArg) {
185
- options.transformer = nextArg;
186
- i++;
187
- }
188
- break;
189
- case "--colorSpace":
190
- if (nextArg) {
191
- options.colorSpace = nextArg;
192
- i++;
193
- }
194
- break;
195
- default:
196
- console.error(`Unknown option: ${arg}`);
197
- printUsage();
198
- process.exit(1);
495
+ break;
496
+ default:
497
+ console.error(`Unknown option: ${arg}`);
498
+ printUsage3();
499
+ process.exit(1);
500
+ }
199
501
  }
502
+ const transformer = await getTransformer3(options.transformer);
503
+ const [img1, img2] = await Promise.all([
504
+ transformer.transform(image1),
505
+ transformer.transform(image2)
506
+ ]);
507
+ if (img1.width !== img2.width || img1.height !== img2.height) {
508
+ throw new Error(
509
+ `Image dimensions do not match: ${img1.width}x${img1.height} vs ${img2.width}x${img2.height}`
510
+ );
511
+ }
512
+ let outputData;
513
+ if (options.outputPath) {
514
+ outputData = new Uint8Array(img1.data.length);
515
+ }
516
+ const startTime = performance.now();
517
+ const score = (0, import_ssim.default)(img1.data, img2.data, outputData, img1.width, img1.height, {});
518
+ const duration = performance.now() - startTime;
519
+ if (options.outputPath && outputData) {
520
+ await transformer.write(
521
+ {
522
+ data: outputData,
523
+ width: img1.width,
524
+ height: img1.height
525
+ },
526
+ options.outputPath
527
+ );
528
+ }
529
+ const result = {
530
+ score,
531
+ width: img1.width,
532
+ height: img1.height,
533
+ duration
534
+ };
535
+ console.log(`completed in: ${result.duration.toFixed(2)}ms`);
536
+ console.log(`dimensions: ${result.width}x${result.height}`);
537
+ console.log(
538
+ `SSIM score: ${result.score.toFixed(6)} (0=different, 1=identical)`
539
+ );
540
+ console.log(`similarity: ${(result.score * 100).toFixed(2)}%`);
541
+ if (options.outputPath && outputData) {
542
+ console.log(`SSIM map saved to: ${options.outputPath}`);
543
+ }
544
+ if (result.score < 0.95) {
545
+ process.exit(1);
546
+ } else {
547
+ console.log(`Images are highly similar!`);
548
+ process.exit(0);
549
+ }
550
+ } catch (error) {
551
+ console.error(
552
+ "Error:",
553
+ error instanceof Error ? error.message : String(error)
554
+ );
555
+ process.exit(1);
200
556
  }
201
- return { image1, image2, options };
202
557
  }
203
- var getTransformer = async (transformer) => {
204
- if (!transformer || transformer === "pngjs") {
205
- const { default: transformer2 } = await import("@blazediff/pngjs-transformer");
206
- return transformer2;
558
+ var import_ssim, getTransformer3;
559
+ var init_ssim = __esm({
560
+ "src/commands/ssim.ts"() {
561
+ "use strict";
562
+ import_ssim = __toESM(require("@blazediff/ssim/ssim"));
563
+ getTransformer3 = async (transformer) => {
564
+ if (!transformer || transformer === "pngjs") {
565
+ const { default: transformer2 } = await import("@blazediff/pngjs-transformer");
566
+ return transformer2;
567
+ }
568
+ if (transformer === "sharp") {
569
+ const { default: transformer2 } = await import("@blazediff/sharp-transformer");
570
+ return transformer2;
571
+ }
572
+ throw new Error(`Unknown transformer: ${transformer}`);
573
+ };
574
+ if (typeof require !== "undefined" && require.main === module) {
575
+ main3();
576
+ }
207
577
  }
208
- if (transformer === "sharp") {
209
- const { default: transformer2 } = await import("@blazediff/sharp-transformer");
210
- return transformer2;
578
+ });
579
+
580
+ // src/commands/msssim.ts
581
+ var msssim_exports = {};
582
+ __export(msssim_exports, {
583
+ default: () => main4
584
+ });
585
+ function printUsage4() {
586
+ console.log(`
587
+ Usage: blazediff msssim <image1> <image2> [options]
588
+
589
+ Arguments:
590
+ image1 Path to the first image
591
+ image2 Path to the second image
592
+
593
+ Options:
594
+ -o, --output <path> Output path for MS-SSIM map visualization
595
+ --transformer <name> Specify transformer to use (e.g. pngjs, sharp)
596
+ -h, --help Show this help message
597
+
598
+ Examples:
599
+ blazediff msssim image1.png image2.png
600
+ blazediff msssim image1.png image2.png -o msssim-map.png
601
+ `);
602
+ }
603
+ async function main4() {
604
+ try {
605
+ const args = process.argv.slice(2);
606
+ if (args.length === 0 || args.includes("-h") || args.includes("--help")) {
607
+ printUsage4();
608
+ process.exit(0);
609
+ }
610
+ if (args.length < 2) {
611
+ console.error("Error: Two image paths are required");
612
+ printUsage4();
613
+ process.exit(1);
614
+ }
615
+ const image1 = args[0];
616
+ const image2 = args[1];
617
+ const options = {};
618
+ for (let i = 2; i < args.length; i++) {
619
+ const arg = args[i];
620
+ const nextArg = args[i + 1];
621
+ switch (arg) {
622
+ case "-o":
623
+ case "--output":
624
+ if (nextArg) {
625
+ options.outputPath = nextArg;
626
+ i++;
627
+ }
628
+ break;
629
+ case "--transformer":
630
+ if (nextArg) {
631
+ options.transformer = nextArg;
632
+ i++;
633
+ }
634
+ break;
635
+ default:
636
+ console.error(`Unknown option: ${arg}`);
637
+ printUsage4();
638
+ process.exit(1);
639
+ }
640
+ }
641
+ const transformer = await getTransformer4(options.transformer);
642
+ const [img1, img2] = await Promise.all([
643
+ transformer.transform(image1),
644
+ transformer.transform(image2)
645
+ ]);
646
+ if (img1.width !== img2.width || img1.height !== img2.height) {
647
+ throw new Error(
648
+ `Image dimensions do not match: ${img1.width}x${img1.height} vs ${img2.width}x${img2.height}`
649
+ );
650
+ }
651
+ let outputData;
652
+ if (options.outputPath) {
653
+ outputData = new Uint8Array(img1.data.length);
654
+ }
655
+ const startTime = performance.now();
656
+ const score = (0, import_msssim.default)(img1.data, img2.data, outputData, img1.width, img1.height, {});
657
+ const duration = performance.now() - startTime;
658
+ if (options.outputPath && outputData) {
659
+ await transformer.write(
660
+ {
661
+ data: outputData,
662
+ width: img1.width,
663
+ height: img1.height
664
+ },
665
+ options.outputPath
666
+ );
667
+ }
668
+ const result = {
669
+ score,
670
+ width: img1.width,
671
+ height: img1.height,
672
+ duration
673
+ };
674
+ console.log(`completed in: ${result.duration.toFixed(2)}ms`);
675
+ console.log(`dimensions: ${result.width}x${result.height}`);
676
+ console.log(
677
+ `MS-SSIM score: ${result.score.toFixed(6)} (0=different, 1=identical)`
678
+ );
679
+ console.log(`similarity: ${(result.score * 100).toFixed(2)}%`);
680
+ if (options.outputPath && outputData) {
681
+ console.log(`MS-SSIM map saved to: ${options.outputPath}`);
682
+ }
683
+ if (result.score < 0.95) {
684
+ process.exit(1);
685
+ } else {
686
+ console.log(`Images are highly similar!`);
687
+ process.exit(0);
688
+ }
689
+ } catch (error) {
690
+ console.error(
691
+ "Error:",
692
+ error instanceof Error ? error.message : String(error)
693
+ );
694
+ process.exit(1);
211
695
  }
212
- throw new Error(`Unknown transformer: ${transformer}`);
213
- };
214
- async function main() {
696
+ }
697
+ var import_msssim, getTransformer4;
698
+ var init_msssim = __esm({
699
+ "src/commands/msssim.ts"() {
700
+ "use strict";
701
+ import_msssim = __toESM(require("@blazediff/ssim/msssim"));
702
+ getTransformer4 = async (transformer) => {
703
+ if (!transformer || transformer === "pngjs") {
704
+ const { default: transformer2 } = await import("@blazediff/pngjs-transformer");
705
+ return transformer2;
706
+ }
707
+ if (transformer === "sharp") {
708
+ const { default: transformer2 } = await import("@blazediff/sharp-transformer");
709
+ return transformer2;
710
+ }
711
+ throw new Error(`Unknown transformer: ${transformer}`);
712
+ };
713
+ if (typeof require !== "undefined" && require.main === module) {
714
+ main4();
715
+ }
716
+ }
717
+ });
718
+
719
+ // src/commands/hitchhikers-ssim.ts
720
+ var hitchhikers_ssim_exports = {};
721
+ __export(hitchhikers_ssim_exports, {
722
+ default: () => main5
723
+ });
724
+ function printUsage5() {
725
+ console.log(`
726
+ Usage: blazediff hitchhikers-ssim <image1> <image2> [options]
727
+
728
+ Arguments:
729
+ image1 Path to the first image
730
+ image2 Path to the second image
731
+
732
+ Options:
733
+ -o, --output <path> Output path for SSIM map visualization
734
+ --transformer <name> Specify transformer to use (e.g. pngjs, sharp)
735
+ --window-size <size> Window size (default: 11)
736
+ --window-stride <size> Window stride (default: windowSize for non-overlapping)
737
+ --no-cov-pooling Use mean pooling instead of CoV pooling
738
+ -h, --help Show this help message
739
+
740
+ About Hitchhiker's SSIM:
741
+ - Uses rectangular windows with integral images (summed area tables)
742
+ - O(1) window computation regardless of window size
743
+ - Coefficient of Variation (CoV) pooling by default
744
+ - Significantly faster than Gaussian-based SSIM
745
+
746
+ Reference: "A Hitchhiker's Guide to Structural Similarity"
747
+ (IEEE Access, 2021) by Venkataramanan et al.
748
+
749
+ Examples:
750
+ blazediff hitchhikers-ssim image1.png image2.png
751
+ blazediff hitchhikers-ssim image1.png image2.png -o ssim-map.png
752
+ blazediff hitchhikers-ssim image1.png image2.png --window-size 16 --window-stride 8
753
+ blazediff hitchhikers-ssim image1.png image2.png --no-cov-pooling
754
+ `);
755
+ }
756
+ async function main5() {
215
757
  try {
216
- const { image1, image2, options } = parseArgs();
217
- const transformer = await getTransformer(options.transformer);
218
- const result = await blazeDiffBin(image1, image2, {
219
- outputPath: options.outputPath,
220
- transformer,
221
- coreOptions: {
222
- threshold: options.threshold,
223
- alpha: options.alpha,
224
- aaColor: options.aaColor,
225
- diffColor: options.diffColor,
226
- diffColorAlt: options.diffColorAlt,
227
- includeAA: options.includeAA,
228
- diffMask: options.diffMask
758
+ const args = process.argv.slice(2);
759
+ if (args.length === 0 || args.includes("-h") || args.includes("--help")) {
760
+ printUsage5();
761
+ process.exit(0);
762
+ }
763
+ if (args.length < 2) {
764
+ console.error("Error: Two image paths are required");
765
+ printUsage5();
766
+ process.exit(1);
767
+ }
768
+ const image1 = args[0];
769
+ const image2 = args[1];
770
+ const options = {
771
+ covPooling: true
772
+ };
773
+ for (let i = 2; i < args.length; i++) {
774
+ const arg = args[i];
775
+ const nextArg = args[i + 1];
776
+ switch (arg) {
777
+ case "-o":
778
+ case "--output":
779
+ if (nextArg) {
780
+ options.outputPath = nextArg;
781
+ i++;
782
+ }
783
+ break;
784
+ case "--transformer":
785
+ if (nextArg) {
786
+ options.transformer = nextArg;
787
+ i++;
788
+ }
789
+ break;
790
+ case "--window-size":
791
+ if (nextArg) {
792
+ const size = Number.parseInt(nextArg, 10);
793
+ if (Number.isNaN(size) || size <= 0) {
794
+ throw new Error(`Invalid window size: ${nextArg}`);
795
+ }
796
+ options.windowSize = size;
797
+ i++;
798
+ }
799
+ break;
800
+ case "--window-stride":
801
+ if (nextArg) {
802
+ const stride = Number.parseInt(nextArg, 10);
803
+ if (Number.isNaN(stride) || stride <= 0) {
804
+ throw new Error(`Invalid window stride: ${nextArg}`);
805
+ }
806
+ options.windowStride = stride;
807
+ i++;
808
+ }
809
+ break;
810
+ case "--no-cov-pooling":
811
+ options.covPooling = false;
812
+ break;
813
+ default:
814
+ console.error(`Unknown option: ${arg}`);
815
+ printUsage5();
816
+ process.exit(1);
229
817
  }
230
- });
231
- console.log(`matched in: ${result.duration.toFixed(2)}ms`);
818
+ }
819
+ const transformer = await getTransformer5(options.transformer);
820
+ const [img1, img2] = await Promise.all([
821
+ transformer.transform(image1),
822
+ transformer.transform(image2)
823
+ ]);
824
+ if (img1.width !== img2.width || img1.height !== img2.height) {
825
+ throw new Error(
826
+ `Image dimensions do not match: ${img1.width}x${img1.height} vs ${img2.width}x${img2.height}`
827
+ );
828
+ }
829
+ let outputData;
830
+ if (options.outputPath) {
831
+ outputData = new Uint8Array(img1.data.length);
832
+ }
833
+ const ssimOptions = {};
834
+ if (typeof options.windowSize === "number") {
835
+ ssimOptions.windowSize = options.windowSize;
836
+ }
837
+ if (typeof options.windowStride === "number") {
838
+ ssimOptions.windowStride = options.windowStride;
839
+ }
840
+ if (typeof options.covPooling === "boolean") {
841
+ ssimOptions.covPooling = options.covPooling;
842
+ }
843
+ const startTime = performance.now();
844
+ const score = (0, import_hitchhikers_ssim.default)(img1.data, img2.data, outputData, img1.width, img1.height, ssimOptions);
845
+ const duration = performance.now() - startTime;
846
+ if (options.outputPath && outputData) {
847
+ await transformer.write(
848
+ {
849
+ data: outputData,
850
+ width: img1.width,
851
+ height: img1.height
852
+ },
853
+ options.outputPath
854
+ );
855
+ }
856
+ const result = {
857
+ score,
858
+ width: img1.width,
859
+ height: img1.height,
860
+ duration,
861
+ poolingMethod: ssimOptions.covPooling === false ? "mean" : "CoV",
862
+ windowSize: ssimOptions.windowSize || 11,
863
+ windowStride: ssimOptions.windowStride || ssimOptions.windowSize || 11
864
+ };
865
+ console.log(`completed in: ${result.duration.toFixed(2)}ms`);
232
866
  console.log(`dimensions: ${result.width}x${result.height}`);
233
- console.log(`different pixels: ${result.diffCount}`);
867
+ console.log(`window: ${result.windowSize}x${result.windowSize} (stride: ${result.windowStride})`);
868
+ console.log(`pooling: ${result.poolingMethod}`);
234
869
  console.log(
235
- `error: ${(result.diffCount / (result.width * result.height) * 100).toFixed(2)}%`
870
+ `Hitchhiker's SSIM score: ${result.score.toFixed(6)} (0=different, 1=identical)`
236
871
  );
237
- if (result.diffCount > 0 && result.outputData) {
238
- console.log(`diff image: ${options.outputPath}`);
872
+ console.log(`similarity: ${(result.score * 100).toFixed(2)}%`);
873
+ if (options.outputPath && outputData) {
874
+ console.log(`SSIM map saved to: ${options.outputPath}`);
239
875
  }
240
- if (result.diffCount > 0) {
876
+ if (result.score < 0.95) {
241
877
  process.exit(1);
242
878
  } else {
243
- console.log(`Images are identical!`);
879
+ console.log(`Images are highly similar!`);
244
880
  process.exit(0);
245
881
  }
246
882
  } catch (error) {
@@ -251,6 +887,93 @@ async function main() {
251
887
  process.exit(1);
252
888
  }
253
889
  }
890
+ var import_hitchhikers_ssim, getTransformer5;
891
+ var init_hitchhikers_ssim = __esm({
892
+ "src/commands/hitchhikers-ssim.ts"() {
893
+ "use strict";
894
+ import_hitchhikers_ssim = __toESM(require("@blazediff/ssim/hitchhikers-ssim"));
895
+ getTransformer5 = async (transformer) => {
896
+ if (!transformer || transformer === "pngjs") {
897
+ const { default: transformer2 } = await import("@blazediff/pngjs-transformer");
898
+ return transformer2;
899
+ }
900
+ if (transformer === "sharp") {
901
+ const { default: transformer2 } = await import("@blazediff/sharp-transformer");
902
+ return transformer2;
903
+ }
904
+ throw new Error(`Unknown transformer: ${transformer}`);
905
+ };
906
+ if (typeof require !== "undefined" && require.main === module) {
907
+ main5();
908
+ }
909
+ }
910
+ });
911
+
912
+ // src/cli.ts
913
+ function printUsage6() {
914
+ console.log(`
915
+ Usage: blazediff <command> <image1> <image2> [options]
916
+
917
+ Commands:
918
+ diff Pixel-by-pixel comparison (default)
919
+ gmsd Gradient Magnitude Similarity Deviation metric
920
+ ssim Structural Similarity Index (Gaussian-based)
921
+ msssim Multi-Scale Structural Similarity Index
922
+ hitchhikers-ssim Hitchhiker's SSIM (fast, integral image-based)
923
+
924
+ Options:
925
+ -h, --help Show this help message
926
+
927
+ Examples:
928
+ blazediff diff image1.png image2.png -o diff.png
929
+ blazediff gmsd image1.png image2.png
930
+ blazediff ssim image1.png image2.png -o ssim-map.png
931
+ blazediff msssim image1.png image2.png
932
+ blazediff hitchhikers-ssim image1.png image2.png
933
+
934
+ # Default command (diff) if no command specified
935
+ blazediff image1.png image2.png
936
+
937
+ For command-specific help, use:
938
+ blazediff <command> --help
939
+ `);
940
+ }
941
+ async function main6() {
942
+ const args = process.argv.slice(2);
943
+ if (args.length === 0) {
944
+ printUsage6();
945
+ process.exit(0);
946
+ }
947
+ const command = args[0];
948
+ const validCommands = ["diff", "gmsd", "ssim", "msssim", "hitchhikers-ssim"];
949
+ const isCommand = validCommands.includes(command);
950
+ if ((args.includes("-h") || args.includes("--help")) && !isCommand) {
951
+ printUsage6();
952
+ process.exit(0);
953
+ }
954
+ if (isCommand) {
955
+ process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
956
+ switch (command) {
957
+ case "diff":
958
+ await Promise.resolve().then(() => (init_diff(), diff_exports));
959
+ break;
960
+ case "gmsd":
961
+ await Promise.resolve().then(() => (init_gmsd(), gmsd_exports));
962
+ break;
963
+ case "ssim":
964
+ await Promise.resolve().then(() => (init_ssim(), ssim_exports));
965
+ break;
966
+ case "msssim":
967
+ await Promise.resolve().then(() => (init_msssim(), msssim_exports));
968
+ break;
969
+ case "hitchhikers-ssim":
970
+ await Promise.resolve().then(() => (init_hitchhikers_ssim(), hitchhikers_ssim_exports));
971
+ break;
972
+ }
973
+ } else {
974
+ await Promise.resolve().then(() => (init_diff(), diff_exports));
975
+ }
976
+ }
254
977
  if (typeof require !== "undefined" && require.main === module) {
255
- main();
978
+ main6();
256
979
  }