1 /* 2 * Copyright (c) 2007, 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 6198111 27 * @summary Test verifies that PNG image reader correctly handles 28 * hIST chunk if length of image palette in not power of two. 29 * 30 * @run main ShortHistogramTest 15 31 */ 32 33 import java.awt.Color; 34 import java.awt.Graphics2D; 35 import java.awt.image.BufferedImage; 36 import java.awt.image.DataBuffer; 37 import java.awt.image.IndexColorModel; 38 import java.awt.image.Raster; 39 import java.io.File; 40 import java.io.IOException; 41 import java.util.Arrays; 42 import java.util.Random; 43 import javax.imageio.IIOImage; 44 import javax.imageio.ImageIO; 45 import javax.imageio.ImageTypeSpecifier; 46 import javax.imageio.ImageWriteParam; 47 import javax.imageio.ImageWriter; 48 import javax.imageio.metadata.IIOInvalidTreeException; 49 import javax.imageio.metadata.IIOMetadata; 50 import javax.imageio.metadata.IIOMetadataNode; 51 import javax.imageio.stream.ImageOutputStream; 52 import org.w3c.dom.NamedNodeMap; 53 import org.w3c.dom.Node; 54 55 public class ShortHistogramTest { 56 57 public static void main(String[] args) throws IOException { 58 int numColors = 15; 59 if (args.length > 0) { 60 try { 61 numColors = Integer.parseInt(args[0]); 62 } catch (NumberFormatException e) { 63 System.out.println("Invalid number of colors: " + args[0]); 64 } 65 } 66 System.out.println("Test number of colors: " + numColors); 67 68 ShortHistogramTest t = new ShortHistogramTest(numColors); 69 t.doTest(); 70 } 71 72 int numColors; 73 74 public ShortHistogramTest(int numColors) { 75 this.numColors = numColors; 76 } 77 78 public void doTest() throws IOException { 79 BufferedImage bi = createTestImage(numColors); 80 81 File f = writeImageWithHist(bi); 82 System.out.println("Test file is " + f.getCanonicalPath()); 83 84 try { 85 ImageIO.read(f); 86 } catch (IOException e) { 87 throw new RuntimeException("Test FAILED!", e); 88 } 89 System.out.println("Test PASSED!"); 90 } 91 92 protected File writeImageWithHist(BufferedImage bi) throws IOException { 93 File f = File.createTempFile("hist_", ".png", new File(".")); 94 95 ImageWriter writer = ImageIO.getImageWritersByFormatName("PNG").next(); 96 97 ImageOutputStream ios = ImageIO.createImageOutputStream(f); 98 writer.setOutput(ios); 99 100 ImageWriteParam param = writer.getDefaultWriteParam(); 101 ImageTypeSpecifier type = new ImageTypeSpecifier(bi); 102 103 IIOMetadata imgMetadata = writer.getDefaultImageMetadata(type, param); 104 105 /* add hIST node to image metadata */ 106 imgMetadata = upgradeMetadata(imgMetadata, bi); 107 108 IIOImage iio_img = new IIOImage(bi, 109 null, // no thumbnails 110 imgMetadata); 111 112 writer.write(iio_img); 113 ios.flush(); 114 ios.close(); 115 return f; 116 } 117 118 private IIOMetadata upgradeMetadata(IIOMetadata src, BufferedImage bi) { 119 String format = src.getNativeMetadataFormatName(); 120 System.out.println("Native format: " + format); 121 Node root = src.getAsTree(format); 122 123 // add hIST node 124 Node n = lookupChildNode(root, "hIST"); 125 if (n == null) { 126 System.out.println("Appending new hIST node..."); 127 Node hIST = gethISTNode(bi); 128 root.appendChild(hIST); 129 } 130 131 System.out.println("Upgraded metadata tree:"); 132 dump(root, ""); 133 134 System.out.println("Merging metadata..."); 135 try { 136 src.mergeTree(format, root); 137 } catch (IIOInvalidTreeException e) { 138 throw new RuntimeException("Test FAILED!", e); 139 } 140 return src; 141 } 142 143 private IIOMetadataNode gethISTNode(BufferedImage bi) { 144 IndexColorModel icm = (IndexColorModel)bi.getColorModel(); 145 int mapSize = icm.getMapSize(); 146 147 int[] hist = new int[mapSize]; 148 Arrays.fill(hist, 0); 149 150 Raster r = bi.getData(); 151 for (int y = 0; y < bi.getHeight(); y++) { 152 for (int x = 0; x < bi.getWidth(); x++) { 153 int s = r.getSample(x, y, 0); 154 hist[s] ++; 155 } 156 } 157 158 IIOMetadataNode hIST = new IIOMetadataNode("hIST"); 159 for (int i = 0; i < hist.length; i++) { 160 IIOMetadataNode n = new IIOMetadataNode("hISTEntry"); 161 n.setAttribute("index", "" + i); 162 n.setAttribute("value", "" + hist[i]); 163 hIST.appendChild(n); 164 } 165 166 return hIST; 167 } 168 169 private static Node lookupChildNode(Node root, String name) { 170 Node n = root.getFirstChild(); 171 while (n != null && !name.equals(n.getNodeName())) { 172 n = n.getNextSibling(); 173 } 174 return n; 175 } 176 177 private static void dump(Node node, String ident) { 178 if (node == null) { 179 return; 180 } 181 182 System.out.printf("%s%s\n", ident, node.getNodeName()); 183 184 // dump node attributes... 185 NamedNodeMap attribs = node.getAttributes(); 186 if (attribs != null) { 187 for (int i = 0; i < attribs.getLength(); i++) { 188 Node a = attribs.item(i); 189 System.out.printf("%s %s: %s\n", ident, 190 a.getNodeName(), a.getNodeValue()); 191 } 192 } 193 // dump node children... 194 dump(node.getFirstChild(), ident + " "); 195 196 dump(node.getNextSibling(), ident); 197 } 198 199 protected BufferedImage createTestImage(int numColors) { 200 201 IndexColorModel icm = createTestICM(numColors); 202 int w = numColors * 10; 203 int h = 20; 204 205 BufferedImage img = new BufferedImage(w, h, 206 BufferedImage.TYPE_BYTE_INDEXED, icm); 207 208 Graphics2D g = img.createGraphics(); 209 for (int i = 0; i < numColors; i++) { 210 int rgb = icm.getRGB(i); 211 //System.out.printf("pixel %d, rgb %x\n", i, rgb); 212 g.setColor(new Color(rgb)); 213 g.fillRect(i * 10, 0, w - i * 10, h); 214 } 215 g.dispose(); 216 217 return img; 218 } 219 220 protected IndexColorModel createTestICM(int numColors) { 221 int[] palette = createTestPalette(numColors); 222 223 int numBits = getNumBits(numColors); 224 225 IndexColorModel icm = new IndexColorModel(numBits, numColors, 226 palette, 0, false, -1, 227 DataBuffer.TYPE_BYTE); 228 return icm; 229 } 230 231 protected static int getNumBits(int numColors) { 232 if (numColors < 0 || 256 < numColors) { 233 throw new RuntimeException("Unsupported number of colors: " + 234 numColors); 235 } 236 237 int numBits = 1; 238 int limit = 1 << numBits; 239 while (numColors > limit) { 240 numBits++; 241 limit = 1 << numBits; 242 } 243 return numBits; 244 } 245 246 private static Random rnd = new Random(); 247 248 protected static int[] createTestPalette(int numColors) { 249 int[] palette = new int[numColors]; 250 for (int i = 0; i < numColors; i++) { 251 int r = rnd.nextInt(256); 252 int g = rnd.nextInt(256); 253 int b = rnd.nextInt(256); 254 255 palette[i] = 0xff000000 | (r << 16) | (g << 8) | b; 256 } 257 return palette; 258 } 259 }