<html><head>

<style id="css_styles"> 
blockquote.cite { margin-left: 5px; margin-right: 0px; padding-left: 10px; padding-right:0px; border-left: 1px solid #cccccc }
blockquote.cite2 {margin-left: 5px; margin-right: 0px; padding-left: 10px; padding-right:0px; border-left: 1px solid #cccccc; margin-top: 3px; padding-top: 0px; }
a img { border: 0px; }
li[style='text-align: center;'], li[style='text-align: center; '], li[style='text-align: right;'], li[style='text-align: right; '] {  list-style-position: inside;}
body { font-family: Helvetica; font-size: 9pt; }
.quote { margin-left: 1em; margin-right: 1em; border-left: 5px #ebebeb solid; padding-left: 0.3em; }
a.em-mention[href] { text-decoration: none; color: inherit; border-radius: 3px; padding-left: 2px; padding-right: 2px; background-color: #e2e2e2; }
._em_placeholder {color: gray; border-bottom: 1px dotted lightblue;} ._em_placeholder:before{color:gray; content: '{{ ';} ._em_placeholder:after{color:gray; content: ' }}';}

 </style>
</head>
<body style="overflow-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;">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).<div><br /></div><div style="">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?</div><div><div><br /></div><div>Specifically this is the performance I’m observing on my MacBook:</div><div><br /></div><div>Benchmark                                    Mode  Cnt   Score   Error  Units</div><div>JPEG_Default_vs_RGB.measureDefaultImageType  avgt   15  42.589 ± 0.137  ms/op</div><div>JPEG_Default_vs_RGB.measureRGBImageType      avgt   15  35.624 ± 0.589  ms/op</div><div><br /></div><div>The first “default” approach uses ImageIO.read(inputStream).</div><div><br /></div><div>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.</div><div><br /></div><div>This derives from the observation that in JPEGImageReader we create a one-line <font face="Courier New">raster</font> field that uses this 3-byte RGB model. Later in <font face="Courier New">acceptPixels()</font> we call <font face="Courier New">target.setRect(x, y, raster)</font> . Here <font face="Courier New">target</font> 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 <font face="Courier New">raster</font>) to BGR-encoded data (for <font face="Courier New">target</font>).</div></div><div><br /></div><div>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.</div><div><br /></div><div style="">IMO the major “con” is: <font face="Courier New">target.getType()</font> would change from <font face="Courier New">BufferedImage.TYPE_3BYTE_BGR</font> to <font face="Courier New">BufferedImage.TYPE_CUSTOM</font> . 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.)</div><div style=""><br /></div><div style="">Any thoughts / feedback?</div><div style=""><br /></div><div style="">Regards,</div><div style=""> - Jeremy</div><div style=""><br /></div><div style="">Below is the JMH code used to generate the output above:</div><div style=""><br /></div><div style="">package org.sun.awt.image;</div><div style=""><br /></div><div style="">import org.openjdk.jmh.annotations.*;</div><div style="">import org.openjdk.jmh.infra.Blackhole;</div><div style=""><br /></div><div style="">import javax.imageio.ImageIO;</div><div style="">import javax.imageio.ImageReadParam;</div><div style="">import javax.imageio.ImageReader;</div><div style="">import java.awt.*;</div><div style="">import java.awt.color.ColorSpace;</div><div style="">import java.awt.image.*;</div><div style="">import java.io.ByteArrayInputStream;</div><div style="">import java.io.ByteArrayOutputStream;</div><div style="">import java.io.IOException;</div><div style="">import java.util.Iterator;</div><div style="">import java.util.Random;</div><div style="">import java.util.concurrent.TimeUnit;</div><div style=""><br /></div><div style="">@BenchmarkMode(Mode.AverageTime)</div><div style="">@OutputTimeUnit(TimeUnit.MILLISECONDS)</div><div style="">@Warmup(iterations = 5, time = 1)</div><div style="">@Measurement(iterations = 5, time = 20)</div><div style="">@Fork(3)</div><div style="">@State(Scope.Thread)</div><div style="">public class JPEG_Default_vs_RGB {</div><div style=""><br /></div><div style="">    byte[] jpgImageData;</div><div style=""><br /></div><div style="">    @Setup</div><div style="">    public void setup() throws Exception {</div><div style="">        jpgImageData = createImageData(2_500);</div><div style="">    }</div><div style=""><br /></div><div style="">    @Benchmark</div><div style="">    public void measureDefaultImageType(Blackhole bh) throws Exception {</div><div style="">        BufferedImage bi = readJPG(false);</div><div style="">        bi.flush();</div><div style="">        bh.consume(bi);</div><div style="">    }</div><div style=""><br /></div><div style="">    @Benchmark</div><div style="">    public void measureRGBImageType(Blackhole bh) throws Exception {</div><div style="">        BufferedImage bi = readJPG(true);</div><div style="">        bi.flush();</div><div style="">        bh.consume(bi);</div><div style="">    }</div><div style=""><br /></div><div style="">    private BufferedImage readJPG(boolean useRGBTarget) throws Exception {</div><div style="">        Iterator<ImageReader> readers;</div><div style="">        try (ByteArrayInputStream byteIn = new ByteArrayInputStream(jpgImageData)) {</div><div style="">            if (!useRGBTarget)</div><div style="">                return ImageIO.read(byteIn);</div><div style=""><br /></div><div style="">            readers = ImageIO.getImageReaders(ImageIO.createImageInputStream(byteIn));</div><div style="">            if (!readers.hasNext()) {</div><div style="">                throw new IOException("No reader found for the given file.”);</div><div style="">            }</div><div style="">        }</div><div style=""><br /></div><div style="">        ImageReader reader = readers.next();</div><div style="">        try (ByteArrayInputStream byteIn = new ByteArrayInputStream(jpgImageData)) {</div><div style="">            reader.setInput(ImageIO.createImageInputStream(byteIn));</div><div style=""><br /></div><div style="">            int width = reader.getWidth(0);</div><div style="">            int height = reader.getHeight(0);</div><div style=""><br /></div><div style="">            // this is copied from how BufferedImage sets up a TYPE_3BYTE_BGR image,</div><div style="">            // except we use {0, 1, 2} to make it an RGB image:</div><div style="">            ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);</div><div style="">            int[] nBits = {8, 8, 8};</div><div style="">            int[] bOffs = {0, 1, 2};</div><div style="">            ColorModel colorModel = new ComponentColorModel(cs, nBits, false, false,</div><div style="">                    Transparency.OPAQUE,</div><div style="">                    DataBuffer.TYPE_BYTE);</div><div style="">            WritableRaster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,</div><div style="">                    width, height, width * 3, 3, bOffs, null);</div><div style=""><br /></div><div style="">            BufferedImage rgbImage = new BufferedImage(colorModel, raster, false, null);</div><div style=""><br /></div><div style="">            ImageReadParam param = reader.getDefaultReadParam();</div><div style="">            param.setDestination(rgbImage);</div><div style=""><br /></div><div style="">            reader.read(0, param);</div><div style=""><br /></div><div style="">            return rgbImage;</div><div style="">        } finally {</div><div style="">            reader.dispose();</div><div style="">        }</div><div style="">    }</div><div style=""><br /></div><div style="">    /**</div><div style="">     * Create a large sample image stored as a JPG</div><div style="">     *</div><div style="">     * @return the byte representation of the JPG image.</div><div style="">     */</div><div style="">    private static byte[] createImageData(int squareSize) throws Exception {</div><div style="">        BufferedImage bi = new BufferedImage(squareSize, squareSize,</div><div style="">                BufferedImage.TYPE_INT_RGB);</div><div style="">        Random r = new Random(0);</div><div style="">        Graphics2D g = bi.createGraphics();</div><div style="">        for (int a = 0; a < 20000; a++) {</div><div style="">            g.setColor(new Color(r.nextInt(0xffffff)));</div><div style="">            int radius = 10 + r.nextInt(90);</div><div style="">            g.fillOval(r.nextInt(bi.getWidth()), r.nextInt(bi.getHeight()),</div><div style="">                    radius, radius);</div><div style="">        }</div><div style="">        g.dispose();</div><div style=""><br /></div><div style="">        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {</div><div style="">            ImageIO.write(bi, "jpg", out);</div><div style="">            return out.toByteArray();</div><div style="">        }</div><div style="">    }</div><div style="">}</div></body></html>