001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.img; 020 021import java.awt.Dimension; 022import java.awt.Graphics2D; 023import java.awt.Point; 024import java.awt.image.BufferedImage; 025import java.awt.image.DataBufferInt; 026import java.io.BufferedInputStream; 027import java.io.FileInputStream; 028import java.io.IOException; 029import java.io.InputStream; 030import java.net.URL; 031public class PSDReader { 032 033 /** 034 * File read status: No errors. 035 */ 036 public static final int STATUS_OK = 0; 037 038 /** 039 * File read status: Error decoding file (may be partially decoded) 040 */ 041 public static final int STATUS_FORMAT_ERROR = 1; 042 043 /** 044 * File read status: Unable to open source. 045 */ 046 public static final int STATUS_OPEN_ERROR = 2; 047 048 /** 049 * File read status: Unsupported format 050 */ 051 public static final int STATUS_UNSUPPORTED = 3; 052 053 public static int ImageType = BufferedImage.TYPE_INT_ARGB; 054 055 protected BufferedInputStream input; 056 protected int frameCount; 057 protected BufferedImage[] frames; 058 protected int status = 0; 059 protected int nChan; 060 protected int width; 061 protected int height; 062 protected int nLayers; 063 protected int miscLen; 064 protected boolean hasLayers; 065 protected LayerInfo[] layers; 066 protected short[] lineLengths; 067 protected int lineIndex; 068 protected boolean rleEncoded; 069 070 protected class LayerInfo { 071 int x, y, w, h; 072 int nChan; 073 int[] chanID; 074 int alpha; 075 } 076 077 /** 078 * Gets the number of layers read from file. 079 * @return frame count 080 */ 081 public int getFrameCount() { 082 return frameCount; 083 } 084 085 protected void setInput(InputStream stream) { 086 // open input stream 087 init(); 088 if (stream == null) { 089 status = STATUS_OPEN_ERROR; 090 } else { 091 if (stream instanceof BufferedInputStream) 092 input = (BufferedInputStream) stream; 093 else 094 input = new BufferedInputStream(stream); 095 } 096 } 097 098 protected void setInput(String name) { 099 // open input file 100 init(); 101 try { 102 name = name.trim(); 103 if (name.startsWith("file:")) { 104 name = name.substring(5); 105 while (name.startsWith("/")) 106 name = name.substring(1); 107 } 108 if (name.indexOf("://") > 0) { 109 URL url = new URL(name); 110 input = new BufferedInputStream(url.openStream()); 111 } else { 112 input = new BufferedInputStream(new FileInputStream(name)); 113 } 114 } catch (IOException e) { 115 status = STATUS_OPEN_ERROR; 116 } 117 } 118 119 /** 120 * Gets display duration for specified frame. Always returns 0. 121 * 122 */ 123 public int getDelay(int forFrame) { 124 return 0; 125 } 126 127 /** 128 * Gets the image contents of frame n. Note that this expands the image 129 * to the full frame size (if the layer was smaller) and any subsequent 130 * use of getLayer() will return the full image. 131 * 132 * @return BufferedImage representation of frame, or null if n is invalid. 133 */ 134 public BufferedImage getFrame(int n) { 135 BufferedImage im = null; 136 if ((n >= 0) && (n < nLayers)) { 137 im = frames[n]; 138 LayerInfo info = layers[n]; 139 if ((info.w != width) || (info.h != height)) { 140 BufferedImage temp = 141 new BufferedImage(width, height, ImageType); 142 Graphics2D gc = temp.createGraphics(); 143 gc.drawImage(im, info.x, info.y, null); 144 gc.dispose(); 145 im = temp; 146 frames[n] = im; 147 } 148 } 149 return im; 150 } 151 152 /** 153 * Gets maximum image size. Individual layers may be smaller. 154 * 155 * @return maximum image dimensions 156 */ 157 public Dimension getFrameSize() { 158 return new Dimension(width, height); 159 } 160 161 /** 162 * Gets the first (or only) image read. 163 * 164 * @return BufferedImage containing first frame, or null if none. 165 */ 166 public BufferedImage getImage() { 167 return getFrame(0); 168 } 169 170 /** 171 * Gets the image contents of layer n. May be smaller than full frame 172 * size - use getFrameOffset() to obtain position of subimage within 173 * main image area. 174 * 175 * @return BufferedImage representation of layer, or null if n is invalid. 176 */ 177 public BufferedImage getLayer(int n) { 178 BufferedImage im = null; 179 if ((n >= 0) && (n < nLayers)) { 180 im = frames[n]; 181 } 182 return im; 183 } 184 185 /** 186 * Gets the subimage offset of layer n if it is smaller than the 187 * full frame size. 188 * 189 * @return Point indicating offset from upper left corner of frame. 190 */ 191 public Point getLayerOffset(int n) { 192 Point p = null; 193 if ((n >= 0) && (n < nLayers)) { 194 int x = layers[n].x; 195 int y = layers[n].y; 196 p = new Point(x, y); 197 } 198 if (p == null) { 199 p = new Point(0, 0); 200 } 201 return p; 202 } 203 204 /** 205 * Reads PhotoShop layers from stream. 206 * 207 * @param InputStream in PhotoShop format. 208 * @return read status code (0 = no errors) 209 */ 210 public int read(InputStream stream) { 211 setInput(stream); 212 process(); 213 return status; 214 } 215 216 /** 217 * Reads PhotoShop file from specified source (file or URL string) 218 * 219 * @param name String containing source 220 * @return read status code (0 = no errors) 221 */ 222 public int read(String name) { 223 setInput(name); 224 process(); 225 return status; 226 } 227 228 /** 229 * Closes input stream and discards contents of all frames. 230 * 231 */ 232 public void reset() { 233 init(); 234 } 235 236 protected void close() { 237 if (input != null) { 238 try { 239 input.close(); 240 } catch (Exception e) {} 241 input = null; 242 } 243 } 244 protected boolean err() { 245 return status != STATUS_OK; 246 } 247 248 protected byte[] fillBytes(int size, int value) { 249 // create byte array filled with given value 250 byte[] b = new byte[size]; 251 if (value != 0) { 252 byte v = (byte) value; 253 for (int i = 0; i < size; i++) { 254 b[i] = v; 255 } 256 } 257 return b; 258 } 259 260 protected void init() { 261 close(); 262 frameCount = 0; 263 frames = null; 264 layers = null; 265 hasLayers = true; 266 status = STATUS_OK; 267 } 268 269 protected void makeDummyLayer() { 270 // creat dummy layer for non-layered image 271 rleEncoded = readShort() == 1; 272 hasLayers = false; 273 nLayers = 1; 274 layers = new LayerInfo[1]; 275 LayerInfo layer = new LayerInfo(); 276 layers[0] = layer; 277 layer.h = height; 278 layer.w = width; 279 int nc = Math.min(nChan, 4); 280 if (rleEncoded) { 281 // get list of rle encoded line lengths for all channels 282 readLineLengths(height * nc); 283 } 284 layer.nChan = nc; 285 layer.chanID = new int[nc]; 286 for (int i = 0; i < nc; i++) { 287 int id = i; 288 if (i == 3) id = -1; 289 layer.chanID[i] = id; 290 } 291 } 292 293 protected void readLineLengths(int nLines) { 294 // read list of rle encoded line lengths 295 lineLengths = new short[nLines]; 296 for (int i = 0; i < nLines; i++) { 297 lineLengths[i] = readShort(); 298 } 299 lineIndex = 0; 300 } 301 302 protected BufferedImage makeImage(int w, int h, byte[] r, byte[] g, byte[] b, byte[] a) { 303 // create image from given plane data 304 BufferedImage im = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); 305 int[] data = ((DataBufferInt) im.getRaster().getDataBuffer()).getData(); 306 int n = w * h; 307 int j = 0; 308 while (j < n) { 309 try { 310 int ac = a[j] & 0xff; 311 int rc = r[j] & 0xff; 312 int gc = g[j] & 0xff; 313 int bc = b[j] & 0xff; 314 data[j] = (((((ac << 8) | rc) << 8) | gc) << 8) | bc; 315 } catch (Exception e) {} 316 j++; 317 } 318 return im; 319 } 320 321 protected void process() { 322 // decode PSD file 323 if (err()) return; 324 readHeader(); 325 if (err()) return; 326 readLayerInfo(); 327 if (err()) return; 328 if (nLayers == 0) { 329 makeDummyLayer(); 330 if (err()) return; 331 } 332 readLayers(); 333 } 334 335 protected int readByte() { 336 // read single byte from input 337 int curByte = 0; 338 try { 339 curByte = input.read(); 340 } catch (IOException e) { 341 status = STATUS_FORMAT_ERROR; 342 } 343 return curByte; 344 } 345 346 protected int readBytes(byte[] bytes, int n) { 347 // read multiple bytes from input 348 if (bytes == null) return 0; 349 int r = 0; 350 try { 351 r = input.read(bytes, 0, n); 352 } catch (IOException e) { 353 status = STATUS_FORMAT_ERROR; 354 } 355 if (r < n) { 356 status = STATUS_FORMAT_ERROR; 357 } 358 return r; 359 } 360 361 protected void readHeader() { 362 // read PSD header info 363 String sig = readString(4); 364 int ver = readShort(); 365 skipBytes(6); 366 nChan = readShort(); 367 height = readInt(); 368 width = readInt(); 369 int depth = readShort(); 370 int mode = readShort(); 371 int cmLen = readInt(); 372 skipBytes(cmLen); 373 int imResLen = readInt(); 374 skipBytes(imResLen); 375 376 // require 8-bit RGB data 377 if ((!sig.equals("8BPS")) || (ver != 1)) { 378 status = STATUS_FORMAT_ERROR; 379 } else if ((depth != 8) || (mode != 3)) { 380 status = STATUS_UNSUPPORTED; 381 } 382 } 383 384 protected int readInt() { 385 // read big-endian 32-bit integer 386 return (((((readByte() << 8) | readByte()) << 8) | readByte()) << 8) 387 | readByte(); 388 } 389 390 protected void readLayerInfo() { 391 // read layer header info 392 miscLen = readInt(); 393 if (miscLen == 0) { 394 return; // no layers, only base image 395 } 396 readInt(); 397 nLayers = readShort(); 398 if (nLayers > 0) { 399 layers = new LayerInfo[nLayers]; 400 } 401 for (int i = 0; i < nLayers; i++) { 402 LayerInfo info = new LayerInfo(); 403 layers[i] = info; 404 info.y = readInt(); 405 info.x = readInt(); 406 info.h = readInt() - info.y; 407 info.w = readInt() - info.x; 408 info.nChan = readShort(); 409 info.chanID = new int[info.nChan]; 410 for (int j = 0; j < info.nChan; j++) { 411 int id = readShort(); 412 readInt(); 413 info.chanID[j] = id; 414 } 415 String s = readString(4); 416 if (!s.equals("8BIM")) { 417 status = STATUS_FORMAT_ERROR; 418 return; 419 } 420 skipBytes(4); // blend mode 421 info.alpha = readByte(); 422 readByte(); 423 readByte(); 424 readByte(); // filler 425 int extraSize = readInt(); 426 skipBytes(extraSize); 427 } 428 } 429 430 protected void readLayers() { 431 // read and convert each layer to BufferedImage 432 frameCount = nLayers; 433 frames = new BufferedImage[nLayers]; 434 for (int i = 0; i < nLayers; i++) { 435 LayerInfo info = layers[i]; 436 byte[] r = null, g = null, b = null, a = null; 437 for (int j = 0; j < info.nChan; j++) { 438 int id = info.chanID[j]; 439 switch (id) { 440 case 0 : r = readPlane(info.w, info.h); break; 441 case 1 : g = readPlane(info.w, info.h); break; 442 case 2 : b = readPlane(info.w, info.h); break; 443 case -1 : a = readPlane(info.w, info.h); break; 444 default : readPlane(info.w, info.h); 445 } 446 if (err()) break; 447 } 448 if (err()) break; 449 int n = info.w * info.h; 450 if (r == null) r = fillBytes(n, 0); 451 if (g == null) g = fillBytes(n, 0); 452 if (b == null) b = fillBytes(n, 0); 453 if (a == null) a = fillBytes(n, 255); 454 455 BufferedImage im = makeImage(info.w, info.h, r, g, b, a); 456 frames[i] = im; 457 } 458 lineLengths = null; 459 if ((miscLen > 0) && !err()) { 460 int n = readInt(); // global layer mask info len 461 skipBytes(n); 462 } 463 } 464 465 protected byte[] readPlane(int w, int h) { 466 // read a single color plane 467 byte[] b = null; 468 int size = w * h; 469 if (hasLayers) { 470 // get RLE compression info for channel 471 rleEncoded = readShort() == 1; 472 if (rleEncoded) { 473 // list of encoded line lengths 474 readLineLengths(h); 475 } 476 } 477 478 if (rleEncoded) { 479 b = readPlaneCompressed(w, h); 480 } else { 481 b = new byte[size]; 482 readBytes(b, size); 483 } 484 485 return b; 486 487 } 488 489 protected byte[] readPlaneCompressed(int w, int h) { 490 byte[] b = new byte[w * h]; 491 byte[] s = new byte[w * 2]; 492 int pos = 0; 493 for (int i = 0; i < h; i++) { 494 if (lineIndex >= lineLengths.length) { 495 status = STATUS_FORMAT_ERROR; 496 return null; 497 } 498 int len = lineLengths[lineIndex++]; 499 readBytes(s, len); 500 decodeRLE(s, 0, len, b, pos); 501 pos += w; 502 } 503 return b; 504 } 505 506 protected void decodeRLE(byte[] src, int sindex, int slen, byte[] dst, int dindex) { 507 try { 508 int max = sindex + slen; 509 while (sindex < max) { 510 byte b = src[sindex++]; 511 int n = b; 512 if (n < 0) { 513 // dup next byte 1-n times 514 n = 1 - n; 515 b = src[sindex++]; 516 for (int i = 0; i < n; i++) { 517 dst[dindex++] = b; 518 } 519 } else { 520 // copy next n+1 bytes 521 n = n + 1; 522 System.arraycopy(src, sindex, dst, dindex, n); 523 dindex += n; 524 sindex += n; 525 } 526 } 527 } catch (Exception e) { 528 status = STATUS_FORMAT_ERROR; 529 } 530 } 531 532 protected short readShort() { 533 // read big-endian 16-bit integer 534 return (short) ((readByte() << 8) | readByte()); 535 } 536 537 protected String readString(int len) { 538 // read string of specified length 539 String s = ""; 540 for (int i = 0; i < len; i++) { 541 s = s + (char) readByte(); 542 } 543 return s; 544 } 545 546 protected void skipBytes(int n) { 547 // skip over n input bytes 548 for (int i = 0; i < n; i++) { 549 readByte(); 550 } 551 } 552}