Skip to content

Commit 7af9909

Browse files
EvgenyB1001Andrei Stryhelski
authored andcommitted
Add threshold functionality for pdfrender
1 parent 0ad2676 commit 7af9909

File tree

9 files changed

+349
-12
lines changed

9 files changed

+349
-12
lines changed

itext.tests/itext.io.tests/itext/io/util/ImageMagickHelperTest.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,5 +304,54 @@ public virtual void CompareEqualsImagesAndCheckFuzzinessTest() {
304304
StandardOutUtil.RestoreStandardOut(storedPrintStream);
305305
}
306306
}
307+
308+
[NUnit.Framework.Test]
309+
public virtual void CompareEqualImagesAndGetResult() {
310+
String image = SOURCE_FOLDER + "image.png";
311+
String diff = DESTINATION_FOLDER + "diff_equalImages_result.png";
312+
ImageMagickCompareResult result = new ImageMagickHelper().RunImageMagickImageCompareAndGetResult(image, image
313+
, diff, "1");
314+
NUnit.Framework.Assert.IsTrue(result.IsComparingResultSuccessful());
315+
NUnit.Framework.Assert.AreEqual(0, result.GetDiffPixels());
316+
}
317+
318+
[NUnit.Framework.Test]
319+
public virtual void CompareDifferentImagesAndGetResult() {
320+
String image = SOURCE_FOLDER + "image.png";
321+
String image2 = SOURCE_FOLDER + "Im1_1.jpg";
322+
String diff = DESTINATION_FOLDER + "diff_equalImages.png";
323+
ImageMagickCompareResult result = new ImageMagickHelper().RunImageMagickImageCompareAndGetResult(image, image2
324+
, diff, "1");
325+
NUnit.Framework.Assert.IsFalse(result.IsComparingResultSuccessful());
326+
}
327+
328+
[NUnit.Framework.Test]
329+
public virtual void RunImageMagickImageCompareEqualWithThreshold() {
330+
String image = SOURCE_FOLDER + "image.png";
331+
String image2 = SOURCE_FOLDER + "image.png";
332+
String diff = DESTINATION_FOLDER + "diff_equalImages.png";
333+
bool result = new ImageMagickHelper().RunImageMagickImageCompareWithThreshold(image, image2, diff, "0", 0);
334+
NUnit.Framework.Assert.IsTrue(result);
335+
}
336+
337+
[NUnit.Framework.Test]
338+
public virtual void RunImageMagickImageCompareWithEnoughThreshold() {
339+
String image = SOURCE_FOLDER + "image.png";
340+
String image2 = SOURCE_FOLDER + "Im1_1.jpg";
341+
String diff = DESTINATION_FOLDER + "diff_equalImages.png";
342+
bool result = new ImageMagickHelper().RunImageMagickImageCompareWithThreshold(image, image2, diff, "20", 2000000
343+
);
344+
NUnit.Framework.Assert.IsTrue(result);
345+
}
346+
347+
[NUnit.Framework.Test]
348+
public virtual void RunImageMagickImageCompareWithNotEnoughThreshold() {
349+
String image = SOURCE_FOLDER + "image.png";
350+
String image2 = SOURCE_FOLDER + "Im1_1.jpg";
351+
String diff = DESTINATION_FOLDER + "diff_equalImages.png";
352+
bool result = new ImageMagickHelper().RunImageMagickImageCompareWithThreshold(image, image2, diff, "20", 2000
353+
);
354+
NUnit.Framework.Assert.IsFalse(result);
355+
}
307356
}
308357
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2021 iText Group NV
4+
Authors: iText Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
using System;
24+
using iText.Test;
25+
26+
namespace iText.IO.Util {
27+
public class ProcessInfoTest : ExtendedITextTest {
28+
[NUnit.Framework.Test]
29+
public virtual void GetExitCodeTest() {
30+
int exitCode = 1;
31+
ProcessInfo processInfo = new ProcessInfo(exitCode, null, null);
32+
NUnit.Framework.Assert.AreEqual(exitCode, processInfo.GetExitCode());
33+
}
34+
35+
[NUnit.Framework.Test]
36+
public virtual void GetProcessStdOutput() {
37+
String stdOutput = "output";
38+
ProcessInfo processInfo = new ProcessInfo(0, stdOutput, null);
39+
NUnit.Framework.Assert.AreEqual(stdOutput, processInfo.GetProcessStdOutput());
40+
}
41+
42+
[NUnit.Framework.Test]
43+
public virtual void GetProcessErrOutput() {
44+
String stdOutput = "output";
45+
ProcessInfo processInfo = new ProcessInfo(0, null, stdOutput);
46+
NUnit.Framework.Assert.AreEqual(stdOutput, processInfo.GetProcessErrOutput());
47+
}
48+
}
49+
}

itext.tests/itext.io.tests/itext/io/util/SystemUtilTest.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,5 +155,18 @@ public virtual void RunProcessAndWaitWithWorkingDirectoryTest() {
155155
NUnit.Framework.Assert.False(result);
156156
NUnit.Framework.Assert.True(FileUtil.FileExists(diff));
157157
}
158+
159+
[NUnit.Framework.Test]
160+
public void RunProcessAndGetProcessInfoTest() {
161+
String imageMagickPath = SystemUtil.GetEnvironmentVariable(ImageMagickHelper.MAGICK_COMPARE_ENVIRONMENT_VARIABLE);
162+
if (imageMagickPath == null) {
163+
imageMagickPath = SystemUtil.GetEnvironmentVariable(ImageMagickHelper.MAGICK_COMPARE_ENVIRONMENT_VARIABLE_LEGACY);
164+
}
165+
166+
ProcessInfo processInfo = SystemUtil.RunProcessAndGetProcessInfo(imageMagickPath,"--version");
167+
168+
NUnit.Framework.Assert.NotNull(processInfo);
169+
NUnit.Framework.Assert.AreEqual(0, processInfo.GetExitCode());
170+
}
158171
}
159172
}

itext/itext.io/itext/io/IoExceptionMessage.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,9 @@ public sealed class IoExceptionMessage {
5959
public const String GHOSTSCRIPT_FAILED = "GhostScript failed for <filename>";
6060

6161
public const String CANNOT_OPEN_OUTPUT_DIRECTORY = "Cannot open output directory for <filename>";
62+
63+
public const String IMAGE_MAGICK_OUTPUT_IS_NULL = "ImageMagick process output is null.";
64+
65+
public const String IMAGE_MAGICK_PROCESS_EXECUTION_FAILED = "ImageMagick process execution finished with errors: ";
6266
}
6367
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2021 iText Group NV
4+
Authors: iText Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
namespace iText.IO.Util {
24+
/// <summary>
25+
/// A helper data class, which aggregates true/false result of ImageMagick comparing
26+
/// as well as the number of different pixels.
27+
/// </summary>
28+
public sealed class ImageMagickCompareResult {
29+
private readonly bool result;
30+
31+
private readonly long diffPixels;
32+
33+
/// <summary>Creates an instance that contains ImageMagick comparing result information.</summary>
34+
/// <param name="result">true, if the compared images are equal.</param>
35+
/// <param name="diffPixels">number of different pixels.</param>
36+
public ImageMagickCompareResult(bool result, long diffPixels) {
37+
this.result = result;
38+
this.diffPixels = diffPixels;
39+
}
40+
41+
/// <summary>Returns image compare boolean value.</summary>
42+
/// <returns>true if the compared images are equal.</returns>
43+
public bool IsComparingResultSuccessful() {
44+
return result;
45+
}
46+
47+
/// <summary>Getter for a different pixels count.</summary>
48+
/// <returns>Returns a a different pixels count.</returns>
49+
public long GetDiffPixels() {
50+
return diffPixels;
51+
}
52+
}
53+
}

itext/itext.io/itext/io/util/ImageMagickHelper.cs

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ source product.
4242
4343
*/
4444
using System;
45+
using System.Text.RegularExpressions;
4546
using iText.IO;
4647

4748
namespace iText.IO.Util {
@@ -67,6 +68,10 @@ public class ImageMagickHelper {
6768

6869
private const String TEMP_FILE_PREFIX = "itext_im_io_temp";
6970

71+
private const String DIFF_PIXELS_OUTPUT_REGEXP = "^\\d+\\.*\\d*(e\\+\\d+)?";
72+
73+
private static readonly Regex pattern = iText.IO.Util.StringUtil.RegexCompile(DIFF_PIXELS_OUTPUT_REGEXP);
74+
7075
private String compareExec;
7176

7277
/// <summary>
@@ -127,6 +132,59 @@ public virtual bool RunImageMagickImageCompare(String outImageFilePath, String c
127132
/// <returns>boolean result of comparing: true - images are visually equal</returns>
128133
public virtual bool RunImageMagickImageCompare(String outImageFilePath, String cmpImageFilePath, String diffImageName
129134
, String fuzzValue) {
135+
ImageMagickCompareResult compareResult = RunImageMagickImageCompareAndGetResult(outImageFilePath, cmpImageFilePath
136+
, diffImageName, fuzzValue);
137+
return compareResult.IsComparingResultSuccessful();
138+
}
139+
140+
/// <summary>
141+
/// Runs imageMagick to visually compare images with the specified fuzziness value and given threshold
142+
/// and generate difference output.
143+
/// </summary>
144+
/// <param name="outImageFilePath">Path to the output image file</param>
145+
/// <param name="cmpImageFilePath">Path to the cmp image file</param>
146+
/// <param name="diffImageName">Path to the difference output image file</param>
147+
/// <param name="fuzzValue">
148+
/// String fuzziness value to compare images. Should be formatted as string with integer
149+
/// or decimal number. Can be null, if it is not required to use fuzziness
150+
/// </param>
151+
/// <param name="threshold">Long value of accepted threshold.</param>
152+
/// <returns>boolean result of comparing: true - images are visually equal</returns>
153+
public virtual bool RunImageMagickImageCompareWithThreshold(String outImageFilePath, String cmpImageFilePath
154+
, String diffImageName, String fuzzValue, long threshold) {
155+
ImageMagickCompareResult compareResult = RunImageMagickImageCompareAndGetResult(outImageFilePath, cmpImageFilePath
156+
, diffImageName, fuzzValue);
157+
if (compareResult.IsComparingResultSuccessful()) {
158+
return true;
159+
}
160+
else {
161+
return compareResult.GetDiffPixels() <= threshold;
162+
}
163+
}
164+
165+
/// <summary>Runs imageMagick to visually compare images with the specified fuzziness value and generate difference output.
166+
/// </summary>
167+
/// <remarks>
168+
/// Runs imageMagick to visually compare images with the specified fuzziness value and generate difference output.
169+
/// This method returns an object of
170+
/// <see cref="ImageMagickCompareResult"/>
171+
/// , containing comparing result information,
172+
/// such as boolean result value and the number of different pixels.
173+
/// </remarks>
174+
/// <param name="outImageFilePath">Path to the output image file</param>
175+
/// <param name="cmpImageFilePath">Path to the cmp image file</param>
176+
/// <param name="diffImageName">Path to the difference output image file</param>
177+
/// <param name="fuzzValue">
178+
/// String fuzziness value to compare images. Should be formatted as string with integer
179+
/// or decimal number. Can be null, if it is not required to use fuzziness
180+
/// </param>
181+
/// <returns>
182+
/// an object of
183+
/// <see cref="ImageMagickCompareResult"/>
184+
/// . containing comparing result information.
185+
/// </returns>
186+
public virtual ImageMagickCompareResult RunImageMagickImageCompareAndGetResult(String outImageFilePath, String
187+
cmpImageFilePath, String diffImageName, String fuzzValue) {
130188
if (!ValidateFuzziness(fuzzValue)) {
131189
throw new ArgumentException("Invalid fuzziness value: " + fuzzValue);
132190
}
@@ -137,14 +195,19 @@ public virtual bool RunImageMagickImageCompare(String outImageFilePath, String c
137195
try {
138196
replacementOutFile = FileUtil.CreateTempCopy(outImageFilePath, TEMP_FILE_PREFIX, null);
139197
replacementCmpFile = FileUtil.CreateTempCopy(cmpImageFilePath, TEMP_FILE_PREFIX, null);
140-
replacementDiff = FileUtil.CreateTempFile(TEMP_FILE_PREFIX, null).ToString();
198+
// ImageMagick generates difference images in .png format, therefore we can specify it.
199+
// For some reason .webp comparison fails if the extension of diff image is not specified.
200+
replacementDiff = FileUtil.CreateTempFile(TEMP_FILE_PREFIX, ".png").FullName;
141201
String currCompareParams = fuzzValue + " '" + replacementOutFile + "' '" + replacementCmpFile + "' '" + replacementDiff
142202
+ "'";
143-
bool result = SystemUtil.RunProcessAndWait(compareExec, currCompareParams);
203+
ProcessInfo processInfo = SystemUtil.RunProcessAndGetProcessInfo(compareExec, currCompareParams);
204+
bool comparingResult = processInfo.GetExitCode() == 0;
205+
long diffPixels = ParseImageMagickProcessOutput(processInfo.GetProcessErrOutput());
206+
ImageMagickCompareResult resultInfo = new ImageMagickCompareResult(comparingResult, diffPixels);
144207
if (FileUtil.FileExists(replacementDiff)) {
145208
FileUtil.Copy(replacementDiff, diffImageName);
146209
}
147-
return result;
210+
return resultInfo;
148211
}
149212
finally {
150213
FileUtil.RemoveFiles(new String[] { replacementOutFile, replacementCmpFile, replacementDiff });
@@ -166,5 +229,27 @@ internal static bool ValidateFuzziness(String fuzziness) {
166229
}
167230
}
168231
}
232+
233+
private static long ParseImageMagickProcessOutput(String processOutput) {
234+
if (null == processOutput) {
235+
throw new ArgumentException(IoExceptionMessage.IMAGE_MAGICK_OUTPUT_IS_NULL);
236+
}
237+
if (String.IsNullOrEmpty(processOutput)) {
238+
return 0L;
239+
}
240+
String[] processOutputLines = iText.IO.Util.StringUtil.Split(processOutput, "\n");
241+
foreach (String line in processOutputLines) {
242+
try {
243+
Matcher matcher = iText.IO.Util.Matcher.Match(pattern, line);
244+
if (matcher.Find()) {
245+
return (long)Double.Parse(matcher.Group(), System.Globalization.CultureInfo.InvariantCulture);
246+
}
247+
}
248+
catch (FormatException) {
249+
}
250+
}
251+
// Nothing should be done here because of the exception, that will be thrown later.
252+
throw new System.IO.IOException(IoExceptionMessage.IMAGE_MAGICK_PROCESS_EXECUTION_FAILED + processOutput);
253+
}
169254
}
170255
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2021 iText Group NV
4+
Authors: iText Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
using System;
24+
25+
namespace iText.IO.Util {
26+
/// <summary>Class contains a process information, such as process exit code and process output.</summary>
27+
public sealed class ProcessInfo {
28+
private readonly int exitCode;
29+
30+
private readonly String processStdOutput;
31+
32+
private readonly String processErrOutput;
33+
34+
/// <summary>
35+
/// Create a new instance, containing a process information,
36+
/// such as process exit code, process standard and error outputs.
37+
/// </summary>
38+
/// <param name="exitCode">exit code of the process.</param>
39+
/// <param name="processStdOutput">the standard output of the process.</param>
40+
/// <param name="processErrOutput">the error output of the process.</param>
41+
public ProcessInfo(int exitCode, String processStdOutput, String processErrOutput) {
42+
this.exitCode = exitCode;
43+
this.processStdOutput = processStdOutput;
44+
this.processErrOutput = processErrOutput;
45+
}
46+
47+
/// <summary>Getter for a process exit code.</summary>
48+
/// <returns>Returns a process exit code.</returns>
49+
public int GetExitCode() {
50+
return exitCode;
51+
}
52+
53+
/// <summary>Getter for a standard process output.</summary>
54+
/// <returns>Returns a process standard output string.</returns>
55+
public String GetProcessStdOutput() {
56+
return processStdOutput;
57+
}
58+
59+
/// <summary>Getter for an error process output.</summary>
60+
/// <returns>Returns a process error output string.</returns>
61+
public String GetProcessErrOutput() {
62+
return processErrOutput;
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)