1 /*
   2  * Copyright (c) 2007, 2017, 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 package org.jemmy.image;
  24 
  25 import java.awt.Color;
  26 
  27 import java.awt.image.BufferedImage;
  28 
  29 import java.io.InputStream;
  30 import java.io.IOException;
  31 import java.io.FileInputStream;
  32 
  33 import org.jemmy.JemmyException;
  34 
  35 import java.util.zip.DataFormatException;
  36 import java.util.zip.Inflater;
  37 
  38 /**
  39  * Allows to load PNG graphical file.
  40  * @author Alexandre Iline
  41  */
  42 public class PNGDecoder extends Object {
  43 
  44     private final InputStream in;
  45 
  46     /**
  47      * Constructs a PNGDecoder object.
  48      * @param in input stream to read PNG image from.
  49      */
  50     public PNGDecoder(InputStream in) {
  51         this.in = in;
  52     }
  53 
  54     byte read() throws IOException {
  55         byte b = (byte)in.read();
  56         return(b);
  57     }
  58 
  59     int readInt() throws IOException {
  60         byte b[] = read(4);
  61         return(((b[0]&0xff)<<24) +
  62                ((b[1]&0xff)<<16) +
  63                ((b[2]&0xff)<<8) +
  64                ((b[3]&0xff)));
  65     }
  66 
  67     byte[] read(int count) throws IOException {
  68         byte[] result = new byte[count];
  69         for(int i = 0; i < count; i++) {
  70             result[i] = read();
  71         }
  72         return(result);
  73     }
  74 
  75     boolean compare(byte[] b1, byte[] b2) {
  76         if(b1.length != b2.length) {
  77             return(false);
  78         }
  79         for(int i = 0; i < b1.length; i++) {
  80             if(b1[i] != b2[i]) {
  81                 return(false);
  82             }
  83         }
  84         return(true);
  85     }
  86 
  87     void checkEquality(byte[] b1, byte[] b2) {
  88         if(!compare(b1, b2)) {
  89             throw(new JemmyException("Format error"));
  90         }
  91     }
  92 
  93     /**
  94      * Decodes image from an input stream passed into constructor.
  95      * @return a BufferedImage object
  96      * @throws IOException
  97      */
  98     public BufferedImage decode() throws IOException {
  99         return decode(true);
 100     }
 101 
 102     /**
 103      * Decodes image from an input stream passed into constructor.
 104      * @return a BufferedImage object
 105      * @param closeStream requests method to close the stream after the image is read
 106      * @throws IOException
 107      */
 108     public BufferedImage decode(boolean closeStream) throws IOException {
 109 
 110         byte[] id = read(12);
 111         checkEquality(id, new byte[] {-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13});
 112 
 113         byte[] ihdr = read(4);
 114         checkEquality(ihdr, "IHDR".getBytes());
 115 
 116         int width = readInt();
 117         int height = readInt();
 118 
 119         BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
 120 
 121         byte[] head = read(5);
 122         int mode;
 123         if(compare(head, new byte[]{1, 0, 0, 0, 0})) {
 124             mode = PNGEncoder.BW_MODE;
 125         } else if(compare(head, new byte[]{8, 0, 0, 0, 0})) {
 126             mode = PNGEncoder.GREYSCALE_MODE;
 127         } else if(compare(head, new byte[]{8, 2, 0, 0, 0})) {
 128             mode = PNGEncoder.COLOR_MODE;
 129         } else {
 130             throw(new JemmyException("Format error"));
 131         }
 132 
 133         readInt();//!!crc
 134 
 135         int size = readInt();
 136 
 137         byte[] idat = read(4);
 138         checkEquality(idat, "IDAT".getBytes());
 139 
 140         byte[] data = read(size);
 141 
 142 
 143         Inflater inflater = new Inflater();
 144         inflater.setInput(data, 0, size);
 145 
 146         int color;
 147 
 148         try {
 149             switch (mode) {
 150             case PNGEncoder.BW_MODE:
 151                 {
 152                     int bytes = (int)(width / 8);
 153                     if((width % 8) != 0) {
 154                         bytes++;
 155                     }
 156                     byte colorset;
 157                     byte[] row = new byte[bytes];
 158                     for (int y = 0; y < height; y++) {
 159                         inflater.inflate(new byte[1]);
 160                         inflater.inflate(row);
 161                         for (int x = 0; x < bytes; x++) {
 162                             colorset = row[x];
 163                             for (int sh = 0; sh < 8; sh++) {
 164                                 if(x * 8 + sh >= width) {
 165                                     break;
 166                                 }
 167                                 if((colorset & 0x80) == 0x80) {
 168                                     result.setRGB(x * 8 + sh, y, Color.white.getRGB());
 169                                 } else {
 170                                     result.setRGB(x * 8 + sh, y, Color.black.getRGB());
 171                                 }
 172                                 colorset <<= 1;
 173                             }
 174                         }
 175                     }
 176                 }
 177                 break;
 178             case PNGEncoder.GREYSCALE_MODE:
 179                 {
 180                     byte[] row = new byte[width];
 181                     for (int y = 0; y < height; y++) {
 182                         inflater.inflate(new byte[1]);
 183                         inflater.inflate(row);
 184                         for (int x = 0; x < width; x++) {
 185                             color = row[x];
 186                             result.setRGB(x, y, (color << 16) + (color << 8) + color);
 187                         }
 188                     }
 189                 }
 190                 break;
 191             case PNGEncoder.COLOR_MODE:
 192                 {
 193                     byte[] row = new byte[width * 3];
 194                     for (int y = 0; y < height; y++) {
 195                         inflater.inflate(new byte[1]);
 196                         inflater.inflate(row);
 197                         for (int x = 0; x < width; x++) {
 198                             result.setRGB(x, y,
 199                                           ((row[x * 3 + 0]&0xff) << 16) +
 200                                           ((row[x * 3 + 1]&0xff) << 8) +
 201                                           ((row[x * 3 + 2]&0xff)));
 202                         }
 203                     }
 204                 }
 205             }
 206         } catch(DataFormatException e) {
 207             throw(new JemmyException("ZIP error", e));
 208         }
 209 
 210         readInt();//!!crc
 211         readInt();//0
 212 
 213         byte[] iend = read(4);
 214         checkEquality(iend, "IEND".getBytes());
 215 
 216         readInt();//!!crc
 217         if (closeStream) {
 218             in.close();
 219         }
 220 
 221         return(result);
 222     }
 223 
 224     /**
 225      * Decodes image from file.
 226      * @param fileName a file to read image from
 227      * @return a BufferedImage instance.
 228      */
 229     public static BufferedImage decode(String fileName) {
 230         try {
 231             return(new PNGDecoder(new FileInputStream(fileName)).decode());
 232         } catch(IOException e) {
 233             throw(new JemmyException("IOException during image reading", e));
 234         }
 235     }
 236 
 237     /**
 238      * Decodes image from input stream
 239      * @param inputStream a file to read image from
 240      * @param closeStream requests method to close the stream after the image is read
 241      * @return a BufferedImage instance.
 242      */
 243     public static BufferedImage decode(InputStream inputStream, boolean closeStream) {
 244         try {
 245             return(new PNGDecoder(inputStream).decode(closeStream));
 246         } catch(IOException e) {
 247             throw(new JemmyException("IOException during image reading", e));
 248         }
 249     }
 250 
 251     /**
 252      * Decodes image from resource.
 253      * @param loader ClassLoader to use to get the resource
 254      * @param resource Image resource name
 255      * @return a BufferedImage instance.
 256      */
 257     public static BufferedImage decode(ClassLoader loader, String resource) {
 258         try {
 259             InputStream resourceStream = loader.getResourceAsStream(resource);
 260             if (resourceStream == null) {
 261                 throw new JemmyException("Resouce '" + resource + "' could not be found!");
 262             }
 263             return(new PNGDecoder(resourceStream).decode());
 264         } catch(IOException e) {
 265             throw(new JemmyException("IOException during image reading", e));
 266         }
 267     }
 268 
 269 }