Mega Code Archive

 
Categories / Java / 2D Graphics GUI
 

Decodes a PhotoShop ( psd) file into one or more frames

import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Point; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; /**  * Class PSDReader - Decodes a PhotoShop (.psd) file into one or more frames.  * Supports uncompressed or RLE-compressed RGB files only. Each layer may be  * retrieved as a full frame BufferedImage, or as a smaller image with an offset  * if the layer does not occupy the full frame size. Transparency in the  * original psd file is preserved in the returned BufferedImage's. Does not  * support additional features in PS versions higher than 3.0. Example: <br>  *   * <pre>  * PSDReader r = new PSDReader();  * r.read(&quot;sample.psd&quot;);  * int n = r.getFrameCount();  * for (int i = 0; i &lt; n; i++) {  *   BufferedImage image = r.getLayer(i);  *   Point offset = r.getLayerOffset(i);  *   // do something with image  * }  * </pre>  *   * No copyright asserted on the source code of this class. May be used for any  * purpose. Please forward any corrections to kweiner@fmsware.com.  *   * @author Kevin Weiner, FM Software.  * @version 1.1 January 2004 [bug fix; add RLE support]  *   */ public class PSDReader {   /**    * File read status: No errors.    */   public static final int STATUS_OK = 0;   /**    * File read status: Error decoding file (may be partially decoded)    */   public static final int STATUS_FORMAT_ERROR = 1;   /**    * File read status: Unable to open source.    */   public static final int STATUS_OPEN_ERROR = 2;   /**    * File read status: Unsupported format    */   public static final int STATUS_UNSUPPORTED = 3;   public static int ImageType = BufferedImage.TYPE_INT_ARGB;   protected BufferedInputStream input;   protected int frameCount;   protected BufferedImage[] frames;   protected int status = 0;   protected int nChan;   protected int width;   protected int height;   protected int nLayers;   protected int miscLen;   protected boolean hasLayers;   protected LayerInfo[] layers;   protected short[] lineLengths;   protected int lineIndex;   protected boolean rleEncoded;   protected class LayerInfo {     int x, y, w, h;     int nChan;     int[] chanID;     int alpha;   }   /**    * Gets the number of layers read from file.    *     * @return frame count    */   public int getFrameCount() {     return frameCount;   }   protected void setInput(InputStream stream) {     // open input stream     init();     if (stream == null) {       status = STATUS_OPEN_ERROR;     } else {       if (stream instanceof BufferedInputStream)         input = (BufferedInputStream) stream;       else         input = new BufferedInputStream(stream);     }   }   protected void setInput(String name) {     // open input file     init();     try {       name = name.trim();       if (name.startsWith("file:")) {         name = name.substring(5);         while (name.startsWith("/"))           name = name.substring(1);       }       if (name.indexOf("://") > 0) {         URL url = new URL(name);         input = new BufferedInputStream(url.openStream());       } else {         input = new BufferedInputStream(new FileInputStream(name));       }     } catch (IOException e) {       status = STATUS_OPEN_ERROR;     }   }   /**    * Gets display duration for specified frame. Always returns 0.    *     */   public int getDelay(int forFrame) {     return 0;   }   /**    * Gets the image contents of frame n. Note that this expands the image to the    * full frame size (if the layer was smaller) and any subsequent use of    * getLayer() will return the full image.    *     * @return BufferedImage representation of frame, or null if n is invalid.    */   public BufferedImage getFrame(int n) {     BufferedImage im = null;     if ((n >= 0) && (n < nLayers)) {       im = frames[n];       LayerInfo info = layers[n];       if ((info.w != width) || (info.h != height)) {         BufferedImage temp = new BufferedImage(width, height, ImageType);         Graphics2D gc = temp.createGraphics();         gc.drawImage(im, info.x, info.y, null);         gc.dispose();         im = temp;         frames[n] = im;       }     }     return im;   }   /**    * Gets maximum image size. Individual layers may be smaller.    *     * @return maximum image dimensions    */   public Dimension getFrameSize() {     return new Dimension(width, height);   }   /**    * Gets the first (or only) image read.    *     * @return BufferedImage containing first frame, or null if none.    */   public BufferedImage getImage() {     return getFrame(0);   }   /**    * Gets the image contents of layer n. May be smaller than full frame size -    * use getFrameOffset() to obtain position of subimage within main image area.    *     * @return BufferedImage representation of layer, or null if n is invalid.    */   public BufferedImage getLayer(int n) {     BufferedImage im = null;     if ((n >= 0) && (n < nLayers)) {       im = frames[n];     }     return im;   }   /**    * Gets the subimage offset of layer n if it is smaller than the full frame    * size.    *     * @return Point indicating offset from upper left corner of frame.    */   public Point getLayerOffset(int n) {     Point p = null;     if ((n >= 0) && (n < nLayers)) {       int x = layers[n].x;       int y = layers[n].y;       p = new Point(x, y);     }     if (p == null) {       p = new Point(0, 0);     }     return p;   }   /**    * Reads PhotoShop layers from stream.    *     * @param InputStream    *          in PhotoShop format.    * @return read status code (0 = no errors)    */   public int read(InputStream stream) {     setInput(stream);     process();     return status;   }   /**    * Reads PhotoShop file from specified source (file or URL string)    *     * @param name    *          String containing source    * @return read status code (0 = no errors)    */   public int read(String name) {     setInput(name);     process();     return status;   }   /**    * Closes input stream and discards contents of all frames.    *     */   public void reset() {     init();   }   protected void close() {     if (input != null) {       try {         input.close();       } catch (Exception e) {       }       input = null;     }   }   protected boolean err() {     return status != STATUS_OK;   }   protected byte[] fillBytes(int size, int value) {     // create byte array filled with given value     byte[] b = new byte[size];     if (value != 0) {       byte v = (byte) value;       for (int i = 0; i < size; i++) {         b[i] = v;       }     }     return b;   }   protected void init() {     close();     frameCount = 0;     frames = null;     layers = null;     hasLayers = true;     status = STATUS_OK;   }   protected void makeDummyLayer() {     // creat dummy layer for non-layered image     rleEncoded = readShort() == 1;     hasLayers = false;     nLayers = 1;     layers = new LayerInfo[1];     LayerInfo layer = new LayerInfo();     layers[0] = layer;     layer.h = height;     layer.w = width;     int nc = Math.min(nChan, 4);     if (rleEncoded) {       // get list of rle encoded line lengths for all channels       readLineLengths(height * nc);     }     layer.nChan = nc;     layer.chanID = new int[nc];     for (int i = 0; i < nc; i++) {       int id = i;       if (i == 3)         id = -1;       layer.chanID[i] = id;     }   }   protected void readLineLengths(int nLines) {     // read list of rle encoded line lengths     lineLengths = new short[nLines];     for (int i = 0; i < nLines; i++) {       lineLengths[i] = readShort();     }     lineIndex = 0;   }   protected BufferedImage makeImage(int w, int h, byte[] r, byte[] g, byte[] b, byte[] a) {     // create image from given plane data     BufferedImage im = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);     int[] data = ((DataBufferInt) im.getRaster().getDataBuffer()).getData();     int n = w * h;     int j = 0;     while (j < n) {       try {         int ac = a[j] & 0xff;         int rc = r[j] & 0xff;         int gc = g[j] & 0xff;         int bc = b[j] & 0xff;         data[j] = (((((ac << 8) | rc) << 8) | gc) << 8) | bc;       } catch (Exception e) {       }       j++;     }     return im;   }   protected void process() {     // decode PSD file     if (err())       return;     readHeader();     if (err())       return;     readLayerInfo();     if (err())       return;     if (nLayers == 0) {       makeDummyLayer();       if (err())         return;     }     readLayers();   }   protected int readByte() {     // read single byte from input     int curByte = 0;     try {       curByte = input.read();     } catch (IOException e) {       status = STATUS_FORMAT_ERROR;     }     return curByte;   }   protected int readBytes(byte[] bytes, int n) {     // read multiple bytes from input     if (bytes == null)       return 0;     int r = 0;     try {       r = input.read(bytes, 0, n);     } catch (IOException e) {       status = STATUS_FORMAT_ERROR;     }     if (r < n) {       status = STATUS_FORMAT_ERROR;     }     return r;   }   protected void readHeader() {     // read PSD header info     String sig = readString(4);     int ver = readShort();     skipBytes(6);     nChan = readShort();     height = readInt();     width = readInt();     int depth = readShort();     int mode = readShort();     int cmLen = readInt();     skipBytes(cmLen);     int imResLen = readInt();     skipBytes(imResLen);     // require 8-bit RGB data     if ((!sig.equals("8BPS")) || (ver != 1)) {       status = STATUS_FORMAT_ERROR;     } else if ((depth != 8) || (mode != 3)) {       status = STATUS_UNSUPPORTED;     }   }   protected int readInt() {     // read big-endian 32-bit integer     return (((((readByte() << 8) | readByte()) << 8) | readByte()) << 8) | readByte();   }   protected void readLayerInfo() {     // read layer header info     miscLen = readInt();     if (miscLen == 0) {       return; // no layers, only base image     }     int layerInfoLen = readInt();     nLayers = readShort();     if (nLayers > 0) {       layers = new LayerInfo[nLayers];     }     for (int i = 0; i < nLayers; i++) {       LayerInfo info = new LayerInfo();       layers[i] = info;       info.y = readInt();       info.x = readInt();       info.h = readInt() - info.y;       info.w = readInt() - info.x;       info.nChan = readShort();       info.chanID = new int[info.nChan];       for (int j = 0; j < info.nChan; j++) {         int id = readShort();         int size = readInt();         info.chanID[j] = id;       }       String s = readString(4);       if (!s.equals("8BIM")) {         status = STATUS_FORMAT_ERROR;         return;       }       skipBytes(4); // blend mode       info.alpha = readByte();       int clipping = readByte();       int flags = readByte();       readByte(); // filler       int extraSize = readInt();       skipBytes(extraSize);     }   }   protected void readLayers() {     // read and convert each layer to BufferedImage     frameCount = nLayers;     frames = new BufferedImage[nLayers];     for (int i = 0; i < nLayers; i++) {       LayerInfo info = layers[i];       byte[] r = null, g = null, b = null, a = null;       for (int j = 0; j < info.nChan; j++) {         int id = info.chanID[j];         switch (id) {         case 0:           r = readPlane(info.w, info.h);           break;         case 1:           g = readPlane(info.w, info.h);           break;         case 2:           b = readPlane(info.w, info.h);           break;         case -1:           a = readPlane(info.w, info.h);           break;         default:           readPlane(info.w, info.h);         }         if (err())           break;       }       if (err())         break;       int n = info.w * info.h;       if (r == null)         r = fillBytes(n, 0);       if (g == null)         g = fillBytes(n, 0);       if (b == null)         b = fillBytes(n, 0);       if (a == null)         a = fillBytes(n, 255);       BufferedImage im = makeImage(info.w, info.h, r, g, b, a);       frames[i] = im;     }     lineLengths = null;     if ((miscLen > 0) && !err()) {       int n = readInt(); // global layer mask info len       skipBytes(n);     }   }   protected byte[] readPlane(int w, int h) {     // read a single color plane     byte[] b = null;     int size = w * h;     if (hasLayers) {       // get RLE compression info for channel       rleEncoded = readShort() == 1;       if (rleEncoded) {         // list of encoded line lengths         readLineLengths(h);       }     }     if (rleEncoded) {       b = readPlaneCompressed(w, h);     } else {       b = new byte[size];       readBytes(b, size);     }     return b;   }   protected byte[] readPlaneCompressed(int w, int h) {     byte[] b = new byte[w * h];     byte[] s = new byte[w * 2];     int pos = 0;     for (int i = 0; i < h; i++) {       if (lineIndex >= lineLengths.length) {         status = STATUS_FORMAT_ERROR;         return null;       }       int len = lineLengths[lineIndex++];       readBytes(s, len);       decodeRLE(s, 0, len, b, pos);       pos += w;     }     return b;   }   protected void decodeRLE(byte[] src, int sindex, int slen, byte[] dst, int dindex) {     try {       int max = sindex + slen;       while (sindex < max) {         byte b = src[sindex++];         int n = (int) b;         if (n < 0) {           // dup next byte 1-n times           n = 1 - n;           b = src[sindex++];           for (int i = 0; i < n; i++) {             dst[dindex++] = b;           }         } else {           // copy next n+1 bytes           n = n + 1;           System.arraycopy(src, sindex, dst, dindex, n);           dindex += n;           sindex += n;         }       }     } catch (Exception e) {       status = STATUS_FORMAT_ERROR;     }   }   protected short readShort() {     // read big-endian 16-bit integer     return (short) ((readByte() << 8) | readByte());   }   protected String readString(int len) {     // read string of specified length     String s = "";     for (int i = 0; i < len; i++) {       s = s + (char) readByte();     }     return s;   }   protected void skipBytes(int n) {     // skip over n input bytes     for (int i = 0; i < n; i++) {       readByte();     }   } }