File: PSDCodec.java

package info (click to toggle)
java-imaging-utilities 0.14.2%2B3-2
  • links: PTS
  • area: main
  • in suites: squeeze
  • size: 2,280 kB
  • ctags: 3,725
  • sloc: java: 31,190; sh: 238; makefile: 53; xml: 30
file content (368 lines) | stat: -rw-r--r-- 10,646 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
/*
 * PSDCodec
 *
 * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 Marco Schmidt.
 * All rights reserved.
 */

package net.sourceforge.jiu.codecs;

import java.io.DataInput;
import java.io.IOException;
import net.sourceforge.jiu.codecs.ImageCodec;
import net.sourceforge.jiu.codecs.InvalidFileStructureException;
import net.sourceforge.jiu.codecs.UnsupportedTypeException;
import net.sourceforge.jiu.codecs.WrongFileFormatException;
import net.sourceforge.jiu.data.MemoryGray8Image;
import net.sourceforge.jiu.data.MemoryPaletted8Image;
import net.sourceforge.jiu.data.MemoryRGB24Image;
import net.sourceforge.jiu.data.Gray8Image;
import net.sourceforge.jiu.data.Palette;
import net.sourceforge.jiu.data.Paletted8Image;
import net.sourceforge.jiu.data.RGB24Image;
import net.sourceforge.jiu.ops.MissingParameterException;
import net.sourceforge.jiu.ops.OperationFailedException;

/**
 * A codec to read images from Photoshop PSD files.
 * PSD was created by Adobe for their
 * <a href="http://www.adobe.com/store/products/photoshop.html">Photoshop</a>
 * image editing software.
 * Note that only a small subset of valid PSD files is supported by this codec.
 * Typical file extension is <code>.psd</code>.
 * @author Marco Schmidt
 */
public class PSDCodec extends ImageCodec
{
	private final static int MAGIC_8BPS = 0x38425053;
	private final static int COLOR_MODE_GRAYSCALE = 1;
	private final static int COLOR_MODE_INDEXED = 2;
	private final static int COLOR_MODE_RGB_TRUECOLOR = 3;
	private final static short COMPRESSION_NONE = 0;
	private final static short COMPRESSION_PACKBITS = 1;
	private int magic;
	private int channels;
	private int height;
	private int width;
	private int depth;
	private int colorMode;
	private short compression;
	private DataInput in;
	private Gray8Image gray8Image;
	private Palette palette;
	private Paletted8Image paletted8Image;
	private RGB24Image rgb24Image;

	private void allocate()
	{
		gray8Image = null;
		paletted8Image = null;
		rgb24Image = null;
		if (depth == 8 && colorMode == COLOR_MODE_RGB_TRUECOLOR)
		{
			rgb24Image = new MemoryRGB24Image(getBoundsWidth(), getBoundsHeight());
			setImage(rgb24Image);
		}
		else
		if (channels == 1 && depth == 8 && colorMode == 2)
		{
			paletted8Image = new MemoryPaletted8Image(width, height, palette);
			setImage(paletted8Image);
		}
		else
		if (channels == 1 && depth == 8 && colorMode == COLOR_MODE_GRAYSCALE)
		{
			gray8Image = new MemoryGray8Image(width, height);
			setImage(gray8Image);
		}
		else
		{
			throw new IllegalArgumentException("Unknown image type in PSD file.");
		}
	}

	private static String getColorTypeName(int colorMode)
	{
		switch(colorMode)
		{
			case(0): return "Black & white";
			case(1): return "Grayscale";
			case(2): return "Indexed";
			case(3): return "RGB truecolor";
			case(4): return "CMYK truecolor";
			case(7): return "Multichannel";
			case(8): return "Duotone";
			case(9): return "Lab";
			default: return "Unknown (" + colorMode + ")";
		}
	}

	public String getFormatName()
	{
		return "Photoshop (PSD)";
	}

	public String[] getMimeTypes()
	{
		return new String[] {"image/psd", "image/x-psd"};
	}

	public boolean isLoadingSupported()
	{
		return true;
	}

	public boolean isSavingSupported()
	{
		return false;
	}

	/**
	 * Attempts to load an Image from argument stream <code>in</code> (which
	 * could, as an example, be a <code>RandomAccessFile</code> instance, it 
	 * implements the <code>DataInput</code> interface).
	 * Checks a magic byte sequence and then reads all chunks as they appear
	 * in the IFF file.
	 * Will return the resulting image or null if no image body chunk was
	 * encountered before end-of-stream.
	 * Will throw an exception if the file is corrupt, information is missing
	 * or there were reading errors.
	 */
	private void load() throws
		InvalidFileStructureException,
		IOException, 
		UnsupportedTypeException,
		WrongFileFormatException
	{
		loadHeader();
		//System.out.println(width + " x " + height + ", color=" + colorMode + ", channels=" + channels + ", depth=" + depth);
		// check values
		if (width < 1 || height < 1)
		{
			throw new InvalidFileStructureException("Cannot load image. " +
				"Invalid pixel resolution in PSD file header (" + width +
				" x " + height + ").");
		}
		if (colorMode != COLOR_MODE_RGB_TRUECOLOR &&
		    colorMode != COLOR_MODE_GRAYSCALE &&
		    colorMode != COLOR_MODE_INDEXED)
		{
			throw new UnsupportedTypeException("Cannot load image. Only RGB" +
				" truecolor and indexed color are supported for PSD files. " +
				"Found: " +getColorTypeName(colorMode));
		}
		if (depth != 8)
		{
			throw new UnsupportedTypeException("Cannot load image. Only a depth of 8 bits " +
				"per channel is supported (found " + depth + 
				" bits).");
		}

		// COLOR MODE DATA
		int colorModeSize = in.readInt();
		//System.out.println("colorModeSize=" + colorModeSize);
		byte[] colorMap = null;
		if (colorMode == COLOR_MODE_INDEXED)
		{
			if (colorModeSize != 768)
			{
				throw new InvalidFileStructureException("Cannot load image." +
					" Color map length was expected to be 768 (found " + 
					colorModeSize + ").");
			}
			colorMap = new byte[colorModeSize];
			in.readFully(colorMap);
			palette = new Palette(256, 255);
			for (int index = 0; index < 256; index++)
			{
				palette.putSample(Palette.INDEX_RED, index, colorMap[index] & 0xff);
				palette.putSample(Palette.INDEX_GREEN, index, colorMap[256 + index] & 0xff);
				palette.putSample(Palette.INDEX_BLUE, index, colorMap[512 + index] & 0xff);
			}
		}
		else
		{
			in.skipBytes(colorModeSize);
		}
		// IMAGE RESOURCES
		int resourceLength = in.readInt();
		in.skipBytes(resourceLength);
		//System.out.println("resourceLength=" + resourceLength);
		// LAYER AND MASK INFORMATION
		int miscLength = in.readInt();
		in.skipBytes(miscLength);
		//System.out.println("miscLength=" + miscLength);
		// IMAGE DATA
		compression = in.readShort();
		if (compression != COMPRESSION_NONE && compression != COMPRESSION_PACKBITS)
		{
			throw new UnsupportedTypeException("Cannot load image. Unsupported PSD " +
				"compression type (" + compression + ")");
		}
		//System.out.println("compression=" + compression);
		loadImageData();
	}

	/**
	 * Reads the PSD header to private members of this class instance.
	 * @throws IOException if there were reading errors
	 */
	private void loadHeader() throws
		IOException,
		WrongFileFormatException
	{
		magic = in.readInt();
		if (magic != MAGIC_8BPS)
		{
			throw new WrongFileFormatException("Not a valid PSD file " +
				"(wrong magic byte sequence).");
		}
		in.readShort(); // skip version short value
		in.skipBytes(6);
		channels = in.readShort();
		height = in.readInt();
		width = in.readInt();
		depth = in.readShort();
		colorMode = in.readShort();
	}

	private void loadPackbitsCompressedData(byte[] data, int offset, int num) throws
		InvalidFileStructureException,
		IOException
	{
		int x = offset;
		int max = offset + num;
		while (x < max)
		{
			byte n = in.readByte();
			boolean compressed = false;
			int count = -1;
			try
			{
				if (n >= 0)
				{
					// copy next n + 1 bytes literally
					in.readFully(data, x, n + 1);
					x += (n + 1);
				}
				else
				{
					// if n == -128, nothing happens (stupid design decision)
					if (n != -128)
					{
						compressed = true;
						// otherwise, compute counter
						count = -((int)n) + 1;
						// read another byte
						byte value = in.readByte();
						// write this byte counter times to output
						while (count-- > 0)
						{
							data[x++] = value;
						}
					}
				}
			}
			catch (ArrayIndexOutOfBoundsException ioobe)
			{
				/* if the encoder did anything wrong, the above code
				   could potentially write beyond array boundaries
				   (e.g. if runs of data exceed line boundaries);
				   this would result in an IndexOutOfBoundsException
				   thrown by the virtual machine;
				   to give a more understandable error message to the 
				   user, this exception is caught here and a
				   corresponding IOException is thrown */
				throw new InvalidFileStructureException("Error: RLE-compressed image " +
					"file seems to be corrupt (x=" + x +
					", count=" + (compressed ? (-((int)n) + 1) : n) + 
					", compressed=" + (compressed ? "y" : "n") + ", array length=" + data.length + ").");
			}
		}
	}

	private void loadImageData() throws 
		InvalidFileStructureException, 
		IOException
	{
		setBoundsIfNecessary(width, height);
		allocate();
		if (compression == COMPRESSION_PACKBITS)
		{
			// skip counters
			in.skipBytes(2 * channels * height);
		}
		byte[] data = new byte[width];
		int totalScanLines = channels * height;
		int currentScanLine = 0;
		for (int c = 0; c < channels; c++)
		{
			for (int y = 0, destY = - getBoundsY1(); y < height; y++, destY++)
			{
				if (compression == COMPRESSION_PACKBITS)
				{
					loadPackbitsCompressedData(data, 0, width);
				}
				else
				{
					if (compression == COMPRESSION_PACKBITS)
					{
						in.readFully(data, 0, width);
					}
				}
				setProgress(currentScanLine++, totalScanLines);
				if (!isRowRequired(y))
				{
					continue;
				}
				if (rgb24Image != null)
				{
					int channelIndex = RGB24Image.INDEX_RED;
					if (c == 1)
					{
						channelIndex = RGB24Image.INDEX_GREEN;
					}
					if (c == 2)
					{
						channelIndex = RGB24Image.INDEX_BLUE;
					}
					rgb24Image.putByteSamples(channelIndex, 0, destY, getBoundsWidth(), 1, data, getBoundsX1());
				}
				if (gray8Image != null)
				{
					gray8Image.putByteSamples(0, 0, destY, getBoundsWidth(), 1, data, getBoundsX1());
				}
				if (paletted8Image != null)
				{
					paletted8Image.putByteSamples(0, 0, destY, getBoundsWidth(), 1, data, getBoundsX1());
				}
			}
		}
	}

	public void process() throws
		OperationFailedException
	{
		initModeFromIOObjects();
		try
		{
			if (getMode() == CodecMode.LOAD)
			{
				in = getInputAsDataInput();
				if (in == null)
				{
					throw new MissingParameterException("Input stream / file missing.");
				}
				load();
			}
			else
			{
				throw new OperationFailedException("Only loading is supported in PSD codec.");
			}
		}
		catch (IOException ioe)
		{
			throw new OperationFailedException("I/O error: " + ioe.toString());
		}
	}
}