3 import java.awt.image.*;
\r
8 * Class PSDReader - Decodes a PhotoShop (.psd) file into one or more frames.
\r
9 * Supports uncompressed or RLE-compressed RGB files only. Each layer may be
\r
10 * retrieved as a full frame BufferedImage, or as a smaller image with an
\r
11 * offset if the layer does not occupy the full frame size. Transparency
\r
12 * in the original psd file is preserved in the returned BufferedImage's.
\r
13 * Does not support additional features in PS versions higher than 3.0.
\r
16 * PSDReader r = new PSDReader();
\r
17 * r.read("sample.psd");
\r
18 * int n = r.getFrameCount();
\r
19 * for (int i = 0; i < n; i++) {
\r
20 * BufferedImage image = r.getLayer(i);
\r
21 * Point offset = r.getLayerOffset(i);
\r
22 * // do something with image
\r
25 * No copyright asserted on the source code of this class. May be used for
\r
26 * any purpose. Please forward any corrections to kweiner@fmsware.com.
\r
28 * @author Kevin Weiner, FM Software.
\r
29 * @version 1.1 January 2004 [bug fix; add RLE support]
\r
32 public class PSDReader {
\r
35 * File read status: No errors.
\r
37 public static final int STATUS_OK = 0;
\r
40 * File read status: Error decoding file (may be partially decoded)
\r
42 public static final int STATUS_FORMAT_ERROR = 1;
\r
45 * File read status: Unable to open source.
\r
47 public static final int STATUS_OPEN_ERROR = 2;
\r
50 * File read status: Unsupported format
\r
52 public static final int STATUS_UNSUPPORTED = 3;
\r
54 public static int ImageType = BufferedImage.TYPE_INT_ARGB;
\r
56 protected BufferedInputStream input;
\r
57 protected int frameCount;
\r
58 protected BufferedImage[] frames;
\r
59 protected int status = 0;
\r
60 protected int nChan;
\r
61 protected int width;
\r
62 protected int height;
\r
63 protected int nLayers;
\r
64 protected int miscLen;
\r
65 protected boolean hasLayers;
\r
66 protected LayerInfo[] layers;
\r
67 protected short[] lineLengths;
\r
68 protected int lineIndex;
\r
69 protected boolean rleEncoded;
\r
71 protected class LayerInfo {
\r
79 * Gets the number of layers read from file.
\r
80 * @return frame count
\r
82 public int getFrameCount() {
\r
86 protected void setInput(InputStream stream) {
\r
87 // open input stream
\r
89 if (stream == null) {
\r
90 status = STATUS_OPEN_ERROR;
\r
92 if (stream instanceof BufferedInputStream)
\r
93 input = (BufferedInputStream) stream;
\r
95 input = new BufferedInputStream(stream);
\r
99 protected void setInput(String name) {
\r
103 name = name.trim();
\r
104 if (name.startsWith("file:")) {
\r
105 name = name.substring(5);
\r
106 while (name.startsWith("/"))
\r
107 name = name.substring(1);
\r
109 if (name.indexOf("://") > 0) {
\r
110 URL url = new URL(name);
\r
111 input = new BufferedInputStream(url.openStream());
\r
113 input = new BufferedInputStream(new FileInputStream(name));
\r
115 } catch (IOException e) {
\r
116 status = STATUS_OPEN_ERROR;
\r
121 * Gets display duration for specified frame. Always returns 0.
\r
124 public int getDelay(int forFrame) {
\r
129 * Gets the image contents of frame n. Note that this expands the image
\r
130 * to the full frame size (if the layer was smaller) and any subsequent
\r
131 * use of getLayer() will return the full image.
\r
133 * @return BufferedImage representation of frame, or null if n is invalid.
\r
135 public BufferedImage getFrame(int n) {
\r
136 BufferedImage im = null;
\r
137 if ((n >= 0) && (n < nLayers)) {
\r
139 LayerInfo info = layers[n];
\r
140 if ((info.w != width) || (info.h != height)) {
\r
141 BufferedImage temp =
\r
142 new BufferedImage(width, height, ImageType);
\r
143 Graphics2D gc = temp.createGraphics();
\r
144 gc.drawImage(im, info.x, info.y, null);
\r
154 * Gets maximum image size. Individual layers may be smaller.
\r
156 * @return maximum image dimensions
\r
158 public Dimension getFrameSize() {
\r
159 return new Dimension(width, height);
\r
163 * Gets the first (or only) image read.
\r
165 * @return BufferedImage containing first frame, or null if none.
\r
167 public BufferedImage getImage() {
\r
168 return getFrame(0);
\r
172 * Gets the image contents of layer n. May be smaller than full frame
\r
173 * size - use getFrameOffset() to obtain position of subimage within
\r
176 * @return BufferedImage representation of layer, or null if n is invalid.
\r
178 public BufferedImage getLayer(int n) {
\r
179 BufferedImage im = null;
\r
180 if ((n >= 0) && (n < nLayers)) {
\r
187 * Gets the subimage offset of layer n if it is smaller than the
\r
190 * @return Point indicating offset from upper left corner of frame.
\r
192 public Point getLayerOffset(int n) {
\r
194 if ((n >= 0) && (n < nLayers)) {
\r
195 int x = layers[n].x;
\r
196 int y = layers[n].y;
\r
197 p = new Point(x, y);
\r
200 p = new Point(0, 0);
\r
206 * Reads PhotoShop layers from stream.
\r
208 * @param InputStream in PhotoShop format.
\r
209 * @return read status code (0 = no errors)
\r
211 public int read(InputStream stream) {
\r
218 * Reads PhotoShop file from specified source (file or URL string)
\r
220 * @param name String containing source
\r
221 * @return read status code (0 = no errors)
\r
223 public int read(String name) {
\r
230 * Closes input stream and discards contents of all frames.
\r
233 public void reset() {
\r
237 protected void close() {
\r
238 if (input != null) {
\r
241 } catch (Exception e) {}
\r
245 protected boolean err() {
\r
246 return status != STATUS_OK;
\r
249 protected byte[] fillBytes(int size, int value) {
\r
250 // create byte array filled with given value
\r
251 byte[] b = new byte[size];
\r
253 byte v = (byte) value;
\r
254 for (int i = 0; i < size; i++) {
\r
261 protected void init() {
\r
267 status = STATUS_OK;
\r
270 protected void makeDummyLayer() {
\r
271 // creat dummy layer for non-layered image
\r
272 rleEncoded = readShort() == 1;
\r
275 layers = new LayerInfo[1];
\r
276 LayerInfo layer = new LayerInfo();
\r
280 int nc = Math.min(nChan, 4);
\r
282 // get list of rle encoded line lengths for all channels
\r
283 readLineLengths(height * nc);
\r
286 layer.chanID = new int[nc];
\r
287 for (int i = 0; i < nc; i++) {
\r
289 if (i == 3) id = -1;
\r
290 layer.chanID[i] = id;
\r
294 protected void readLineLengths(int nLines) {
\r
295 // read list of rle encoded line lengths
\r
296 lineLengths = new short[nLines];
\r
297 for (int i = 0; i < nLines; i++) {
\r
298 lineLengths[i] = readShort();
\r
303 protected BufferedImage makeImage(int w, int h, byte[] r, byte[] g, byte[] b, byte[] a) {
\r
304 // create image from given plane data
\r
305 BufferedImage im = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
\r
306 int[] data = ((DataBufferInt) im.getRaster().getDataBuffer()).getData();
\r
311 int ac = a[j] & 0xff;
\r
312 int rc = r[j] & 0xff;
\r
313 int gc = g[j] & 0xff;
\r
314 int bc = b[j] & 0xff;
\r
315 data[j] = (((((ac << 8) | rc) << 8) | gc) << 8) | bc;
\r
316 } catch (Exception e) {}
\r
322 protected void process() {
\r
329 if (nLayers == 0) {
\r
336 protected int readByte() {
\r
337 // read single byte from input
\r
340 curByte = input.read();
\r
341 } catch (IOException e) {
\r
342 status = STATUS_FORMAT_ERROR;
\r
347 protected int readBytes(byte[] bytes, int n) {
\r
348 // read multiple bytes from input
\r
349 if (bytes == null) return 0;
\r
352 r = input.read(bytes, 0, n);
\r
353 } catch (IOException e) {
\r
354 status = STATUS_FORMAT_ERROR;
\r
357 status = STATUS_FORMAT_ERROR;
\r
362 protected void readHeader() {
\r
363 // read PSD header info
\r
364 String sig = readString(4);
\r
365 int ver = readShort();
\r
367 nChan = readShort();
\r
368 height = readInt();
\r
370 int depth = readShort();
\r
371 int mode = readShort();
\r
372 int cmLen = readInt();
\r
374 int imResLen = readInt();
\r
375 skipBytes(imResLen);
\r
377 // require 8-bit RGB data
\r
378 if ((!sig.equals("8BPS")) || (ver != 1)) {
\r
379 status = STATUS_FORMAT_ERROR;
\r
380 } else if ((depth != 8) || (mode != 3)) {
\r
381 status = STATUS_UNSUPPORTED;
\r
385 protected int readInt() {
\r
386 // read big-endian 32-bit integer
\r
387 return (((((readByte() << 8) | readByte()) << 8) | readByte()) << 8)
\r
391 protected void readLayerInfo() {
\r
392 // read layer header info
\r
393 miscLen = readInt();
\r
394 if (miscLen == 0) {
\r
395 return; // no layers, only base image
\r
397 int layerInfoLen = readInt();
\r
398 nLayers = readShort();
\r
400 layers = new LayerInfo[nLayers];
\r
402 for (int i = 0; i < nLayers; i++) {
\r
403 LayerInfo info = new LayerInfo();
\r
405 info.y = readInt();
\r
406 info.x = readInt();
\r
407 info.h = readInt() - info.y;
\r
408 info.w = readInt() - info.x;
\r
409 info.nChan = readShort();
\r
410 info.chanID = new int[info.nChan];
\r
411 for (int j = 0; j < info.nChan; j++) {
\r
412 int id = readShort();
\r
413 int size = readInt();
\r
414 info.chanID[j] = id;
\r
416 String s = readString(4);
\r
417 if (!s.equals("8BIM")) {
\r
418 status = STATUS_FORMAT_ERROR;
\r
421 skipBytes(4); // blend mode
\r
422 info.alpha = readByte();
\r
423 int clipping = readByte();
\r
424 int flags = readByte();
\r
425 readByte(); // filler
\r
426 int extraSize = readInt();
\r
427 skipBytes(extraSize);
\r
431 protected void readLayers() {
\r
432 // read and convert each layer to BufferedImage
\r
433 frameCount = nLayers;
\r
434 frames = new BufferedImage[nLayers];
\r
435 for (int i = 0; i < nLayers; i++) {
\r
436 LayerInfo info = layers[i];
\r
437 byte[] r = null, g = null, b = null, a = null;
\r
438 for (int j = 0; j < info.nChan; j++) {
\r
439 int id = info.chanID[j];
\r
441 case 0 : r = readPlane(info.w, info.h); break;
\r
442 case 1 : g = readPlane(info.w, info.h); break;
\r
443 case 2 : b = readPlane(info.w, info.h); break;
\r
444 case -1 : a = readPlane(info.w, info.h); break;
\r
445 default : readPlane(info.w, info.h);
\r
450 int n = info.w * info.h;
\r
451 if (r == null) r = fillBytes(n, 0);
\r
452 if (g == null) g = fillBytes(n, 0);
\r
453 if (b == null) b = fillBytes(n, 0);
\r
454 if (a == null) a = fillBytes(n, 255);
\r
456 BufferedImage im = makeImage(info.w, info.h, r, g, b, a);
\r
459 lineLengths = null;
\r
460 if ((miscLen > 0) && !err()) {
\r
461 int n = readInt(); // global layer mask info len
\r
466 protected byte[] readPlane(int w, int h) {
\r
467 // read a single color plane
\r
471 // get RLE compression info for channel
\r
472 rleEncoded = readShort() == 1;
\r
474 // list of encoded line lengths
\r
475 readLineLengths(h);
\r
480 b = readPlaneCompressed(w, h);
\r
482 b = new byte[size];
\r
483 readBytes(b, size);
\r
490 protected byte[] readPlaneCompressed(int w, int h) {
\r
491 byte[] b = new byte[w * h];
\r
492 byte[] s = new byte[w * 2];
\r
494 for (int i = 0; i < h; i++) {
\r
495 if (lineIndex >= lineLengths.length) {
\r
496 status = STATUS_FORMAT_ERROR;
\r
499 int len = lineLengths[lineIndex++];
\r
501 decodeRLE(s, 0, len, b, pos);
\r
507 protected void decodeRLE(byte[] src, int sindex, int slen, byte[] dst, int dindex) {
\r
509 int max = sindex + slen;
\r
510 while (sindex < max) {
\r
511 byte b = src[sindex++];
\r
514 // dup next byte 1-n times
\r
517 for (int i = 0; i < n; i++) {
\r
521 // copy next n+1 bytes
\r
523 System.arraycopy(src, sindex, dst, dindex, n);
\r
528 } catch (Exception e) {
\r
529 status = STATUS_FORMAT_ERROR;
\r
533 protected short readShort() {
\r
534 // read big-endian 16-bit integer
\r
535 return (short) ((readByte() << 8) | readByte());
\r
538 protected String readString(int len) {
\r
539 // read string of specified length
\r
541 for (int i = 0; i < len; i++) {
\r
542 s = s + (char) readByte();
\r
547 protected void skipBytes(int n) {
\r
548 // skip over n input bytes
\r
549 for (int i = 0; i < n; i++) {
\r