tons of changes
[org.ibex.xt.git] / src / org / ibex / xt / PSDReader.java
1 package org.ibex.xt;\r
2 import java.awt.*;\r
3 import java.awt.image.*;\r
4 import java.net.*;\r
5 import java.io.*;\r
6 \r
7 /**\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
14  * Example:\r
15  * <br><pre>\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
23  *    }\r
24  * </pre>\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
27  *\r
28  * @author Kevin Weiner, FM Software.\r
29  * @version 1.1 January 2004  [bug fix; add RLE support]\r
30  *\r
31  */\r
32 public class PSDReader {\r
33 \r
34     /**\r
35      * File read status: No errors.\r
36      */\r
37        public static final int STATUS_OK = 0;\r
38 \r
39     /**\r
40      * File read status: Error decoding file (may be partially decoded)\r
41      */\r
42     public static final int STATUS_FORMAT_ERROR = 1;\r
43 \r
44     /**\r
45      * File read status: Unable to open source.\r
46      */\r
47     public static final int STATUS_OPEN_ERROR = 2;\r
48 \r
49     /**\r
50      * File read status: Unsupported format\r
51      */\r
52     public static final int STATUS_UNSUPPORTED = 3;\r
53 \r
54     public static int ImageType = BufferedImage.TYPE_INT_ARGB;\r
55 \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
70     \r
71     protected class LayerInfo {\r
72         int x, y, w, h;\r
73         int nChan;\r
74         int[] chanID;\r
75         int alpha;\r
76     }\r
77 \r
78     /**\r
79      * Gets the number of layers read from file.\r
80      * @return frame count\r
81      */\r
82     public int getFrameCount() {\r
83         return frameCount;\r
84     }\r
85     \r
86     protected void setInput(InputStream stream) {\r
87         // open input stream\r
88         init();\r
89         if (stream == null) {\r
90             status = STATUS_OPEN_ERROR;\r
91         } else {\r
92             if (stream instanceof BufferedInputStream)\r
93                 input = (BufferedInputStream) stream;\r
94             else\r
95                 input = new BufferedInputStream(stream);\r
96         }\r
97     }\r
98     \r
99     protected void setInput(String name) {\r
100         // open input file\r
101         init();\r
102         try {\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
108             }\r
109             if (name.indexOf("://") > 0) {\r
110                 URL url = new URL(name);\r
111                 input = new BufferedInputStream(url.openStream());\r
112             } else {\r
113                 input = new BufferedInputStream(new FileInputStream(name));\r
114             }\r
115         } catch (IOException e) {\r
116             status = STATUS_OPEN_ERROR;\r
117         }\r
118     }\r
119     \r
120     /**\r
121      * Gets display duration for specified frame.  Always returns 0.\r
122      *\r
123      */\r
124     public int getDelay(int forFrame) {\r
125         return 0;\r
126     }\r
127     \r
128     /**\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
132      *\r
133      * @return BufferedImage representation of frame, or null if n is invalid.\r
134      */\r
135     public BufferedImage getFrame(int n) {\r
136         BufferedImage im = null;\r
137         if ((n >= 0) && (n < nLayers)) {\r
138             im = frames[n];\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
145                 gc.dispose();\r
146                 im = temp;\r
147                 frames[n] = im;\r
148             }\r
149         }\r
150         return im;\r
151     }\r
152     \r
153     /**\r
154      * Gets maximum image size.  Individual layers may be smaller.\r
155      *\r
156      * @return maximum image dimensions\r
157      */\r
158     public Dimension getFrameSize() {\r
159         return new Dimension(width, height);\r
160     }\r
161     \r
162     /**\r
163      * Gets the first (or only) image read.\r
164      *\r
165      * @return BufferedImage containing first frame, or null if none.\r
166      */\r
167     public BufferedImage getImage() {\r
168         return getFrame(0);\r
169     }\r
170     \r
171     /**\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
174      * main image area.\r
175      *\r
176      * @return BufferedImage representation of layer, or null if n is invalid.\r
177      */\r
178     public BufferedImage getLayer(int n) {\r
179         BufferedImage im = null;\r
180         if ((n >= 0) && (n < nLayers)) {\r
181             im = frames[n];\r
182         }\r
183         return im;\r
184     }\r
185     \r
186     /**\r
187      * Gets the subimage offset of layer n if it is smaller than the\r
188      * full frame size.\r
189      *\r
190      * @return Point indicating offset from upper left corner of frame.\r
191      */\r
192     public Point getLayerOffset(int n) {\r
193         Point p = null;\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
198         }\r
199         if (p == null) {\r
200             p = new Point(0, 0);\r
201         }\r
202         return p;\r
203     }\r
204     \r
205     /**\r
206      * Reads PhotoShop layers from stream.\r
207      *\r
208      * @param InputStream in PhotoShop format.\r
209      * @return read status code (0 = no errors)\r
210      */\r
211     public int read(InputStream stream) {\r
212         setInput(stream);\r
213         process();\r
214         return status;\r
215     }\r
216     \r
217     /**\r
218      * Reads PhotoShop file from specified source (file or URL string)\r
219      *\r
220      * @param name String containing source\r
221      * @return read status code (0 = no errors)\r
222      */\r
223     public int read(String name) {\r
224         setInput(name);\r
225         process();\r
226         return status;\r
227     }\r
228     \r
229     /**\r
230      * Closes input stream and discards contents of all frames.\r
231      *\r
232      */\r
233     public void reset() {\r
234         init();\r
235     }\r
236     \r
237     protected void close() {\r
238         if (input != null) {\r
239             try {\r
240                 input.close();\r
241             } catch (Exception e) {}\r
242             input = null;\r
243         }\r
244     }\r
245     protected boolean err() {\r
246         return status != STATUS_OK;\r
247     }\r
248     \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
252         if (value != 0) {\r
253             byte v = (byte) value;\r
254             for (int i = 0; i < size; i++) {\r
255                 b[i] = v;\r
256             }\r
257         }\r
258         return b;\r
259     }\r
260     \r
261     protected void init() {\r
262         close();\r
263         frameCount = 0;\r
264         frames = null;\r
265         layers = null;\r
266         hasLayers = true;\r
267         status = STATUS_OK;\r
268     }\r
269     \r
270     protected void makeDummyLayer() {\r
271         // creat dummy layer for non-layered image\r
272         rleEncoded = readShort() == 1;\r
273         hasLayers = false;\r
274         nLayers = 1;\r
275         layers = new LayerInfo[1];\r
276         LayerInfo layer = new LayerInfo();\r
277         layers[0] = layer;\r
278         layer.h = height;\r
279         layer.w = width;\r
280         int nc = Math.min(nChan, 4);\r
281         if (rleEncoded) {\r
282             // get list of rle encoded line lengths for all channels\r
283             readLineLengths(height * nc);\r
284         }\r
285         layer.nChan = nc;\r
286         layer.chanID = new int[nc];\r
287         for (int i = 0; i < nc; i++) {\r
288             int id = i;\r
289             if (i == 3) id = -1;\r
290             layer.chanID[i] = id;\r
291         }\r
292     }\r
293 \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
299         }\r
300         lineIndex = 0;\r
301     }\r
302 \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
307         int n = w * h;\r
308         int j = 0;\r
309         while (j < n) {\r
310             try {\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
317             j++;\r
318         }\r
319         return im;\r
320     }\r
321     \r
322     protected void process() {\r
323         // decode PSD file\r
324         if (err()) return;\r
325         readHeader();\r
326         if (err()) return;\r
327         readLayerInfo();\r
328         if (err()) return;\r
329         if (nLayers == 0) {\r
330             makeDummyLayer();\r
331             if (err()) return;\r
332         }\r
333         readLayers();\r
334     }\r
335     \r
336     protected int readByte() {\r
337         // read single byte from input\r
338         int curByte = 0;\r
339         try {\r
340             curByte = input.read();\r
341         } catch (IOException e) {\r
342             status = STATUS_FORMAT_ERROR;\r
343         }\r
344         return curByte;\r
345     }\r
346 \r
347     protected int readBytes(byte[] bytes, int n) {\r
348         // read multiple bytes from input\r
349         if (bytes == null) return 0;\r
350         int r = 0;\r
351         try {\r
352             r = input.read(bytes, 0, n);\r
353         } catch (IOException e) {\r
354             status = STATUS_FORMAT_ERROR;\r
355         }\r
356         if (r < n) {\r
357             status = STATUS_FORMAT_ERROR;\r
358         }\r
359         return r;\r
360     }\r
361     \r
362     protected void readHeader() {\r
363         // read PSD header info\r
364         String sig = readString(4);\r
365         int ver = readShort();\r
366         skipBytes(6);\r
367         nChan = readShort();\r
368         height = readInt();\r
369         width = readInt();\r
370         int depth = readShort();\r
371         int mode = readShort();\r
372         int cmLen = readInt();\r
373         skipBytes(cmLen);\r
374         int imResLen = readInt();\r
375         skipBytes(imResLen);\r
376 \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
382         }\r
383     }\r
384     \r
385     protected int readInt() {\r
386         // read big-endian 32-bit integer\r
387         return (((((readByte() << 8) | readByte()) << 8) | readByte()) << 8)\r
388             | readByte();\r
389     }\r
390     \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
396         }\r
397         int layerInfoLen = readInt();\r
398         nLayers = readShort();\r
399         if (nLayers > 0) {\r
400             layers = new LayerInfo[nLayers];\r
401         }\r
402         for (int i = 0; i < nLayers; i++) {\r
403             LayerInfo info = new LayerInfo();\r
404             layers[i] = info;\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
415             }\r
416             String s = readString(4);\r
417             if (!s.equals("8BIM")) {\r
418                 status = STATUS_FORMAT_ERROR;\r
419                 return;\r
420             }\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
428         }\r
429     }\r
430     \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
440                 switch (id) {\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
446                 }\r
447                 if (err()) break;\r
448             }\r
449             if (err()) break;\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
455 \r
456             BufferedImage im = makeImage(info.w, info.h, r, g, b, a);\r
457             frames[i] = im;\r
458         }\r
459         lineLengths = null;\r
460         if ((miscLen > 0) && !err()) {\r
461             int n = readInt(); // global layer mask info len\r
462             skipBytes(n);\r
463         }\r
464     }\r
465     \r
466     protected byte[] readPlane(int w, int h) {\r
467         // read a single color plane\r
468         byte[] b = null;\r
469         int size = w * h;\r
470         if (hasLayers) {\r
471             // get RLE compression info for channel\r
472             rleEncoded = readShort() == 1;\r
473             if (rleEncoded) {\r
474                 // list of encoded line lengths\r
475                 readLineLengths(h);\r
476             }\r
477         }\r
478 \r
479         if (rleEncoded) {\r
480             b = readPlaneCompressed(w, h);\r
481         } else {\r
482             b = new byte[size];\r
483             readBytes(b, size);\r
484         }\r
485 \r
486         return b;\r
487 \r
488     }\r
489 \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
493         int pos = 0;\r
494         for (int i = 0; i < h; i++) {\r
495             if (lineIndex >= lineLengths.length) {\r
496                 status = STATUS_FORMAT_ERROR;\r
497                 return null;\r
498             }\r
499             int len = lineLengths[lineIndex++];\r
500             readBytes(s, len);\r
501             decodeRLE(s, 0, len, b, pos);\r
502             pos += w;\r
503         }\r
504         return b;\r
505     }\r
506 \r
507     protected void decodeRLE(byte[] src, int sindex, int slen, byte[] dst, int dindex) {\r
508         try {\r
509             int max = sindex + slen;\r
510             while (sindex < max) {\r
511                 byte b = src[sindex++];\r
512                 int n = (int) b;\r
513                 if (n < 0) {\r
514                     // dup next byte 1-n times\r
515                     n = 1 - n;\r
516                     b = src[sindex++];\r
517                     for (int i = 0; i < n; i++) {\r
518                         dst[dindex++] = b;\r
519                     }\r
520                 } else {\r
521                     // copy next n+1 bytes\r
522                     n = n + 1;\r
523                     System.arraycopy(src, sindex, dst, dindex, n);\r
524                     dindex += n;\r
525                     sindex += n;\r
526                 }\r
527             }\r
528         } catch (Exception e) {\r
529             status = STATUS_FORMAT_ERROR;\r
530         }\r
531     }\r
532     \r
533     protected short readShort() {\r
534         // read big-endian 16-bit integer\r
535         return (short) ((readByte() << 8) | readByte());\r
536     }\r
537     \r
538     protected String readString(int len) {\r
539         // read string of specified length\r
540         String s = "";\r
541         for (int i = 0; i < len; i++) {\r
542             s = s + (char) readByte();\r
543         }\r
544         return s;\r
545     }\r
546     \r
547     protected void skipBytes(int n) {\r
548         // skip over n input bytes\r
549         for (int i = 0; i < n; i++) {\r
550             readByte();\r
551         }\r
552     }\r
553 }\r