@blazediff/bin 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -12
- package/dist/cli.js +898 -294
- package/dist/commands/diff.js +255 -0
- package/dist/commands/gmsd.js +201 -0
- package/dist/commands/hitchhikers-ssim.js +217 -0
- package/dist/commands/msssim.js +163 -0
- package/dist/commands/ssim.js +163 -0
- package/dist/index.d.mts +34 -11
- package/dist/index.d.ts +34 -11
- package/dist/index.js +1 -1
- package/dist/index.mjs +0 -1
- package/package.json +6 -5
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,105 +30,29 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
30
|
mod
|
|
24
31
|
));
|
|
25
32
|
|
|
26
|
-
// src/
|
|
27
|
-
var
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
options.transformer.transform(image1Path),
|
|
35
|
-
options.transformer.transform(image2Path)
|
|
36
|
-
]);
|
|
37
|
-
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)) {
|
|
38
41
|
throw new Error(
|
|
39
|
-
`
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
if (isGmsdModeOptions(options)) {
|
|
43
|
-
let outputData2;
|
|
44
|
-
if (options.outputPath) {
|
|
45
|
-
outputData2 = new Uint8Array(image1.data.length);
|
|
46
|
-
}
|
|
47
|
-
const startTime2 = performance.now();
|
|
48
|
-
const score = (0, import_gmsd.default)(
|
|
49
|
-
image1.data,
|
|
50
|
-
image2.data,
|
|
51
|
-
outputData2,
|
|
52
|
-
image1.width,
|
|
53
|
-
image1.height,
|
|
54
|
-
options.options || {}
|
|
55
|
-
);
|
|
56
|
-
const duration2 = performance.now() - startTime2;
|
|
57
|
-
if (options.outputPath && outputData2) {
|
|
58
|
-
await options.transformer.write(
|
|
59
|
-
{
|
|
60
|
-
data: outputData2,
|
|
61
|
-
width: image1.width,
|
|
62
|
-
height: image1.height
|
|
63
|
-
},
|
|
64
|
-
options.outputPath
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
return {
|
|
68
|
-
mode: "gmsd",
|
|
69
|
-
width: image1.width,
|
|
70
|
-
height: image1.height,
|
|
71
|
-
outputData: outputData2,
|
|
72
|
-
duration: duration2,
|
|
73
|
-
score
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
let outputData;
|
|
77
|
-
if (options.outputPath) {
|
|
78
|
-
outputData = new Uint8Array(image1.data.length);
|
|
79
|
-
}
|
|
80
|
-
const startTime = performance.now();
|
|
81
|
-
const diffCount = (0, import_core.default)(
|
|
82
|
-
image1.data,
|
|
83
|
-
image2.data,
|
|
84
|
-
outputData,
|
|
85
|
-
image1.width,
|
|
86
|
-
image1.height,
|
|
87
|
-
options.options || {}
|
|
88
|
-
);
|
|
89
|
-
const duration = performance.now() - startTime;
|
|
90
|
-
if (diffCount > 0 && options.outputPath && outputData) {
|
|
91
|
-
await options.transformer.write(
|
|
92
|
-
{
|
|
93
|
-
data: outputData,
|
|
94
|
-
width: image1.width,
|
|
95
|
-
height: image1.height
|
|
96
|
-
},
|
|
97
|
-
options.outputPath
|
|
42
|
+
`Invalid RGB color format: ${colorStr}. Expected format: r,g,b (e.g., 255,0,0)`
|
|
98
43
|
);
|
|
99
44
|
}
|
|
100
|
-
return
|
|
101
|
-
mode: "diff",
|
|
102
|
-
diffCount,
|
|
103
|
-
width: image1.width,
|
|
104
|
-
height: image1.height,
|
|
105
|
-
outputData,
|
|
106
|
-
duration
|
|
107
|
-
};
|
|
45
|
+
return [parts[0], parts[1], parts[2]];
|
|
108
46
|
}
|
|
109
|
-
|
|
110
|
-
// src/cli.ts
|
|
111
47
|
function printUsage() {
|
|
112
48
|
console.log(`
|
|
113
|
-
Usage: blazediff <image1> <image2> [options]
|
|
49
|
+
Usage: blazediff diff <image1> <image2> [options]
|
|
114
50
|
|
|
115
51
|
Arguments:
|
|
116
52
|
image1 Path to the first image
|
|
117
53
|
image2 Path to the second image
|
|
118
54
|
|
|
119
|
-
|
|
120
|
-
-m, --mode <mode> Comparison mode: "diff" or "gmsd" (default: diff)
|
|
121
|
-
--transformer <name> Specify transformer to use (e.g. pngjs, sharp)
|
|
122
|
-
-h, --help Show this help message
|
|
123
|
-
|
|
124
|
-
Diff Mode Options (default mode):
|
|
55
|
+
Options:
|
|
125
56
|
-o, --output <path> Output path for the diff image
|
|
126
57
|
-t, --threshold <num> Matching threshold (0 to 1, default: 0.1)
|
|
127
58
|
-a, --alpha <num> Opacity of original image in diff (default: 0.1)
|
|
@@ -131,237 +62,823 @@ Diff Mode Options (default mode):
|
|
|
131
62
|
--include-aa Include anti-aliasing detection
|
|
132
63
|
--diff-mask Draw diff over transparent background
|
|
133
64
|
--color-space <name> Specify color space to use (e.g. yiq, ycbcr)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
-o, --output <path> Output path for GMS similarity map (grayscale visualization)
|
|
137
|
-
--downsample <0|1> Downsample factor: 0=full-res, 1=2x downsample (default: 0)
|
|
138
|
-
--gmsd-c <num> Stability constant for GMSD (default: 170)
|
|
65
|
+
--transformer <name> Specify transformer to use (e.g. pngjs, sharp)
|
|
66
|
+
-h, --help Show this help message
|
|
139
67
|
|
|
140
68
|
Examples:
|
|
141
|
-
|
|
142
|
-
blazediff image1.png image2.png
|
|
143
|
-
blazediff image1.png image2.png
|
|
144
|
-
blazediff image1.png image2.png --threshold 0.2 --alpha 0.3
|
|
145
|
-
|
|
146
|
-
# GMSD similarity score
|
|
147
|
-
blazediff image1.png image2.png --mode gmsd
|
|
148
|
-
blazediff image1.png image2.png --mode gmsd --downsample 1
|
|
149
|
-
blazediff image1.png image2.png --mode gmsd -o gms-map.png
|
|
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
|
|
150
72
|
`);
|
|
151
73
|
}
|
|
152
|
-
function
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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.read(image1),
|
|
173
|
+
transformer.read(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
|
|
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)}%`
|
|
157
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);
|
|
158
240
|
}
|
|
159
|
-
return [parts[0], parts[1], parts[2]];
|
|
160
241
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
+
}
|
|
171
261
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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++;
|
|
207
314
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
break;
|
|
218
|
-
case "--diff-color":
|
|
219
|
-
if (nextArg) {
|
|
220
|
-
options.diffColor = parseRGB(nextArg);
|
|
221
|
-
i++;
|
|
222
|
-
}
|
|
223
|
-
break;
|
|
224
|
-
case "--diff-color-alt":
|
|
225
|
-
if (nextArg) {
|
|
226
|
-
options.diffColorAlt = parseRGB(nextArg);
|
|
227
|
-
i++;
|
|
228
|
-
}
|
|
229
|
-
break;
|
|
230
|
-
case "--include-aa":
|
|
231
|
-
options.includeAA = true;
|
|
232
|
-
break;
|
|
233
|
-
case "--diff-mask":
|
|
234
|
-
options.diffMask = true;
|
|
235
|
-
break;
|
|
236
|
-
case "--transformer":
|
|
237
|
-
if (nextArg) {
|
|
238
|
-
options.transformer = nextArg;
|
|
239
|
-
i++;
|
|
240
|
-
}
|
|
241
|
-
break;
|
|
242
|
-
case "--colorSpace":
|
|
243
|
-
if (nextArg) {
|
|
244
|
-
options.colorSpace = nextArg;
|
|
245
|
-
i++;
|
|
246
|
-
}
|
|
247
|
-
break;
|
|
248
|
-
case "-m":
|
|
249
|
-
case "--mode":
|
|
250
|
-
if (nextArg) {
|
|
251
|
-
if (nextArg !== "diff" && nextArg !== "gmsd") {
|
|
252
|
-
throw new Error(
|
|
253
|
-
`Invalid mode: ${nextArg}. Must be "diff" or "gmsd"`
|
|
254
|
-
);
|
|
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++;
|
|
255
324
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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++;
|
|
265
336
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (nextArg) {
|
|
272
|
-
const gmsdC = parseFloat(nextArg);
|
|
273
|
-
if (Number.isNaN(gmsdC) || gmsdC <= 0) {
|
|
274
|
-
throw new Error(
|
|
275
|
-
`Invalid gmsd-c: ${nextArg}. Must be a positive number`
|
|
276
|
-
);
|
|
337
|
+
break;
|
|
338
|
+
case "--transformer":
|
|
339
|
+
if (nextArg) {
|
|
340
|
+
options.transformer = nextArg;
|
|
341
|
+
i++;
|
|
277
342
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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.read(image1),
|
|
355
|
+
transformer.read(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);
|
|
286
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
|
+
);
|
|
416
|
+
process.exit(1);
|
|
287
417
|
}
|
|
288
|
-
return { image1, image2, options };
|
|
289
418
|
}
|
|
290
|
-
var
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
+
}
|
|
298
438
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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() {
|
|
302
465
|
try {
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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++;
|
|
488
|
+
}
|
|
489
|
+
break;
|
|
490
|
+
case "--transformer":
|
|
491
|
+
if (nextArg) {
|
|
492
|
+
options.transformer = nextArg;
|
|
493
|
+
i++;
|
|
494
|
+
}
|
|
495
|
+
break;
|
|
496
|
+
default:
|
|
497
|
+
console.error(`Unknown option: ${arg}`);
|
|
498
|
+
printUsage3();
|
|
499
|
+
process.exit(1);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const transformer = await getTransformer3(options.transformer);
|
|
503
|
+
const [img1, img2] = await Promise.all([
|
|
504
|
+
transformer.read(image1),
|
|
505
|
+
transformer.read(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
|
+
);
|
|
333
528
|
}
|
|
529
|
+
const result = {
|
|
530
|
+
score,
|
|
531
|
+
width: img1.width,
|
|
532
|
+
height: img1.height,
|
|
533
|
+
duration
|
|
534
|
+
};
|
|
334
535
|
console.log(`completed in: ${result.duration.toFixed(2)}ms`);
|
|
335
536
|
console.log(`dimensions: ${result.width}x${result.height}`);
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
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;
|
|
343
567
|
}
|
|
344
|
-
if (
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
console.log(`Images are highly similar!`);
|
|
348
|
-
process.exit(0);
|
|
568
|
+
if (transformer === "sharp") {
|
|
569
|
+
const { default: transformer2 } = await import("@blazediff/sharp-transformer");
|
|
570
|
+
return transformer2;
|
|
349
571
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
572
|
+
throw new Error(`Unknown transformer: ${transformer}`);
|
|
573
|
+
};
|
|
574
|
+
if (typeof require !== "undefined" && require.main === module) {
|
|
575
|
+
main3();
|
|
576
|
+
}
|
|
577
|
+
}
|
|
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.read(image1),
|
|
644
|
+
transformer.read(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
|
|
354
666
|
);
|
|
355
|
-
|
|
356
|
-
|
|
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);
|
|
695
|
+
}
|
|
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;
|
|
357
710
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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() {
|
|
757
|
+
try {
|
|
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);
|
|
363
817
|
}
|
|
364
818
|
}
|
|
819
|
+
const transformer = await getTransformer5(options.transformer);
|
|
820
|
+
const [img1, img2] = await Promise.all([
|
|
821
|
+
transformer.read(image1),
|
|
822
|
+
transformer.read(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`);
|
|
866
|
+
console.log(`dimensions: ${result.width}x${result.height}`);
|
|
867
|
+
console.log(`window: ${result.windowSize}x${result.windowSize} (stride: ${result.windowStride})`);
|
|
868
|
+
console.log(`pooling: ${result.poolingMethod}`);
|
|
869
|
+
console.log(
|
|
870
|
+
`Hitchhiker's SSIM score: ${result.score.toFixed(6)} (0=different, 1=identical)`
|
|
871
|
+
);
|
|
872
|
+
console.log(`similarity: ${(result.score * 100).toFixed(2)}%`);
|
|
873
|
+
if (options.outputPath && outputData) {
|
|
874
|
+
console.log(`SSIM map saved to: ${options.outputPath}`);
|
|
875
|
+
}
|
|
876
|
+
if (result.score < 0.95) {
|
|
877
|
+
process.exit(1);
|
|
878
|
+
} else {
|
|
879
|
+
console.log(`Images are highly similar!`);
|
|
880
|
+
process.exit(0);
|
|
881
|
+
}
|
|
365
882
|
} catch (error) {
|
|
366
883
|
console.error(
|
|
367
884
|
"Error:",
|
|
@@ -370,6 +887,93 @@ async function main() {
|
|
|
370
887
|
process.exit(1);
|
|
371
888
|
}
|
|
372
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
|
+
}
|
|
373
977
|
if (typeof require !== "undefined" && require.main === module) {
|
|
374
|
-
|
|
978
|
+
main6();
|
|
375
979
|
}
|