odiff-bin
Version:
The fastest image difference tool in the world (Zig port of odiff)
196 lines (162 loc) • 5.01 kB
JavaScript
// @ts-check
const path = require("path");
const { execFile } = require("child_process");
function optionsToArgs(options) {
let argArray = ["--parsable-stdout"];
if (!options) {
return argArray;
}
const setArgWithValue = (name, value) => {
argArray.push(`--${name}=${value.toString()}`);
};
const setFlag = (name, value) => {
if (value) {
argArray.push(`--${name}`);
}
};
Object.entries(options).forEach((optionEntry) => {
/**
* @type {[keyof import('./odiff').ODiffOptions, unknown]}
* @ts-expect-error */
const [option, value] = optionEntry;
switch (option) {
case "failOnLayoutDiff":
setFlag("fail-on-layout", value);
break;
case "outputDiffMask":
setFlag("diff-mask", value);
break;
case "threshold":
setArgWithValue("threshold", value);
break;
case "diffColor":
setArgWithValue("diff-color", value);
break;
case "antialiasing":
setFlag("antialiasing", value);
break;
case "captureDiffLines":
setFlag("output-diff-lines", value);
break;
case "reduceRamUsage":
setFlag("reduce-ram-usage", value);
break;
case "ignoreRegions": {
const regions = value
.map(
(region) => `${region.x1}:${region.y1}-${region.x2}:${region.y2}`
)
.join(",");
setArgWithValue("ignore", regions);
break;
}
}
});
return argArray;
}
/** @type {(stdout: string) => Partial<{ diffCount: number, diffPercentage: number, diffLines: number[] }>} */
function parsePixelDiffStdout(stdout) {
try {
const lines = stdout.trim().split('\n');
if (lines.length >= 1) {
const diffCount = parseInt(lines[0]);
if (isNaN(diffCount)) {
throw new Error(`Invalid diff count: ${lines[0]}`);
}
const result = { diffCount };
// Calculate percentage (odiff outputs absolute count, we calculate percentage)
// This is approximate - in a real implementation we'd need image dimensions
if (lines.length >= 2) {
const diffPercentage = parseFloat(lines[1]);
if (!isNaN(diffPercentage)) {
result.diffPercentage = diffPercentage;
}
}
// Parse diff lines if present
if (lines.length >= 3) {
const diffLines = lines.slice(2)
.map(line => parseInt(line.trim()))
.filter(line => !isNaN(line));
if (diffLines.length > 0) {
result.diffLines = diffLines;
}
}
return result;
} else {
throw new Error(`No output lines found: ${stdout}`);
}
} catch (e) {
console.warn(
"Can't parse output from internal process. Please submit an issue at https://github.com/odiff-project/odiff/issues/new with the following stacktrace:",
e
);
}
return {};
}
const CMD_BIN_HELPER_MSG =
"Usage: odiff [OPTION]... [BASE] [COMPARING] [DIFF]\nTry `odiff --help' for more information.\n";
const NO_FILE_ODIFF_ERROR_REGEX = /Could not load.*image:\s*(.+)/;
async function compare(basePath, comparePath, diffOutput, options = {}) {
return new Promise((resolve, reject) => {
let producedStdout, producedStdError;
const binaryPath =
options && options.__binaryPath
? options.__binaryPath
: path.join(__dirname, "bin", "odiff.exe");
execFile(
binaryPath,
[basePath, comparePath, diffOutput, ...optionsToArgs(options)],
(_, stdout, stderr) => {
producedStdout = stdout;
producedStdError = stderr;
}
).on("close", (code) => {
switch (code) {
case 0:
resolve({ match: true });
break;
case 21:
resolve({ match: false, reason: "layout-diff" });
break;
case 22:
resolve({
match: false,
reason: "pixel-diff",
...parsePixelDiffStdout(producedStdout),
});
break;
case 1:
/** @type string */
const originalErrorMessage = (
producedStdError || "Invalid Argument Exception"
).replace(CMD_BIN_HELPER_MSG, "");
const noFileOrDirectoryMatches = originalErrorMessage.match(
NO_FILE_ODIFF_ERROR_REGEX
);
if (options.noFailOnFsErrors && noFileOrDirectoryMatches?.[1]) {
resolve({
match: false,
reason: "file-not-exists",
file: noFileOrDirectoryMatches[1],
});
} else {
reject(new TypeError(originalErrorMessage));
}
break;
default:
reject(
new Error(
(producedStdError || producedStdout).replace(
CMD_BIN_HELPER_MSG,
""
)
)
);
break;
}
});
});
}
module.exports = {
compare,
};