001 /* 002 * PSDCodec 003 * 004 * Copyright (c) 2000, 2001, 2002, 2003 Marco Schmidt. 005 * All rights reserved. 006 */ 007 008 package net.sourceforge.jiu.codecs; 009 010 import java.io.DataInput; 011 import java.io.IOException; 012 import net.sourceforge.jiu.codecs.ImageCodec; 013 import net.sourceforge.jiu.codecs.InvalidFileStructureException; 014 import net.sourceforge.jiu.codecs.UnsupportedTypeException; 015 import net.sourceforge.jiu.codecs.WrongFileFormatException; 016 import net.sourceforge.jiu.data.MemoryGray8Image; 017 import net.sourceforge.jiu.data.MemoryPaletted8Image; 018 import net.sourceforge.jiu.data.MemoryRGB24Image; 019 import net.sourceforge.jiu.data.Gray8Image; 020 import net.sourceforge.jiu.data.Palette; 021 import net.sourceforge.jiu.data.Paletted8Image; 022 import net.sourceforge.jiu.data.RGB24Image; 023 import net.sourceforge.jiu.ops.MissingParameterException; 024 import net.sourceforge.jiu.ops.OperationFailedException; 025 026 /** 027 * A codec to read images from Photoshop PSD files. 028 * PSD was created by Adobe for their 029 * <a href="http://www.adobe.com/store/products/photoshop.html">Photoshop</a> 030 * image editing software. 031 * Note that only a small subset of valid PSD files is supported by this codec. 032 * Typical file extension is <code>.psd</code>. 033 * @author Marco Schmidt 034 */ 035 public class PSDCodec extends ImageCodec 036 { 037 private final static int MAGIC_8BIM = 0x3842494d; 038 private final static int MAGIC_8BPS = 0x38425053; 039 private final static int COLOR_MODE_GRAYSCALE = 1; 040 private final static int COLOR_MODE_INDEXED = 2; 041 private final static int COLOR_MODE_RGB_TRUECOLOR = 3; 042 private final static short COMPRESSION_NONE = 0; 043 private final static short COMPRESSION_PACKBITS = 1; 044 private int magic; 045 private int version; 046 private int channels; 047 private int height; 048 private int width; 049 private int depth; 050 private int colorMode; 051 private short compression; 052 private DataInput in; 053 private Gray8Image gray8Image; 054 private Palette palette; 055 private Paletted8Image paletted8Image; 056 private RGB24Image rgb24Image; 057 058 private void allocate() 059 { 060 gray8Image = null; 061 paletted8Image = null; 062 rgb24Image = null; 063 if (depth == 8 && colorMode == COLOR_MODE_RGB_TRUECOLOR) 064 { 065 rgb24Image = new MemoryRGB24Image(getBoundsWidth(), getBoundsHeight()); 066 setImage(rgb24Image); 067 } 068 else 069 if (channels == 1 && depth == 8 && colorMode == 2) 070 { 071 paletted8Image = new MemoryPaletted8Image(width, height, palette); 072 setImage(paletted8Image); 073 } 074 else 075 if (channels == 1 && depth == 8 && colorMode == COLOR_MODE_GRAYSCALE) 076 { 077 gray8Image = new MemoryGray8Image(width, height); 078 setImage(gray8Image); 079 } 080 else 081 { 082 throw new IllegalArgumentException("Unknown image type in PSD file."); 083 } 084 } 085 086 private static String getColorTypeName(int colorMode) 087 { 088 switch(colorMode) 089 { 090 case(0): return "Black & white"; 091 case(1): return "Grayscale"; 092 case(2): return "Indexed"; 093 case(3): return "RGB truecolor"; 094 case(4): return "CMYK truecolor"; 095 case(7): return "Multichannel"; 096 case(8): return "Duotone"; 097 case(9): return "Lab"; 098 default: return "Unknown (" + colorMode + ")"; 099 } 100 } 101 102 public String getFormatName() 103 { 104 return "Photoshop (PSD)"; 105 } 106 107 public String[] getMimeTypes() 108 { 109 return new String[] {"image/psd", "image/x-psd"}; 110 } 111 112 public boolean isLoadingSupported() 113 { 114 return true; 115 } 116 117 public boolean isSavingSupported() 118 { 119 return false; 120 } 121 122 /** 123 * Attempts to load an Image from argument stream <code>in</code> (which 124 * could, as an example, be a <code>RandomAccessFile</code> instance, it 125 * implements the <code>DataInput</code> interface). 126 * Checks a magic byte sequence and then reads all chunks as they appear 127 * in the IFF file. 128 * Will return the resulting image or null if no image body chunk was 129 * encountered before end-of-stream. 130 * Will throw an exception if the file is corrupt, information is missing 131 * or there were reading errors. 132 */ 133 private void load() throws 134 InvalidFileStructureException, 135 IOException, 136 UnsupportedTypeException, 137 WrongFileFormatException 138 { 139 loadHeader(); 140 //System.out.println(width + " x " + height + ", color=" + colorMode + ", channels=" + channels + ", depth=" + depth); 141 // check values 142 if (width < 1 || height < 1) 143 { 144 throw new InvalidFileStructureException("Cannot load image. " + 145 "Invalid pixel resolution in PSD file header (" + width + 146 " x " + height + ")."); 147 } 148 if (colorMode != COLOR_MODE_RGB_TRUECOLOR && 149 colorMode != COLOR_MODE_GRAYSCALE && 150 colorMode != COLOR_MODE_INDEXED) 151 { 152 throw new UnsupportedTypeException("Cannot load image. Only RGB" + 153 " truecolor and indexed color are supported for PSD files. " + 154 "Found: " +getColorTypeName(colorMode)); 155 } 156 if (depth != 8) 157 { 158 throw new UnsupportedTypeException("Cannot load image. Only a depth of 8 bits " + 159 "per channel is supported (found " + depth + 160 " bits)."); 161 } 162 163 // COLOR MODE DATA 164 int colorModeSize = in.readInt(); 165 //System.out.println("colorModeSize=" + colorModeSize); 166 byte[] colorMap = null; 167 if (colorMode == COLOR_MODE_INDEXED) 168 { 169 if (colorModeSize != 768) 170 { 171 throw new InvalidFileStructureException("Cannot load image." + 172 " Color map length was expected to be 768 (found " + 173 colorModeSize + ")."); 174 } 175 colorMap = new byte[colorModeSize]; 176 in.readFully(colorMap); 177 palette = new Palette(256, 255); 178 int offset = 0; 179 for (int index = 0; index < 256; index++) 180 { 181 palette.putSample(Palette.INDEX_RED, index, colorMap[index] & 0xff); 182 palette.putSample(Palette.INDEX_GREEN, index, colorMap[256 + index] & 0xff); 183 palette.putSample(Palette.INDEX_BLUE, index, colorMap[512 + index] & 0xff); 184 } 185 } 186 else 187 { 188 in.skipBytes(colorModeSize); 189 } 190 // IMAGE RESOURCES 191 int resourceLength = in.readInt(); 192 in.skipBytes(resourceLength); 193 //System.out.println("resourceLength=" + resourceLength); 194 // LAYER AND MASK INFORMATION 195 int miscLength = in.readInt(); 196 in.skipBytes(miscLength); 197 //System.out.println("miscLength=" + miscLength); 198 // IMAGE DATA 199 compression = in.readShort(); 200 if (compression != COMPRESSION_NONE && compression != COMPRESSION_PACKBITS) 201 { 202 throw new UnsupportedTypeException("Cannot load image. Unsupported PSD " + 203 "compression type (" + compression + ")"); 204 } 205 //System.out.println("compression=" + compression); 206 loadImageData(); 207 } 208 209 /** 210 * Reads the PSD header to private members of this class instance. 211 * @throws IOException if there were reading errors 212 */ 213 private void loadHeader() throws 214 IOException, 215 WrongFileFormatException 216 { 217 magic = in.readInt(); 218 if (magic != MAGIC_8BPS) 219 { 220 throw new WrongFileFormatException("Not a valid PSD file " + 221 "(wrong magic byte sequence)."); 222 } 223 version = in.readShort(); 224 in.skipBytes(6); 225 channels = in.readShort(); 226 height = in.readInt(); 227 width = in.readInt(); 228 depth = in.readShort(); 229 colorMode = in.readShort(); 230 } 231 232 private void loadPackbitsCompressedData(byte[] data, int offset, int num) throws 233 InvalidFileStructureException, 234 IOException 235 { 236 int x = offset; 237 int max = offset + num; 238 while (x < max) 239 { 240 byte n = in.readByte(); 241 boolean compressed = false; 242 int count = -1; 243 try 244 { 245 if (n >= 0) 246 { 247 // copy next n + 1 bytes literally 248 in.readFully(data, x, n + 1); 249 x += (n + 1); 250 } 251 else 252 { 253 // if n == -128, nothing happens (stupid design decision) 254 if (n != -128) 255 { 256 compressed = true; 257 // otherwise, compute counter 258 count = -((int)n) + 1; 259 // read another byte 260 byte value = in.readByte(); 261 // write this byte counter times to output 262 while (count-- > 0) 263 { 264 data[x++] = value; 265 } 266 } 267 } 268 } 269 catch (ArrayIndexOutOfBoundsException ioobe) 270 { 271 /* if the encoder did anything wrong, the above code 272 could potentially write beyond array boundaries 273 (e.g. if runs of data exceed line boundaries); 274 this would result in an IndexOutOfBoundsException 275 thrown by the virtual machine; 276 to give a more understandable error message to the 277 user, this exception is caught here and a 278 corresponding IOException is thrown */ 279 throw new InvalidFileStructureException("Error: RLE-compressed image " + 280 "file seems to be corrupt (x=" + x + 281 ", count=" + (compressed ? (-((int)n) + 1) : n) + 282 ", compressed=" + (compressed ? "y" : "n") + ", array length=" + data.length + ")."); 283 } 284 } 285 } 286 287 private void loadImageData() throws 288 InvalidFileStructureException, 289 IOException 290 { 291 setBoundsIfNecessary(width, height); 292 allocate(); 293 if (compression == COMPRESSION_PACKBITS) 294 { 295 // skip counters 296 in.skipBytes(2 * channels * height); 297 } 298 byte[] data = new byte[width]; 299 int totalScanLines = channels * height; 300 int currentScanLine = 0; 301 for (int c = 0; c < channels; c++) 302 { 303 int offset = 0; 304 for (int y = 0, destY = - getBoundsY1(); y < height; y++, destY++) 305 { 306 //System.out.println("channel=" + c + ", y=" + y + ", destY=" + destY); 307 if (compression == COMPRESSION_PACKBITS) 308 { 309 loadPackbitsCompressedData(data, 0, width); 310 } 311 else 312 { 313 if (compression == COMPRESSION_PACKBITS) 314 { 315 in.readFully(data, 0, width); 316 } 317 } 318 setProgress(currentScanLine++, totalScanLines); 319 if (!isRowRequired(y)) 320 { 321 continue; 322 } 323 if (rgb24Image != null) 324 { 325 int channelIndex = RGB24Image.INDEX_RED; 326 if (c == 1) 327 { 328 channelIndex = RGB24Image.INDEX_GREEN; 329 } 330 if (c == 2) 331 { 332 channelIndex = RGB24Image.INDEX_BLUE; 333 } 334 rgb24Image.putByteSamples(channelIndex, 0, destY, getBoundsWidth(), 1, data, getBoundsX1()); 335 } 336 if (gray8Image != null) 337 { 338 gray8Image.putByteSamples(0, 0, destY, getBoundsWidth(), 1, data, getBoundsX1()); 339 } 340 if (paletted8Image != null) 341 { 342 paletted8Image.putByteSamples(0, 0, destY, getBoundsWidth(), 1, data, getBoundsX1()); 343 } 344 } 345 } 346 } 347 348 public void process() throws 349 OperationFailedException 350 { 351 initModeFromIOObjects(); 352 try 353 { 354 if (getMode() == CodecMode.LOAD) 355 { 356 in = getInputAsDataInput(); 357 if (in == null) 358 { 359 throw new MissingParameterException("Input stream / file missing."); 360 } 361 load(); 362 } 363 else 364 { 365 throw new OperationFailedException("Only loading is supported in PSD codec."); 366 } 367 } 368 catch (IOException ioe) 369 { 370 throw new OperationFailedException("I/O error: " + ioe.toString()); 371 } 372 } 373 }