Skip to content

Commit 9be9e3d

Browse files
committed
Add possibility to return the number of different pixels in ImageMagickHelper class
DEVSIX-5870
1 parent 0c457d4 commit 9be9e3d

File tree

8 files changed

+329
-5
lines changed

8 files changed

+329
-5
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.itextpdf.commons.utils;
2+
3+
/**
4+
* Class contains a process information, such as process exit code and process output.
5+
*/
6+
public final class ProcessInfo {
7+
8+
private final int exitCode;
9+
private final String processStdOutput;
10+
private final String processErrOutput;
11+
12+
/**
13+
* Create a new instance, containing a process information,
14+
* such as process exit code, process standard and error outputs.
15+
*
16+
* @param exitCode exit code of the process.
17+
* @param processStdOutput the standard output of the process.
18+
* @param processErrOutput the error output of the process.
19+
*/
20+
public ProcessInfo(int exitCode, String processStdOutput, String processErrOutput) {
21+
this.exitCode = exitCode;
22+
this.processStdOutput = processStdOutput;
23+
this.processErrOutput = processErrOutput;
24+
}
25+
26+
/**
27+
* Getter for a process exit code.
28+
*
29+
* @return Returns a process exit code.
30+
*/
31+
public int getExitCode() {
32+
return exitCode;
33+
}
34+
35+
/**
36+
* Getter for a standard process output.
37+
*
38+
* @return Returns a process standard output string.
39+
*/
40+
public String getProcessStdOutput() {
41+
return processStdOutput;
42+
}
43+
44+
/**
45+
* Getter for an error process output.
46+
*
47+
* @return Returns a process error output string.
48+
*/
49+
public String getProcessErrOutput() {
50+
return processErrOutput;
51+
}
52+
}

commons/src/main/java/com/itextpdf/commons/utils/SystemUtil.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ This file is part of the iText (R) project.
4545
import java.io.BufferedReader;
4646
import java.io.File;
4747
import java.io.IOException;
48+
import java.io.InputStream;
4849
import java.io.InputStreamReader;
4950
import java.util.ArrayList;
5051
import java.util.Collections;
@@ -130,6 +131,14 @@ public static StringBuilder runProcessAndCollectErrors(String execPath, String p
130131
return printProcessErrorsOutput(runProcess(execPath, params, null));
131132
}
132133

134+
public static ProcessInfo runProcessAndGetProcessInfo(String command, String params) throws IOException,
135+
InterruptedException {
136+
Process p = runProcess(command, params, null);
137+
String processStdOutput = printProcessStandardOutput(p).toString();
138+
String processErrOutput = printProcessErrorsOutput(p).toString();
139+
return new ProcessInfo(p.waitFor(), processStdOutput, processErrOutput);
140+
}
141+
133142
static Process runProcess(String execPath, String params, String workingDirPath) throws IOException {
134143
List<String> cmdList = prepareProcessArguments(execPath, params);
135144
String[] cmdArray = cmdList.toArray(new String[0]);
@@ -181,8 +190,16 @@ static String getProcessOutput(Process p) throws IOException {
181190
}
182191

183192
static StringBuilder printProcessErrorsOutput(Process p) throws IOException {
193+
return printProcessOutput(p.getErrorStream());
194+
}
195+
196+
static StringBuilder printProcessStandardOutput(Process p) throws IOException {
197+
return printProcessOutput(p.getInputStream());
198+
}
199+
200+
private static StringBuilder printProcessOutput(InputStream processStream) throws IOException {
184201
StringBuilder builder = new StringBuilder();
185-
BufferedReader bre = new BufferedReader(new InputStreamReader(p.getErrorStream()));
202+
BufferedReader bre = new BufferedReader(new InputStreamReader(processStream));
186203
String line;
187204
while ((line = bre.readLine()) != null) {
188205
System.out.println(line);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.itextpdf.commons.utils;
2+
3+
import com.itextpdf.test.ExtendedITextTest;
4+
import com.itextpdf.test.annotations.type.UnitTest;
5+
6+
import org.junit.Assert;
7+
import org.junit.Test;
8+
import org.junit.experimental.categories.Category;
9+
10+
@Category(UnitTest.class)
11+
public class ProcessInfoTest extends ExtendedITextTest {
12+
13+
@Test
14+
public void getExitCodeTest() {
15+
int exitCode = 1;
16+
ProcessInfo processInfo = new ProcessInfo(exitCode, null, null);
17+
18+
Assert.assertEquals(exitCode, processInfo.getExitCode());
19+
}
20+
21+
@Test
22+
public void getProcessStdOutput() {
23+
String stdOutput = "output";
24+
ProcessInfo processInfo = new ProcessInfo(0, stdOutput, null);
25+
26+
Assert.assertEquals(stdOutput, processInfo.getProcessStdOutput());
27+
}
28+
29+
@Test
30+
public void getProcessErrOutput() {
31+
String stdOutput = "output";
32+
ProcessInfo processInfo = new ProcessInfo(0, null, stdOutput);
33+
34+
Assert.assertEquals(stdOutput, processInfo.getProcessErrOutput());
35+
}
36+
}

commons/src/test/java/com/itextpdf/commons/utils/SystemUtilTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,19 @@ public void runProcessAndWaitWithWorkingDirectoryTest() throws IOException, Inte
165165
Assert.assertTrue(FileUtil.fileExists(diff));
166166
}
167167

168+
@Test
169+
public void runProcessAndGetProcessInfoTest() throws IOException, InterruptedException {
170+
String imageMagickPath = SystemUtil.getPropertyOrEnvironmentVariable(MAGICK_COMPARE_ENVIRONMENT_VARIABLE);
171+
if (imageMagickPath == null) {
172+
imageMagickPath = SystemUtil.getPropertyOrEnvironmentVariable(MAGICK_COMPARE_ENVIRONMENT_VARIABLE_LEGACY);
173+
}
174+
175+
ProcessInfo processInfo = SystemUtil.runProcessAndGetProcessInfo(imageMagickPath,"--version");
176+
177+
Assert.assertNotNull(processInfo);
178+
Assert.assertEquals(0, processInfo.getExitCode());
179+
}
180+
168181

169182
static class TestProcess extends Process {
170183

io/src/main/java/com/itextpdf/io/exceptions/IoExceptionMessage.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,7 @@ public final class IoExceptionMessage {
6363
+ " environment variable to a CLI command that can run the Ghostscript application. See BUILDING.MD in the root of the repository for more details.";
6464
public static final String GHOSTSCRIPT_FAILED = "GhostScript failed for <filename>";
6565
public static final String CANNOT_OPEN_OUTPUT_DIRECTORY = "Cannot open output directory for <filename>";
66-
66+
public static final String IMAGE_MAGICK_OUTPUT_IS_NULL = "ImageMagick process output is null.";
67+
public static final String IMAGE_MAGICK_PROCESS_EXECUTION_FAILED =
68+
"ImageMagick process execution finished with errors: ";
6769
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.itextpdf.io.util;
2+
3+
/**
4+
* A helper data class, which aggregates true/false result of ImageMagick comparing
5+
* as well as the number of different pixels.
6+
*/
7+
public final class ImageMagickCompareResult {
8+
9+
private final boolean result;
10+
private final long diffPixels;
11+
12+
/**
13+
* Creates an instance that contains ImageMagick comparing result information.
14+
*
15+
* @param result true, if the compared images are equal.
16+
* @param diffPixels number of different pixels.
17+
*/
18+
public ImageMagickCompareResult(boolean result, long diffPixels) {
19+
this.result = result;
20+
this.diffPixels = diffPixels;
21+
}
22+
23+
/**
24+
* Returns image compare boolean value.
25+
*
26+
* @return true if the compared images are equal.
27+
*/
28+
public boolean isComparingResultSuccessful() {
29+
return result;
30+
}
31+
32+
/**
33+
* Getter for a different pixels count.
34+
*
35+
* @return Returns a a different pixels count.
36+
*/
37+
public long getDiffPixels() {
38+
return diffPixels;
39+
}
40+
}

io/src/main/java/com/itextpdf/io/util/ImageMagickHelper.java

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,13 @@ This file is part of the iText (R) project.
4444
package com.itextpdf.io.util;
4545

4646
import com.itextpdf.commons.utils.FileUtil;
47+
import com.itextpdf.commons.utils.ProcessInfo;
4748
import com.itextpdf.commons.utils.SystemUtil;
4849
import com.itextpdf.io.exceptions.IoExceptionMessage;
4950

50-
import java.io.File;
5151
import java.io.IOException;
52+
import java.util.regex.Matcher;
53+
import java.util.regex.Pattern;
5254

5355
/**
5456
* A utility class that is used as an interface to run 3rd-party tool ImageMagick.
@@ -71,6 +73,9 @@ public class ImageMagickHelper {
7173
static final String MAGICK_COMPARE_KEYWORD = "ImageMagick Studio LLC";
7274

7375
private static final String TEMP_FILE_PREFIX = "itext_im_io_temp";
76+
private static final String DIFF_PIXELS_OUTPUT_REGEXP = "^\\d+\\.*\\d*(e\\+\\d+)?";
77+
78+
private static final Pattern pattern = Pattern.compile(DIFF_PIXELS_OUTPUT_REGEXP);
7479

7580
private String compareExec;
7681

@@ -141,6 +146,58 @@ public boolean runImageMagickImageCompare(String outImageFilePath, String cmpIma
141146
*/
142147
public boolean runImageMagickImageCompare(String outImageFilePath, String cmpImageFilePath,
143148
String diffImageName, String fuzzValue) throws IOException, InterruptedException {
149+
ImageMagickCompareResult compareResult = runImageMagickImageCompareAndGetResult(outImageFilePath,
150+
cmpImageFilePath, diffImageName, fuzzValue);
151+
152+
return compareResult.isComparingResultSuccessful();
153+
}
154+
155+
/**
156+
* Runs imageMagick to visually compare images with the specified fuzziness value and given threshold
157+
* and generate difference output.
158+
*
159+
* @param outImageFilePath Path to the output image file
160+
* @param cmpImageFilePath Path to the cmp image file
161+
* @param diffImageName Path to the difference output image file
162+
* @param fuzzValue String fuzziness value to compare images. Should be formatted as string with integer
163+
* or decimal number. Can be null, if it is not required to use fuzziness
164+
* @param threshold Long value of accepted threshold.
165+
*
166+
* @return boolean result of comparing: true - images are visually equal
167+
*
168+
* @throws IOException if there are file's reading/writing issues
169+
* @throws InterruptedException if there is thread interruption while executing ImageMagick.
170+
*/
171+
public boolean runImageMagickImageCompareWithThreshold(String outImageFilePath, String cmpImageFilePath,
172+
String diffImageName, String fuzzValue, long threshold) throws IOException, InterruptedException {
173+
ImageMagickCompareResult compareResult = runImageMagickImageCompareAndGetResult(outImageFilePath,
174+
cmpImageFilePath, diffImageName, fuzzValue);
175+
176+
if (compareResult.isComparingResultSuccessful()) {
177+
return true;
178+
} else {
179+
return compareResult.getDiffPixels() <= threshold;
180+
}
181+
}
182+
183+
/**
184+
* Runs imageMagick to visually compare images with the specified fuzziness value and generate difference output.
185+
* This method returns an object of {@link ImageMagickCompareResult}, containing comparing result information,
186+
* such as boolean result value and the number of different pixels.
187+
*
188+
* @param outImageFilePath Path to the output image file
189+
* @param cmpImageFilePath Path to the cmp image file
190+
* @param diffImageName Path to the difference output image file
191+
* @param fuzzValue String fuzziness value to compare images. Should be formatted as string with integer
192+
* or decimal number. Can be null, if it is not required to use fuzziness
193+
*
194+
* @return an object of {@link ImageMagickCompareResult}. containing comparing result information.
195+
*
196+
* @throws IOException if there are file's reading/writing issues
197+
* @throws InterruptedException if there is thread interruption while executing ImageMagick.
198+
*/
199+
public ImageMagickCompareResult runImageMagickImageCompareAndGetResult(String outImageFilePath,
200+
String cmpImageFilePath, String diffImageName, String fuzzValue) throws IOException, InterruptedException {
144201
if (!validateFuzziness(fuzzValue)) {
145202
throw new IllegalArgumentException("Invalid fuzziness value: " + fuzzValue);
146203
}
@@ -160,12 +217,15 @@ public boolean runImageMagickImageCompare(String outImageFilePath, String cmpIma
160217
+ replacementOutFile + "' '"
161218
+ replacementCmpFile + "' '"
162219
+ replacementDiff + "'";
163-
boolean result = SystemUtil.runProcessAndWait(compareExec, currCompareParams);
220+
ProcessInfo processInfo = SystemUtil.runProcessAndGetProcessInfo(compareExec, currCompareParams);
221+
boolean comparingResult = processInfo.getExitCode() == 0;
222+
long diffPixels = parseImageMagickProcessOutput(processInfo.getProcessErrOutput());
223+
ImageMagickCompareResult resultInfo = new ImageMagickCompareResult(comparingResult, diffPixels);
164224

165225
if (FileUtil.fileExists(replacementDiff)) {
166226
FileUtil.copy(replacementDiff, diffImageName);
167227
}
168-
return result;
228+
return resultInfo;
169229
} finally {
170230
FileUtil.removeFiles(new String[] {replacementOutFile, replacementCmpFile, replacementDiff});
171231
}
@@ -184,4 +244,29 @@ static boolean validateFuzziness(String fuzziness) {
184244
}
185245
}
186246
}
247+
248+
private static long parseImageMagickProcessOutput(String processOutput) throws IOException {
249+
if (null == processOutput) {
250+
throw new IllegalArgumentException(IoExceptionMessage.IMAGE_MAGICK_OUTPUT_IS_NULL);
251+
}
252+
253+
if (processOutput.isEmpty()) {
254+
return 0L;
255+
}
256+
257+
String[] processOutputLines = processOutput.split("\n");
258+
259+
for (String line : processOutputLines) {
260+
try {
261+
Matcher matcher = pattern.matcher(line);
262+
if (matcher.find()) {
263+
return (long) Double.valueOf(matcher.group()).longValue();
264+
}
265+
} catch (NumberFormatException e) {
266+
// Nothing should be done here because of the exception, that will be thrown later.
267+
}
268+
}
269+
270+
throw new IOException(IoExceptionMessage.IMAGE_MAGICK_PROCESS_EXECUTION_FAILED + processOutput);
271+
}
187272
}

0 commit comments

Comments
 (0)