@blazediff/core-native 4.0.0 → 4.1.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/index.d.ts +77 -1
- package/dist/index.js +69 -17
- package/dist/index.mjs +67 -16
- package/package.json +7 -7
package/dist/index.d.ts
CHANGED
|
@@ -9,9 +9,14 @@ interface BlazeDiffOptions {
|
|
|
9
9
|
compression?: number;
|
|
10
10
|
/** JPEG quality (1-100). Default: 90 */
|
|
11
11
|
quality?: number;
|
|
12
|
+
/** Run structured interpretation after raw pixel diff */
|
|
13
|
+
interpret?: boolean;
|
|
14
|
+
/** Output format for diff: "png" (default) or "html" (interpret report) */
|
|
15
|
+
outputFormat?: "png" | "html";
|
|
12
16
|
}
|
|
13
17
|
type BlazeDiffResult = {
|
|
14
18
|
match: true;
|
|
19
|
+
interpretation?: InterpretResult;
|
|
15
20
|
} | {
|
|
16
21
|
match: false;
|
|
17
22
|
reason: "layout-diff";
|
|
@@ -20,11 +25,66 @@ type BlazeDiffResult = {
|
|
|
20
25
|
reason: "pixel-diff";
|
|
21
26
|
diffCount: number;
|
|
22
27
|
diffPercentage: number;
|
|
28
|
+
interpretation?: InterpretResult;
|
|
23
29
|
} | {
|
|
24
30
|
match: false;
|
|
25
31
|
reason: "file-not-exists";
|
|
26
32
|
file: string;
|
|
27
33
|
};
|
|
34
|
+
interface BoundingBox {
|
|
35
|
+
x: number;
|
|
36
|
+
y: number;
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
}
|
|
40
|
+
interface ShapeStats {
|
|
41
|
+
fillRatio: number;
|
|
42
|
+
borderRatio: number;
|
|
43
|
+
innerFillRatio: number;
|
|
44
|
+
centerDensity: number;
|
|
45
|
+
rowOccupancy: number;
|
|
46
|
+
colOccupancy: number;
|
|
47
|
+
}
|
|
48
|
+
interface ColorDeltaStats {
|
|
49
|
+
meanDelta: number;
|
|
50
|
+
maxDelta: number;
|
|
51
|
+
}
|
|
52
|
+
interface GradientStats {
|
|
53
|
+
edgeScore: number;
|
|
54
|
+
}
|
|
55
|
+
interface ClassificationSignals {
|
|
56
|
+
blendsWithBgInImg1: boolean;
|
|
57
|
+
blendsWithBgInImg2: boolean;
|
|
58
|
+
lowColorDelta: boolean;
|
|
59
|
+
lowEdgeChange: boolean;
|
|
60
|
+
denseFill: boolean;
|
|
61
|
+
sparseFill: boolean;
|
|
62
|
+
tinyRegion: boolean;
|
|
63
|
+
confidence: number;
|
|
64
|
+
}
|
|
65
|
+
interface ChangeRegion {
|
|
66
|
+
bbox: BoundingBox;
|
|
67
|
+
pixelCount: number;
|
|
68
|
+
percentage: number;
|
|
69
|
+
position: string;
|
|
70
|
+
shape: string;
|
|
71
|
+
shapeStats: ShapeStats;
|
|
72
|
+
changeType: string;
|
|
73
|
+
signals: ClassificationSignals;
|
|
74
|
+
confidence: number;
|
|
75
|
+
colorDelta: ColorDeltaStats;
|
|
76
|
+
gradient: GradientStats;
|
|
77
|
+
}
|
|
78
|
+
interface InterpretResult {
|
|
79
|
+
summary: string;
|
|
80
|
+
diffCount: number;
|
|
81
|
+
totalRegions: number;
|
|
82
|
+
regions: ChangeRegion[];
|
|
83
|
+
severity: string;
|
|
84
|
+
diffPercentage: number;
|
|
85
|
+
width: number;
|
|
86
|
+
height: number;
|
|
87
|
+
}
|
|
28
88
|
/**
|
|
29
89
|
* Compare two images (PNG or JPEG) and optionally generate a diff image.
|
|
30
90
|
*
|
|
@@ -55,5 +115,21 @@ declare function getBinaryPath(): string;
|
|
|
55
115
|
* Returns true if the native module loaded successfully.
|
|
56
116
|
*/
|
|
57
117
|
declare function hasNativeBinding(): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Interpret the diff between two images, returning structured analysis results.
|
|
120
|
+
*
|
|
121
|
+
* Uses native N-API bindings when available for better performance.
|
|
122
|
+
* Falls back to execFile if native bindings are unavailable.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* const result = await interpret('expected.png', 'actual.png');
|
|
127
|
+
* console.log(result.summary);
|
|
128
|
+
* for (const region of result.regions) {
|
|
129
|
+
* console.log(`${region.position}: ${region.changeType} (${region.percentage.toFixed(2)}%)`);
|
|
130
|
+
* }
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
declare function interpret(image1Path: string, image2Path: string, options?: Pick<BlazeDiffOptions, "threshold" | "antialiasing">): Promise<InterpretResult>;
|
|
58
134
|
|
|
59
|
-
export { type BlazeDiffOptions, type BlazeDiffResult, compare, getBinaryPath, hasNativeBinding };
|
|
135
|
+
export { type BlazeDiffOptions, type BlazeDiffResult, type BoundingBox, type ChangeRegion, type ClassificationSignals, type ColorDeltaStats, type GradientStats, type InterpretResult, type ShapeStats, compare, getBinaryPath, hasNativeBinding, interpret };
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,8 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
compare: () => compare,
|
|
34
34
|
getBinaryPath: () => getBinaryPath,
|
|
35
|
-
hasNativeBinding: () => hasNativeBinding
|
|
35
|
+
hasNativeBinding: () => hasNativeBinding,
|
|
36
|
+
interpret: () => interpret
|
|
36
37
|
});
|
|
37
38
|
module.exports = __toCommonJS(index_exports);
|
|
38
39
|
|
|
@@ -119,25 +120,19 @@ function tryLoadNativeBinding() {
|
|
|
119
120
|
return null;
|
|
120
121
|
}
|
|
121
122
|
function convertNapiResult(result) {
|
|
123
|
+
const interpretation = result.interpretation ?? void 0;
|
|
122
124
|
if (result.matchResult) {
|
|
123
|
-
return { match: true };
|
|
125
|
+
return { match: true, interpretation };
|
|
124
126
|
}
|
|
125
127
|
if (result.reason === "layout-diff") {
|
|
126
128
|
return { match: false, reason: "layout-diff" };
|
|
127
129
|
}
|
|
128
|
-
if (result.reason === "pixel-diff") {
|
|
129
|
-
return {
|
|
130
|
-
match: false,
|
|
131
|
-
reason: "pixel-diff",
|
|
132
|
-
diffCount: result.diffCount ?? 0,
|
|
133
|
-
diffPercentage: result.diffPercentage ?? 0
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
130
|
return {
|
|
137
131
|
match: false,
|
|
138
132
|
reason: "pixel-diff",
|
|
139
133
|
diffCount: result.diffCount ?? 0,
|
|
140
|
-
diffPercentage: result.diffPercentage ?? 0
|
|
134
|
+
diffPercentage: result.diffPercentage ?? 0,
|
|
135
|
+
interpretation
|
|
141
136
|
};
|
|
142
137
|
}
|
|
143
138
|
function convertToNapiOptions(options) {
|
|
@@ -146,7 +141,9 @@ function convertToNapiOptions(options) {
|
|
|
146
141
|
antialiasing: options?.antialiasing,
|
|
147
142
|
diffMask: options?.diffMask,
|
|
148
143
|
compression: options?.compression,
|
|
149
|
-
quality: options?.quality
|
|
144
|
+
quality: options?.quality,
|
|
145
|
+
interpret: options?.interpret,
|
|
146
|
+
outputFormat: options?.outputFormat
|
|
150
147
|
};
|
|
151
148
|
}
|
|
152
149
|
function resolveBinaryPath() {
|
|
@@ -195,16 +192,21 @@ function getBinaryPathInternal() {
|
|
|
195
192
|
}
|
|
196
193
|
function buildArgs(diffOutput, options) {
|
|
197
194
|
const args = [];
|
|
195
|
+
const useInterpret = options?.interpret || options?.outputFormat === "html";
|
|
198
196
|
if (diffOutput) args.push(diffOutput);
|
|
197
|
+
if (useInterpret) args.push("--interpret");
|
|
199
198
|
args.push("--output-format=json");
|
|
200
199
|
if (!options) return args;
|
|
201
200
|
if (options.threshold !== void 0)
|
|
202
201
|
args.push(`--threshold=${options.threshold}`);
|
|
203
202
|
if (options.antialiasing) args.push("--antialiasing");
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
203
|
+
if (!useInterpret) {
|
|
204
|
+
if (options.diffMask) args.push("--diff-mask");
|
|
205
|
+
if (options.compression !== void 0)
|
|
206
|
+
args.push(`--compression=${options.compression}`);
|
|
207
|
+
if (options.quality !== void 0)
|
|
208
|
+
args.push(`--quality=${options.quality}`);
|
|
209
|
+
}
|
|
208
210
|
return args;
|
|
209
211
|
}
|
|
210
212
|
function parseJsonOutput(text) {
|
|
@@ -225,6 +227,9 @@ function detectMissingFile(error, basePath, comparePath) {
|
|
|
225
227
|
async function execFileCompare(basePath, comparePath, diffOutput, options) {
|
|
226
228
|
const binaryPath = getBinaryPathInternal();
|
|
227
229
|
const args = [basePath, comparePath, ...buildArgs(diffOutput, options)];
|
|
230
|
+
if (options?.interpret || options?.outputFormat === "html") {
|
|
231
|
+
return execFileInterpretCompare(binaryPath, args, basePath, comparePath);
|
|
232
|
+
}
|
|
228
233
|
try {
|
|
229
234
|
await execFileAsync(binaryPath, args);
|
|
230
235
|
return { match: true };
|
|
@@ -257,6 +262,35 @@ async function execFileCompare(basePath, comparePath, diffOutput, options) {
|
|
|
257
262
|
throw new Error(output || `blazediff exited with code ${code}`);
|
|
258
263
|
}
|
|
259
264
|
}
|
|
265
|
+
async function execFileInterpretCompare(binaryPath, args, basePath, comparePath) {
|
|
266
|
+
try {
|
|
267
|
+
const { stdout } = await execFileAsync(binaryPath, args);
|
|
268
|
+
const interpretation = JSON.parse(stdout);
|
|
269
|
+
return { match: true, interpretation };
|
|
270
|
+
} catch (err) {
|
|
271
|
+
const { code, stdout, stderr } = err;
|
|
272
|
+
if (code === 1 && stdout) {
|
|
273
|
+
const interpretation = JSON.parse(stdout);
|
|
274
|
+
return {
|
|
275
|
+
match: false,
|
|
276
|
+
reason: "pixel-diff",
|
|
277
|
+
diffCount: interpretation.diffCount,
|
|
278
|
+
diffPercentage: interpretation.diffPercentage,
|
|
279
|
+
interpretation
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const errorOutput = stderr || stdout || "";
|
|
283
|
+
if (code === 2) {
|
|
284
|
+
const missingFile = detectMissingFile(errorOutput, basePath, comparePath);
|
|
285
|
+
if (missingFile) {
|
|
286
|
+
return { match: false, reason: "file-not-exists", file: missingFile };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
throw new Error(
|
|
290
|
+
errorOutput || `blazediff --interpret exited with code ${code}`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
260
294
|
async function compare(basePath, comparePath, diffOutput, options) {
|
|
261
295
|
const binding = tryLoadNativeBinding();
|
|
262
296
|
if (binding) {
|
|
@@ -285,9 +319,27 @@ function getBinaryPath() {
|
|
|
285
319
|
function hasNativeBinding() {
|
|
286
320
|
return tryLoadNativeBinding() !== null;
|
|
287
321
|
}
|
|
322
|
+
async function interpret(image1Path, image2Path, options) {
|
|
323
|
+
const binding = tryLoadNativeBinding();
|
|
324
|
+
if (binding) {
|
|
325
|
+
return binding.interpretImages(image1Path, image2Path, {
|
|
326
|
+
threshold: options?.threshold,
|
|
327
|
+
antialiasing: options?.antialiasing
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
const result = await compare(image1Path, image2Path, void 0, {
|
|
331
|
+
...options,
|
|
332
|
+
interpret: true
|
|
333
|
+
});
|
|
334
|
+
if ("interpretation" in result && result.interpretation) {
|
|
335
|
+
return result.interpretation;
|
|
336
|
+
}
|
|
337
|
+
throw new Error("Interpretation result missing from compare");
|
|
338
|
+
}
|
|
288
339
|
// Annotate the CommonJS export names for ESM import in node:
|
|
289
340
|
0 && (module.exports = {
|
|
290
341
|
compare,
|
|
291
342
|
getBinaryPath,
|
|
292
|
-
hasNativeBinding
|
|
343
|
+
hasNativeBinding,
|
|
344
|
+
interpret
|
|
293
345
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -77,25 +77,19 @@ function tryLoadNativeBinding() {
|
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
79
79
|
function convertNapiResult(result) {
|
|
80
|
+
const interpretation = result.interpretation ?? void 0;
|
|
80
81
|
if (result.matchResult) {
|
|
81
|
-
return { match: true };
|
|
82
|
+
return { match: true, interpretation };
|
|
82
83
|
}
|
|
83
84
|
if (result.reason === "layout-diff") {
|
|
84
85
|
return { match: false, reason: "layout-diff" };
|
|
85
86
|
}
|
|
86
|
-
if (result.reason === "pixel-diff") {
|
|
87
|
-
return {
|
|
88
|
-
match: false,
|
|
89
|
-
reason: "pixel-diff",
|
|
90
|
-
diffCount: result.diffCount ?? 0,
|
|
91
|
-
diffPercentage: result.diffPercentage ?? 0
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
87
|
return {
|
|
95
88
|
match: false,
|
|
96
89
|
reason: "pixel-diff",
|
|
97
90
|
diffCount: result.diffCount ?? 0,
|
|
98
|
-
diffPercentage: result.diffPercentage ?? 0
|
|
91
|
+
diffPercentage: result.diffPercentage ?? 0,
|
|
92
|
+
interpretation
|
|
99
93
|
};
|
|
100
94
|
}
|
|
101
95
|
function convertToNapiOptions(options) {
|
|
@@ -104,7 +98,9 @@ function convertToNapiOptions(options) {
|
|
|
104
98
|
antialiasing: options?.antialiasing,
|
|
105
99
|
diffMask: options?.diffMask,
|
|
106
100
|
compression: options?.compression,
|
|
107
|
-
quality: options?.quality
|
|
101
|
+
quality: options?.quality,
|
|
102
|
+
interpret: options?.interpret,
|
|
103
|
+
outputFormat: options?.outputFormat
|
|
108
104
|
};
|
|
109
105
|
}
|
|
110
106
|
function resolveBinaryPath() {
|
|
@@ -153,16 +149,21 @@ function getBinaryPathInternal() {
|
|
|
153
149
|
}
|
|
154
150
|
function buildArgs(diffOutput, options) {
|
|
155
151
|
const args = [];
|
|
152
|
+
const useInterpret = options?.interpret || options?.outputFormat === "html";
|
|
156
153
|
if (diffOutput) args.push(diffOutput);
|
|
154
|
+
if (useInterpret) args.push("--interpret");
|
|
157
155
|
args.push("--output-format=json");
|
|
158
156
|
if (!options) return args;
|
|
159
157
|
if (options.threshold !== void 0)
|
|
160
158
|
args.push(`--threshold=${options.threshold}`);
|
|
161
159
|
if (options.antialiasing) args.push("--antialiasing");
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
160
|
+
if (!useInterpret) {
|
|
161
|
+
if (options.diffMask) args.push("--diff-mask");
|
|
162
|
+
if (options.compression !== void 0)
|
|
163
|
+
args.push(`--compression=${options.compression}`);
|
|
164
|
+
if (options.quality !== void 0)
|
|
165
|
+
args.push(`--quality=${options.quality}`);
|
|
166
|
+
}
|
|
166
167
|
return args;
|
|
167
168
|
}
|
|
168
169
|
function parseJsonOutput(text) {
|
|
@@ -183,6 +184,9 @@ function detectMissingFile(error, basePath, comparePath) {
|
|
|
183
184
|
async function execFileCompare(basePath, comparePath, diffOutput, options) {
|
|
184
185
|
const binaryPath = getBinaryPathInternal();
|
|
185
186
|
const args = [basePath, comparePath, ...buildArgs(diffOutput, options)];
|
|
187
|
+
if (options?.interpret || options?.outputFormat === "html") {
|
|
188
|
+
return execFileInterpretCompare(binaryPath, args, basePath, comparePath);
|
|
189
|
+
}
|
|
186
190
|
try {
|
|
187
191
|
await execFileAsync(binaryPath, args);
|
|
188
192
|
return { match: true };
|
|
@@ -215,6 +219,35 @@ async function execFileCompare(basePath, comparePath, diffOutput, options) {
|
|
|
215
219
|
throw new Error(output || `blazediff exited with code ${code}`);
|
|
216
220
|
}
|
|
217
221
|
}
|
|
222
|
+
async function execFileInterpretCompare(binaryPath, args, basePath, comparePath) {
|
|
223
|
+
try {
|
|
224
|
+
const { stdout } = await execFileAsync(binaryPath, args);
|
|
225
|
+
const interpretation = JSON.parse(stdout);
|
|
226
|
+
return { match: true, interpretation };
|
|
227
|
+
} catch (err) {
|
|
228
|
+
const { code, stdout, stderr } = err;
|
|
229
|
+
if (code === 1 && stdout) {
|
|
230
|
+
const interpretation = JSON.parse(stdout);
|
|
231
|
+
return {
|
|
232
|
+
match: false,
|
|
233
|
+
reason: "pixel-diff",
|
|
234
|
+
diffCount: interpretation.diffCount,
|
|
235
|
+
diffPercentage: interpretation.diffPercentage,
|
|
236
|
+
interpretation
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
const errorOutput = stderr || stdout || "";
|
|
240
|
+
if (code === 2) {
|
|
241
|
+
const missingFile = detectMissingFile(errorOutput, basePath, comparePath);
|
|
242
|
+
if (missingFile) {
|
|
243
|
+
return { match: false, reason: "file-not-exists", file: missingFile };
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
throw new Error(
|
|
247
|
+
errorOutput || `blazediff --interpret exited with code ${code}`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
218
251
|
async function compare(basePath, comparePath, diffOutput, options) {
|
|
219
252
|
const binding = tryLoadNativeBinding();
|
|
220
253
|
if (binding) {
|
|
@@ -243,8 +276,26 @@ function getBinaryPath() {
|
|
|
243
276
|
function hasNativeBinding() {
|
|
244
277
|
return tryLoadNativeBinding() !== null;
|
|
245
278
|
}
|
|
279
|
+
async function interpret(image1Path, image2Path, options) {
|
|
280
|
+
const binding = tryLoadNativeBinding();
|
|
281
|
+
if (binding) {
|
|
282
|
+
return binding.interpretImages(image1Path, image2Path, {
|
|
283
|
+
threshold: options?.threshold,
|
|
284
|
+
antialiasing: options?.antialiasing
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
const result = await compare(image1Path, image2Path, void 0, {
|
|
288
|
+
...options,
|
|
289
|
+
interpret: true
|
|
290
|
+
});
|
|
291
|
+
if ("interpretation" in result && result.interpretation) {
|
|
292
|
+
return result.interpretation;
|
|
293
|
+
}
|
|
294
|
+
throw new Error("Interpretation result missing from compare");
|
|
295
|
+
}
|
|
246
296
|
export {
|
|
247
297
|
compare,
|
|
248
298
|
getBinaryPath,
|
|
249
|
-
hasNativeBinding
|
|
299
|
+
hasNativeBinding,
|
|
300
|
+
interpret
|
|
250
301
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blazediff/core-native",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Native Rust binaries for blazediff - the fastest image diff in the world",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
"dist"
|
|
21
21
|
],
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"@blazediff/core-native-darwin-arm64": "4.
|
|
24
|
-
"@blazediff/core-native-darwin-x64": "4.
|
|
25
|
-
"@blazediff/core-native-linux-arm64": "4.
|
|
26
|
-
"@blazediff/core-native-linux-x64": "4.
|
|
27
|
-
"@blazediff/core-native-win32-arm64": "4.
|
|
28
|
-
"@blazediff/core-native-win32-x64": "4.
|
|
23
|
+
"@blazediff/core-native-darwin-arm64": "4.1.0",
|
|
24
|
+
"@blazediff/core-native-darwin-x64": "4.1.0",
|
|
25
|
+
"@blazediff/core-native-linux-arm64": "4.1.0",
|
|
26
|
+
"@blazediff/core-native-linux-x64": "4.1.0",
|
|
27
|
+
"@blazediff/core-native-win32-arm64": "4.1.0",
|
|
28
|
+
"@blazediff/core-native-win32-x64": "4.1.0"
|
|
29
29
|
},
|
|
30
30
|
"keywords": [
|
|
31
31
|
"image",
|