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

org.robolectric.shadows.ImageUtil Maven / Gradle / Ivy

package org.robolectric.shadows;

import static java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR;
import static java.awt.RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
import static java.awt.image.BufferedImage.TYPE_INT_ARGB_PRE;
import static java.awt.image.BufferedImage.TYPE_INT_RGB;
import static javax.imageio.ImageIO.createImageInputStream;

import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Point;
import com.google.auto.value.AutoValue;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import org.robolectric.shadow.api.Shadow;

public class ImageUtil {
  private static final String FORMAT_NAME_JPEG = "jpg";
  private static final String FORMAT_NAME_PNG = "png";
  private static boolean initialized;

  static Point getImageSizeFromStream(InputStream is) {
    if (!initialized) {
      // Stops ImageIO from creating temp files when reading images
      // from input stream.
      ImageIO.setUseCache(false);
      initialized = true;
    }

    try {
      ImageInputStream imageStream = createImageInputStream(is);
      Iterator readers = ImageIO.getImageReaders(imageStream);
      if (!readers.hasNext()) return null;

      ImageReader reader = readers.next();
      try {
        reader.setInput(imageStream);
        return new Point(reader.getWidth(0), reader.getHeight(0));
      } finally {
        reader.dispose();
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  static RobolectricBufferedImage getImageFromStream(InputStream is) {
    return getImageFromStream(null, is);
  }

  static RobolectricBufferedImage getImageFromStream(String fileName, InputStream is) {
    if (!initialized) {
      // Stops ImageIO from creating temp files when reading images
      // from input stream.
      ImageIO.setUseCache(false);
      initialized = true;
    }

    String format = null;
    try {
      ImageInputStream imageStream = createImageInputStream(is);
      Iterator readers = ImageIO.getImageReaders(imageStream);
      if (!readers.hasNext()) {
        return null;
      }

      ImageReader reader = readers.next();
      try {
        reader.setInput(imageStream);
        format = reader.getFormatName();
        int minIndex = reader.getMinIndex();
        BufferedImage image = reader.read(minIndex);
        return RobolectricBufferedImage.create(image, ("image/" + format).toLowerCase());
      } finally {
        reader.dispose();
      }
    } catch (IOException e) {
      Throwable cause = e.getCause();
      if (FORMAT_NAME_PNG.equalsIgnoreCase(format)
          && cause instanceof IIOException
          && cause.getMessage() != null
          && cause.getMessage().contains("Invalid chunk length")) {
        String pngFileName = "(" + (fileName == null ? "not given PNG file name" : fileName) + ")";
        System.err.println(
            "The PNG file"
                + pngFileName
                + " cannot be decoded. This may be due to an OpenJDK issue with certain PNG files."
                + " See https://github.com/robolectric/robolectric/issues/6812 for more details.");
      }
      throw new RuntimeException(e);
    }
  }

  static boolean scaledBitmap(Bitmap src, Bitmap dst, boolean filter) {
    if (src == null || dst == null) {
      return false;
    }
    int srcWidth = src.getWidth();
    int srcHeight = src.getHeight();
    int dstWidth = dst.getWidth();
    int dstHeight = dst.getHeight();
    if (srcWidth <= 0 || srcHeight <= 0 || dstWidth <= 0 || dstHeight <= 0) {
      return false;
    }
    BufferedImage before = ((ShadowLegacyBitmap) Shadow.extract(src)).getBufferedImage();
    if (before == null || before.getColorModel() == null) {
      return false;
    }
    int imageType = getBufferedImageType(src.getConfig(), before.getColorModel().hasAlpha());
    BufferedImage after = new BufferedImage(dstWidth, dstHeight, imageType);
    Graphics2D graphics2D = after.createGraphics();
    graphics2D.setRenderingHint(
        RenderingHints.KEY_INTERPOLATION,
        filter ? VALUE_INTERPOLATION_BILINEAR : VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
    graphics2D.drawImage(before, 0, 0, dstWidth, dstHeight, 0, 0, srcWidth, srcHeight, null);
    graphics2D.dispose();
    ((ShadowLegacyBitmap) Shadow.extract(dst)).setBufferedImage(after);
    return true;
  }

  public static boolean writeToStream(
      Bitmap realBitmap, CompressFormat format, int quality, OutputStream stream) {
    if ((quality < 0) || (quality > 100)) {
      throw new IllegalArgumentException("Quality out of bounds!");
    }

    try {
      ImageWriter writer = null;
      Iterator iter = ImageIO.getImageWritersByFormatName(getFormatName(format));
      if (iter.hasNext()) {
        writer = iter.next();
      }
      if (writer == null) {
        return false;
      }
      try (ImageOutputStream ios = ImageIO.createImageOutputStream(stream)) {
        writer.setOutput(ios);
        ImageWriteParam iwparam = writer.getDefaultWriteParam();
        iwparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        iwparam.setCompressionQuality((quality / 100f));
        int width = realBitmap.getWidth();
        int height = realBitmap.getHeight();
        boolean needAlphaChannel = needAlphaChannel(format);
        BufferedImage bufferedImage =
            ((ShadowLegacyBitmap) Shadow.extract(realBitmap)).getBufferedImage();
        if (bufferedImage == null) {
          bufferedImage =
              new BufferedImage(
                  realBitmap.getWidth(), realBitmap.getHeight(), BufferedImage.TYPE_INT_ARGB);
        }
        int outputImageType = getBufferedImageType(realBitmap.getConfig(), needAlphaChannel);
        if (outputImageType != BufferedImage.TYPE_INT_ARGB) {
          // re-encode image data with a type that is compatible with the output format.
          BufferedImage outputBufferedImage = new BufferedImage(width, height, outputImageType);
          Graphics2D g = outputBufferedImage.createGraphics();
          g.drawImage(bufferedImage, 0, 0, null);
          g.dispose();
          bufferedImage = outputBufferedImage;
        }
        writer.write(null, new IIOImage(bufferedImage, null, null), iwparam);
        ios.flush();
        writer.dispose();
      }
    } catch (IOException ignore) {
      return false;
    }

    return true;
  }

  private static String getFormatName(CompressFormat compressFormat) {
    switch (compressFormat) {
      case JPEG:
        return FORMAT_NAME_JPEG;
      case WEBP:
      case WEBP_LOSSY:
      case WEBP_LOSSLESS:
      case PNG:
        return FORMAT_NAME_PNG;
    }
    throw new UnsupportedOperationException("Cannot convert format: " + compressFormat);
  }

  private static boolean needAlphaChannel(CompressFormat compressFormat) {
    return !FORMAT_NAME_JPEG.equals(getFormatName(compressFormat));
  }

  private static int getBufferedImageType(Bitmap.Config config, boolean needAlphaChannel) {
    if (config == null) {
      return needAlphaChannel ? TYPE_INT_ARGB : TYPE_INT_RGB;
    }
    switch (config) {
      case RGB_565:
        return BufferedImage.TYPE_USHORT_565_RGB;
      case RGBA_F16:
        return needAlphaChannel ? TYPE_INT_ARGB_PRE : TYPE_INT_RGB;
      case ALPHA_8:
      case ARGB_4444:
      case ARGB_8888:
      case HARDWARE:
      default:
        return needAlphaChannel ? TYPE_INT_ARGB : TYPE_INT_RGB;
    }
  }

  @AutoValue
  abstract static class RobolectricBufferedImage {
    abstract BufferedImage getBufferedImage();

    abstract String getMimeType();

    public Point getWidthAndHeight() {
      return new Point(getBufferedImage().getWidth(), getBufferedImage().getHeight());
    }

    static RobolectricBufferedImage create(BufferedImage bufferedImage, String mimeType) {
      return new AutoValue_ImageUtil_RobolectricBufferedImage(bufferedImage, mimeType);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy