Making ImageIO.read() 15% faster for JPEGs

Jeremy Wood mickleness at gmail.com
Tue Jan 6 16:59:50 UTC 2026


I’m exploring the performance of loading BufferedImages. I recently 
noticed that ImageIO.read() can be made ~15% faster by changing the 
BufferedImage type (so we'd avoid a RGB -> BGR conversion).

IMO this wouldn’t be too hard to program, but it might be considered too 
invasive to accept. Is there any interest/support in exploring this idea 
if I submit a PR for it?

Specifically this is the performance I’m observing on my MacBook:

Benchmark                                    Mode  Cnt   Score   Error  
Units
JPEG_Default_vs_RGB.measureDefaultImageType  avgt   15  42.589 ± 0.137  
ms/op
JPEG_Default_vs_RGB.measureRGBImageType      avgt   15  35.624 ± 0.589  
ms/op

The first “default” approach uses ImageIO.read(inputStream).

The second “RGB” approach creates a BufferedImage target with a custom 
ColorModel that is similar to TYPE_3BYTE_BGR, except it reverse the 
colors so they are ordered RGB.

This derives from the observation that in JPEGImageReader we create a 
one-line raster field that uses this 3-byte RGB model. Later in 
acceptPixels() we call target.setRect(x, y, raster) . Here target is the 
WritableRaster of the final BufferedImage. By default it will be a 
3-byte BGR. So we’re spending 15+% of our time converting RGB-encoded 
data (from raster) to BGR-encoded data (for target).

So the “pros” of my proposal should include a faster loading time for 
many JPEG images. I’d argue ImageIO should always default to the fastest 
(reasonable) implementation possible.

IMO the major “con” is: target.getType() would change from 
BufferedImage.TYPE_3BYTE_BGR to BufferedImage.TYPE_CUSTOM . This doesn’t 
technically violate any documentation that I know of, but it seems (IMO) 
like something some clients will have made assumptions about, and 
therefore some downstream code may break. (And maybe other devs here can 
identify other problems I’m not anticipating.)

Any thoughts / feedback?

Regards,
  - Jeremy

Below is the JMH code used to generate the output above:

package org.sun.awt.image;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;

import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 20)
@Fork(3)
@State(Scope.Thread)
public class JPEG_Default_vs_RGB {

     byte[] jpgImageData;

     @Setup
     public void setup() throws Exception {
         jpgImageData = createImageData(2_500);
     }

     @Benchmark
     public void measureDefaultImageType(Blackhole bh) throws Exception {
         BufferedImage bi = readJPG(false);
         bi.flush();
         bh.consume(bi);
     }

     @Benchmark
     public void measureRGBImageType(Blackhole bh) throws Exception {
         BufferedImage bi = readJPG(true);
         bi.flush();
         bh.consume(bi);
     }

     private BufferedImage readJPG(boolean useRGBTarget) throws Exception 
{
         Iterator<ImageReader> readers;
         try (ByteArrayInputStream byteIn = new 
ByteArrayInputStream(jpgImageData)) {
             if (!useRGBTarget)
                 return ImageIO.read(byteIn);

             readers = 
ImageIO.getImageReaders(ImageIO.createImageInputStream(byteIn));
             if (!readers.hasNext()) {
                 throw new IOException("No reader found for the given 
file.”);
             }
         }

         ImageReader reader = readers.next();
         try (ByteArrayInputStream byteIn = new 
ByteArrayInputStream(jpgImageData)) {
             reader.setInput(ImageIO.createImageInputStream(byteIn));

             int width = reader.getWidth(0);
             int height = reader.getHeight(0);

             // this is copied from how BufferedImage sets up a 
TYPE_3BYTE_BGR image,
             // except we use {0, 1, 2} to make it an RGB image:
             ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
             int[] nBits = {8, 8, 8};
             int[] bOffs = {0, 1, 2};
             ColorModel colorModel = new ComponentColorModel(cs, nBits, 
false, false,
                     Transparency.OPAQUE,
                     DataBuffer.TYPE_BYTE);
             WritableRaster raster = 
Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
                     width, height, width * 3, 3, bOffs, null);

             BufferedImage rgbImage = new BufferedImage(colorModel, 
raster, false, null);

             ImageReadParam param = reader.getDefaultReadParam();
             param.setDestination(rgbImage);

             reader.read(0, param);

             return rgbImage;
         } finally {
             reader.dispose();
         }
     }

     /**
      * Create a large sample image stored as a JPG
      *
      * @return the byte representation of the JPG image.
      */
     private static byte[] createImageData(int squareSize) throws 
Exception {
         BufferedImage bi = new BufferedImage(squareSize, squareSize,
                 BufferedImage.TYPE_INT_RGB);
         Random r = new Random(0);
         Graphics2D g = bi.createGraphics();
         for (int a = 0; a < 20000; a++) {
             g.setColor(new Color(r.nextInt(0xffffff)));
             int radius = 10 + r.nextInt(90);
             g.fillOval(r.nextInt(bi.getWidth()), 
r.nextInt(bi.getHeight()),
                     radius, radius);
         }
         g.dispose();

         try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
             ImageIO.write(bi, "jpg", out);
             return out.toByteArray();
         }
     }
}
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/client-libs-dev/attachments/20260106/0a88cb17/attachment-0001.htm>


More information about the client-libs-dev mailing list