Skip to content

Commit 14477e9

Browse files
EvgenyB1001Andrei Stryhelski
authored andcommitted
Add threshold functionality for pdfrender
1 parent 70f1de6 commit 14477e9

File tree

8 files changed

+398
-7
lines changed

8 files changed

+398
-7
lines changed

io/src/main/java/com/itextpdf/io/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: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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+
package com.itextpdf.io.util;
24+
25+
/**
26+
* A helper data class, which aggregates true/false result of ImageMagick comparing
27+
* as well as the number of different pixels.
28+
*/
29+
public final class ImageMagickCompareResult {
30+
31+
private final boolean result;
32+
private final long diffPixels;
33+
34+
/**
35+
* Creates an instance that contains ImageMagick comparing result information.
36+
*
37+
* @param result true, if the compared images are equal.
38+
* @param diffPixels number of different pixels.
39+
*/
40+
public ImageMagickCompareResult(boolean result, long diffPixels) {
41+
this.result = result;
42+
this.diffPixels = diffPixels;
43+
}
44+
45+
/**
46+
* Returns image compare boolean value.
47+
*
48+
* @return true if the compared images are equal.
49+
*/
50+
public boolean isComparingResultSuccessful() {
51+
return result;
52+
}
53+
54+
/**
55+
* Getter for a different pixels count.
56+
*
57+
* @return Returns a a different pixels count.
58+
*/
59+
public long getDiffPixels() {
60+
return diffPixels;
61+
}
62+
}

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

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ This file is part of the iText (R) project.
4545

4646
import com.itextpdf.io.IoExceptionMessage;
4747

48-
import java.io.File;
4948
import java.io.IOException;
49+
import java.util.regex.Matcher;
50+
import java.util.regex.Pattern;
5051

5152
/**
5253
* A utility class that is used as an interface to run 3rd-party tool ImageMagick.
@@ -69,6 +70,9 @@ public class ImageMagickHelper {
6970
static final String MAGICK_COMPARE_KEYWORD = "ImageMagick Studio LLC";
7071

7172
private static final String TEMP_FILE_PREFIX = "itext_im_io_temp";
73+
private static final String DIFF_PIXELS_OUTPUT_REGEXP = "^\\d+\\.*\\d*(e\\+\\d+)?";
74+
75+
private static final Pattern pattern = Pattern.compile(DIFF_PIXELS_OUTPUT_REGEXP);
7276

7377
private String compareExec;
7478

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

163222
if (FileUtil.fileExists(replacementDiff)) {
164223
FileUtil.copy(replacementDiff, diffImageName);
165224
}
166-
return result;
225+
return resultInfo;
167226
} finally {
168227
FileUtil.removeFiles(new String[] {replacementOutFile, replacementCmpFile, replacementDiff});
169228
}
@@ -182,4 +241,29 @@ static boolean validateFuzziness(String fuzziness) {
182241
}
183242
}
184243
}
244+
245+
private static long parseImageMagickProcessOutput(String processOutput) throws IOException {
246+
if (null == processOutput) {
247+
throw new IllegalArgumentException(IoExceptionMessage.IMAGE_MAGICK_OUTPUT_IS_NULL);
248+
}
249+
250+
if (processOutput.isEmpty()) {
251+
return 0L;
252+
}
253+
254+
String[] processOutputLines = processOutput.split("\n");
255+
256+
for (String line : processOutputLines) {
257+
try {
258+
Matcher matcher = pattern.matcher(line);
259+
if (matcher.find()) {
260+
return (long) Double.valueOf(matcher.group()).longValue();
261+
}
262+
} catch (NumberFormatException e) {
263+
// Nothing should be done here because of the exception, that will be thrown later.
264+
}
265+
}
266+
267+
throw new IOException(IoExceptionMessage.IMAGE_MAGICK_PROCESS_EXECUTION_FAILED + processOutput);
268+
}
185269
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
package com.itextpdf.io.util;
24+
25+
/**
26+
* Class contains a process information, such as process exit code and process output.
27+
*/
28+
public final class ProcessInfo {
29+
30+
private final int exitCode;
31+
private final String processStdOutput;
32+
private final String processErrOutput;
33+
34+
/**
35+
* Create a new instance, containing a process information,
36+
* such as process exit code, process standard and error outputs.
37+
*
38+
* @param exitCode exit code of the process.
39+
* @param processStdOutput the standard output of the process.
40+
* @param processErrOutput the error output of the process.
41+
*/
42+
public ProcessInfo(int exitCode, String processStdOutput, String processErrOutput) {
43+
this.exitCode = exitCode;
44+
this.processStdOutput = processStdOutput;
45+
this.processErrOutput = processErrOutput;
46+
}
47+
48+
/**
49+
* Getter for a process exit code.
50+
*
51+
* @return Returns a process exit code.
52+
*/
53+
public int getExitCode() {
54+
return exitCode;
55+
}
56+
57+
/**
58+
* Getter for a standard process output.
59+
*
60+
* @return Returns a process standard output string.
61+
*/
62+
public String getProcessStdOutput() {
63+
return processStdOutput;
64+
}
65+
66+
/**
67+
* Getter for an error process output.
68+
*
69+
* @return Returns a process error output string.
70+
*/
71+
public String getProcessErrOutput() {
72+
return processErrOutput;
73+
}
74+
}

io/src/main/java/com/itextpdf/io/util/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;
@@ -132,6 +133,14 @@ public static StringBuilder runProcessAndCollectErrors(String execPath, String p
132133
return printProcessErrorsOutput(runProcess(execPath, params, null));
133134
}
134135

136+
public static ProcessInfo runProcessAndGetProcessInfo(String command, String params) throws IOException,
137+
InterruptedException {
138+
Process p = runProcess(command, params, null);
139+
String processStdOutput = printProcessStandardOutput(p).toString();
140+
String processErrOutput = printProcessErrorsOutput(p).toString();
141+
return new ProcessInfo(p.waitFor(), processStdOutput, processErrOutput);
142+
}
143+
135144
static Process runProcess(String execPath, String params, String workingDirPath) throws IOException {
136145
List<String> cmdList = prepareProcessArguments(execPath, params);
137146
String[] cmdArray = cmdList.toArray(new String[cmdList.size()]);
@@ -183,8 +192,16 @@ static String getProcessOutput(Process p) throws IOException {
183192
}
184193

185194
static StringBuilder printProcessErrorsOutput(Process p) throws IOException {
195+
return printProcessOutput(p.getErrorStream());
196+
}
197+
198+
static StringBuilder printProcessStandardOutput(Process p) throws IOException {
199+
return printProcessOutput(p.getInputStream());
200+
}
201+
202+
private static StringBuilder printProcessOutput(InputStream processStream) throws IOException {
186203
StringBuilder builder = new StringBuilder();
187-
BufferedReader bre = new BufferedReader(new InputStreamReader(p.getErrorStream()));
204+
BufferedReader bre = new BufferedReader(new InputStreamReader(processStream));
188205
String line;
189206
while ((line = bre.readLine()) != null) {
190207
System.out.println(line);

0 commit comments

Comments
 (0)