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. Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package org.jemmy.image;
  26 
  27 import java.awt.AWTException;
  28 import java.awt.Rectangle;
  29 import java.awt.Robot;
  30 import java.awt.Toolkit;
  31 
  32 import java.awt.image.BufferedImage;
  33 
  34 import java.io.BufferedOutputStream;
  35 import java.io.ByteArrayOutputStream;
  36 import java.io.File;
  37 import java.io.FileNotFoundException;
  38 import java.io.IOException;
  39 import java.io.FileOutputStream;
  40 import java.io.OutputStream;
  41 
  42 import java.util.zip.CRC32;
  43 import java.util.zip.Deflater;
  44 import java.util.zip.DeflaterOutputStream;
  45 import org.jemmy.control.ScreenArea;
  46 
  47 /**
  48  * This class allows to encode BufferedImage into B/W, greyscale or true color PNG
  49  * image format with maximum compression.<br>
  50  * It also provides complete functionality for capturing full screen, part of
  51  * screen or single component, encoding and saving captured image info PNG file.
  52  * @author Adam Sotona
  53  * @version 1.0
  54  */
  55 public class PNGEncoder extends Object {
  56 
  57     /** black and white image mode. */
  58     public static final byte BW_MODE = 0;
  59     /** grey scale image mode. */
  60     public static final byte GREYSCALE_MODE = 1;
  61     /** full color image mode. */
  62     public static final byte COLOR_MODE = 2;
  63 
  64     OutputStream out;
  65     CRC32 crc;
  66     byte mode;
  67 
  68     public PNGEncoder(File file) throws FileNotFoundException {
  69         this(new FileOutputStream(file));
  70     }
  71     /** public constructor of PNGEncoder class with greyscale mode by default.
  72      * @param out output stream for PNG image format to write into
  73      */
  74     public PNGEncoder(OutputStream out) {
  75         this(out, GREYSCALE_MODE);
  76     }
  77 
  78     /** public constructor of PNGEncoder class.
  79      * @param out output stream for PNG image format to write into
  80      * @param mode BW_MODE, GREYSCALE_MODE or COLOR_MODE
  81      */
  82     public PNGEncoder(OutputStream out, byte mode) {
  83         crc=new CRC32();
  84         this.out = out;
  85         if (mode<0 || mode>2)
  86             throw new IllegalArgumentException("Unknown color mode");
  87         this.mode = mode;
  88     }
  89 
  90     void write(int i) throws IOException {
  91         byte b[]={(byte)((i>>24)&0xff),(byte)((i>>16)&0xff),(byte)((i>>8)&0xff),(byte)(i&0xff)};
  92         write(b);
  93     }
  94 
  95     void write(byte b[]) throws IOException {
  96         out.write(b);
  97         crc.update(b);
  98     }
  99 
 100     /** main encoding method (stays blocked till encoding is finished).
 101      * @param image BufferedImage to encode
 102      * @throws IOException IOException
 103      */
 104     public void encode(BufferedImage image) throws IOException {
 105         encode(image, true);
 106     }
 107 
 108     /** main encoding method (stays blocked till encoding is finished).
 109      * @param image BufferedImage to encode
 110      * @param closeStream requests method to close the stream after the image is written
 111      * @throws IOException IOException
 112      */
 113     public void encode(BufferedImage image, boolean closeStream) throws IOException {
 114         int width = image.getWidth(null);
 115         int height = image.getHeight(null);
 116         final byte id[] = {-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13};
 117         write(id);
 118         crc.reset();
 119         write("IHDR".getBytes());
 120         write(width);
 121         write(height);
 122         byte head[]=null;
 123         switch (mode) {
 124             case BW_MODE: head=new byte[]{1, 0, 0, 0, 0}; break;
 125             case GREYSCALE_MODE: head=new byte[]{8, 0, 0, 0, 0}; break;
 126             case COLOR_MODE: head=new byte[]{8, 2, 0, 0, 0}; break;
 127         }
 128         write(head);
 129         write((int) crc.getValue());
 130         ByteArrayOutputStream compressed = new ByteArrayOutputStream(65536);
 131         BufferedOutputStream bos = new BufferedOutputStream( new DeflaterOutputStream(compressed, new Deflater(9)));
 132         int pixel;
 133         int color;
 134         int colorset;
 135         switch (mode) {
 136             case BW_MODE:
 137                 int rest=width%8;
 138                 int bytes=width/8;
 139                 for (int y=0;y<height;y++) {
 140                     bos.write(0);
 141                     for (int x=0;x<bytes;x++) {
 142                         colorset=0;
 143                         for (int sh=0; sh<8; sh++) {
 144                             pixel=image.getRGB(x*8+sh,y);
 145                             color=((pixel >> 16) & 0xff);
 146                             color+=((pixel >> 8) & 0xff);
 147                             color+=(pixel & 0xff);
 148                             colorset<<=1;
 149                             if (color>=3*128)
 150                                 colorset|=1;
 151                         }
 152                         bos.write((byte)colorset);
 153                     }
 154                     if (rest>0) {
 155                         colorset=0;
 156                         for (int sh=0; sh<width%8; sh++) {
 157                             pixel=image.getRGB(bytes*8+sh,y);
 158                             color=((pixel >> 16) & 0xff);
 159                             color+=((pixel >> 8) & 0xff);
 160                             color+=(pixel & 0xff);
 161                             colorset<<=1;
 162                             if (color>=3*128)
 163                                 colorset|=1;
 164                         }
 165                         colorset<<=8-rest;
 166                         bos.write((byte)colorset);
 167                     }
 168                 }
 169                 break;
 170             case GREYSCALE_MODE:
 171                 for (int y=0;y<height;y++) {
 172                     bos.write(0);
 173                     for (int x=0;x<width;x++) {
 174                         pixel=image.getRGB(x,y);
 175                         color=((pixel >> 16) & 0xff);
 176                         color+=((pixel >> 8) & 0xff);
 177                         color+=(pixel & 0xff);
 178                         bos.write((byte)(color/3));
 179                     }
 180                 }
 181                 break;
 182              case COLOR_MODE:
 183                 for (int y=0;y<height;y++) {
 184                     bos.write(0);
 185                     for (int x=0;x<width;x++) {
 186                         pixel=image.getRGB(x,y);
 187                         bos.write((byte)((pixel >> 16) & 0xff));
 188                         bos.write((byte)((pixel >> 8) & 0xff));
 189                         bos.write((byte)(pixel & 0xff));
 190                     }
 191                 }
 192                 break;
 193         }
 194         bos.close();
 195         write(compressed.size());
 196         crc.reset();
 197         write("IDAT".getBytes());
 198         write(compressed.toByteArray());
 199         write((int) crc.getValue());
 200         write(0);
 201         crc.reset();
 202         write("IEND".getBytes());
 203         write((int) crc.getValue());
 204         out.flush();
 205         if (closeStream) {
 206             out.close();
 207         }
 208     }
 209 
 210     /** Static method performing screen capture into PNG image format file with given fileName.
 211      * @param rect Rectangle of screen to be captured
 212      * @param fileName file name for screen capture PNG image file */
 213     public static void captureScreen(Rectangle rect, String fileName) {
 214         captureScreen(rect, fileName, GREYSCALE_MODE);
 215     }
 216 
 217     /** Static method performing screen capture into PNG image format file with given fileName.
 218      * @param rect Rectangle of screen to be captured
 219      * @param mode image color mode
 220      * @param fileName file name for screen capture PNG image file */
 221     public static void captureScreen(Rectangle rect, String fileName, byte mode) {
 222         try {
 223             BufferedImage capture=new Robot().createScreenCapture(rect);
 224             BufferedOutputStream file=new BufferedOutputStream(new FileOutputStream(fileName));
 225             PNGEncoder encoder=new PNGEncoder(file, mode);
 226             encoder.encode(capture);
 227         } catch (AWTException awte) {
 228             awte.printStackTrace();
 229         } catch (IOException ioe) {
 230             ioe.printStackTrace();
 231         }
 232     }
 233 
 234      /** Static method performing one component screen capture into PNG image format file with given fileName.
 235       * @param comp Component to be captured
 236       * @param fileName String image target filename */
 237     public static void captureScreen(ScreenArea comp, String fileName) {
 238         captureScreen(comp, fileName, GREYSCALE_MODE);
 239     }
 240 
 241     /** Static method performing one component screen capture into PNG image format file with given fileName.
 242      * @param comp Component to be captured
 243      * @param fileName String image target filename
 244      * @param mode image color mode */
 245     public static void captureScreen(ScreenArea comp, String fileName, byte mode) {
 246         captureScreen(Utils.convert(comp.getScreenBounds()),
 247             fileName, mode);
 248     }
 249 
 250 
 251     /** Static method performing whole screen capture into PNG image format file with given fileName.
 252      * @param fileName String image target filename */
 253     public static void captureScreen(String fileName) {
 254         captureScreen(fileName, GREYSCALE_MODE);
 255     }
 256 
 257     /** Static method performing whole screen capture into PNG image format file with given fileName.
 258      * @param fileName String image target filename
 259      * @param mode image color mode */
 260     public static void captureScreen(String fileName, byte mode) {
 261         captureScreen(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()), fileName, mode);
 262     }
 263 }