org.niord.core.repo.ThumbnailService.java Source code

Java tutorial

Introduction

Here is the source code for org.niord.core.repo.ThumbnailService.java

Source

/*
 * Copyright 2016 Danish Maritime Authority.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.niord.core.repo;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.niord.core.settings.annotation.Setting;
import org.slf4j.Logger;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Singleton;
import javax.imageio.ImageIO;
import javax.inject.Inject;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Creates thumbnails
 * <p>
 *     An ExecutorService is used to limit load on the system
 * </p>
 */
@Singleton
@Lock(LockType.READ)
public class ThumbnailService {

    private static final int EXECUTOR_POOL_SIZE = 2;

    @Inject
    Logger log;

    @Inject
    @Setting(value = "vipsPath", description = "Path to vips command on Linux. Use for faster thumbnail generation.")
    String vipsCmd;

    Set<String> vipsFileTypes = new HashSet<>();

    @Inject
    FileTypes fileTypes;

    private ExecutorService processPool;

    @PostConstruct
    private void init() {
        processPool = Executors.newFixedThreadPool(EXECUTOR_POOL_SIZE);

        // Enlist image types supported by vips (avoid gif)
        vipsFileTypes.add("image/bmp");
        vipsFileTypes.add("image/jpeg");
        vipsFileTypes.add("image/jpg");
        vipsFileTypes.add("image/tiff");
        vipsFileTypes.add("image/tif");
        vipsFileTypes.add("image/png");
    }

    @PreDestroy
    private void closeDown() {
        if (processPool != null && !processPool.isShutdown()) {
            processPool.shutdown();
            processPool = null;
        }
    }

    /**
     * Returns or creates the a thumbnail for the given file if it is an image.
     * Otherwise, null is returned
     *
     * @param file the file to create a thumbnail for
     * @param size the size of the thumbnail
     * @return the thumbnail file or null if none was found or created
     */
    public Path getThumbnail(final Path file, final IconSize size) {

        // Check that the file exists
        if (!Files.isRegularFile(file)) {
            log.warn("File does not exist: " + file);
            return null;
        }

        final String type = fileTypes.getContentType(file);
        if (type == null || !type.startsWith("image")) {
            log.debug("File not an image: " + file);
            return null;
        }

        // Submit the thumbnail creation job to the process pool.
        // Please note, this is used as a way of restricting load on the system,
        // not per se as a way of executing the task asynchronously.
        try {
            return processPool.submit(() -> createThumbnail(file, type, size)).get();
        } catch (InterruptedException | ExecutionException e) {
            log.debug("Error creating thumbnail");
        }

        return null;
    }

    /**
     * Returns or creates the a thumbnail for the given file if it is an image.
     * Otherwise, null is returned
     *
     * @param file the file to create a thumbnail for
     * @param type the type of image
     * @param size the size of the thumbnail
     * @return the thumbnail file or null if none was found or created
     */
    public Path createThumbnail(Path file, String type, IconSize size) {

        try {
            // Construct the thumbnail name by appending "_thumb_size" to the file name
            String thumbName = String.format("%s_thumb_%d.%s",
                    FilenameUtils.removeExtension(file.getFileName().toString()), size.getSize(),
                    FilenameUtils.getExtension(file.getFileName().toString()));

            // Check if the thumbnail already exists
            Path thumbFile = file.getParent().resolve(thumbName);
            if (Files.isRegularFile(thumbFile) && Files.getLastModifiedTime(thumbFile).toMillis() >= Files
                    .getLastModifiedTime(file).toMillis()) {
                return thumbFile;
            }

            // Check whether to use VIPS or java
            if (StringUtils.isNotBlank(vipsCmd) && vipsFileTypes.contains(type.toLowerCase())) {
                // Use VIPS
                createThumbnailUsingVips(file, thumbFile, size);

            } else {
                // Use java APIs
                createThumbnailUsingJava(file, thumbFile, size);
            }

            return thumbFile;

        } catch (IOException e) {
            // Alas, no thumbnail
            return null;
        }
    }

    /**
     * Creates a thumbnail for the image file using libvips
     *
     * @param file      the image file
     * @param thumbFile the resulting thumbnail file
     * @param size      the size of the thumbnail
     */
    private void createThumbnailUsingVips(Path file, Path thumbFile, IconSize size) throws IOException {

        try {

            // Example command: vipsthumbnail -s 64 -p bilinear -o thumb.png image2.jpg
            final Process proc = new ProcessBuilder(vipsCmd, "-s", String.valueOf(size.getSize()), "-p", "bilinear",
                    "-o", thumbFile.toString(), file.toString()).directory(new File(System.getProperty("user.dir")))
                            .inheritIO().start();

            BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
            String line;
            while ((line = br.readLine()) != null) {
                log.debug(line);
            }

            //Wait to get exit value
            int exitValue = proc.waitFor();
            log.debug("Exit Value is " + exitValue);

            // Update the timestamp of the thumbnail file to match the change date of the image file
            Files.setLastModifiedTime(thumbFile, Files.getLastModifiedTime(file));

        } catch (IOException | InterruptedException e) {
            log.error("Error creating thumbnail for image " + file, e);
            throw new IOException(e);
        }
    }

    /**
     * Creates a thumbnail for the image file using plain old java
     * @param file the image file
     * @param thumbFile the resulting thumbnail file
     * @param size the size of the thumbnail
     */
    private void createThumbnailUsingJava(Path file, Path thumbFile, IconSize size) throws IOException {

        try {
            BufferedImage image = ImageIO.read(file.toFile());

            int w = image.getWidth();
            int h = image.getHeight();

            // Never scale up
            if (w <= size.getSize() && h <= size.getSize()) {
                FileUtils.copyFile(file.toFile(), thumbFile.toFile());

            } else {
                // Compute the scale factor
                double dx = (double) size.getSize() / (double) w;
                double dy = (double) size.getSize() / (double) h;
                double d = Math.min(dx, dy);

                // Create the thumbnail
                BufferedImage thumbImage = new BufferedImage((int) Math.round(w * d), (int) Math.round(h * d),
                        BufferedImage.TYPE_INT_RGB);
                Graphics2D g2d = thumbImage.createGraphics();
                AffineTransform at = AffineTransform.getScaleInstance(d, d);
                g2d.drawRenderedImage(image, at);
                g2d.dispose();

                // Save the thumbnail
                ImageIO.write(thumbImage, FilenameUtils.getExtension(thumbFile.getFileName().toString()),
                        thumbFile.toFile());

                // Releas resources
                image.flush();
                thumbImage.flush();
            }

        } catch (Exception e) {
            log.error("Error creating thumbnail for image " + file, e);
            throw new IOException(e);
        }
    }
}