Skip to content

Commit f3ad7a2

Browse files
committed
Add possibility to return the number of different pixels in ImageMagickHelper class
DEVSIX-5870 Autoported commit. Original commit hash: [9be9e3d23]
1 parent 149983e commit f3ad7a2

File tree

9 files changed

+282
-6
lines changed

9 files changed

+282
-6
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using iText.Test;
3+
4+
namespace iText.Commons.Utils {
5+
public class ProcessInfoTest : ExtendedITextTest {
6+
[NUnit.Framework.Test]
7+
public virtual void GetExitCodeTest() {
8+
int exitCode = 1;
9+
ProcessInfo processInfo = new ProcessInfo(exitCode, null, null);
10+
NUnit.Framework.Assert.AreEqual(exitCode, processInfo.GetExitCode());
11+
}
12+
13+
[NUnit.Framework.Test]
14+
public virtual void GetProcessStdOutput() {
15+
String stdOutput = "output";
16+
ProcessInfo processInfo = new ProcessInfo(0, stdOutput, null);
17+
NUnit.Framework.Assert.AreEqual(stdOutput, processInfo.GetProcessStdOutput());
18+
}
19+
20+
[NUnit.Framework.Test]
21+
public virtual void GetProcessErrOutput() {
22+
String stdOutput = "output";
23+
ProcessInfo processInfo = new ProcessInfo(0, null, stdOutput);
24+
NUnit.Framework.Assert.AreEqual(stdOutput, processInfo.GetProcessErrOutput());
25+
}
26+
}
27+
}

itext.tests/itext.commons.tests/itext/commons/utils/SystemUtilTest.cs

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

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,5 +305,54 @@ public virtual void CompareEqualsImagesAndCheckFuzzinessTest() {
305305
StandardOutUtil.RestoreStandardOut(storedPrintStream);
306306
}
307307
}
308+
309+
[NUnit.Framework.Test]
310+
public virtual void CompareEqualImagesAndGetResult() {
311+
String image = SOURCE_FOLDER + "image.png";
312+
String diff = DESTINATION_FOLDER + "diff_equalImages_result.png";
313+
ImageMagickCompareResult result = new ImageMagickHelper().RunImageMagickImageCompareAndGetResult(image, image
314+
, diff, "1");
315+
NUnit.Framework.Assert.IsTrue(result.IsComparingResultSuccessful());
316+
NUnit.Framework.Assert.AreEqual(0, result.GetDiffPixels());
317+
}
318+
319+
[NUnit.Framework.Test]
320+
public virtual void CompareDifferentImagesAndGetResult() {
321+
String image = SOURCE_FOLDER + "image.png";
322+
String image2 = SOURCE_FOLDER + "Im1_1.jpg";
323+
String diff = DESTINATION_FOLDER + "diff_equalImages.png";
324+
ImageMagickCompareResult result = new ImageMagickHelper().RunImageMagickImageCompareAndGetResult(image, image2
325+
, diff, "1");
326+
NUnit.Framework.Assert.IsFalse(result.IsComparingResultSuccessful());
327+
}
328+
329+
[NUnit.Framework.Test]
330+
public virtual void RunImageMagickImageCompareEqualWithThreshold() {
331+
String image = SOURCE_FOLDER + "image.png";
332+
String image2 = SOURCE_FOLDER + "image.png";
333+
String diff = DESTINATION_FOLDER + "diff_equalImages.png";
334+
bool result = new ImageMagickHelper().RunImageMagickImageCompareWithThreshold(image, image2, diff, "0", 0);
335+
NUnit.Framework.Assert.IsTrue(result);
336+
}
337+
338+
[NUnit.Framework.Test]
339+
public virtual void RunImageMagickImageCompareWithEnoughThreshold() {
340+
String image = SOURCE_FOLDER + "image.png";
341+
String image2 = SOURCE_FOLDER + "Im1_1.jpg";
342+
String diff = DESTINATION_FOLDER + "diff_equalImages.png";
343+
bool result = new ImageMagickHelper().RunImageMagickImageCompareWithThreshold(image, image2, diff, "20", 2000000
344+
);
345+
NUnit.Framework.Assert.IsTrue(result);
346+
}
347+
348+
[NUnit.Framework.Test]
349+
public virtual void RunImageMagickImageCompareWithNotEnoughThreshold() {
350+
String image = SOURCE_FOLDER + "image.png";
351+
String image2 = SOURCE_FOLDER + "Im1_1.jpg";
352+
String diff = DESTINATION_FOLDER + "diff_equalImages.png";
353+
bool result = new ImageMagickHelper().RunImageMagickImageCompareWithThreshold(image, image2, diff, "20", 2000
354+
);
355+
NUnit.Framework.Assert.IsFalse(result);
356+
}
308357
}
309358
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
3+
namespace iText.Commons.Utils {
4+
/// <summary>Class contains a process information, such as process exit code and process output.</summary>
5+
public sealed class ProcessInfo {
6+
private readonly int exitCode;
7+
8+
private readonly String processStdOutput;
9+
10+
private readonly String processErrOutput;
11+
12+
/// <summary>
13+
/// Create a new instance, containing a process information,
14+
/// such as process exit code, process standard and error outputs.
15+
/// </summary>
16+
/// <param name="exitCode">exit code of the process.</param>
17+
/// <param name="processStdOutput">the standard output of the process.</param>
18+
/// <param name="processErrOutput">the error output of the process.</param>
19+
public ProcessInfo(int exitCode, String processStdOutput, String processErrOutput) {
20+
this.exitCode = exitCode;
21+
this.processStdOutput = processStdOutput;
22+
this.processErrOutput = processErrOutput;
23+
}
24+
25+
/// <summary>Getter for a process exit code.</summary>
26+
/// <returns>Returns a process exit code.</returns>
27+
public int GetExitCode() {
28+
return exitCode;
29+
}
30+
31+
/// <summary>Getter for a standard process output.</summary>
32+
/// <returns>Returns a process standard output string.</returns>
33+
public String GetProcessStdOutput() {
34+
return processStdOutput;
35+
}
36+
37+
/// <summary>Getter for an error process output.</summary>
38+
/// <returns>Returns a process error output string.</returns>
39+
public String GetProcessErrOutput() {
40+
return processErrOutput;
41+
}
42+
}
43+
}

itext/itext.commons/itext/commons/utils/SystemUtil.cs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ namespace iText.Commons.Utils {
5656
public class SystemUtil {
5757
private const String SPLIT_REGEX = "((\".+?\"|[^'\\s]|'.+?')+)\\s*";
5858

59+
private const int STD_OUTPUT_INDEX = 0;
60+
private const int ERR_OUTPUT_INDEX = 1;
61+
5962
public static long GetTimeBasedSeed() {
6063
return DateTime.Now.Ticks + Environment.TickCount;
6164
}
@@ -120,6 +123,18 @@ public static String RunProcessAndGetOutput(String exec, String @params) {
120123

121124
return processOutput;
122125
}
126+
127+
public static ProcessInfo RunProcessAndGetProcessInfo(String exec, String @params) {
128+
using (Process proc = new Process()) {
129+
SetProcessStartInfo(proc, exec, @params, null);
130+
proc.Start();
131+
StringBuilder[] outputStreamStrings = GetProcessOutputBuilders(proc);
132+
String processStdOutput = outputStreamStrings[STD_OUTPUT_INDEX].ToString();
133+
String processErrOutput = outputStreamStrings[ERR_OUTPUT_INDEX].ToString();
134+
proc.WaitForExit();
135+
return new ProcessInfo(proc.ExitCode, processStdOutput, processErrOutput);
136+
}
137+
}
123138

124139
public static StringBuilder RunProcessAndCollectErrors(String exec, String @params) {
125140
StringBuilder errorsBuilder;
@@ -186,15 +201,25 @@ internal static String[] SplitIntoProcessArguments(String command, String @param
186201
}
187202

188203
internal static String GetProcessOutput(Process p) {
204+
StringBuilder[] builders = GetProcessOutputBuilders(p);
205+
206+
return builders[STD_OUTPUT_INDEX].ToString()
207+
+ '\n'
208+
+ builders[ERR_OUTPUT_INDEX].ToString();
209+
}
210+
211+
internal static StringBuilder[] GetProcessOutputBuilders(Process p) {
189212
StringBuilder bri = new StringBuilder();
190213
StringBuilder bre = new StringBuilder();
191-
do
192-
{
214+
do {
193215
bri.Append(p.StandardOutput.ReadToEnd());
194216
bre.Append(p.StandardError.ReadToEnd());
195217
} while (!p.HasExited);
218+
219+
Console.Out.WriteLine(bre.ToString());
196220

197-
return bri.ToString() + '\n' + bre.ToString();
221+
StringBuilder[] resultOutputArray = new[] { bri, bre };
222+
return resultOutputArray;
198223
}
199224

200225
internal static StringBuilder GetProcessErrorsOutput(Process p) {

itext/itext.io/itext/io/exceptions/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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
namespace iText.IO.Util {
2+
/// <summary>
3+
/// A helper data class, which aggregates true/false result of ImageMagick comparing
4+
/// as well as the number of different pixels.
5+
/// </summary>
6+
public sealed class ImageMagickCompareResult {
7+
private readonly bool result;
8+
9+
private readonly long diffPixels;
10+
11+
/// <summary>Creates an instance that contains ImageMagick comparing result information.</summary>
12+
/// <param name="result">true, if the compared images are equal.</param>
13+
/// <param name="diffPixels">number of different pixels.</param>
14+
public ImageMagickCompareResult(bool result, long diffPixels) {
15+
this.result = result;
16+
this.diffPixels = diffPixels;
17+
}
18+
19+
/// <summary>Returns image compare boolean value.</summary>
20+
/// <returns>true if the compared images are equal.</returns>
21+
public bool IsComparingResultSuccessful() {
22+
return result;
23+
}
24+
25+
/// <summary>Getter for a different pixels count.</summary>
26+
/// <returns>Returns a a different pixels count.</returns>
27+
public long GetDiffPixels() {
28+
return diffPixels;
29+
}
30+
}
31+
}

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

Lines changed: 86 additions & 2 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.Commons.Utils;
4647
using iText.IO.Exceptions;
4748

@@ -68,6 +69,11 @@ public class ImageMagickHelper {
6869

6970
private const String TEMP_FILE_PREFIX = "itext_im_io_temp";
7071

72+
private const String DIFF_PIXELS_OUTPUT_REGEXP = "^\\d+\\.*\\d*(e\\+\\d+)?";
73+
74+
private static readonly Regex pattern = iText.Commons.Utils.StringUtil.RegexCompile(DIFF_PIXELS_OUTPUT_REGEXP
75+
);
76+
7177
private String compareExec;
7278

7379
/// <summary>
@@ -128,6 +134,59 @@ public virtual bool RunImageMagickImageCompare(String outImageFilePath, String c
128134
/// <returns>boolean result of comparing: true - images are visually equal</returns>
129135
public virtual bool RunImageMagickImageCompare(String outImageFilePath, String cmpImageFilePath, String diffImageName
130136
, String fuzzValue) {
137+
ImageMagickCompareResult compareResult = RunImageMagickImageCompareAndGetResult(outImageFilePath, cmpImageFilePath
138+
, diffImageName, fuzzValue);
139+
return compareResult.IsComparingResultSuccessful();
140+
}
141+
142+
/// <summary>
143+
/// Runs imageMagick to visually compare images with the specified fuzziness value and given threshold
144+
/// and generate difference output.
145+
/// </summary>
146+
/// <param name="outImageFilePath">Path to the output image file</param>
147+
/// <param name="cmpImageFilePath">Path to the cmp image file</param>
148+
/// <param name="diffImageName">Path to the difference output image file</param>
149+
/// <param name="fuzzValue">
150+
/// String fuzziness value to compare images. Should be formatted as string with integer
151+
/// or decimal number. Can be null, if it is not required to use fuzziness
152+
/// </param>
153+
/// <param name="threshold">Long value of accepted threshold.</param>
154+
/// <returns>boolean result of comparing: true - images are visually equal</returns>
155+
public virtual bool RunImageMagickImageCompareWithThreshold(String outImageFilePath, String cmpImageFilePath
156+
, String diffImageName, String fuzzValue, long threshold) {
157+
ImageMagickCompareResult compareResult = RunImageMagickImageCompareAndGetResult(outImageFilePath, cmpImageFilePath
158+
, diffImageName, fuzzValue);
159+
if (compareResult.IsComparingResultSuccessful()) {
160+
return true;
161+
}
162+
else {
163+
return compareResult.GetDiffPixels() <= threshold;
164+
}
165+
}
166+
167+
/// <summary>Runs imageMagick to visually compare images with the specified fuzziness value and generate difference output.
168+
/// </summary>
169+
/// <remarks>
170+
/// Runs imageMagick to visually compare images with the specified fuzziness value and generate difference output.
171+
/// This method returns an object of
172+
/// <see cref="ImageMagickCompareResult"/>
173+
/// , containing comparing result information,
174+
/// such as boolean result value and the number of different pixels.
175+
/// </remarks>
176+
/// <param name="outImageFilePath">Path to the output image file</param>
177+
/// <param name="cmpImageFilePath">Path to the cmp image file</param>
178+
/// <param name="diffImageName">Path to the difference output image file</param>
179+
/// <param name="fuzzValue">
180+
/// String fuzziness value to compare images. Should be formatted as string with integer
181+
/// or decimal number. Can be null, if it is not required to use fuzziness
182+
/// </param>
183+
/// <returns>
184+
/// an object of
185+
/// <see cref="ImageMagickCompareResult"/>
186+
/// . containing comparing result information.
187+
/// </returns>
188+
public virtual ImageMagickCompareResult RunImageMagickImageCompareAndGetResult(String outImageFilePath, String
189+
cmpImageFilePath, String diffImageName, String fuzzValue) {
131190
if (!ValidateFuzziness(fuzzValue)) {
132191
throw new ArgumentException("Invalid fuzziness value: " + fuzzValue);
133192
}
@@ -143,11 +202,14 @@ public virtual bool RunImageMagickImageCompare(String outImageFilePath, String c
143202
replacementDiff = FileUtil.CreateTempFile(TEMP_FILE_PREFIX, ".png").FullName;
144203
String currCompareParams = fuzzValue + " '" + replacementOutFile + "' '" + replacementCmpFile + "' '" + replacementDiff
145204
+ "'";
146-
bool result = SystemUtil.RunProcessAndWait(compareExec, currCompareParams);
205+
ProcessInfo processInfo = SystemUtil.RunProcessAndGetProcessInfo(compareExec, currCompareParams);
206+
bool comparingResult = processInfo.GetExitCode() == 0;
207+
long diffPixels = ParseImageMagickProcessOutput(processInfo.GetProcessErrOutput());
208+
ImageMagickCompareResult resultInfo = new ImageMagickCompareResult(comparingResult, diffPixels);
147209
if (FileUtil.FileExists(replacementDiff)) {
148210
FileUtil.Copy(replacementDiff, diffImageName);
149211
}
150-
return result;
212+
return resultInfo;
151213
}
152214
finally {
153215
FileUtil.RemoveFiles(new String[] { replacementOutFile, replacementCmpFile, replacementDiff });
@@ -169,5 +231,27 @@ internal static bool ValidateFuzziness(String fuzziness) {
169231
}
170232
}
171233
}
234+
235+
private static long ParseImageMagickProcessOutput(String processOutput) {
236+
if (null == processOutput) {
237+
throw new ArgumentException(IoExceptionMessage.IMAGE_MAGICK_OUTPUT_IS_NULL);
238+
}
239+
if (String.IsNullOrEmpty(processOutput)) {
240+
return 0L;
241+
}
242+
String[] processOutputLines = iText.Commons.Utils.StringUtil.Split(processOutput, "\n");
243+
foreach (String line in processOutputLines) {
244+
try {
245+
Matcher matcher = iText.Commons.Utils.Matcher.Match(pattern, line);
246+
if (matcher.Find()) {
247+
return (long)Double.Parse(matcher.Group(), System.Globalization.CultureInfo.InvariantCulture);
248+
}
249+
}
250+
catch (FormatException) {
251+
}
252+
}
253+
// Nothing should be done here because of the exception, that will be thrown later.
254+
throw new System.IO.IOException(IoExceptionMessage.IMAGE_MAGICK_PROCESS_EXECUTION_FAILED + processOutput);
255+
}
172256
}
173257
}

port-hash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0c457d47891c1d9215a7cbc24200e7dd6638594f
1+
9be9e3d23515ea9b1cb417b29b520371f691e1b8

0 commit comments

Comments
 (0)