odiff-bin
Version:
The fastest image difference tool in the world
185 lines (154 loc) • 4.67 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 parts = stdout.split(";");
if (parts.length === 2) {
const [diffCount, diffPercentage] = parts;
return {
diffCount: parseInt(diffCount),
diffPercentage: parseFloat(diffPercentage),
};
} else if (parts.length === 3) {
const [diffCount, diffPercentage, linesPart] = parts;
return {
diffCount: parseInt(diffCount),
diffPercentage: parseFloat(diffPercentage),
diffLines: linesPart.split(",").flatMap((line) => {
let parsedInt = parseInt(line);
return isNaN(parsedInt) ? [] : parsedInt;
}),
};
} else {
throw new Error(`Weird pixel diff stdout: ${stdout}`);
}
} catch (e) {
console.warn(
"Can't parse output from internal process. Please submit an issue at https://github.com/dmtrKovalenko/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 = /no\s+'([^']+)'\s+file\s+or\s+directory/;
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 124:
/** @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,
};