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