package fr.openwide.core.jpa.more.util.image.service; import static fr.openwide.core.jpa.more.property.JpaMorePropertyIds.IMAGE_MAGICK_CONVERT_BINARY_PATH; import java.awt.AlphaComposite; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Map; import javax.annotation.PostConstruct; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.stream.FileImageOutputStream; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.ExecuteWatchdog; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.devlib.schmidt.imageinfo.ImageInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import fr.openwide.core.jpa.exception.ServiceException; import fr.openwide.core.jpa.more.util.image.exception.ImageThumbnailGenerationException; import fr.openwide.core.jpa.more.util.image.model.ImageInformation; import fr.openwide.core.jpa.more.util.image.model.ImageThumbnailFormat; import fr.openwide.core.spring.property.service.IPropertyService; @Service("imageService") public class ImageServiceImpl implements IImageService { private static final Logger LOGGER = LoggerFactory.getLogger(ImageServiceImpl.class); private static final int IMAGE_MAGICK_CONVERT_TIMEOUT = 15000; private File imageMagickConvertBinary; @Autowired private IPropertyService propertyService; @PostConstruct private void init() { imageMagickConvertBinary = getImageMagickConvertBinary(propertyService.get(IMAGE_MAGICK_CONVERT_BINARY_PATH)); } @Override public ImageInformation getImageInformation(File source) { InputStream is = null; try { is = new FileInputStream(source); return getImageInformation(is); } catch (FileNotFoundException e) { LOGGER.error(String.format("File %1$s not found while trying to get image information", source.getAbsolutePath()), e); return new ImageInformation(); } finally { if (is != null) { try { is.close(); } catch (IOException e) { } } } } @Override public ImageInformation getImageInformation(InputStream source) { ImageInformation imageInformation = new ImageInformation(); ImageInfo imageInfo = new ImageInfo(); imageInfo.setInput(source); if (imageInfo.check()) { imageInformation.setSizeDetected(true); imageInformation.setWidth(imageInfo.getWidth()); imageInformation.setHeight(imageInfo.getHeight()); } return imageInformation; } @Override public void generateThumbnail(File source, File destination, ImageThumbnailFormat thumbnailFormat) throws ImageThumbnailGenerationException { if (isImageMagickConvertAvailable()) { generateThumbnailWithImageMagickConvert(source, destination, thumbnailFormat); } else { generateThumbnailWithJava(source, destination, thumbnailFormat); } } private File getImageMagickConvertBinary(File imageMagickConvertBinaryCandidate) { if (imageMagickConvertBinaryCandidate == null) { LOGGER.warn("ImageMagick's convert binary is not configured. Using Java to scale images."); return null; } if (!imageMagickConvertBinaryCandidate.exists()) { LOGGER.warn("ImageMagick's convert binary {} does not exist. Using Java to scale images.", imageMagickConvertBinaryCandidate.getAbsolutePath()); return null; } if (!imageMagickConvertBinaryCandidate.isFile()) { LOGGER.warn("ImageMagick's convert binary {} is not a file. Using Java to scale images.", imageMagickConvertBinaryCandidate.getAbsolutePath()); return null; } if (!imageMagickConvertBinaryCandidate.canExecute()) { LOGGER.warn("ImageMagick's convert binary {} is not executable. Using Java to scale images.", imageMagickConvertBinaryCandidate.getAbsolutePath()); return null; } return imageMagickConvertBinaryCandidate; } private boolean isImageMagickConvertAvailable() { if (imageMagickConvertBinary != null) { return true; } else { return false; } } private void generateThumbnailWithImageMagickConvert(File source, File destination, ImageThumbnailFormat thumbnailFormat) throws ImageThumbnailGenerationException { try { CommandLine commandLine = new CommandLine(imageMagickConvertBinary); commandLine.addArgument("-auto-orient"); commandLine.addArgument("-thumbnail"); if (thumbnailFormat.isAllowEnlarge()) { commandLine.addArgument("${width}x${height}"); } else { commandLine.addArgument("${width}x${height}>"); } commandLine.addArgument("-quality"); commandLine.addArgument("${quality}"); commandLine.addArgument("${originalFilePath}"); commandLine.addArgument("${targetFilePath}"); Map<String, String> parameters = new HashMap<String, String>(); parameters.put("width", String.valueOf(thumbnailFormat.getWidth())); parameters.put("height", String.valueOf(thumbnailFormat.getHeight())); parameters.put("quality", String.valueOf(thumbnailFormat.getQuality())); parameters.put("originalFilePath", source.getAbsolutePath()); parameters.put("targetFilePath", destination.getAbsolutePath()); commandLine.setSubstitutionMap(parameters); DefaultExecutor executor = new DefaultExecutor(); ExecuteWatchdog watchdog = new ExecuteWatchdog(IMAGE_MAGICK_CONVERT_TIMEOUT); executor.setWatchdog(watchdog); executor.execute(commandLine); } catch (RuntimeException | IOException e) { throw new ImageThumbnailGenerationException(String.format("Unable to generate a thumbnail for file %1$s", source.getAbsolutePath()), e); } } private void generateThumbnailWithJava(File source, File destination, ImageThumbnailFormat thumbnailFormat) throws ImageThumbnailGenerationException { try { BufferedImage originalImage = ImageIO.read(source); if (originalImage == null) { throw new ServiceException("Image cannot be read."); } int type = (originalImage.getType() == 0 ? BufferedImage.TYPE_INT_ARGB : originalImage.getType()); int originalImageWidth = originalImage.getWidth(); int originalImageHeight = originalImage.getHeight(); double widthRatio = (double) thumbnailFormat.getWidth() / (double) originalImageWidth; double heightRatio = (double) thumbnailFormat.getHeight() /(double) originalImageHeight; if (widthRatio < 1.0d || heightRatio < 1.0d) { double ratio = Math.min(widthRatio, heightRatio); int resizedImageWidth = (int) (ratio * originalImageWidth); int resizedImageHeight = (int) (ratio * originalImageHeight); BufferedImage resizedImage = new BufferedImage(resizedImageWidth, resizedImageHeight, type); Graphics2D g = resizedImage.createGraphics(); g.setComposite(AlphaComposite.Src); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,RenderingHints.VALUE_COLOR_RENDER_QUALITY); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); AffineTransform tx = new AffineTransform(); tx.scale(ratio, ratio); g.drawImage(originalImage, 0, 0, resizedImageWidth, resizedImageHeight, null); g.dispose(); FileImageOutputStream outputStream = null; try { Iterator<ImageWriter> imageWritersIterator =ImageIO.getImageWritersByFormatName( thumbnailFormat.getJavaFormatName(FilenameUtils.getExtension(destination.getName()).toLowerCase(Locale.ROOT))); ImageWriter writer = imageWritersIterator.next(); ImageWriteParam iwp = writer.getDefaultWriteParam(); if (iwp.canWriteCompressed()) { iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); iwp.setCompressionQuality(thumbnailFormat.getQuality() / 100f); } outputStream = new FileImageOutputStream(destination); writer.setOutput(outputStream); IIOImage image = new IIOImage(resizedImage, null, null); writer.write(null, image, iwp); writer.dispose(); } catch (RuntimeException | IOException e) { throw new ServiceException(e); } finally { if (outputStream != null) { outputStream.close(); } } } else { FileUtils.copyFile(source, destination); } } catch (RuntimeException | IOException | ServiceException e) { throw new ImageThumbnailGenerationException(String.format("Unable to generate a thumbnail for file %1$s", source.getAbsolutePath()), e); } } }