Skip to content

Commit

Permalink
feat: Add new --output-diff-lines option (#78)
Browse files Browse the repository at this point in the history
* feat: Add new --output-diff-lines option

* Revert www.cypress-diff.png

* Fix OS versions

* Bump cache id

* Use macos 11

* Review + readme update

* Bump node on CI to 18
  • Loading branch information
dmtrKovalenko authored Mar 2, 2023
1 parent 075c6b7 commit 52e1aed
Show file tree
Hide file tree
Showing 17 changed files with 121 additions and 43 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-20.04, macos-11]
steps:
- uses: actions/setup-node@v2
with:
node-version: '13'
node-version: '18'
- uses: actions/checkout@v2.3.2

- name: Install esy
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ export type ODiffOptions = Partial<{
threshold: number;
/** If this is true, antialiased pixels are not counted to the diff of an image */
antialiasing: boolean;
/** If `true` reason: "pixel-diff" output will contain the set of line indexes containing different pixels */
captureDiffLines: boolean;
/** An array of regions to ignore in the diff. */
ignoreRegions: Array<{
x1: number;
Expand All @@ -133,6 +135,8 @@ declare function compare(
diffCount: number;
/** Percentage of different pixels in the whole image */
diffPercentage: number;
/** Individual line indexes containing different pixels. Guaranteed to be ordered and distinct. */
diffLines?: number[];
}
| {
match: false;
Expand Down
6 changes: 3 additions & 3 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Build npm release

variables:
esy__ci_cache_version: v2 # this is available to all jobs in env as $ESY__CI_CACHE_VERSION or in azure config as $(esy__ci_cache_version)
esy__ci_cache_version: v3 # this is available to all jobs in env as $ESY__CI_CACHE_VERSION or in azure config as $(esy__ci_cache_version)

trigger:
- main
Expand All @@ -19,7 +19,7 @@ jobs:
- template: .ci/build-platform.yml
parameters:
platform: MacOS
vmImage: macOS-latest
vmImage: macOS-11

# Need windows-2019 to do esy import/export-dependencies
# which assumes you have bsdtar (tar.exe) in your system
Expand All @@ -38,7 +38,7 @@ jobs:
- MacOS
- Windows_x64
pool:
vmImage: macOS-latest
vmImage: macOS-11
demands: node.js
steps:
- template: .ci/cross-release.yml
6 changes: 4 additions & 2 deletions bin/Main.re
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ let main =
stdoutParsableString,
antialiasing,
ignoreRegions,
diffLines,
) => {
module IO1 = (val getIOModule(img1Path));
module IO2 = (val getIOModule(img2Path));
Expand All @@ -48,6 +49,7 @@ let main =
~failOnLayoutChange,
~antialiasing,
~ignoreRegions,
~diffLines,
~diffPixel=
Color.ofHexString(diffColorHex)
|> (
Expand All @@ -61,12 +63,12 @@ let main =
|> (
fun
| Layout => {diff: None, exitCode: 21}
| Pixel((diffOutput, diffCount, stdoutParsableString))
| Pixel((diffOutput, diffCount, stdoutParsableString, _))
when diffCount == 0 => {
exitCode: 0,
diff: Some(diffOutput),
}
| Pixel((diffOutput, diffCount, diffPercentage)) => {
| Pixel((diffOutput, diffCount, diffPercentage, _)) => {
IO1.saveImage(diffOutput, diffPath);
{exitCode: 22, diff: Some(diffOutput)};
}
Expand Down
15 changes: 14 additions & 1 deletion bin/ODiffBin.re
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ let antialiasing = {
);
};

let diffLines = {
Arg.(
value
& flag
& info(
["output-diff-lines"],
~doc=
"With this flag enabled, output result in case of different images will output lines for all the different pixels",
)
);
};

let ignoreRegions = {
Arg.(
value
Expand Down Expand Up @@ -129,10 +141,11 @@ let cmd = {
$ parsableOutput
$ antialiasing
$ ignoreRegions
$ diffLines
),
Term.info(
"odiff",
~version="2.5.0",
~version="2.6.0",
~doc="Find difference between 2 images.",
~exits=[
Term.exit_info(0, ~doc="on image match"),
Expand Down
23 changes: 19 additions & 4 deletions bin/Print.re
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,34 @@ let printDiffResult = (makeParsableOutput, result) => {
</Pastel>

// SUCCESS
| (Pixel((_output, diffCount, _percentage)), true) when diffCount === 0 => ""
| (Pixel((_output, diffCount, _percentage)), false) when diffCount === 0 =>
| (Pixel((_output, diffCount, _percentage, _lines)), true)
when diffCount === 0 => ""
| (Pixel((_output, diffCount, _percentage, _lines)), false)
when diffCount === 0 =>
<Pastel>
<Pastel color=Green bold=true> "Success! " </Pastel>
"Images are equal.\n"
<Pastel dim=true> "No diff output created." </Pastel>
</Pastel>

// FAILURE
| (Pixel((_output, diffCount, diffPercentage)), true) =>
| (Pixel((_output, diffCount, diffPercentage, stack)), true) when !Stack.is_empty(stack) =>
Int.to_string(diffCount)
++ ";"
++ Float.to_string(diffPercentage)
++ ";"
++ (
stack
|> Stack.fold(
(acc, line) => (line |> Int.to_string) ++ "," ++ acc,
"",
)
)

| (Pixel((_output, diffCount, diffPercentage, _)), true) =>
Int.to_string(diffCount) ++ ";" ++ Float.to_string(diffPercentage)

| (Pixel((_output, diffCount, diffPercentage)), false) =>
| (Pixel((_output, diffCount, diffPercentage, _lines)), false) =>
<Pastel>
<Pastel color=Red bold=true> "Failure! " </Pastel>
"Images are different.\n"
Expand Down
4 changes: 4 additions & 0 deletions bin/node-bindings/odiff.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export type ODiffOptions = Partial<{
threshold: number;
/** If this is true, antialiased pixels are not counted to the diff of an image */
antialiasing: boolean;
/** If `true` reason: "pixel-diff" output will contain the set of line indexes containing different pixels */
captureDiffLines: boolean;
/** An array of regions to ignore in the diff. */
ignoreRegions: Array<{
x1: number;
Expand All @@ -35,6 +37,8 @@ declare function compare(
diffCount: number;
/** Percentage of different pixels in the whole image */
diffPercentage: number;
/** Individual line indexes containing different pixels. Guaranteed to be ordered and distinct. */
diffLines?: number[];
}
| {
match: false;
Expand Down
20 changes: 18 additions & 2 deletions bin/node-bindings/odiff.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ function optionsToArgs(options) {
setFlag("antialiasing", value);
break;

case "captureDiffLines":
setFlag("output-diff-lines", value);
break;

case "ignoreRegions": {
const regions = value
.map(
Expand All @@ -62,7 +66,7 @@ function optionsToArgs(options) {
return argArray;
}

/** @type {(stdout: string) => Partial<{ diffCount: number, diffPercentage: number }>} */
/** @type {(stdout: string) => Partial<{ diffCount: number, diffPercentage: number, diffLines: number[] }>} */
function parsePixelDiffStdout(stdout) {
try {
const parts = stdout.split(";");
Expand All @@ -74,6 +78,18 @@ function parsePixelDiffStdout(stdout) {
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}`);
}
Expand Down Expand Up @@ -131,7 +147,7 @@ async function compare(basePath, comparePath, diffOutput, options = {}) {
/no\n\s*`(.*)'\sfile or\n\s*directory/
);

if (options.noFailOnFsErrors && noFileOrDirectoryMatches[1]) {
if (options.noFailOnFsErrors && noFileOrDirectoryMatches?.[1]) {
resolve({
match: false,
reason: "file-not-exists",
Expand Down
Binary file modified images/www.cypress-diff.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "odiff",
"version": "2.5.0",
"version": "2.6.0",
"description": "The fastest image difference tool.",
"license": "MIT",
"esy": {
Expand Down
13 changes: 11 additions & 2 deletions src/Diff.re
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ let maxYIQPossibleDelta = 35215.;

type diffVariant('a) =
| Layout
| Pixel(('a, int, float));
| Pixel(('a, int, float, Stack.t(int)));

let computeIngoreRegionOffsets = width => {
List.map((((x1, y1), (x2, y2))) => {
Expand All @@ -27,6 +27,7 @@ module MakeDiff = (IO1: ImageIO.ImageIO, IO2: ImageIO.ImageIO) => {
comp: ImageIO.img(IO2.t),
~antialiasing=false,
~outputDiffMask=false,
~diffLines=false,
~diffPixel: (int, int, int)=redPixel,
~threshold=0.1,
~ignoreRegions=[],
Expand All @@ -36,8 +37,14 @@ module MakeDiff = (IO1: ImageIO.ImageIO, IO2: ImageIO.ImageIO) => {
let diffOutput = outputDiffMask ? IO1.makeSameAsLayout(base) : base;

let diffPixelQueue = Queue.create();
let diffLinesStack = Stack.create();

let countDifference = (x, y) => {
diffPixelQueue |> Queue.push((x, y));

if (diffLinesStack |> Stack.is_empty || diffLinesStack |> Stack.top < y) {
diffLinesStack |> Stack.push(y);
}
};

let ignoreRegions =
Expand Down Expand Up @@ -114,7 +121,7 @@ module MakeDiff = (IO1: ImageIO.ImageIO, IO2: ImageIO.ImageIO) => {
*. Float.of_int(diffCount)
/. (Float.of_int(base.width) *. Float.of_int(base.height));

(diffOutput, diffCount, diffPercentage);
(diffOutput, diffCount, diffPercentage, diffLinesStack);
};

let diff =
Expand All @@ -126,6 +133,7 @@ module MakeDiff = (IO1: ImageIO.ImageIO, IO2: ImageIO.ImageIO) => {
~diffPixel=redPixel,
~failOnLayoutChange=true,
~antialiasing=false,
~diffLines=false,
~ignoreRegions=[],
(),
) =>
Expand All @@ -141,6 +149,7 @@ module MakeDiff = (IO1: ImageIO.ImageIO, IO2: ImageIO.ImageIO) => {
~diffPixel,
~outputDiffMask,
~antialiasing,
~diffLines,
~ignoreRegions,
(),
);
Expand Down
12 changes: 6 additions & 6 deletions test/Test_Core.re
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe("CORE: Antialiasing", ({test, _}) => {
let img1 = loadImage("test/test-images/aa/antialiasing-on.png");
let img2 = loadImage("test/test-images/aa/antialiasing-off.png");

let (_, diffPixels, diffPercentage) =
let (_, diffPixels, diffPercentage, _) =
PNG_Diff.compare(
img1,
img2,
Expand All @@ -27,7 +27,7 @@ describe("CORE: Antialiasing", ({test, _}) => {
let img1 = loadImage("test/test-images/aa/antialiasing-on.png");
let img2 = loadImage("test/test-images/aa/antialiasing-off-small.png");

let (_, diffPixels, diffPercentage) =
let (_, diffPixels, diffPercentage, _) =
PNG_Diff.compare(
img1,
img2,
Expand All @@ -46,7 +46,7 @@ describe("CORE: Threshold", ({test, _}) => {
let img1 = Png.IO.loadImage("test/test-images/png/orange.png");
let img2 = Png.IO.loadImage("test/test-images/png/orange_changed.png");

let (_, diffPixels, diffPercentage) =
let (_, diffPixels, diffPercentage, _) =
PNG_Diff.compare(img1, img2, ~threshold=0.5, ());
expect.int(diffPixels).toBe(222);
expect.float(diffPercentage).toBeCloseTo(0.19);
Expand All @@ -58,7 +58,7 @@ describe("CORE: Ignore Regions", ({test, _}) => {
let img1 = Png.IO.loadImage("test/test-images/png/orange.png");
let img2 = Png.IO.loadImage("test/test-images/png/orange_changed.png");

let (_diffOutput, diffPixels, diffPercentage) =
let (_diffOutput, diffPixels, diffPercentage, _) =
PNG_Diff.compare(
img1,
img2,
Expand All @@ -79,12 +79,12 @@ describe("CORE: Diff Color", ({test, _}) => {
let img1 = Png.IO.loadImage("test/test-images/png/orange.png");
let img2 = Png.IO.loadImage("test/test-images/png/orange_changed.png");

let (diffOutput, _, _) =
let (diffOutput, _, _, _) =
PNG_Diff.compare(img1, img2, ~diffPixel=(0, 255, 0), ());

let originalDiff =
Png.IO.loadImage("test/test-images/png/orange_diff_green.png");
let (diffMaskOfDiff, diffOfDiffPixels, diffOfDiffPercentage) =
let (diffMaskOfDiff, diffOfDiffPixels, diffOfDiffPercentage, _) =
PNG_Diff.compare(originalDiff, diffOutput, ());

if (diffOfDiffPixels > 0) {
Expand Down
10 changes: 5 additions & 5 deletions test/Test_IO_BMP.re
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe("IO: BMP", ({test, _}) => {
let img1 = Bmp.IO.loadImage("test/test-images/bmp/clouds.bmp");
let img2 = Bmp.IO.loadImage("test/test-images/bmp/clouds-2.bmp");

let (_, diffPixels, diffPercentage) = Diff.compare(img1, img2, ());
let (_, diffPixels, diffPercentage, _) = Diff.compare(img1, img2, ());

expect.int(diffPixels).toBe(191);
expect.float(diffPercentage).toBeCloseTo(0.076);
Expand All @@ -19,13 +19,13 @@ describe("IO: BMP", ({test, _}) => {
let img1 = Bmp.IO.loadImage("test/test-images/bmp/clouds.bmp");
let img2 = Bmp.IO.loadImage("test/test-images/bmp/clouds-2.bmp");

let (_, diffPixels, diffPercentage) =
let (_, diffPixels, diffPercentage, _) =
Diff.compare(img1, img2, ~outputDiffMask=false, ());

let img1 = Bmp.IO.loadImage("test/test-images/bmp/clouds.bmp");
let img2 = Bmp.IO.loadImage("test/test-images/bmp/clouds-2.bmp");

let (_, diffPixelsMask, diffPercentageMask) =
let (_, diffPixelsMask, diffPercentageMask, _) =
Diff.compare(img1, img2, ~outputDiffMask=true, ());

expect.int(diffPixels).toBe(diffPixelsMask);
Expand All @@ -36,11 +36,11 @@ describe("IO: BMP", ({test, _}) => {
let img1 = Bmp.IO.loadImage("test/test-images/bmp/clouds.bmp");
let img2 = Bmp.IO.loadImage("test/test-images/bmp/clouds-2.bmp");

let (diffOutput, _, _) = Diff.compare(img1, img2, ());
let (diffOutput, _, _, _) = Diff.compare(img1, img2, ());

let originalDiff =
Png.IO.loadImage("test/test-images/bmp/clouds-diff.png");
let (diffMaskOfDiff, diffOfDiffPixels, diffOfDiffPercentage) =
let (diffMaskOfDiff, diffOfDiffPixels, diffOfDiffPercentage, _) =
Output_Diff.compare(originalDiff, diffOutput, ());

if (diffOfDiffPixels > 0) {
Expand Down
Loading

0 comments on commit 52e1aed

Please sign in to comment.