psd文件格式解析 http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_75067 參考資料 http://www.kingsquare.nl/php-psd-readerphp
<? /* This file is released under the GPL, any version you like * * PHP PSD reader class, v1.3 * * By Tim de Koning * * Kingsquare Information Services, 22 jan 2007 * * example use: * ------------ * <?php * include_once('classPhpPsdReader.php') * header("Content-type: image/jpeg"); * print imagejpeg(imagecreatefrompsd('test.psd')); * ?> * * More info, bugs or requests, contact info@kingsquare.nl * * Latest version and demo: http://www.kingsquare.nl/phppsdreader * * TODO * ---- * - read color values for "multichannel data" PSD files * - find and implement (hunter)lab to RGB algorithm * - fix 32 bit colors... has something to do with gamma and exposure available since CS2, but dunno how to read them... */ class PhpPsdReader { var $infoArray; var $fp; var $fileName; var $tempFileName; var $colorBytesLength; function PhpPsdReader($fileName) { set_time_limit(0); $this->infoArray = array(); $this->fileName = $fileName; $this->fp = fopen($this->fileName,'r'); if (fread($this->fp,4)=='8BPS') { $this->infoArray['version id'] = $this->_getInteger(2); fseek($this->fp,6,SEEK_CUR); // 6 bytes of 0's $this->infoArray['channels'] = $this->_getInteger(2); $this->infoArray['rows'] = $this->_getInteger(4); $this->infoArray['columns'] = $this->_getInteger(4); $this->infoArray['colorDepth'] = $this->_getInteger(2); $this->infoArray['colorMode'] = $this->_getInteger(2); /* COLOR MODE DATA SECTION */ //4bytes Length The length of the following color data. $this->infoArray['colorModeDataSectionLength'] = $this->_getInteger(4); fseek($this->fp,$this->infoArray['colorModeDataSectionLength'],SEEK_CUR); // ignore this snizzle /* IMAGE RESOURCES */ $this->infoArray['imageResourcesSectionLength'] = $this->_getInteger(4); fseek($this->fp,$this->infoArray['imageResourcesSectionLength'],SEEK_CUR); // ignore this snizzle /* LAYER AND MASK */ $this->infoArray['layerMaskDataSectionLength'] = $this->_getInteger(4); fseek($this->fp,$this->infoArray['layerMaskDataSectionLength'],SEEK_CUR); // ignore this snizzle /* IMAGE DATA */ $this->infoArray['compressionType'] = $this->_getInteger(2); $this->infoArray['oneColorChannelPixelBytes'] = $this->infoArray['colorDepth']/8; $this->colorBytesLength = $this->infoArray['rows']*$this->infoArray['columns']*$this->infoArray['oneColorChannelPixelBytes']; if ($this->infoArray['colorMode']==2) { $this->infoArray['error'] = 'images with indexed colours are not supported yet'; return false; } } else { $this->infoArray['error'] = 'invalid or unsupported psd'; return false; } } function getImage() { // decompress image data if required switch($this->infoArray['compressionType']) { // case 2:, case 3: zip not supported yet.. case 1: // packed bits $this->infoArray['scanLinesByteCounts'] = array(); for ($i=0; $i<($this->infoArray['rows']*$this->infoArray['channels']); $i++) $this->infoArray['scanLinesByteCounts'][] = $this->_getInteger(2); $this->tempFileName = tempnam(realpath('/tmp'),'decompressedImageData'); $tfp = fopen($this->tempFileName,'wb'); foreach ($this->infoArray['scanLinesByteCounts'] as $scanLinesByteCount) { fwrite($tfp,$this->_getPackedBitsDecoded(fread($this->fp,$scanLinesByteCount))); } fclose($tfp); fclose($this->fp); $this->fp = fopen($this->tempFileName,'r'); default: // continue with current file handle; break; } // let's write pixel by pixel.... $image = imagecreatetruecolor($this->infoArray['columns'],$this->infoArray['rows']); for ($rowPointer = 0; ($rowPointer < $this->infoArray['rows']); $rowPointer++) { for ($columnPointer = 0; ($columnPointer < $this->infoArray['columns']); $columnPointer++) { /* The color mode of the file. Supported values are: Bitmap=0; Grayscale=1; Indexed=2; RGB=3; CMYK=4; Multichannel=7; Duotone=8; Lab=9. */ switch ($this->infoArray['colorMode']) { case 2: // indexed... info should be able to extract from color mode data section. not implemented yet, so is grayscale exit; break; case 0: // bit by bit if ($columnPointer == 0) $bitPointer = 0; if ($bitPointer==0) $currentByteBits = str_pad(base_convert(bin2hex(fread($this->fp,1)), 16, 2),8,'0',STR_PAD_LEFT); $r = $g = $b = (($currentByteBits[$bitPointer]=='1')?0:255); $bitPointer++; if ($bitPointer==8) $bitPointer = 0; break; case 1: case 8: // 8 is indexed with 1 color..., so grayscale $r = $g = $b = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); break; case 4: // CMYK $c = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); $currentPointerPos = ftell($this->fp); fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR); $m = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR); $y = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR); $k = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); fseek($this->fp,$currentPointerPos); $r = round(($c * $k) / (pow(2,$this->infoArray['colorDepth'])-1)); $g = round(($m * $k) / (pow(2,$this->infoArray['colorDepth'])-1)); $b = round(($y * $k) / (pow(2,$this->infoArray['colorDepth'])-1)); break; case 9: // hunter Lab // i still need an understandable lab2rgb convert algorithm... if you have one, please let me know! $l = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); $currentPointerPos = ftell($this->fp); fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR); $a = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR); $b = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); fseek($this->fp,$currentPointerPos); $r = $l; $g = $a; $b = $b; break; default: $r = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); $currentPointerPos = ftell($this->fp); fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR); $g = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR); $b = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); fseek($this->fp,$currentPointerPos); break; } if (($this->infoArray['oneColorChannelPixelBytes']==2)) { $r = $r >> 8; $g = $g >> 8; $b = $b >> 8; } elseif (($this->infoArray['oneColorChannelPixelBytes']==4)) { $r = $r >> 24; $g = $g >> 24; $b = $b >> 24; } $pixelColor = imagecolorallocate($image,$r,$g,$b); imagesetpixel($image,$columnPointer,$rowPointer,$pixelColor); } } fclose($this->fp); if (isset($this->tempFileName)) unlink($this->tempFileName); return $image; } /** * * PRIVATE FUNCTIONS * */ function _getPackedBitsDecoded($string) { /* The PackBits algorithm will precede a block of data with a one byte header n, where n is interpreted as follows: n Meaning 0 to 127 Copy the next n + 1 symbols verbatim -127 to -1 Repeat the next symbol 1 - n times -128 Do nothing Decoding: Step 1. Read the block header (n). Step 2. If the header is an EOF exit. Step 3. If n is non-negative, copy the next n + 1 symbols to the output stream and go to step 1. Step 4. If n is negative, write 1 - n copies of the next symbol to the output stream and go to step 1. */ $stringPointer = 0; $returnString = ''; while (1) { if (isset($string[$stringPointer])) $headerByteValue = $this->_unsignedToSigned(hexdec(bin2hex($string[$stringPointer])),1); else return $returnString; $stringPointer++; if ($headerByteValue >= 0) { for ($i=0; $i <= $headerByteValue; $i++) { $returnString .= $string[$stringPointer]; $stringPointer++; } } else { if ($headerByteValue != -128) { $copyByte = $string[$stringPointer]; $stringPointer++; for ($i=0; $i < (1-$headerByteValue); $i++) { $returnString .= $copyByte; } } } } } function _unsignedToSigned($int,$byteSize=1) { switch($byteSize) { case 1: if ($int<128) return $int; else return -256+$int; break; case 2: if ($int<32768) return $int; else return -65536+$int; case 4: if ($int<2147483648) return $int; else return -4294967296+$int; default: return $int; } } function _hexReverse($hex) { $output = ''; if (strlen($hex)%2) return false; for ($pointer = strlen($hex);$pointer>=0;$pointer-=2) $output .= substr($hex,$pointer,2); return $output; } function _getInteger($byteCount=1) { switch ($byteCount) { case 4: // for some strange reason this is still broken... return @reset(unpack('N',fread($this->fp,4))); break; case 2: return @reset(unpack('n',fread($this->fp,2))); break; default: return hexdec($this->_hexReverse(bin2hex(fread($this->fp,$byteCount)))); } } } /** * Returns an image identifier representing the image obtained from the given filename, using only GD, returns an empty string on failure * * @param string $fileName * @return image identifier */ function imagecreatefrompsd($fileName) { $psdReader = new PhpPsdReader($fileName); if (isset($psdReader->infoArray['error'])) return ''; else return $psdReader->getImage(); } ?>