All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.itextpdf.io.util.ImageMagickHelper Maven / Gradle / Ivy

There is a newer version: 8.0.5
Show newest version
/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2023 Apryse Group NV
    Authors: Apryse Software.

    This program is offered under a commercial and under the AGPL license.
    For commercial licensing, contact us at https://itextpdf.com/sales.  For AGPL licensing, see below.

    AGPL licensing:
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see .
 */
package com.itextpdf.io.util;

import com.itextpdf.commons.utils.FileUtil;
import com.itextpdf.commons.utils.ProcessInfo;
import com.itextpdf.commons.utils.SystemUtil;
import com.itextpdf.io.exceptions.IoExceptionMessageConstant;

import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A utility class that is used as an interface to run 3rd-party tool ImageMagick.
 * ImageMagick among other things allows to compare images and this class provides means to utilize this feature.
 *
 * 

* The ImageMagick needs to be installed independently on the system. This class provides a convenient * way to run it by passing a terminal command. The command can either be specified explicitly or by a mean * of environment variable {@link #MAGICK_COMPARE_ENVIRONMENT_VARIABLE}. */ public class ImageMagickHelper { /** * The name of the environment variable with the command to execute ImageMagic comparison operations. */ public static final String MAGICK_COMPARE_ENVIRONMENT_VARIABLE = "ITEXT_MAGICK_COMPARE_EXEC"; @Deprecated static final String MAGICK_COMPARE_ENVIRONMENT_VARIABLE_LEGACY = "compareExec"; static final String MAGICK_COMPARE_KEYWORD = "ImageMagick Studio LLC"; private static final String TEMP_FILE_PREFIX = "itext_im_io_temp"; private static final String DIFF_PIXELS_OUTPUT_REGEXP = "^\\d+\\.*\\d*(e\\+\\d+)?"; private static final Pattern pattern = Pattern.compile(DIFF_PIXELS_OUTPUT_REGEXP); private String compareExec; /** * Creates new instance that will rely on ImageMagick execution command defined by {@link * #MAGICK_COMPARE_ENVIRONMENT_VARIABLE} environment variable. */ public ImageMagickHelper() { this(null); } /** * Creates new instance that will rely on ImageMagick execution command defined as passed argument. * * @param newCompareExec the ImageMagick execution command; if null - environment variables will be used instead */ public ImageMagickHelper(String newCompareExec) { compareExec = newCompareExec; if (compareExec == null) { compareExec = SystemUtil.getPropertyOrEnvironmentVariable(MAGICK_COMPARE_ENVIRONMENT_VARIABLE); if (compareExec == null) { compareExec = SystemUtil.getPropertyOrEnvironmentVariable(MAGICK_COMPARE_ENVIRONMENT_VARIABLE_LEGACY); } } if (!CliCommandUtil.isVersionCommandExecutable(compareExec, MAGICK_COMPARE_KEYWORD)) { throw new IllegalArgumentException(IoExceptionMessageConstant.COMPARE_COMMAND_SPECIFIED_INCORRECTLY); } } /** * Returns a command that is used to run the utility. * This command doesn't contain command parameters. Parameters are added on specific * methods invocation. * * @return a string command */ public String getCliExecutionCommand() { return compareExec; } /** * Runs imageMagick to visually compare images and generate difference output. * *

* Note, that this method may create temporary files. * * @param outImageFilePath Path to the output image file * @param cmpImageFilePath Path to the cmp image file * @param diffImageName Path to the difference output image file * @return boolean result of comparing: true - images are visually equal * @throws IOException if there are file's reading/writing issues * @throws InterruptedException if there is thread interruption while executing ImageMagick. */ public boolean runImageMagickImageCompare(String outImageFilePath, String cmpImageFilePath, String diffImageName) throws IOException, InterruptedException { return runImageMagickImageCompare(outImageFilePath, cmpImageFilePath, diffImageName, null); } /** * Runs imageMagick to visually compare images with the specified fuzziness value and generate difference output. * *

* Note, that this method may create temporary files. * * @param outImageFilePath Path to the output image file * @param cmpImageFilePath Path to the cmp image file * @param diffImageName Path to the difference output image file * @param fuzzValue String fuzziness value to compare images. Should be formatted as string with integer * or decimal number. Can be null, if it is not required to use fuzziness * @return boolean result of comparing: true - images are visually equal * @throws IOException if there are file's reading/writing issues * @throws InterruptedException if there is thread interruption while executing ImageMagick. */ public boolean runImageMagickImageCompare(String outImageFilePath, String cmpImageFilePath, String diffImageName, String fuzzValue) throws IOException, InterruptedException { ImageMagickCompareResult compareResult = runImageMagickImageCompareAndGetResult(outImageFilePath, cmpImageFilePath, diffImageName, fuzzValue); return compareResult.isComparingResultSuccessful(); } /** * Runs imageMagick to visually compare images with the specified fuzziness value and given threshold * and generate difference output. * *

* Note, that this method may create temporary files. * * @param outImageFilePath Path to the output image file * @param cmpImageFilePath Path to the cmp image file * @param diffImageName Path to the difference output image file * @param fuzzValue String fuzziness value to compare images. Should be formatted as string with integer * or decimal number. Can be null, if it is not required to use fuzziness * @param threshold Long value of accepted threshold. * * @return boolean result of comparing: true - images are visually equal * * @throws IOException if there are file's reading/writing issues * @throws InterruptedException if there is thread interruption while executing ImageMagick. */ public boolean runImageMagickImageCompareWithThreshold(String outImageFilePath, String cmpImageFilePath, String diffImageName, String fuzzValue, long threshold) throws IOException, InterruptedException { ImageMagickCompareResult compareResult = runImageMagickImageCompareAndGetResult(outImageFilePath, cmpImageFilePath, diffImageName, fuzzValue); if (compareResult.isComparingResultSuccessful()) { return true; } else { return compareResult.getDiffPixels() <= threshold; } } /** * Runs imageMagick to visually compare images with the specified fuzziness value and generate difference output. * This method returns an object of {@link ImageMagickCompareResult}, containing comparing result information, * such as boolean result value and the number of different pixels. * *

* Note, that this method may create temporary files. * * @param outImageFilePath Path to the output image file * @param cmpImageFilePath Path to the cmp image file * @param diffImageName Path to the difference output image file * @param fuzzValue String fuzziness value to compare images. Should be formatted as string with integer * or decimal number. Can be null, if it is not required to use fuzziness * * @return an object of {@link ImageMagickCompareResult}. containing comparing result information. * * @throws IOException if there are file's reading/writing issues * @throws InterruptedException if there is thread interruption while executing ImageMagick. */ public ImageMagickCompareResult runImageMagickImageCompareAndGetResult(String outImageFilePath, String cmpImageFilePath, String diffImageName, String fuzzValue) throws IOException, InterruptedException { if (!validateFuzziness(fuzzValue)) { throw new IllegalArgumentException("Invalid fuzziness value: " + fuzzValue); } fuzzValue = (fuzzValue == null) ? "" : " -metric AE -fuzz %".replace("", fuzzValue); String replacementOutFile = null; String replacementCmpFile = null; String replacementDiff = null; try { replacementOutFile = FileUtil.createTempCopy(outImageFilePath, TEMP_FILE_PREFIX, null); replacementCmpFile = FileUtil.createTempCopy(cmpImageFilePath, TEMP_FILE_PREFIX, null); // ImageMagick generates difference images in .png format, therefore we can specify it. // For some reason .webp comparison fails if the extension of diff image is not specified. replacementDiff = FileUtil.createTempFile(TEMP_FILE_PREFIX, ".png").getAbsolutePath(); String currCompareParams = fuzzValue + " '" + replacementOutFile + "' '" + replacementCmpFile + "' '" + replacementDiff + "'"; ProcessInfo processInfo = SystemUtil.runProcessAndGetProcessInfo(compareExec, currCompareParams); boolean comparingResult = processInfo.getExitCode() == 0; long diffPixels = parseImageMagickProcessOutput(processInfo.getProcessErrOutput()); ImageMagickCompareResult resultInfo = new ImageMagickCompareResult(comparingResult, diffPixels); if (FileUtil.fileExists(replacementDiff)) { FileUtil.copy(replacementDiff, diffImageName); } return resultInfo; } finally { FileUtil.removeFiles(new String[] {replacementOutFile, replacementCmpFile, replacementDiff}); } } static boolean validateFuzziness(String fuzziness) { if (null == fuzziness) { return true; } else { try { return Double.parseDouble(fuzziness) >= 0; } catch (NumberFormatException e) { // In case of an exception the string could not be parsed to double, // therefore it is considered to be invalid. return false; } } } private static long parseImageMagickProcessOutput(String processOutput) throws IOException { if (null == processOutput) { throw new IllegalArgumentException(IoExceptionMessageConstant.IMAGE_MAGICK_OUTPUT_IS_NULL); } if (processOutput.isEmpty()) { return 0L; } String[] processOutputLines = processOutput.split("\n"); for (String line : processOutputLines) { try { Matcher matcher = pattern.matcher(line); if (matcher.find()) { return (long) Double.valueOf(matcher.group()).longValue(); } } catch (NumberFormatException e) { // Nothing should be done here because of the exception, that will be thrown later. } } throw new IOException(IoExceptionMessageConstant.IMAGE_MAGICK_PROCESS_EXECUTION_FAILED + processOutput); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy