Skip to content

Commit 2a965df

Browse files
Jeremy Woodjayathirthrao
authored andcommitted
8374377: PNGImageDecoder Slow For 8-bit PNGs
Reviewed-by: jdv, prr
1 parent a855224 commit 2a965df

File tree

3 files changed

+400
-4
lines changed

3 files changed

+400
-4
lines changed

src/java.desktop/share/classes/sun/awt/image/PNGImageDecoder.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -302,8 +302,16 @@ public void produceImage() throws IOException, ImageFormatException {
302302
int bitsPerPixel = samplesPerPixel*bitDepth;
303303
int bytesPerPixel = (bitsPerPixel+7)>>3;
304304
int pass, passLimit;
305-
if(interlaceMethod==0) { pass = -1; passLimit = 0; }
306-
else { pass = 0; passLimit = 7; }
305+
boolean isDirectByteCopy;
306+
if(interlaceMethod==0) {
307+
pass = -1;
308+
passLimit = 0;
309+
isDirectByteCopy = bPixels != null && bitDepth == 8;
310+
} else {
311+
pass = 0;
312+
passLimit = 7;
313+
isDirectByteCopy = false;
314+
}
307315
while(++pass<=passLimit) {
308316
int row = startingRow[pass];
309317
int rowInc = rowIncrement[pass];
@@ -334,7 +342,11 @@ public void produceImage() throws IOException, ImageFormatException {
334342
int spos=0;
335343
int pixel = 0;
336344
while (col < width) {
337-
if(wPixels !=null) {
345+
if (isDirectByteCopy) {
346+
System.arraycopy(rowByteBuffer, spos, bPixels, col + rowOffset, width);
347+
spos += width;
348+
break;
349+
} else if(wPixels !=null) {
338350
switch(combinedType) {
339351
case COLOR|ALPHA|(8<<3):
340352
wPixels[col+rowOffset] =
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/*
2+
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8374377
27+
* @summary This test confirms the PNGImageProducer decodes 8-bit interlaced
28+
* and non-interlaced PNGs correctly.
29+
*/
30+
31+
import javax.imageio.IIOImage;
32+
import javax.imageio.ImageIO;
33+
import javax.imageio.ImageWriteParam;
34+
import javax.imageio.ImageWriter;
35+
import javax.imageio.stream.ImageOutputStream;
36+
import java.awt.Color;
37+
import java.awt.Graphics2D;
38+
import java.awt.Image;
39+
import java.awt.Toolkit;
40+
import java.awt.image.BufferedImage;
41+
import java.awt.image.ColorModel;
42+
import java.awt.image.ImageConsumer;
43+
import java.awt.image.IndexColorModel;
44+
import java.io.ByteArrayOutputStream;
45+
import java.io.IOException;
46+
import java.util.Hashtable;
47+
import java.util.Iterator;
48+
import java.util.Random;
49+
import java.util.concurrent.CompletableFuture;
50+
import java.util.concurrent.ExecutionException;
51+
52+
/**
53+
* The proposed change for 8374377 affects how 8-bit PNGs are decoded.
54+
* So this test confirms that 8-bit PNGs (both interlaced and non-interlaced)
55+
* are still decoded by the PNGImageDecoder so they match what ImageIO decodes.
56+
*
57+
* This test has never failed.
58+
*/
59+
public class PngImageDecoder8BitTest {
60+
61+
static BufferedImage createBufferedImage(Image img)
62+
throws ExecutionException, InterruptedException {
63+
CompletableFuture<BufferedImage> future = new CompletableFuture<>();
64+
img.getSource().startProduction(new ImageConsumer() {
65+
private int imageWidth, imageHeight;
66+
private BufferedImage bi;
67+
68+
@Override
69+
public void setDimensions(int width, int height) {
70+
imageWidth = width;
71+
imageHeight = height;
72+
}
73+
74+
@Override
75+
public void setProperties(Hashtable<?, ?> props) {
76+
// intentionally empty
77+
}
78+
79+
@Override
80+
public void setColorModel(ColorModel model) {
81+
// intentionally empty
82+
}
83+
84+
@Override
85+
public void setHints(int hintflags) {
86+
// intentionally empty
87+
}
88+
89+
@Override
90+
public void setPixels(int x, int y, int w, int h, ColorModel model,
91+
byte[] pixels, int off, int scansize) {
92+
if (bi == null) {
93+
bi = new BufferedImage(imageWidth, imageHeight,
94+
BufferedImage.TYPE_BYTE_INDEXED,
95+
(IndexColorModel) model);
96+
}
97+
98+
if (w == imageWidth && h == imageHeight) {
99+
// this is how interlaced PNGs are decoded:
100+
bi.getRaster().setDataElements(0, 0,
101+
imageWidth, imageHeight, pixels);
102+
return;
103+
}
104+
105+
if (h != 1) {
106+
throw new UnsupportedOperationException(
107+
"this test requires h = 1");
108+
}
109+
if (off != 0) {
110+
throw new UnsupportedOperationException(
111+
"this test requires off = 0");
112+
}
113+
114+
bi.getRaster().setDataElements(x, y, w, 1, pixels);
115+
}
116+
117+
@Override
118+
public void setPixels(int x, int y, int w, int h, ColorModel model,
119+
int[] pixels, int off, int scansize) {
120+
throw new UnsupportedOperationException();
121+
}
122+
123+
@Override
124+
public void imageComplete(int status) {
125+
future.complete(bi);
126+
}
127+
});
128+
return future.get();
129+
}
130+
131+
public static void main(String[] args) throws Exception {
132+
BufferedImage expected = createImageData();
133+
for (boolean interlace : new boolean[] { false, true} ) {
134+
System.out.println("Testing interlacing = "+ interlace);
135+
byte[] imageData = encodePNG(expected, interlace);
136+
137+
Image i = Toolkit.getDefaultToolkit().createImage(imageData);
138+
BufferedImage actual = createBufferedImage(i);
139+
140+
testCorrectness(expected, actual);
141+
}
142+
System.out.println("Confirmed that 8-bit PNGs decode correctly " +
143+
"whether we use interlacing or not.");
144+
}
145+
146+
/**
147+
* Create a large sample image stored as an 8-bit PNG.
148+
*/
149+
private static BufferedImage createImageData() {
150+
BufferedImage bi = new BufferedImage(6000, 6000,
151+
BufferedImage.TYPE_BYTE_INDEXED);
152+
Random r = new Random(0);
153+
Graphics2D g = bi.createGraphics();
154+
for (int a = 0; a < 20000; a++) {
155+
g.setColor(new Color(r.nextInt(0xffffff)));
156+
int radius = 10 + r.nextInt(90);
157+
g.fillOval(r.nextInt(bi.getWidth()), r.nextInt(bi.getHeight()),
158+
radius, radius);
159+
}
160+
g.dispose();
161+
return bi;
162+
}
163+
164+
/**
165+
* Encode an image as 8-bit PNG.
166+
*/
167+
private static byte[] encodePNG(BufferedImage bi, boolean interlace)
168+
throws IOException {
169+
Iterator<ImageWriter> writers =
170+
ImageIO.getImageWritersByFormatName("png");
171+
if (!writers.hasNext()) {
172+
throw new IllegalStateException("No PNG writers found");
173+
}
174+
ImageWriter writer = writers.next();
175+
176+
ImageWriteParam param = writer.getDefaultWriteParam();
177+
if (interlace) {
178+
param.setProgressiveMode(ImageWriteParam.MODE_DEFAULT);
179+
}
180+
181+
try (ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
182+
ImageOutputStream imageOut =
183+
ImageIO.createImageOutputStream(byteOut)) {
184+
writer.setOutput(imageOut);
185+
writer.write(null, new IIOImage(bi, null, null), param);
186+
return byteOut.toByteArray();
187+
} finally {
188+
writer.dispose();
189+
}
190+
}
191+
192+
/**
193+
* This throws an Error if the two images are not identical.
194+
* <p>
195+
* This unit test is intended to accompany a performance enhancement for
196+
* PNGImageDecoder. This method makes sure the enhancement didn't cost us
197+
* any accuracy.
198+
*/
199+
private static void testCorrectness(BufferedImage expected,
200+
BufferedImage actual) {
201+
if (expected.getWidth() != actual.getWidth()) {
202+
throw new RuntimeException("expected.getWidth() = " +
203+
expected.getWidth() + ", actual.getWidth() = " +
204+
actual.getWidth());
205+
}
206+
if (expected.getHeight() != actual.getHeight()) {
207+
throw new RuntimeException("expected.getHeight() = " +
208+
expected.getHeight() + ", actual.getHeight() = " +
209+
actual.getHeight());
210+
}
211+
for (int y = 0; y < expected.getHeight(); y++) {
212+
for (int x = 0; x < expected.getWidth(); x++) {
213+
int argb1 = expected.getRGB(x, y);
214+
int argb2 = actual.getRGB(x, y);
215+
if (argb1 != argb2) {
216+
throw new RuntimeException("x = " + x + ", y = " + y +
217+
" argb1 = " + Integer.toUnsignedString(argb1, 16) +
218+
" argb2 = " + Integer.toUnsignedString(argb2, 16));
219+
}
220+
}
221+
}
222+
}
223+
}

0 commit comments

Comments
 (0)