Skip to content

Commit 52e1aed

Browse files
feat: Add new --output-diff-lines option (#78)
* 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
1 parent 075c6b7 commit 52e1aed

17 files changed

+121
-43
lines changed

.github/workflows/build.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ jobs:
77
runs-on: ${{ matrix.os }}
88
strategy:
99
matrix:
10-
os: [ubuntu-latest, macos-latest]
10+
os: [ubuntu-20.04, macos-11]
1111
steps:
1212
- uses: actions/setup-node@v2
1313
with:
14-
node-version: '13'
14+
node-version: '18'
1515
- uses: actions/[email protected]
1616

1717
- name: Install esy

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ export type ODiffOptions = Partial<{
109109
threshold: number;
110110
/** If this is true, antialiased pixels are not counted to the diff of an image */
111111
antialiasing: boolean;
112+
/** If `true` reason: "pixel-diff" output will contain the set of line indexes containing different pixels */
113+
captureDiffLines: boolean;
112114
/** An array of regions to ignore in the diff. */
113115
ignoreRegions: Array<{
114116
x1: number;
@@ -133,6 +135,8 @@ declare function compare(
133135
diffCount: number;
134136
/** Percentage of different pixels in the whole image */
135137
diffPercentage: number;
138+
/** Individual line indexes containing different pixels. Guaranteed to be ordered and distinct. */
139+
diffLines?: number[];
136140
}
137141
| {
138142
match: false;

azure-pipelines.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: Build npm release
22

33
variables:
4-
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)
4+
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)
55

66
trigger:
77
- main
@@ -19,7 +19,7 @@ jobs:
1919
- template: .ci/build-platform.yml
2020
parameters:
2121
platform: MacOS
22-
vmImage: macOS-latest
22+
vmImage: macOS-11
2323

2424
# Need windows-2019 to do esy import/export-dependencies
2525
# which assumes you have bsdtar (tar.exe) in your system
@@ -38,7 +38,7 @@ jobs:
3838
- MacOS
3939
- Windows_x64
4040
pool:
41-
vmImage: macOS-latest
41+
vmImage: macOS-11
4242
demands: node.js
4343
steps:
4444
- template: .ci/cross-release.yml

bin/Main.re

+4-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ let main =
3030
stdoutParsableString,
3131
antialiasing,
3232
ignoreRegions,
33+
diffLines,
3334
) => {
3435
module IO1 = (val getIOModule(img1Path));
3536
module IO2 = (val getIOModule(img2Path));
@@ -48,6 +49,7 @@ let main =
4849
~failOnLayoutChange,
4950
~antialiasing,
5051
~ignoreRegions,
52+
~diffLines,
5153
~diffPixel=
5254
Color.ofHexString(diffColorHex)
5355
|> (
@@ -61,12 +63,12 @@ let main =
6163
|> (
6264
fun
6365
| Layout => {diff: None, exitCode: 21}
64-
| Pixel((diffOutput, diffCount, stdoutParsableString))
66+
| Pixel((diffOutput, diffCount, stdoutParsableString, _))
6567
when diffCount == 0 => {
6668
exitCode: 0,
6769
diff: Some(diffOutput),
6870
}
69-
| Pixel((diffOutput, diffCount, diffPercentage)) => {
71+
| Pixel((diffOutput, diffCount, diffPercentage, _)) => {
7072
IO1.saveImage(diffOutput, diffPath);
7173
{exitCode: 22, diff: Some(diffOutput)};
7274
}

bin/ODiffBin.re

+14-1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,18 @@ let antialiasing = {
9191
);
9292
};
9393

94+
let diffLines = {
95+
Arg.(
96+
value
97+
& flag
98+
& info(
99+
["output-diff-lines"],
100+
~doc=
101+
"With this flag enabled, output result in case of different images will output lines for all the different pixels",
102+
)
103+
);
104+
};
105+
94106
let ignoreRegions = {
95107
Arg.(
96108
value
@@ -129,10 +141,11 @@ let cmd = {
129141
$ parsableOutput
130142
$ antialiasing
131143
$ ignoreRegions
144+
$ diffLines
132145
),
133146
Term.info(
134147
"odiff",
135-
~version="2.5.0",
148+
~version="2.6.0",
136149
~doc="Find difference between 2 images.",
137150
~exits=[
138151
Term.exit_info(0, ~doc="on image match"),

bin/Print.re

+19-4
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,34 @@ let printDiffResult = (makeParsableOutput, result) => {
1111
</Pastel>
1212

1313
// SUCCESS
14-
| (Pixel((_output, diffCount, _percentage)), true) when diffCount === 0 => ""
15-
| (Pixel((_output, diffCount, _percentage)), false) when diffCount === 0 =>
14+
| (Pixel((_output, diffCount, _percentage, _lines)), true)
15+
when diffCount === 0 => ""
16+
| (Pixel((_output, diffCount, _percentage, _lines)), false)
17+
when diffCount === 0 =>
1618
<Pastel>
1719
<Pastel color=Green bold=true> "Success! " </Pastel>
1820
"Images are equal.\n"
1921
<Pastel dim=true> "No diff output created." </Pastel>
2022
</Pastel>
2123

2224
// FAILURE
23-
| (Pixel((_output, diffCount, diffPercentage)), true) =>
25+
| (Pixel((_output, diffCount, diffPercentage, stack)), true) when !Stack.is_empty(stack) =>
26+
Int.to_string(diffCount)
27+
++ ";"
28+
++ Float.to_string(diffPercentage)
29+
++ ";"
30+
++ (
31+
stack
32+
|> Stack.fold(
33+
(acc, line) => (line |> Int.to_string) ++ "," ++ acc,
34+
"",
35+
)
36+
)
37+
38+
| (Pixel((_output, diffCount, diffPercentage, _)), true) =>
2439
Int.to_string(diffCount) ++ ";" ++ Float.to_string(diffPercentage)
2540

26-
| (Pixel((_output, diffCount, diffPercentage)), false) =>
41+
| (Pixel((_output, diffCount, diffPercentage, _lines)), false) =>
2742
<Pastel>
2843
<Pastel color=Red bold=true> "Failure! " </Pastel>
2944
"Images are different.\n"

bin/node-bindings/odiff.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export type ODiffOptions = Partial<{
1111
threshold: number;
1212
/** If this is true, antialiased pixels are not counted to the diff of an image */
1313
antialiasing: boolean;
14+
/** If `true` reason: "pixel-diff" output will contain the set of line indexes containing different pixels */
15+
captureDiffLines: boolean;
1416
/** An array of regions to ignore in the diff. */
1517
ignoreRegions: Array<{
1618
x1: number;
@@ -35,6 +37,8 @@ declare function compare(
3537
diffCount: number;
3638
/** Percentage of different pixels in the whole image */
3739
diffPercentage: number;
40+
/** Individual line indexes containing different pixels. Guaranteed to be ordered and distinct. */
41+
diffLines?: number[];
3842
}
3943
| {
4044
match: false;

bin/node-bindings/odiff.js

+18-2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ function optionsToArgs(options) {
4646
setFlag("antialiasing", value);
4747
break;
4848

49+
case "captureDiffLines":
50+
setFlag("output-diff-lines", value);
51+
break;
52+
4953
case "ignoreRegions": {
5054
const regions = value
5155
.map(
@@ -62,7 +66,7 @@ function optionsToArgs(options) {
6266
return argArray;
6367
}
6468

65-
/** @type {(stdout: string) => Partial<{ diffCount: number, diffPercentage: number }>} */
69+
/** @type {(stdout: string) => Partial<{ diffCount: number, diffPercentage: number, diffLines: number[] }>} */
6670
function parsePixelDiffStdout(stdout) {
6771
try {
6872
const parts = stdout.split(";");
@@ -74,6 +78,18 @@ function parsePixelDiffStdout(stdout) {
7478
diffCount: parseInt(diffCount),
7579
diffPercentage: parseFloat(diffPercentage),
7680
};
81+
} else if (parts.length === 3) {
82+
const [diffCount, diffPercentage, linesPart] = parts;
83+
84+
return {
85+
diffCount: parseInt(diffCount),
86+
diffPercentage: parseFloat(diffPercentage),
87+
diffLines: linesPart.split(",").flatMap((line) => {
88+
let parsedInt = parseInt(line);
89+
90+
return isNaN(parsedInt) ? [] : parsedInt;
91+
}),
92+
};
7793
} else {
7894
throw new Error(`Weird pixel diff stdout: ${stdout}`);
7995
}
@@ -131,7 +147,7 @@ async function compare(basePath, comparePath, diffOutput, options = {}) {
131147
/no\n\s*`(.*)'\sfile or\n\s*directory/
132148
);
133149

134-
if (options.noFailOnFsErrors && noFileOrDirectoryMatches[1]) {
150+
if (options.noFailOnFsErrors && noFileOrDirectoryMatches?.[1]) {
135151
resolve({
136152
match: false,
137153
reason: "file-not-exists",

images/www.cypress-diff.png

2.53 MB
Loading

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "odiff",
3-
"version": "2.5.0",
3+
"version": "2.6.0",
44
"description": "The fastest image difference tool.",
55
"license": "MIT",
66
"esy": {

src/Diff.re

+11-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ let maxYIQPossibleDelta = 35215.;
33

44
type diffVariant('a) =
55
| Layout
6-
| Pixel(('a, int, float));
6+
| Pixel(('a, int, float, Stack.t(int)));
77

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

3839
let diffPixelQueue = Queue.create();
40+
let diffLinesStack = Stack.create();
41+
3942
let countDifference = (x, y) => {
4043
diffPixelQueue |> Queue.push((x, y));
44+
45+
if (diffLinesStack |> Stack.is_empty || diffLinesStack |> Stack.top < y) {
46+
diffLinesStack |> Stack.push(y);
47+
}
4148
};
4249

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

117-
(diffOutput, diffCount, diffPercentage);
124+
(diffOutput, diffCount, diffPercentage, diffLinesStack);
118125
};
119126

120127
let diff =
@@ -126,6 +133,7 @@ module MakeDiff = (IO1: ImageIO.ImageIO, IO2: ImageIO.ImageIO) => {
126133
~diffPixel=redPixel,
127134
~failOnLayoutChange=true,
128135
~antialiasing=false,
136+
~diffLines=false,
129137
~ignoreRegions=[],
130138
(),
131139
) =>
@@ -141,6 +149,7 @@ module MakeDiff = (IO1: ImageIO.ImageIO, IO2: ImageIO.ImageIO) => {
141149
~diffPixel,
142150
~outputDiffMask,
143151
~antialiasing,
152+
~diffLines,
144153
~ignoreRegions,
145154
(),
146155
);

test/Test_Core.re

+6-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe("CORE: Antialiasing", ({test, _}) => {
1010
let img1 = loadImage("test/test-images/aa/antialiasing-on.png");
1111
let img2 = loadImage("test/test-images/aa/antialiasing-off.png");
1212

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

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

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

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

82-
let (diffOutput, _, _) =
82+
let (diffOutput, _, _, _) =
8383
PNG_Diff.compare(img1, img2, ~diffPixel=(0, 255, 0), ());
8484

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

9090
if (diffOfDiffPixels > 0) {

test/Test_IO_BMP.re

+5-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ describe("IO: BMP", ({test, _}) => {
99
let img1 = Bmp.IO.loadImage("test/test-images/bmp/clouds.bmp");
1010
let img2 = Bmp.IO.loadImage("test/test-images/bmp/clouds-2.bmp");
1111

12-
let (_, diffPixels, diffPercentage) = Diff.compare(img1, img2, ());
12+
let (_, diffPixels, diffPercentage, _) = Diff.compare(img1, img2, ());
1313

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

22-
let (_, diffPixels, diffPercentage) =
22+
let (_, diffPixels, diffPercentage, _) =
2323
Diff.compare(img1, img2, ~outputDiffMask=false, ());
2424

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

28-
let (_, diffPixelsMask, diffPercentageMask) =
28+
let (_, diffPixelsMask, diffPercentageMask, _) =
2929
Diff.compare(img1, img2, ~outputDiffMask=true, ());
3030

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

39-
let (diffOutput, _, _) = Diff.compare(img1, img2, ());
39+
let (diffOutput, _, _, _) = Diff.compare(img1, img2, ());
4040

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

4646
if (diffOfDiffPixels > 0) {

0 commit comments

Comments
 (0)