PageRenderTime 49ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/psd.class.php

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