├── PSDReader.php ├── README └── examples ├── basic.php ├── basic.psd ├── monkey_bw.php ├── monkey_bw.psd ├── text_transparent_bg.php └── text_transparent_bg.psd /PSDReader.php: -------------------------------------------------------------------------------- 1 | 17 | * 18 | * More info, bugs or requests, contact info@kingsquare.nl 19 | * 20 | * Latest version and demo: http://www.kingsquare.nl/phppsdreader 21 | * 22 | * TODO 23 | * ---- 24 | * - read color values for "multichannel data" PSD files 25 | * - find and implement (hunter)lab to RGB algorithm 26 | * - fix 32 bit colors... has something to do with gamma and exposure available since CS2, but dunno how to read them... 27 | */ 28 | 29 | 30 | class PSDReader { 31 | var $infoArray; 32 | var $fp; 33 | var $fileName; 34 | var $tempFileName; 35 | var $colorBytesLength; 36 | 37 | public function __construct($fileName) { 38 | set_time_limit(0); 39 | $this->infoArray = array(); 40 | $this->fileName = $fileName; 41 | $this->fp = fopen($this->fileName,'r'); 42 | 43 | if (fread($this->fp,4)=='8BPS') { 44 | $this->infoArray['version id'] = $this->_getInteger(2); 45 | fseek($this->fp,6,SEEK_CUR); // 6 bytes of 0's 46 | $this->infoArray['channels'] = $this->_getInteger(2); 47 | $this->infoArray['rows'] = $this->_getInteger(4); 48 | $this->infoArray['columns'] = $this->_getInteger(4); 49 | $this->infoArray['colorDepth'] = $this->_getInteger(2); 50 | $this->infoArray['colorMode'] = $this->_getInteger(2); 51 | 52 | 53 | /* COLOR MODE DATA SECTION */ //4bytes Length The length of the following color data. 54 | $this->infoArray['colorModeDataSectionLength'] = $this->_getInteger(4); 55 | fseek($this->fp,$this->infoArray['colorModeDataSectionLength'],SEEK_CUR); // ignore this snizzle 56 | 57 | /* IMAGE RESOURCES */ 58 | $this->infoArray['imageResourcesSectionLength'] = $this->_getInteger(4); 59 | fseek($this->fp,$this->infoArray['imageResourcesSectionLength'],SEEK_CUR); // ignore this snizzle 60 | 61 | /* LAYER AND MASK */ 62 | $this->infoArray['layerMaskDataSectionLength'] = $this->_getInteger(4); 63 | fseek($this->fp,$this->infoArray['layerMaskDataSectionLength'],SEEK_CUR); // ignore this snizzle 64 | 65 | 66 | /* IMAGE DATA */ 67 | $this->infoArray['compressionType'] = $this->_getInteger(2); 68 | $this->infoArray['oneColorChannelPixelBytes'] = $this->infoArray['colorDepth']/8; 69 | $this->colorBytesLength = $this->infoArray['rows']*$this->infoArray['columns']*$this->infoArray['oneColorChannelPixelBytes']; 70 | 71 | if ($this->infoArray['colorMode']==2) { 72 | $this->infoArray['error'] = 'images with indexed colours are not supported yet'; 73 | return false; 74 | } 75 | } else { 76 | $this->infoArray['error'] = 'invalid or unsupported psd'; 77 | return false; 78 | } 79 | } 80 | 81 | 82 | function getImage() { 83 | // decompress image data if required 84 | switch($this->infoArray['compressionType']) { 85 | // case 2:, case 3: zip not supported yet.. 86 | case 1: 87 | // packed bits 88 | $this->infoArray['scanLinesByteCounts'] = array(); 89 | for ($i=0; $i<($this->infoArray['rows']*$this->infoArray['channels']); $i++) $this->infoArray['scanLinesByteCounts'][] = $this->_getInteger(2); 90 | $this->tempFileName = tempnam(realpath('/tmp'),'decompressedImageData'); 91 | $tfp = fopen($this->tempFileName,'wb'); 92 | foreach ($this->infoArray['scanLinesByteCounts'] as $scanLinesByteCount) { 93 | fwrite($tfp,$this->_getPackedBitsDecoded(fread($this->fp,$scanLinesByteCount))); 94 | } 95 | fclose($tfp); 96 | fclose($this->fp); 97 | $this->fp = fopen($this->tempFileName,'r'); 98 | default: 99 | // continue with current file handle; 100 | break; 101 | } 102 | 103 | // let's write pixel by pixel.... 104 | $image = imagecreatetruecolor($this->infoArray['columns'],$this->infoArray['rows']); 105 | 106 | for ($rowPointer = 0; ($rowPointer < $this->infoArray['rows']); $rowPointer++) { 107 | for ($columnPointer = 0; ($columnPointer < $this->infoArray['columns']); $columnPointer++) { 108 | /* The color mode of the file. Supported values are: Bitmap=0; 109 | Grayscale=1; Indexed=2; RGB=3; CMYK=4; Multichannel=7; 110 | Duotone=8; Lab=9. 111 | */ 112 | switch ($this->infoArray['colorMode']) { 113 | case 2: // indexed... info should be able to extract from color mode data section. not implemented yet, so is grayscale 114 | exit; 115 | break; 116 | case 0: 117 | // bit by bit 118 | if ($columnPointer == 0) $bitPointer = 0; 119 | if ($bitPointer==0) $currentByteBits = str_pad(base_convert(bin2hex(fread($this->fp,1)), 16, 2),8,'0',STR_PAD_LEFT); 120 | $r = $g = $b = (($currentByteBits[$bitPointer]=='1')?0:255); 121 | $bitPointer++; 122 | if ($bitPointer==8) $bitPointer = 0; 123 | break; 124 | 125 | case 1: 126 | case 8: // 8 is indexed with 1 color..., so grayscale 127 | $r = $g = $b = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); 128 | break; 129 | 130 | case 4: // CMYK 131 | $c = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); 132 | $currentPointerPos = ftell($this->fp); 133 | fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR); 134 | $m = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); 135 | fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR); 136 | $y = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); 137 | fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR); 138 | $k = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); 139 | fseek($this->fp,$currentPointerPos); 140 | $r = round(($c * $k) / (pow(2,$this->infoArray['colorDepth'])-1)); 141 | $g = round(($m * $k) / (pow(2,$this->infoArray['colorDepth'])-1)); 142 | $b = round(($y * $k) / (pow(2,$this->infoArray['colorDepth'])-1)); 143 | 144 | break; 145 | 146 | case 9: // hunter Lab 147 | // i still need an understandable lab2rgb convert algorithm... if you have one, please let me know! 148 | $l = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); 149 | $currentPointerPos = ftell($this->fp); 150 | fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR); 151 | $a = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); 152 | fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR); 153 | $b = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); 154 | fseek($this->fp,$currentPointerPos); 155 | 156 | $r = $l; 157 | $g = $a; 158 | $b = $b; 159 | 160 | break; 161 | default: 162 | $r = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); 163 | $currentPointerPos = ftell($this->fp); 164 | fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR); 165 | $g = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); 166 | fseek($this->fp,$this->colorBytesLength-1,SEEK_CUR); 167 | $b = $this->_getInteger($this->infoArray['oneColorChannelPixelBytes']); 168 | fseek($this->fp,$currentPointerPos); 169 | break; 170 | 171 | } 172 | 173 | if (($this->infoArray['oneColorChannelPixelBytes']==2)) { 174 | $r = $r >> 8; 175 | $g = $g >> 8; 176 | $b = $b >> 8; 177 | } elseif (($this->infoArray['oneColorChannelPixelBytes']==4)) { 178 | $r = $r >> 24; 179 | $g = $g >> 24; 180 | $b = $b >> 24; 181 | } 182 | 183 | $pixelColor = imagecolorallocate($image,$r,$g,$b); 184 | imagesetpixel($image,$columnPointer,$rowPointer,$pixelColor); 185 | } 186 | } 187 | fclose($this->fp); 188 | if (isset($this->tempFileName)) unlink($this->tempFileName); 189 | return $image; 190 | } 191 | 192 | /** 193 | * 194 | * PRIVATE FUNCTIONS 195 | * 196 | */ 197 | 198 | function _getPackedBitsDecoded($string) { 199 | /* 200 | The PackBits algorithm will precede a block of data with a one byte header n, where n is interpreted as follows: 201 | n Meaning 202 | 0 to 127 Copy the next n + 1 symbols verbatim 203 | -127 to -1 Repeat the next symbol 1 - n times 204 | -128 Do nothing 205 | 206 | Decoding: 207 | Step 1. Read the block header (n). 208 | Step 2. If the header is an EOF exit. 209 | Step 3. If n is non-negative, copy the next n + 1 symbols to the output stream and go to step 1. 210 | Step 4. If n is negative, write 1 - n copies of the next symbol to the output stream and go to step 1. 211 | 212 | */ 213 | 214 | $stringPointer = 0; 215 | $returnString = ''; 216 | 217 | while (1) { 218 | if (isset($string[$stringPointer])) $headerByteValue = $this->_unsignedToSigned(hexdec(bin2hex($string[$stringPointer])),1); 219 | else return $returnString; 220 | $stringPointer++; 221 | 222 | if ($headerByteValue >= 0) { 223 | for ($i=0; $i <= $headerByteValue; $i++) { 224 | $returnString .= $string[$stringPointer]; 225 | $stringPointer++; 226 | } 227 | } else { 228 | if ($headerByteValue != -128) { 229 | $copyByte = $string[$stringPointer]; 230 | $stringPointer++; 231 | 232 | for ($i=0; $i < (1-$headerByteValue); $i++) { 233 | $returnString .= $copyByte; 234 | } 235 | } 236 | } 237 | } 238 | } 239 | 240 | function _unsignedToSigned($int,$byteSize=1) { 241 | switch($byteSize) { 242 | case 1: 243 | if ($int<128) return $int; 244 | else return -256+$int; 245 | break; 246 | 247 | case 2: 248 | if ($int<32768) return $int; 249 | else return -65536+$int; 250 | 251 | case 4: 252 | if ($int<2147483648) return $int; 253 | else return -4294967296+$int; 254 | 255 | default: 256 | return $int; 257 | } 258 | } 259 | 260 | function _hexReverse($hex) { 261 | $output = ''; 262 | if (strlen($hex)%2) return false; 263 | for ($pointer = strlen($hex);$pointer>=0;$pointer-=2) $output .= substr($hex,$pointer,2); 264 | return $output; 265 | } 266 | 267 | function _getInteger($byteCount=1) { 268 | switch ($byteCount) { 269 | case 4: 270 | // for some strange reason this is still broken... 271 | return @reset(unpack('N',fread($this->fp,4))); 272 | break; 273 | 274 | case 2: 275 | return @reset(unpack('n',fread($this->fp,2))); 276 | break; 277 | 278 | default: 279 | return hexdec($this->_hexReverse(bin2hex(fread($this->fp,$byteCount)))); 280 | } 281 | } 282 | } 283 | 284 | /** 285 | * Returns an image identifier representing the image obtained from the given filename, using only GD, returns an empty string on failure 286 | * 287 | * @param string $fileName 288 | * @return image identifier 289 | */ 290 | 291 | function imagecreatefrompsd($fileName) { 292 | $psdReader = new PSDReader($fileName); 293 | if (isset($psdReader->infoArray['error'])) return ''; 294 | else return $psdReader->getImage(); 295 | } 296 | ?> 297 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | PSD Library - Read .psd files without any 3rd party libraries. 2 | 3 | 4 | Credits Etc... Soon 5 | -------------------------------------------------------------------------------- /examples/basic.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/basic.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasokeric/php-psd/e321798e1ad04396b0b9077ad90abdf4f85bb649/examples/basic.psd -------------------------------------------------------------------------------- /examples/monkey_bw.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/monkey_bw.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasokeric/php-psd/e321798e1ad04396b0b9077ad90abdf4f85bb649/examples/monkey_bw.psd -------------------------------------------------------------------------------- /examples/text_transparent_bg.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/text_transparent_bg.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasokeric/php-psd/e321798e1ad04396b0b9077ad90abdf4f85bb649/examples/text_transparent_bg.psd --------------------------------------------------------------------------------