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