@blazediff/bin 1.6.0 → 2.0.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 +167 -81
- package/bin/blazediff.exe +0 -0
- package/binaries/blazediff-linux-arm64 +0 -0
- package/binaries/blazediff-linux-x64 +0 -0
- package/binaries/blazediff-macos-arm64 +0 -0
- package/binaries/blazediff-macos-x64 +0 -0
- package/binaries/blazediff-windows-arm64.exe +0 -0
- package/binaries/blazediff-windows-x64.exe +0 -0
- package/dist/index.d.mts +44 -67
- package/dist/index.d.ts +44 -67
- package/dist/index.js +108 -1
- package/dist/index.mjs +72 -0
- package/package.json +23 -21
- package/post_install.js +40 -0
- package/dist/cli.js +0 -979
- package/dist/commands/diff.js +0 -255
- package/dist/commands/gmsd.js +0 -201
- package/dist/commands/hitchhikers-ssim.js +0 -217
- package/dist/commands/msssim.js +0 -163
- package/dist/commands/ssim.js +0 -163
package/dist/commands/diff.js
DELETED
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __export = (target, all) => {
|
|
10
|
-
for (var name in all)
|
|
11
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
-
};
|
|
13
|
-
var __copyProps = (to, from, except, desc) => {
|
|
14
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
-
for (let key of __getOwnPropNames(from))
|
|
16
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
-
}
|
|
19
|
-
return to;
|
|
20
|
-
};
|
|
21
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
-
mod
|
|
28
|
-
));
|
|
29
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
-
|
|
31
|
-
// src/commands/diff.ts
|
|
32
|
-
var diff_exports = {};
|
|
33
|
-
__export(diff_exports, {
|
|
34
|
-
default: () => main
|
|
35
|
-
});
|
|
36
|
-
module.exports = __toCommonJS(diff_exports);
|
|
37
|
-
var import_core = __toESM(require("@blazediff/core"));
|
|
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)) {
|
|
41
|
-
throw new Error(
|
|
42
|
-
`Invalid RGB color format: ${colorStr}. Expected format: r,g,b (e.g., 255,0,0)`
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
return [parts[0], parts[1], parts[2]];
|
|
46
|
-
}
|
|
47
|
-
function printUsage() {
|
|
48
|
-
console.log(`
|
|
49
|
-
Usage: blazediff diff <image1> <image2> [options]
|
|
50
|
-
|
|
51
|
-
Arguments:
|
|
52
|
-
image1 Path to the first image
|
|
53
|
-
image2 Path to the second image
|
|
54
|
-
|
|
55
|
-
Options:
|
|
56
|
-
-o, --output <path> Output path for the diff image
|
|
57
|
-
-t, --threshold <num> Matching threshold (0 to 1, default: 0.1)
|
|
58
|
-
-a, --alpha <num> Opacity of original image in diff (default: 0.1)
|
|
59
|
-
--aa-color <r,g,b> Color for anti-aliased pixels (default: 255,255,0)
|
|
60
|
-
--diff-color <r,g,b> Color for different pixels (default: 255,0,0)
|
|
61
|
-
--diff-color-alt <r,g,b> Alternative color for dark differences (default: same as diff-color)
|
|
62
|
-
--include-aa Include anti-aliasing detection
|
|
63
|
-
--diff-mask Draw diff over transparent background
|
|
64
|
-
--color-space <name> Specify color space to use (e.g. yiq, ycbcr)
|
|
65
|
-
--transformer <name> Specify transformer to use (e.g. pngjs, sharp)
|
|
66
|
-
-h, --help Show this help message
|
|
67
|
-
|
|
68
|
-
Examples:
|
|
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
|
|
72
|
-
`);
|
|
73
|
-
}
|
|
74
|
-
var getTransformer = async (transformer) => {
|
|
75
|
-
if (!transformer || transformer === "pngjs") {
|
|
76
|
-
const { default: transformer2 } = await import("@blazediff/pngjs-transformer");
|
|
77
|
-
return transformer2;
|
|
78
|
-
}
|
|
79
|
-
if (transformer === "sharp") {
|
|
80
|
-
const { default: transformer2 } = await import("@blazediff/sharp-transformer");
|
|
81
|
-
return transformer2;
|
|
82
|
-
}
|
|
83
|
-
throw new Error(`Unknown transformer: ${transformer}`);
|
|
84
|
-
};
|
|
85
|
-
async function main() {
|
|
86
|
-
try {
|
|
87
|
-
const args = process.argv.slice(2);
|
|
88
|
-
if (args.length === 0 || args.includes("-h") || args.includes("--help")) {
|
|
89
|
-
printUsage();
|
|
90
|
-
process.exit(0);
|
|
91
|
-
}
|
|
92
|
-
if (args.length < 2) {
|
|
93
|
-
console.error("Error: Two image paths are required");
|
|
94
|
-
printUsage();
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
const image1 = args[0];
|
|
98
|
-
const image2 = args[1];
|
|
99
|
-
const options = {};
|
|
100
|
-
for (let i = 2; i < args.length; i++) {
|
|
101
|
-
const arg = args[i];
|
|
102
|
-
const nextArg = args[i + 1];
|
|
103
|
-
switch (arg) {
|
|
104
|
-
case "-o":
|
|
105
|
-
case "--output":
|
|
106
|
-
if (nextArg) {
|
|
107
|
-
options.outputPath = nextArg;
|
|
108
|
-
i++;
|
|
109
|
-
}
|
|
110
|
-
break;
|
|
111
|
-
case "-t":
|
|
112
|
-
case "--threshold":
|
|
113
|
-
if (nextArg) {
|
|
114
|
-
const threshold = parseFloat(nextArg);
|
|
115
|
-
if (Number.isNaN(threshold) || threshold < 0 || threshold > 1) {
|
|
116
|
-
throw new Error(
|
|
117
|
-
`Invalid threshold: ${nextArg}. Must be between 0 and 1`
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
options.threshold = threshold;
|
|
121
|
-
i++;
|
|
122
|
-
}
|
|
123
|
-
break;
|
|
124
|
-
case "-a":
|
|
125
|
-
case "--alpha":
|
|
126
|
-
if (nextArg) {
|
|
127
|
-
const alpha = parseFloat(nextArg);
|
|
128
|
-
if (Number.isNaN(alpha) || alpha < 0 || alpha > 1) {
|
|
129
|
-
throw new Error(
|
|
130
|
-
`Invalid alpha: ${nextArg}. Must be between 0 and 1`
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
options.alpha = alpha;
|
|
134
|
-
i++;
|
|
135
|
-
}
|
|
136
|
-
break;
|
|
137
|
-
case "--aa-color":
|
|
138
|
-
if (nextArg) {
|
|
139
|
-
options.aaColor = parseRGB(nextArg);
|
|
140
|
-
i++;
|
|
141
|
-
}
|
|
142
|
-
break;
|
|
143
|
-
case "--diff-color":
|
|
144
|
-
if (nextArg) {
|
|
145
|
-
options.diffColor = parseRGB(nextArg);
|
|
146
|
-
i++;
|
|
147
|
-
}
|
|
148
|
-
break;
|
|
149
|
-
case "--diff-color-alt":
|
|
150
|
-
if (nextArg) {
|
|
151
|
-
options.diffColorAlt = parseRGB(nextArg);
|
|
152
|
-
i++;
|
|
153
|
-
}
|
|
154
|
-
break;
|
|
155
|
-
case "--include-aa":
|
|
156
|
-
options.includeAA = true;
|
|
157
|
-
break;
|
|
158
|
-
case "--diff-mask":
|
|
159
|
-
options.diffMask = true;
|
|
160
|
-
break;
|
|
161
|
-
case "--transformer":
|
|
162
|
-
if (nextArg) {
|
|
163
|
-
options.transformer = nextArg;
|
|
164
|
-
i++;
|
|
165
|
-
}
|
|
166
|
-
break;
|
|
167
|
-
case "--color-space":
|
|
168
|
-
if (nextArg) {
|
|
169
|
-
options.colorSpace = nextArg;
|
|
170
|
-
i++;
|
|
171
|
-
}
|
|
172
|
-
break;
|
|
173
|
-
default:
|
|
174
|
-
console.error(`Unknown option: ${arg}`);
|
|
175
|
-
printUsage();
|
|
176
|
-
process.exit(1);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
const transformer = await getTransformer(
|
|
180
|
-
options.transformer
|
|
181
|
-
);
|
|
182
|
-
const [img1, img2] = await Promise.all([
|
|
183
|
-
transformer.transform(image1),
|
|
184
|
-
transformer.transform(image2)
|
|
185
|
-
]);
|
|
186
|
-
if (img1.width !== img2.width || img1.height !== img2.height) {
|
|
187
|
-
throw new Error(
|
|
188
|
-
`Image dimensions do not match: ${img1.width}x${img1.height} vs ${img2.width}x${img2.height}`
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
let outputData;
|
|
192
|
-
if (options.outputPath) {
|
|
193
|
-
outputData = new Uint8Array(img1.data.length);
|
|
194
|
-
}
|
|
195
|
-
const coreOptions = {
|
|
196
|
-
threshold: options.threshold,
|
|
197
|
-
alpha: options.alpha,
|
|
198
|
-
aaColor: options.aaColor,
|
|
199
|
-
diffColor: options.diffColor,
|
|
200
|
-
diffColorAlt: options.diffColorAlt,
|
|
201
|
-
includeAA: options.includeAA,
|
|
202
|
-
diffMask: options.diffMask
|
|
203
|
-
};
|
|
204
|
-
const startTime = performance.now();
|
|
205
|
-
const diffCount = (0, import_core.default)(
|
|
206
|
-
img1.data,
|
|
207
|
-
img2.data,
|
|
208
|
-
outputData,
|
|
209
|
-
img1.width,
|
|
210
|
-
img1.height,
|
|
211
|
-
coreOptions
|
|
212
|
-
);
|
|
213
|
-
const duration = performance.now() - startTime;
|
|
214
|
-
if (diffCount > 0 && options.outputPath && outputData) {
|
|
215
|
-
await transformer.write(
|
|
216
|
-
{
|
|
217
|
-
data: outputData,
|
|
218
|
-
width: img1.width,
|
|
219
|
-
height: img1.height
|
|
220
|
-
},
|
|
221
|
-
options.outputPath
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
const result = {
|
|
225
|
-
diffCount,
|
|
226
|
-
width: img1.width,
|
|
227
|
-
height: img1.height,
|
|
228
|
-
duration
|
|
229
|
-
};
|
|
230
|
-
console.log(`completed in: ${result.duration.toFixed(2)}ms`);
|
|
231
|
-
console.log(`dimensions: ${result.width}x${result.height}`);
|
|
232
|
-
console.log(`different pixels: ${result.diffCount}`);
|
|
233
|
-
console.log(
|
|
234
|
-
`error: ${(result.diffCount / (result.width * result.height) * 100).toFixed(2)}%`
|
|
235
|
-
);
|
|
236
|
-
if (result.diffCount > 0 && outputData && options.outputPath) {
|
|
237
|
-
console.log(`diff image: ${options.outputPath}`);
|
|
238
|
-
}
|
|
239
|
-
if (result.diffCount > 0) {
|
|
240
|
-
process.exit(1);
|
|
241
|
-
} else {
|
|
242
|
-
console.log(`Images are identical!`);
|
|
243
|
-
process.exit(0);
|
|
244
|
-
}
|
|
245
|
-
} catch (error) {
|
|
246
|
-
console.error(
|
|
247
|
-
"Error:",
|
|
248
|
-
error instanceof Error ? error.message : String(error)
|
|
249
|
-
);
|
|
250
|
-
process.exit(1);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
if (typeof require !== "undefined" && require.main === module) {
|
|
254
|
-
main();
|
|
255
|
-
}
|
package/dist/commands/gmsd.js
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __export = (target, all) => {
|
|
10
|
-
for (var name in all)
|
|
11
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
-
};
|
|
13
|
-
var __copyProps = (to, from, except, desc) => {
|
|
14
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
-
for (let key of __getOwnPropNames(from))
|
|
16
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
-
}
|
|
19
|
-
return to;
|
|
20
|
-
};
|
|
21
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
-
mod
|
|
28
|
-
));
|
|
29
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
-
|
|
31
|
-
// src/commands/gmsd.ts
|
|
32
|
-
var gmsd_exports = {};
|
|
33
|
-
__export(gmsd_exports, {
|
|
34
|
-
default: () => main
|
|
35
|
-
});
|
|
36
|
-
module.exports = __toCommonJS(gmsd_exports);
|
|
37
|
-
var import_gmsd = __toESM(require("@blazediff/gmsd"));
|
|
38
|
-
function printUsage() {
|
|
39
|
-
console.log(`
|
|
40
|
-
Usage: blazediff gmsd <image1> <image2> [options]
|
|
41
|
-
|
|
42
|
-
Arguments:
|
|
43
|
-
image1 Path to the first image
|
|
44
|
-
image2 Path to the second image
|
|
45
|
-
|
|
46
|
-
Options:
|
|
47
|
-
-o, --output <path> Output path for GMS similarity map (grayscale visualization)
|
|
48
|
-
--downsample <0|1> Downsample factor: 0=full-res, 1=2x downsample (default: 0)
|
|
49
|
-
--gmsd-c <num> Stability constant for GMSD (default: 170)
|
|
50
|
-
--transformer <name> Specify transformer to use (e.g. pngjs, sharp)
|
|
51
|
-
-h, --help Show this help message
|
|
52
|
-
|
|
53
|
-
Examples:
|
|
54
|
-
blazediff gmsd image1.png image2.png
|
|
55
|
-
blazediff gmsd image1.png image2.png --downsample 1
|
|
56
|
-
blazediff gmsd image1.png image2.png -o gms-map.png
|
|
57
|
-
`);
|
|
58
|
-
}
|
|
59
|
-
var getTransformer = async (transformer) => {
|
|
60
|
-
if (!transformer || transformer === "pngjs") {
|
|
61
|
-
const { default: transformer2 } = await import("@blazediff/pngjs-transformer");
|
|
62
|
-
return transformer2;
|
|
63
|
-
}
|
|
64
|
-
if (transformer === "sharp") {
|
|
65
|
-
const { default: transformer2 } = await import("@blazediff/sharp-transformer");
|
|
66
|
-
return transformer2;
|
|
67
|
-
}
|
|
68
|
-
throw new Error(`Unknown transformer: ${transformer}`);
|
|
69
|
-
};
|
|
70
|
-
async function main() {
|
|
71
|
-
try {
|
|
72
|
-
const args = process.argv.slice(2);
|
|
73
|
-
if (args.length === 0 || args.includes("-h") || args.includes("--help")) {
|
|
74
|
-
printUsage();
|
|
75
|
-
process.exit(0);
|
|
76
|
-
}
|
|
77
|
-
if (args.length < 2) {
|
|
78
|
-
console.error("Error: Two image paths are required");
|
|
79
|
-
printUsage();
|
|
80
|
-
process.exit(1);
|
|
81
|
-
}
|
|
82
|
-
const image1 = args[0];
|
|
83
|
-
const image2 = args[1];
|
|
84
|
-
const options = {};
|
|
85
|
-
for (let i = 2; i < args.length; i++) {
|
|
86
|
-
const arg = args[i];
|
|
87
|
-
const nextArg = args[i + 1];
|
|
88
|
-
switch (arg) {
|
|
89
|
-
case "-o":
|
|
90
|
-
case "--output":
|
|
91
|
-
if (nextArg) {
|
|
92
|
-
options.outputPath = nextArg;
|
|
93
|
-
i++;
|
|
94
|
-
}
|
|
95
|
-
break;
|
|
96
|
-
case "--downsample":
|
|
97
|
-
if (nextArg) {
|
|
98
|
-
const downsample = parseInt(nextArg, 10);
|
|
99
|
-
if (downsample !== 0 && downsample !== 1) {
|
|
100
|
-
throw new Error(`Invalid downsample: ${nextArg}. Must be 0 or 1`);
|
|
101
|
-
}
|
|
102
|
-
options.downsample = downsample;
|
|
103
|
-
i++;
|
|
104
|
-
}
|
|
105
|
-
break;
|
|
106
|
-
case "--gmsd-c":
|
|
107
|
-
if (nextArg) {
|
|
108
|
-
const gmsdC = parseFloat(nextArg);
|
|
109
|
-
if (Number.isNaN(gmsdC) || gmsdC <= 0) {
|
|
110
|
-
throw new Error(
|
|
111
|
-
`Invalid gmsd-c: ${nextArg}. Must be a positive number`
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
options.gmsdC = gmsdC;
|
|
115
|
-
i++;
|
|
116
|
-
}
|
|
117
|
-
break;
|
|
118
|
-
case "--transformer":
|
|
119
|
-
if (nextArg) {
|
|
120
|
-
options.transformer = nextArg;
|
|
121
|
-
i++;
|
|
122
|
-
}
|
|
123
|
-
break;
|
|
124
|
-
default:
|
|
125
|
-
console.error(`Unknown option: ${arg}`);
|
|
126
|
-
printUsage();
|
|
127
|
-
process.exit(1);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
const transformer = await getTransformer(
|
|
131
|
-
options.transformer
|
|
132
|
-
);
|
|
133
|
-
const [img1, img2] = await Promise.all([
|
|
134
|
-
transformer.transform(image1),
|
|
135
|
-
transformer.transform(image2)
|
|
136
|
-
]);
|
|
137
|
-
if (img1.width !== img2.width || img1.height !== img2.height) {
|
|
138
|
-
throw new Error(
|
|
139
|
-
`Image dimensions do not match: ${img1.width}x${img1.height} vs ${img2.width}x${img2.height}`
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
let outputData;
|
|
143
|
-
if (options.outputPath) {
|
|
144
|
-
outputData = new Uint8Array(img1.data.length);
|
|
145
|
-
}
|
|
146
|
-
const gmsdOptions = {
|
|
147
|
-
downsample: options.downsample || 0,
|
|
148
|
-
c: options.gmsdC
|
|
149
|
-
};
|
|
150
|
-
const startTime = performance.now();
|
|
151
|
-
const score = (0, import_gmsd.default)(
|
|
152
|
-
img1.data,
|
|
153
|
-
img2.data,
|
|
154
|
-
outputData,
|
|
155
|
-
img1.width,
|
|
156
|
-
img1.height,
|
|
157
|
-
gmsdOptions
|
|
158
|
-
);
|
|
159
|
-
const duration = performance.now() - startTime;
|
|
160
|
-
if (options.outputPath && outputData) {
|
|
161
|
-
await transformer.write(
|
|
162
|
-
{
|
|
163
|
-
data: outputData,
|
|
164
|
-
width: img1.width,
|
|
165
|
-
height: img1.height
|
|
166
|
-
},
|
|
167
|
-
options.outputPath
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
const result = {
|
|
171
|
-
score,
|
|
172
|
-
width: img1.width,
|
|
173
|
-
height: img1.height,
|
|
174
|
-
duration
|
|
175
|
-
};
|
|
176
|
-
console.log(`completed in: ${result.duration.toFixed(2)}ms`);
|
|
177
|
-
console.log(`dimensions: ${result.width}x${result.height}`);
|
|
178
|
-
console.log(
|
|
179
|
-
`similarity score: ${result.score.toFixed(6)} (0=different, 1=identical)`
|
|
180
|
-
);
|
|
181
|
-
console.log(`similarity: ${(result.score * 100).toFixed(2)}%`);
|
|
182
|
-
if (options.outputPath && outputData) {
|
|
183
|
-
console.log(`GMS map saved to: ${options.outputPath}`);
|
|
184
|
-
}
|
|
185
|
-
if (result.score < 0.95) {
|
|
186
|
-
process.exit(1);
|
|
187
|
-
} else {
|
|
188
|
-
console.log(`Images are highly similar!`);
|
|
189
|
-
process.exit(0);
|
|
190
|
-
}
|
|
191
|
-
} catch (error) {
|
|
192
|
-
console.error(
|
|
193
|
-
"Error:",
|
|
194
|
-
error instanceof Error ? error.message : String(error)
|
|
195
|
-
);
|
|
196
|
-
process.exit(1);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
if (typeof require !== "undefined" && require.main === module) {
|
|
200
|
-
main();
|
|
201
|
-
}
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __export = (target, all) => {
|
|
10
|
-
for (var name in all)
|
|
11
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
-
};
|
|
13
|
-
var __copyProps = (to, from, except, desc) => {
|
|
14
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
-
for (let key of __getOwnPropNames(from))
|
|
16
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
-
}
|
|
19
|
-
return to;
|
|
20
|
-
};
|
|
21
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
-
mod
|
|
28
|
-
));
|
|
29
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
-
|
|
31
|
-
// src/commands/hitchhikers-ssim.ts
|
|
32
|
-
var hitchhikers_ssim_exports = {};
|
|
33
|
-
__export(hitchhikers_ssim_exports, {
|
|
34
|
-
default: () => main
|
|
35
|
-
});
|
|
36
|
-
module.exports = __toCommonJS(hitchhikers_ssim_exports);
|
|
37
|
-
var import_hitchhikers_ssim = __toESM(require("@blazediff/ssim/hitchhikers-ssim"));
|
|
38
|
-
function printUsage() {
|
|
39
|
-
console.log(`
|
|
40
|
-
Usage: blazediff hitchhikers-ssim <image1> <image2> [options]
|
|
41
|
-
|
|
42
|
-
Arguments:
|
|
43
|
-
image1 Path to the first image
|
|
44
|
-
image2 Path to the second image
|
|
45
|
-
|
|
46
|
-
Options:
|
|
47
|
-
-o, --output <path> Output path for SSIM map visualization
|
|
48
|
-
--transformer <name> Specify transformer to use (e.g. pngjs, sharp)
|
|
49
|
-
--window-size <size> Window size (default: 11)
|
|
50
|
-
--window-stride <size> Window stride (default: windowSize for non-overlapping)
|
|
51
|
-
--no-cov-pooling Use mean pooling instead of CoV pooling
|
|
52
|
-
-h, --help Show this help message
|
|
53
|
-
|
|
54
|
-
About Hitchhiker's SSIM:
|
|
55
|
-
- Uses rectangular windows with integral images (summed area tables)
|
|
56
|
-
- O(1) window computation regardless of window size
|
|
57
|
-
- Coefficient of Variation (CoV) pooling by default
|
|
58
|
-
- Significantly faster than Gaussian-based SSIM
|
|
59
|
-
|
|
60
|
-
Reference: "A Hitchhiker's Guide to Structural Similarity"
|
|
61
|
-
(IEEE Access, 2021) by Venkataramanan et al.
|
|
62
|
-
|
|
63
|
-
Examples:
|
|
64
|
-
blazediff hitchhikers-ssim image1.png image2.png
|
|
65
|
-
blazediff hitchhikers-ssim image1.png image2.png -o ssim-map.png
|
|
66
|
-
blazediff hitchhikers-ssim image1.png image2.png --window-size 16 --window-stride 8
|
|
67
|
-
blazediff hitchhikers-ssim image1.png image2.png --no-cov-pooling
|
|
68
|
-
`);
|
|
69
|
-
}
|
|
70
|
-
var getTransformer = async (transformer) => {
|
|
71
|
-
if (!transformer || transformer === "pngjs") {
|
|
72
|
-
const { default: transformer2 } = await import("@blazediff/pngjs-transformer");
|
|
73
|
-
return transformer2;
|
|
74
|
-
}
|
|
75
|
-
if (transformer === "sharp") {
|
|
76
|
-
const { default: transformer2 } = await import("@blazediff/sharp-transformer");
|
|
77
|
-
return transformer2;
|
|
78
|
-
}
|
|
79
|
-
throw new Error(`Unknown transformer: ${transformer}`);
|
|
80
|
-
};
|
|
81
|
-
async function main() {
|
|
82
|
-
try {
|
|
83
|
-
const args = process.argv.slice(2);
|
|
84
|
-
if (args.length === 0 || args.includes("-h") || args.includes("--help")) {
|
|
85
|
-
printUsage();
|
|
86
|
-
process.exit(0);
|
|
87
|
-
}
|
|
88
|
-
if (args.length < 2) {
|
|
89
|
-
console.error("Error: Two image paths are required");
|
|
90
|
-
printUsage();
|
|
91
|
-
process.exit(1);
|
|
92
|
-
}
|
|
93
|
-
const image1 = args[0];
|
|
94
|
-
const image2 = args[1];
|
|
95
|
-
const options = {
|
|
96
|
-
covPooling: true
|
|
97
|
-
};
|
|
98
|
-
for (let i = 2; i < args.length; i++) {
|
|
99
|
-
const arg = args[i];
|
|
100
|
-
const nextArg = args[i + 1];
|
|
101
|
-
switch (arg) {
|
|
102
|
-
case "-o":
|
|
103
|
-
case "--output":
|
|
104
|
-
if (nextArg) {
|
|
105
|
-
options.outputPath = nextArg;
|
|
106
|
-
i++;
|
|
107
|
-
}
|
|
108
|
-
break;
|
|
109
|
-
case "--transformer":
|
|
110
|
-
if (nextArg) {
|
|
111
|
-
options.transformer = nextArg;
|
|
112
|
-
i++;
|
|
113
|
-
}
|
|
114
|
-
break;
|
|
115
|
-
case "--window-size":
|
|
116
|
-
if (nextArg) {
|
|
117
|
-
const size = Number.parseInt(nextArg, 10);
|
|
118
|
-
if (Number.isNaN(size) || size <= 0) {
|
|
119
|
-
throw new Error(`Invalid window size: ${nextArg}`);
|
|
120
|
-
}
|
|
121
|
-
options.windowSize = size;
|
|
122
|
-
i++;
|
|
123
|
-
}
|
|
124
|
-
break;
|
|
125
|
-
case "--window-stride":
|
|
126
|
-
if (nextArg) {
|
|
127
|
-
const stride = Number.parseInt(nextArg, 10);
|
|
128
|
-
if (Number.isNaN(stride) || stride <= 0) {
|
|
129
|
-
throw new Error(`Invalid window stride: ${nextArg}`);
|
|
130
|
-
}
|
|
131
|
-
options.windowStride = stride;
|
|
132
|
-
i++;
|
|
133
|
-
}
|
|
134
|
-
break;
|
|
135
|
-
case "--no-cov-pooling":
|
|
136
|
-
options.covPooling = false;
|
|
137
|
-
break;
|
|
138
|
-
default:
|
|
139
|
-
console.error(`Unknown option: ${arg}`);
|
|
140
|
-
printUsage();
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
const transformer = await getTransformer(options.transformer);
|
|
145
|
-
const [img1, img2] = await Promise.all([
|
|
146
|
-
transformer.transform(image1),
|
|
147
|
-
transformer.transform(image2)
|
|
148
|
-
]);
|
|
149
|
-
if (img1.width !== img2.width || img1.height !== img2.height) {
|
|
150
|
-
throw new Error(
|
|
151
|
-
`Image dimensions do not match: ${img1.width}x${img1.height} vs ${img2.width}x${img2.height}`
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
let outputData;
|
|
155
|
-
if (options.outputPath) {
|
|
156
|
-
outputData = new Uint8Array(img1.data.length);
|
|
157
|
-
}
|
|
158
|
-
const ssimOptions = {};
|
|
159
|
-
if (typeof options.windowSize === "number") {
|
|
160
|
-
ssimOptions.windowSize = options.windowSize;
|
|
161
|
-
}
|
|
162
|
-
if (typeof options.windowStride === "number") {
|
|
163
|
-
ssimOptions.windowStride = options.windowStride;
|
|
164
|
-
}
|
|
165
|
-
if (typeof options.covPooling === "boolean") {
|
|
166
|
-
ssimOptions.covPooling = options.covPooling;
|
|
167
|
-
}
|
|
168
|
-
const startTime = performance.now();
|
|
169
|
-
const score = (0, import_hitchhikers_ssim.default)(img1.data, img2.data, outputData, img1.width, img1.height, ssimOptions);
|
|
170
|
-
const duration = performance.now() - startTime;
|
|
171
|
-
if (options.outputPath && outputData) {
|
|
172
|
-
await transformer.write(
|
|
173
|
-
{
|
|
174
|
-
data: outputData,
|
|
175
|
-
width: img1.width,
|
|
176
|
-
height: img1.height
|
|
177
|
-
},
|
|
178
|
-
options.outputPath
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
const result = {
|
|
182
|
-
score,
|
|
183
|
-
width: img1.width,
|
|
184
|
-
height: img1.height,
|
|
185
|
-
duration,
|
|
186
|
-
poolingMethod: ssimOptions.covPooling === false ? "mean" : "CoV",
|
|
187
|
-
windowSize: ssimOptions.windowSize || 11,
|
|
188
|
-
windowStride: ssimOptions.windowStride || ssimOptions.windowSize || 11
|
|
189
|
-
};
|
|
190
|
-
console.log(`completed in: ${result.duration.toFixed(2)}ms`);
|
|
191
|
-
console.log(`dimensions: ${result.width}x${result.height}`);
|
|
192
|
-
console.log(`window: ${result.windowSize}x${result.windowSize} (stride: ${result.windowStride})`);
|
|
193
|
-
console.log(`pooling: ${result.poolingMethod}`);
|
|
194
|
-
console.log(
|
|
195
|
-
`Hitchhiker's SSIM score: ${result.score.toFixed(6)} (0=different, 1=identical)`
|
|
196
|
-
);
|
|
197
|
-
console.log(`similarity: ${(result.score * 100).toFixed(2)}%`);
|
|
198
|
-
if (options.outputPath && outputData) {
|
|
199
|
-
console.log(`SSIM map saved to: ${options.outputPath}`);
|
|
200
|
-
}
|
|
201
|
-
if (result.score < 0.95) {
|
|
202
|
-
process.exit(1);
|
|
203
|
-
} else {
|
|
204
|
-
console.log(`Images are highly similar!`);
|
|
205
|
-
process.exit(0);
|
|
206
|
-
}
|
|
207
|
-
} catch (error) {
|
|
208
|
-
console.error(
|
|
209
|
-
"Error:",
|
|
210
|
-
error instanceof Error ? error.message : String(error)
|
|
211
|
-
);
|
|
212
|
-
process.exit(1);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
if (typeof require !== "undefined" && require.main === module) {
|
|
216
|
-
main();
|
|
217
|
-
}
|