X-Git-Url: http://git.megacz.com/?p=org.ibex.xt.git;a=blobdiff_plain;f=src%2Forg%2Fibex%2Fxt%2FPSDReader.java;fp=src%2Forg%2Fibex%2Fxt%2FPSDReader.java;h=c661d25313542c4955269a3511adeec30eec1135;hp=0000000000000000000000000000000000000000;hb=e8f5044051de70a1cdad65df9c5ed6bff3cd1a08;hpb=3f3b2560f1b42d5417b8622ae66648ec042b1159 diff --git a/src/org/ibex/xt/PSDReader.java b/src/org/ibex/xt/PSDReader.java new file mode 100644 index 0000000..c661d25 --- /dev/null +++ b/src/org/ibex/xt/PSDReader.java @@ -0,0 +1,553 @@ +package org.ibex.xt; +import java.awt.*; +import java.awt.image.*; +import java.net.*; +import java.io.*; + +/** + * 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: + *
+ *    PSDReader r = new PSDReader();
+ *    r.read("sample.psd");
+ *    int n = r.getFrameCount();
+ *    for (int i = 0; i < n; i++) {
+ *       BufferedImage image = r.getLayer(i);
+ *       Point offset = r.getLayerOffset(i);
+ *       // do something with image
+ *    }
+ * 
+ * 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(); + } + } +}