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.pixel;
  24 
  25 import java.io.*;
  26 import java.util.zip.CRC32;
  27 import java.util.zip.Deflater;
  28 import java.util.zip.DeflaterOutputStream;
  29 import org.jemmy.image.pixel.Raster.Component;
  30 
  31 /**
  32  *
  33  * @author shura
  34  */
  35 public class PNGSaver {
  36 
  37     /**
  38      * black and white image mode.
  39      */
  40     public static final byte BW_MODE = 0;
  41     /**
  42      * grey scale image mode.
  43      */
  44     public static final byte GREYSCALE_MODE = 1;
  45     /**
  46      * full color image mode.
  47      */
  48     public static final byte COLOR_MODE = 2;
  49     OutputStream out;
  50     CRC32 crc;
  51     byte mode;
  52 
  53     /**
  54      *
  55      * @param file
  56      * @throws java.io.FileNotFoundException
  57      */
  58     public PNGSaver(File file) throws FileNotFoundException {
  59         this(new FileOutputStream(file));
  60     }
  61 
  62     /**
  63      * public constructor of PNGEncoder class with greyscale mode by default.
  64      *
  65      * @param out output stream for PNG image format to write into
  66      */
  67     public PNGSaver(OutputStream out) {
  68         this(out, COLOR_MODE);
  69     }
  70 
  71     /**
  72      * public constructor of PNGEncoder class.
  73      *
  74      * @param out output stream for PNG image format to write into
  75      * @param mode BW_MODE, GREYSCALE_MODE or COLOR_MODE
  76      */
  77     public PNGSaver(OutputStream out, byte mode) {
  78         crc = new CRC32();
  79         this.out = out;
  80         if (mode < 0 || mode > 2) {
  81             throw new IllegalArgumentException("Unknown color mode");
  82         }
  83         this.mode = mode;
  84     }
  85 
  86     void write(int i) throws IOException {
  87         byte b[] = {(byte) ((i >> 24) & 0xff), (byte) ((i >> 16) & 0xff), (byte) ((i >> 8) & 0xff), (byte) (i & 0xff)};
  88         write(b);
  89     }
  90 
  91     void write(byte b[]) throws IOException {
  92         out.write(b);
  93         crc.update(b);
  94     }
  95 
  96     /**
  97      * main encoding method (stays blocked till encoding is finished).
  98      *
  99      * @param image BufferedImage to encode
 100      * @throws IOException IOException
 101      */
 102     public void encode(Raster image) throws IOException {
 103         encode(image, true);
 104     }
 105 
 106     /**
 107      * main encoding method (stays blocked till encoding is finished).
 108      *
 109      * @param image BufferedImage to encode
 110      * @param closeStream requests method to close the stream after the image is
 111      * written
 112      * @throws IOException IOException
 113      */
 114     private void encode(Raster image, boolean closeStream) throws IOException {
 115         int width = image.getSize().width;
 116         int height = image.getSize().height;
 117         final byte id[] = {-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13};
 118         write(id);
 119         crc.reset();
 120         write("IHDR".getBytes());
 121         write(width);
 122         write(height);
 123         byte head[] = null;
 124         switch (mode) {
 125             case BW_MODE:
 126                 head = new byte[]{1, 0, 0, 0, 0};
 127                 break;
 128             case GREYSCALE_MODE:
 129                 head = new byte[]{8, 0, 0, 0, 0};
 130                 break;
 131             case COLOR_MODE:
 132                 head = new byte[]{8, 2, 0, 0, 0};
 133                 break;
 134         }
 135         write(head);
 136         write((int) crc.getValue());
 137         ByteArrayOutputStream compressed = new ByteArrayOutputStream(65536);
 138         BufferedOutputStream bos = new BufferedOutputStream(new DeflaterOutputStream(compressed, new Deflater(9)));
 139         int pixel;
 140         int color;
 141         int colorset;
 142         switch (mode) {
 143             case BW_MODE:
 144                 int rest = width % 8;
 145                 int bytes = width / 8;
 146                 for (int y = 0; y < height; y++) {
 147                     bos.write(0);
 148                     for (int x = 0; x < bytes; x++) {
 149                         colorset = 0;
 150                         for (int sh = 0; sh < 8; sh++) {
 151                             pixel = getRGB(image, x * 8 + sh, y);
 152                             color = ((pixel >> 16) & 0xff);
 153                             color += ((pixel >> 8) & 0xff);
 154                             color += (pixel & 0xff);
 155                             colorset <<= 1;
 156                             if (color >= 3 * 128) {
 157                                 colorset |= 1;
 158                             }
 159                         }
 160                         bos.write((byte) colorset);
 161                     }
 162                     if (rest > 0) {
 163                         colorset = 0;
 164                         for (int sh = 0; sh < width % 8; sh++) {
 165                             pixel = getRGB(image, bytes * 8 + sh, y);
 166                             color = ((pixel >> 16) & 0xff);
 167                             color += ((pixel >> 8) & 0xff);
 168                             color += (pixel & 0xff);
 169                             colorset <<= 1;
 170                             if (color >= 3 * 128) {
 171                                 colorset |= 1;
 172                             }
 173                         }
 174                         colorset <<= 8 - rest;
 175                         bos.write((byte) colorset);
 176                     }
 177                 }
 178                 break;
 179             case GREYSCALE_MODE:
 180                 for (int y = 0; y < height; y++) {
 181                     bos.write(0);
 182                     for (int x = 0; x < width; x++) {
 183                         pixel = getRGB(image, x, y);
 184                         color = ((pixel >> 16) & 0xff);
 185                         color += ((pixel >> 8) & 0xff);
 186                         color += (pixel & 0xff);
 187                         bos.write((byte) (color / 3));
 188                     }
 189                 }
 190                 break;
 191             case COLOR_MODE:
 192                 for (int y = 0; y < height; y++) {
 193                     bos.write(0);
 194                     for (int x = 0; x < width; x++) {
 195                         pixel = getRGB(image, x, y);
 196                         bos.write((byte) ((pixel >> 16) & 0xff));
 197                         bos.write((byte) ((pixel >> 8) & 0xff));
 198                         bos.write((byte) (pixel & 0xff));
 199                     }
 200                 }
 201                 break;
 202         }
 203         bos.close();
 204         write(compressed.size());
 205         crc.reset();
 206         write("IDAT".getBytes());
 207         write(compressed.toByteArray());
 208         write((int) crc.getValue());
 209         write(0);
 210         crc.reset();
 211         write("IEND".getBytes());
 212         write((int) crc.getValue());
 213         out.flush();
 214         if (closeStream) {
 215             out.close();
 216         }
 217     }
 218     static final Component[] RGB = new Component[]{
 219         Component.RED, Component.GREEN, Component.BLUE};
 220 
 221     private int getRGB(Raster image, int x, int y) {
 222         int res = 0;
 223         double[] colors = new double[image.getSupported().length];
 224         image.getColors(x, y, colors);
 225         for (Component c : RGB) {
 226             res <<= 8;
 227             res |= (int)(colors[PixelImageComparator.arrayIndexOf(image.getSupported(), c)] * 0xFF);
 228         }
 229         return res;
 230     }
 231 }