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

org.tinymediamanager.core.ImageUtils Maven / Gradle / Ivy

/*
 * Copyright 2012 - 2019 Manuel Laggner
 *
 * 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.tinymediamanager.core;

import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.ImageOutputStream;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.imgscalr.Scalr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tinymediamanager.scraper.http.Url;
import org.tinymediamanager.thirdparty.ImageLoader;

public class ImageUtils {
  private static final Logger LOGGER = LoggerFactory.getLogger(ImageUtils.class);

  /**
   * Scale image to fit in the given width.
   *
   * @param imageBytes
   *          the image bytes
   * @param width
   *          the width
   * @return the input stream
   * @throws IOException
   *           Signals that an I/O exception has occurred.
   */
  public static InputStream scaleImage(byte[] imageBytes, int width) throws IOException {
    BufferedImage originalImage;
    try {
      originalImage = createImage(imageBytes);
    }
    catch (Exception e) {
      throw new IOException(e.getMessage());
    }

    Point size = new Point();
    size.x = width;
    size.y = size.x * originalImage.getHeight() / originalImage.getWidth();

    // BufferedImage scaledImage = Scaling.scale(originalImage, size.x, size.y);
    BufferedImage scaledImage = Scalr.resize(originalImage, Scalr.Method.QUALITY, Scalr.Mode.AUTOMATIC, size.x, size.y, Scalr.OP_ANTIALIAS);
    originalImage = null;

    ImageWriter imgWrtr = null;
    ImageWriteParam imgWrtrPrm = null;

    // here we have two different ways to create our thumb
    // a) a scaled down jpg/png (without transparency) which we have to modify since OpenJDK cannot call native jpg encoders
    // b) a scaled down png (with transparency) which we can store without any more modifying as png
    if (hasTransparentPixels(scaledImage)) {
      // transparent image -> png
      imgWrtr = ImageIO.getImageWritersByFormatName("png").next();
      imgWrtrPrm = imgWrtr.getDefaultWriteParam();

    }
    else {
      // non transparent image -> jpg
      // convert to rgb
      BufferedImage rgb = new BufferedImage(scaledImage.getWidth(), scaledImage.getHeight(), BufferedImage.TYPE_INT_RGB);
      ColorConvertOp xformOp = new ColorConvertOp(null);
      xformOp.filter(scaledImage, rgb);
      imgWrtr = ImageIO.getImageWritersByFormatName("jpg").next();
      imgWrtrPrm = imgWrtr.getDefaultWriteParam();
      imgWrtrPrm.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT);
      imgWrtrPrm.setCompressionQuality(0.80f);

      scaledImage = rgb;
    }

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ImageOutputStream output = ImageIO.createImageOutputStream(baos);
    imgWrtr.setOutput(output);
    IIOImage outputImage = new IIOImage(scaledImage, null, null);
    imgWrtr.write(null, outputImage, imgWrtrPrm);
    imgWrtr.dispose();
    scaledImage = null;

    byte[] bytes = baos.toByteArray();

    output.flush();
    output.close();
    baos.close();

    return new ByteArrayInputStream(bytes);
  }

  /**
   * Scale image to fit in the given width.
   *
   * @param file
   *          the original image file
   * @param width
   *          the width
   * @return the input stream
   * @throws IOException
   *           Signals that an I/O exception has occurred.
   */
  public static InputStream scaleImage(Path file, int width) throws IOException {
    BufferedImage originalImage;
    try {
      originalImage = createImage(file);
    }
    catch (Exception e) {
      throw new IOException(e.getMessage());
    }

    Point size = new Point();
    size.x = width;
    size.y = size.x * originalImage.getHeight() / originalImage.getWidth();

    // BufferedImage scaledImage = Scaling.scale(originalImage, size.x, size.y);
    BufferedImage scaledImage = Scalr.resize(originalImage, Scalr.Method.QUALITY, Scalr.Mode.AUTOMATIC, size.x, size.y, Scalr.OP_ANTIALIAS);
    originalImage = null;

    ImageWriter imgWrtr = null;
    ImageWriteParam imgWrtrPrm = null;

    // here we have two different ways to create our thumb
    // a) a scaled down jpg/png (without transparency) which we have to modify since OpenJDK cannot call native jpg encoders
    // b) a scaled down png (with transparency) which we can store without any more modifying as png
    if (hasTransparentPixels(scaledImage)) {
      // transparent image -> png
      imgWrtr = ImageIO.getImageWritersByFormatName("png").next();
      imgWrtrPrm = imgWrtr.getDefaultWriteParam();

    }
    else {
      // non transparent image -> jpg
      // convert to rgb
      BufferedImage rgb = new BufferedImage(scaledImage.getWidth(), scaledImage.getHeight(), BufferedImage.TYPE_INT_RGB);
      ColorConvertOp xformOp = new ColorConvertOp(null);
      xformOp.filter(scaledImage, rgb);
      imgWrtr = ImageIO.getImageWritersByFormatName("jpg").next();
      imgWrtrPrm = imgWrtr.getDefaultWriteParam();
      imgWrtrPrm.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT);
      imgWrtrPrm.setCompressionQuality(0.80f);

      scaledImage = rgb;
    }

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ImageOutputStream output = ImageIO.createImageOutputStream(baos);
    imgWrtr.setOutput(output);
    IIOImage outputImage = new IIOImage(scaledImage, null, null);
    imgWrtr.write(null, outputImage, imgWrtrPrm);
    imgWrtr.dispose();
    scaledImage = null;

    byte[] bytes = baos.toByteArray();

    output.flush();
    output.close();
    baos.close();

    return new ByteArrayInputStream(bytes);
  }

  static boolean hasTransparentPixels(BufferedImage image) {
    for (int x = 0; x < image.getWidth(); x++) {
      for (int y = 0; y < image.getHeight(); y++) {
        int pixel = image.getRGB(x, y);
        if ((pixel >> 24) == 0x00) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * calculate a new size which fits into maxWidth and maxHeight
   *
   * @param maxWidth
   *          the maximum width of the result
   * @param maxHeight
   *          the maximum height of the result
   * @param originalWidth
   *          the width of the source
   * @param originalHeight
   *          the height of the source
   * @param respectFactor
   *          should we respect the aspect ratio?
   * @return the calculated new size
   */
  public static Point calculateSize(int maxWidth, int maxHeight, int originalWidth, int originalHeight, boolean respectFactor) {
    Point size = new Point();
    if (respectFactor) {
      // calculate on available height
      size.y = maxHeight;
      size.x = (int) (size.y * (double) originalWidth / (double) originalHeight);

      if (size.x > maxWidth) {
        // calculate on available height
        size.x = maxWidth;
        size.y = (int) (size.x * (double) originalHeight / (double) originalWidth);
      }
    }
    else {
      size.x = maxWidth;
      size.y = maxHeight;
    }
    return size;
  }

  public static BufferedImage createImage(byte[] imageData) {
    return createImage(Toolkit.getDefaultToolkit().createImage(imageData));
  }

  public static BufferedImage createImage(Path file) {
    return createImage(Toolkit.getDefaultToolkit().createImage(file.toFile().getAbsolutePath()));
  }

  public static BufferedImage createImage(Image img) {
    return ImageLoader.createImage(img);
  }

  public static Path downloadImage(String urlAsString, Path destinationFolder, String filename) throws InterruptedException, IOException {
    return downloadImage(urlAsString, destinationFolder, filename, false, 0);
  }

  public static Path downloadImage(String urlAsString, Path destinationFolder, String filename, boolean rescale, int newWidth)
      throws InterruptedException, IOException {

    // don't write jpeg -> write jpg
    if (FilenameUtils.getExtension(filename).equalsIgnoreCase("JPEG")) {
      filename = FilenameUtils.getBaseName(filename) + ".jpg";
    }

    Path destFile = destinationFolder.resolve(filename);

    // check if old and new file are the same (possible if you select it in the imagechooser)
    if (urlAsString.startsWith("file:")) {
      String newUrl = urlAsString.replace("file:/", "");
      Path file = Paths.get(newUrl);
      if (file.equals(destFile)) {
        return destFile;
      }
    }

    LOGGER.debug("downloading {} to {}", urlAsString, destFile);

    Path tempFile = null;
    try {
      long timestamp = System.currentTimeMillis();

      try {
        // create a temp file/folder inside the tmm folder
        Path tempFolder = Paths.get(Utils.getTempFolder());
        if (!Files.exists(tempFolder)) {
          Files.createDirectory(tempFolder);
        }
        tempFile = tempFolder.resolve(filename + "." + timestamp + ".part"); // multi episode same file
      }
      catch (Exception e) {
        LOGGER.debug("could not write to temp folder: {}", e.getMessage());

        // could not create the temp folder somehow - put the files into the entity dir
        tempFile = destFile.resolveSibling(filename + "." + timestamp + ".part"); // multi episode same file
      }

      // fetch and store images
      Url url;
      try {
        url = new Url(urlAsString);
      }
      catch (Exception e) {
        LOGGER.error("downloading {} - {}", urlAsString, e.getMessage());
        throw e;
      }

      if (!rescale || newWidth == 0) {
        try (InputStream is = url.getInputStreamWithRetry(5); FileOutputStream outputStream = new FileOutputStream(tempFile.toFile())) {

          IOUtils.copy(is, outputStream);
          Utils.flushFileOutputStreamToDisk(outputStream);
        }
      }
      else {
        try (InputStream is = url.getInputStreamWithRetry(5);
            InputStream scaledIs = ImageUtils.scaleImage(IOUtils.toByteArray(is), newWidth);
            FileOutputStream outputStream = new FileOutputStream(tempFile.toFile())) {

          IOUtils.copy(scaledIs, outputStream);
          Utils.flushFileOutputStreamToDisk(outputStream);
        }
      }

      // check if the file has been downloaded
      if (!Files.exists(tempFile) || Files.size(tempFile) == 0) {
        // cleanup the file
        FileUtils.deleteQuietly(tempFile.toFile());
        throw new IOException("0byte file downloaded: " + filename);
      }

      // delete new destination if existing
      Utils.deleteFileSafely(destFile);

      // move the temp file to the expected filename
      if (!Utils.moveFileSafe(tempFile, destFile)) {
        throw new IOException("renaming temp file failed: " + filename);
      }
    }
    finally {
      // remove temp file
      if (tempFile != null && Files.exists(tempFile)) {
        Utils.deleteFileSafely(tempFile);
      }
    }

    return destFile;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy