Java 9: Image API revamped

Java 9 is inching toward its general availability (27 July, 2017). JDK committers are hard at work to deliver because the countdown has begun. Java Enhancement Proposals (JEP) has published a long list of features as its improvement proposals. It includes many major/minor improvements in the form of new capabilities, syntactical changes, performance tweaks, and the much anticipated modularity. These improvements will soon bring about some noticeable changes in the way we program in Java. In this article, We introduces Java 9 Multi-Resolution Images, a new API that allows a set of images with different resolutions (width and height) to be encapsulated into only one single image.

Multi-Resolution Images API — JEP 251

Goal is to define a multi-resolution images API so that images with resolution variants can easily be manipulated and displayed by developers.

The new API which is defined in the java.awt.image package can help us:

  • Encapsulate many images with different resolutions into an image as its variants.
  • Get all variants in the image.
  • Get a resolution-specific image variant – the best variant to represent the logical image at the indicated size based on a given DPI metric.

Now take a look at MultiResolutionImage with 2 importants methods getResolutionVariant() that returns an Image and getResolutionVariants() that returns a list of Images:

package java.awt.image;

public interface MultiResolutionImage {

    Image getResolutionVariant(double destImageWidth, double destImageHeight);
    public List<Image> getResolutionVariants();
}

Then, we have an abstract class that implements MultiResolutionImage:

  public abstract class AbstractMultiResolutionImage extends java.awt.Image
          implements MultiResolutionImage {

      @Override
      public int getWidth(ImageObserver observer) {
          return getBaseImage().getWidth(observer);
      }

      @Override
      public int getHeight(ImageObserver observer) {
          return getBaseImage().getHeight(observer);
      }

      @Override
      public ImageProducer getSource() {
          return getBaseImage().getSource();
      }

      @Override
      public Graphics getGraphics() {
          throw new UnsupportedOperationException("getGraphics() not supported"
                  + " on Multi-Resolution Images");
      }

      @Override
      public Object getProperty(String name, ImageObserver observer) {
          return getBaseImage().getProperty(name, observer);
      }

      protected abstract Image getBaseImage();
  }

Also, a very simple useful implementation is provided – BaseMultiResolutionImage class:

  public class BaseMultiResolutionImage extends AbstractMultiResolutionImage {

      private final int baseImageIndex;
      private final Image[] resolutionVariants;

      public BaseMultiResolutionImage(Image... resolutionVariants) {
          this(0, resolutionVariants);
      }

      public BaseMultiResolutionImage(int baseImageIndex,
                                      Image... resolutionVariants) { }

      @Override
      public Image getResolutionVariant(double destImageWidth,
                                        double destImageHeight) { }

      private static void checkSize(double width, double height) { }

      @Override
      public List<Image> getResolutionVariants() { }

      @Override
      protected Image getBaseImage() { }
  }

Sample Example

We have 5 images with different resolutions (313 × 480 pixels | 392 × 600 pixels | 501 × 768 pixels | 669 × 1,024 pixels | 1,986 × 3,041 pixels.) which will be encapsulated into only one single MultiResolutionImage image.

Next, we retrieve all variants (List of Images) in that MultiResolutionImage image by using getResolutionVariants() method.Then we get a resolution-specific image variant for each indicated size using getResolutionVariant() method.

    import java.io.IOException;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.List;
    import java.awt.Image;
    import java.awt.image.*;
    import java.util.stream.Collectors;

    import javax.imageio.ImageIO;

    public class Main {

       public static void main(String[] args) throws IOException {

       List<String> imgUrls = List.of("https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Berlin_-_Berliner_Fernsehturm_-_Fensterreinigung.jpg/313px-Berlin_-_Berliner_Fernsehturm_-_Fensterreinigung.jpg",
                    "https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Berlin_-_Berliner_Fernsehturm_-_Fensterreinigung.jpg/392px-Berlin_-_Berliner_Fernsehturm_-_Fensterreinigung.jpg",
                    "https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Berlin_-_Berliner_Fernsehturm_-_Fensterreinigung.jpg/501px-Berlin_-_Berliner_Fernsehturm_-_Fensterreinigung.jpg",
                    "https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Berlin_-_Berliner_Fernsehturm_-_Fensterreinigung.jpg/669px-Berlin_-_Berliner_Fernsehturm_-_Fensterreinigung.jpg",
                    "https://upload.wikimedia.org/wikipedia/commons/9/96/Berlin_-_Berliner_Fernsehturm_-_Fensterreinigung.jpg");


       List<Image> images = imgUrls.stream().map(url -> {
                try {
                    return ImageIO.read(new URL(url));
                } catch (IOException e) {
                    // TODO: Log error
                }
                return null;
            }).collect(Collectors.toList());

       // encapsulate into resoImages
            MultiResolutionImage resoImages = new BaseMultiResolutionImage(images.toArray(new Image[0]));

      // get all variants
            List<Image> variants = resoImages.getResolutionVariants();

       System.out.println("number of images: " + variants.size());

       variants.stream().forEach(System.out::println);

       // get a resolution-specific image variant for each indicated size
     Image variant1 = resoImages.getResolutionVariant(1280, 960);
            System.out.printf("\nImage for destination[%d,%d]: [%d,%d]", 1280, 960, variant1.getWidth(null),
                    variant1.getHeight(null));

     Image variant2 = resoImages.getResolutionVariant(720, 480);
            System.out.printf("\nImage for destination[%d,%d]: [%d,%d]", 720, 480, variant2.getWidth(null),
                    variant2.getHeight(null));

     Image variant3 = resoImages.getResolutionVariant(600, 400);
            System.out.printf("\nImage for destination[%d,%d]: [%d,%d]", 600, 400, variant3.getWidth(null),
                   variant3.getHeight(null));


    Image variant4 = resoImages.getResolutionVariant(320, 240);
            System.out.printf("\nImage for destination[%d,%d]: [%d,%d]", 320, 240, variant4.getWidth(null),
                    variant4.getHeight(null));

      Image variant5 = resoImages.getResolutionVariant(240, 160);
            System.out.printf("\nImage for destination[%d,%d]: [%d,%d]", 240, 160, variant5.getWidth(null),
                    variant5.getHeight(null));
        }
    }

After running the code above, you'll get:

        number of images: 5
        BufferedImage@2d3379b4: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@3bd82cf5 transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 313 height = 479 #numDataElements 3 dataOff[0] = 2
        BufferedImage@544fa968: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@3bd82cf5 transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 392 height = 600 #numDataElements 3 dataOff[0] = 2
        BufferedImage@247bddad: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@3bd82cf5 transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 501 height = 767 #numDataElements 3 dataOff[0] = 2
        BufferedImage@d35dea7: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@3bd82cf5 transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 669 height = 1024 #numDataElements 3 dataOff[0] = 2
        BufferedImage@7770f470: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@3bd82cf5 transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 1986 height = 3041 #numDataElements 3 dataOff[0] = 2

       Image for destination[1280,960]: [1986,3041]
       Image for destination[720,480]: [1986,3041]
       Image for destination[600,400]: [669,1024]
       Image for destination[320,240]: [392,600]
       Image for destination[240,160]: [313,479]