Class-Graphics.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. <?php
  2. /**
  3. * Classes used for reading gif files (in case PHP's GD doesn't provide the
  4. * proper gif-functions).
  5. *
  6. * Gif Util copyright 2003 by Yamasoft (S/C). All rights reserved.
  7. * Do not remove this portion of the header, or use these functions except
  8. * from the original author. To get it, please navigate to:
  9. * http://www.yamasoft.com/php-gif.zip
  10. *
  11. * Simple Machines Forum (SMF)
  12. *
  13. * @package SMF
  14. * @author Simple Machines http://www.simplemachines.org
  15. * @copyright 2012 Simple Machines
  16. * @license http://www.simplemachines.org/about/smf/license.php BSD
  17. *
  18. * @version 2.1 Alpha 1
  19. */
  20. if (!defined('SMF'))
  21. die('No direct access...');
  22. class gif_lzw_compression
  23. {
  24. public $MAX_LZW_BITS;
  25. public $Fresh, $CodeSize, $SetCodeSize, $MaxCode, $MaxCodeSize, $FirstCode, $OldCode;
  26. public $ClearCode, $EndCode, $Next, $Vals, $Stack, $sp, $Buf, $CurBit, $LastBit, $Done, $LastByte;
  27. public function __construct()
  28. {
  29. $this->MAX_LZW_BITS = 12;
  30. unset($this->Next, $this->Vals, $this->Stack, $this->Buf);
  31. $this->Next = range(0, (1 << $this->MAX_LZW_BITS) - 1);
  32. $this->Vals = range(0, (1 << $this->MAX_LZW_BITS) - 1);
  33. $this->Stack = range(0, (1 << ($this->MAX_LZW_BITS + 1)) - 1);
  34. $this->Buf = range(0, 279);
  35. }
  36. public function decompress($data, &$datLen)
  37. {
  38. $stLen = strlen($data);
  39. $datLen = 0;
  40. $ret = '';
  41. $this->LZWCommand($data, true);
  42. while (($iIndex = $this->LZWCommand($data, false)) >= 0)
  43. $ret .= chr($iIndex);
  44. $datLen = $stLen - strlen($data);
  45. if ($iIndex != -2)
  46. return false;
  47. return $ret;
  48. }
  49. public function LZWCommand(&$data, $bInit)
  50. {
  51. if ($bInit)
  52. {
  53. $this->SetCodeSize = ord($data[0]);
  54. $data = substr($data, 1);
  55. $this->CodeSize = $this->SetCodeSize + 1;
  56. $this->ClearCode = 1 << $this->SetCodeSize;
  57. $this->EndCode = $this->ClearCode + 1;
  58. $this->MaxCode = $this->ClearCode + 2;
  59. $this->MaxCodeSize = $this->ClearCode << 1;
  60. $this->GetCode($data, $bInit);
  61. $this->Fresh = 1;
  62. for ($i = 0; $i < $this->ClearCode; $i++)
  63. {
  64. $this->Next[$i] = 0;
  65. $this->Vals[$i] = $i;
  66. }
  67. for (; $i < (1 << $this->MAX_LZW_BITS); $i++)
  68. {
  69. $this->Next[$i] = 0;
  70. $this->Vals[$i] = 0;
  71. }
  72. $this->sp = 0;
  73. return 1;
  74. }
  75. if ($this->Fresh)
  76. {
  77. $this->Fresh = 0;
  78. do
  79. {
  80. $this->FirstCode = $this->GetCode($data, $bInit);
  81. $this->OldCode = $this->FirstCode;
  82. }
  83. while ($this->FirstCode == $this->ClearCode);
  84. return $this->FirstCode;
  85. }
  86. if ($this->sp > 0)
  87. {
  88. $this->sp--;
  89. return $this->Stack[$this->sp];
  90. }
  91. while (($Code = $this->GetCode($data, $bInit)) >= 0)
  92. {
  93. if ($Code == $this->ClearCode)
  94. {
  95. for ($i = 0; $i < $this->ClearCode; $i++)
  96. {
  97. $this->Next[$i] = 0;
  98. $this->Vals[$i] = $i;
  99. }
  100. for (; $i < (1 << $this->MAX_LZW_BITS); $i++)
  101. {
  102. $this->Next[$i] = 0;
  103. $this->Vals[$i] = 0;
  104. }
  105. $this->CodeSize = $this->SetCodeSize + 1;
  106. $this->MaxCodeSize = $this->ClearCode << 1;
  107. $this->MaxCode = $this->ClearCode + 2;
  108. $this->sp = 0;
  109. $this->FirstCode = $this->GetCode($data, $bInit);
  110. $this->OldCode = $this->FirstCode;
  111. return $this->FirstCode;
  112. }
  113. if ($Code == $this->EndCode)
  114. return -2;
  115. $InCode = $Code;
  116. if ($Code >= $this->MaxCode)
  117. {
  118. $this->Stack[$this->sp] = $this->FirstCode;
  119. $this->sp++;
  120. $Code = $this->OldCode;
  121. }
  122. while ($Code >= $this->ClearCode)
  123. {
  124. $this->Stack[$this->sp] = $this->Vals[$Code];
  125. $this->sp++;
  126. if ($Code == $this->Next[$Code]) // Circular table entry, big GIF Error!
  127. return -1;
  128. $Code = $this->Next[$Code];
  129. }
  130. $this->FirstCode = $this->Vals[$Code];
  131. $this->Stack[$this->sp] = $this->FirstCode;
  132. $this->sp++;
  133. if (($Code = $this->MaxCode) < (1 << $this->MAX_LZW_BITS))
  134. {
  135. $this->Next[$Code] = $this->OldCode;
  136. $this->Vals[$Code] = $this->FirstCode;
  137. $this->MaxCode++;
  138. if (($this->MaxCode >= $this->MaxCodeSize) && ($this->MaxCodeSize < (1 << $this->MAX_LZW_BITS)))
  139. {
  140. $this->MaxCodeSize *= 2;
  141. $this->CodeSize++;
  142. }
  143. }
  144. $this->OldCode = $InCode;
  145. if ($this->sp > 0)
  146. {
  147. $this->sp--;
  148. return $this->Stack[$this->sp];
  149. }
  150. }
  151. return $Code;
  152. }
  153. public function GetCode(&$data, $bInit)
  154. {
  155. if ($bInit)
  156. {
  157. $this->CurBit = 0;
  158. $this->LastBit = 0;
  159. $this->Done = 0;
  160. $this->LastByte = 2;
  161. return 1;
  162. }
  163. if (($this->CurBit + $this->CodeSize) >= $this->LastBit)
  164. {
  165. if ($this->Done)
  166. {
  167. // Ran off the end of my bits...
  168. if ($this->CurBit >= $this->LastBit)
  169. return 0;
  170. return -1;
  171. }
  172. $this->Buf[0] = $this->Buf[$this->LastByte - 2];
  173. $this->Buf[1] = $this->Buf[$this->LastByte - 1];
  174. $count = ord($data[0]);
  175. $data = substr($data, 1);
  176. if ($count)
  177. {
  178. for ($i = 0; $i < $count; $i++)
  179. $this->Buf[2 + $i] = ord($data{$i});
  180. $data = substr($data, $count);
  181. }
  182. else
  183. $this->Done = 1;
  184. $this->LastByte = 2 + $count;
  185. $this->CurBit = ($this->CurBit - $this->LastBit) + 16;
  186. $this->LastBit = (2 + $count) << 3;
  187. }
  188. $iRet = 0;
  189. for ($i = $this->CurBit, $j = 0; $j < $this->CodeSize; $i++, $j++)
  190. $iRet |= (($this->Buf[intval($i / 8)] & (1 << ($i % 8))) != 0) << $j;
  191. $this->CurBit += $this->CodeSize;
  192. return $iRet;
  193. }
  194. }
  195. class gif_color_table
  196. {
  197. public $m_nColors;
  198. public $m_arColors;
  199. public function __construct()
  200. {
  201. unset($this->m_nColors, $this->m_arColors);
  202. }
  203. public function load($lpData, $num)
  204. {
  205. $this->m_nColors = 0;
  206. $this->m_arColors = array();
  207. for ($i = 0; $i < $num; $i++)
  208. {
  209. $rgb = substr($lpData, $i * 3, 3);
  210. if (strlen($rgb) < 3)
  211. return false;
  212. $this->m_arColors[] = (ord($rgb[2]) << 16) + (ord($rgb[1]) << 8) + ord($rgb[0]);
  213. $this->m_nColors++;
  214. }
  215. return true;
  216. }
  217. public function toString()
  218. {
  219. $ret = '';
  220. for ($i = 0; $i < $this->m_nColors; $i++)
  221. {
  222. $ret .=
  223. chr(($this->m_arColors[$i] & 0x000000FF)) . // R
  224. chr(($this->m_arColors[$i] & 0x0000FF00) >> 8) . // G
  225. chr(($this->m_arColors[$i] & 0x00FF0000) >> 16); // B
  226. }
  227. return $ret;
  228. }
  229. public function colorIndex($rgb)
  230. {
  231. $rgb = intval($rgb) & 0xFFFFFF;
  232. $r1 = ($rgb & 0x0000FF);
  233. $g1 = ($rgb & 0x00FF00) >> 8;
  234. $b1 = ($rgb & 0xFF0000) >> 16;
  235. $idx = -1;
  236. for ($i = 0; $i < $this->m_nColors; $i++)
  237. {
  238. $r2 = ($this->m_arColors[$i] & 0x000000FF);
  239. $g2 = ($this->m_arColors[$i] & 0x0000FF00) >> 8;
  240. $b2 = ($this->m_arColors[$i] & 0x00FF0000) >> 16;
  241. $d = abs($r2 - $r1) + abs($g2 - $g1) + abs($b2 - $b1);
  242. if (($idx == -1) || ($d < $dif))
  243. {
  244. $idx = $i;
  245. $dif = $d;
  246. }
  247. }
  248. return $idx;
  249. }
  250. }
  251. class gif_file_header
  252. {
  253. public $m_lpVer, $m_nWidth, $m_nHeight, $m_bGlobalClr, $m_nColorRes;
  254. public $m_bSorted, $m_nTableSize, $m_nBgColor, $m_nPixelRatio;
  255. public $m_colorTable;
  256. public function __construct()
  257. {
  258. unset($this->m_lpVer, $this->m_nWidth, $this->m_nHeight, $this->m_bGlobalClr, $this->m_nColorRes);
  259. unset($this->m_bSorted, $this->m_nTableSize, $this->m_nBgColor, $this->m_nPixelRatio, $this->m_colorTable);
  260. }
  261. public function load($lpData, &$hdrLen)
  262. {
  263. $hdrLen = 0;
  264. $this->m_lpVer = substr($lpData, 0, 6);
  265. if (($this->m_lpVer != 'GIF87a') && ($this->m_lpVer != 'GIF89a'))
  266. return false;
  267. list ($this->m_nWidth, $this->m_nHeight) = array_values(unpack('v2', substr($lpData, 6, 4)));
  268. if (!$this->m_nWidth || !$this->m_nHeight)
  269. return false;
  270. $b = ord(substr($lpData, 10, 1));
  271. $this->m_bGlobalClr = ($b & 0x80) ? true : false;
  272. $this->m_nColorRes = ($b & 0x70) >> 4;
  273. $this->m_bSorted = ($b & 0x08) ? true : false;
  274. $this->m_nTableSize = 2 << ($b & 0x07);
  275. $this->m_nBgColor = ord(substr($lpData, 11, 1));
  276. $this->m_nPixelRatio = ord(substr($lpData, 12, 1));
  277. $hdrLen = 13;
  278. if ($this->m_bGlobalClr)
  279. {
  280. $this->m_colorTable = new gif_color_table();
  281. if (!$this->m_colorTable->load(substr($lpData, $hdrLen), $this->m_nTableSize))
  282. return false;
  283. $hdrLen += 3 * $this->m_nTableSize;
  284. }
  285. return true;
  286. }
  287. }
  288. class gif_image_header
  289. {
  290. public $m_nLeft, $m_nTop, $m_nWidth, $m_nHeight, $m_bLocalClr;
  291. public $m_bInterlace, $m_bSorted, $m_nTableSize, $m_colorTable;
  292. public function __construct()
  293. {
  294. unset($this->m_nLeft, $this->m_nTop, $this->m_nWidth, $this->m_nHeight, $this->m_bLocalClr);
  295. unset($this->m_bInterlace, $this->m_bSorted, $this->m_nTableSize, $this->m_colorTable);
  296. }
  297. public function load($lpData, &$hdrLen)
  298. {
  299. $hdrLen = 0;
  300. // Get the width/height/etc. from the header.
  301. list ($this->m_nLeft, $this->m_nTop, $this->m_nWidth, $this->m_nHeight) = array_values(unpack('v4', substr($lpData, 0, 8)));
  302. if (!$this->m_nWidth || !$this->m_nHeight)
  303. return false;
  304. $b = ord($lpData[8]);
  305. $this->m_bLocalClr = ($b & 0x80) ? true : false;
  306. $this->m_bInterlace = ($b & 0x40) ? true : false;
  307. $this->m_bSorted = ($b & 0x20) ? true : false;
  308. $this->m_nTableSize = 2 << ($b & 0x07);
  309. $hdrLen = 9;
  310. if ($this->m_bLocalClr)
  311. {
  312. $this->m_colorTable = new gif_color_table();
  313. if (!$this->m_colorTable->load(substr($lpData, $hdrLen), $this->m_nTableSize))
  314. return false;
  315. $hdrLen += 3 * $this->m_nTableSize;
  316. }
  317. return true;
  318. }
  319. }
  320. class gif_image
  321. {
  322. public $m_disp, $m_bUser, $m_bTrans, $m_nDelay, $m_nTrans, $m_lpComm;
  323. public $m_gih, $m_data, $m_lzw;
  324. public function __construct()
  325. {
  326. unset($this->m_disp, $this->m_bUser, $this->m_nDelay, $this->m_nTrans, $this->m_lpComm, $this->m_data);
  327. $this->m_gih = new gif_image_header();
  328. $this->m_lzw = new gif_lzw_compression();
  329. }
  330. public function load($data, &$datLen)
  331. {
  332. $datLen = 0;
  333. while (true)
  334. {
  335. $b = ord($data[0]);
  336. $data = substr($data, 1);
  337. $datLen++;
  338. switch ($b)
  339. {
  340. // Extension...
  341. case 0x21:
  342. $len = 0;
  343. if (!$this->skipExt($data, $len))
  344. return false;
  345. $datLen += $len;
  346. break;
  347. // Image...
  348. case 0x2C:
  349. // Load the header and color table.
  350. $len = 0;
  351. if (!$this->m_gih->load($data, $len))
  352. return false;
  353. $data = substr($data, $len);
  354. $datLen += $len;
  355. // Decompress the data, and ride on home ;).
  356. $len = 0;
  357. if (!($this->m_data = $this->m_lzw->decompress($data, $len)))
  358. return false;
  359. $data = substr($data, $len);
  360. $datLen += $len;
  361. if ($this->m_gih->m_bInterlace)
  362. $this->deInterlace();
  363. return true;
  364. case 0x3B: // EOF
  365. default:
  366. return false;
  367. }
  368. }
  369. return false;
  370. }
  371. public function skipExt(&$data, &$extLen)
  372. {
  373. $extLen = 0;
  374. $b = ord($data[0]);
  375. $data = substr($data, 1);
  376. $extLen++;
  377. switch ($b)
  378. {
  379. // Graphic Control...
  380. case 0xF9:
  381. $b = ord($data[1]);
  382. $this->m_disp = ($b & 0x1C) >> 2;
  383. $this->m_bUser = ($b & 0x02) ? true : false;
  384. $this->m_bTrans = ($b & 0x01) ? true : false;
  385. list ($this->m_nDelay) = array_values(unpack('v', substr($data, 2, 2)));
  386. $this->m_nTrans = ord($data[4]);
  387. break;
  388. // Comment...
  389. case 0xFE:
  390. $this->m_lpComm = substr($data, 1, ord($data[0]));
  391. break;
  392. // Plain text...
  393. case 0x01:
  394. break;
  395. // Application...
  396. case 0xFF:
  397. break;
  398. }
  399. // Skip default as defs may change.
  400. $b = ord($data[0]);
  401. $data = substr($data, 1);
  402. $extLen++;
  403. while ($b > 0)
  404. {
  405. $data = substr($data, $b);
  406. $extLen += $b;
  407. $b = ord($data[0]);
  408. $data = substr($data, 1);
  409. $extLen++;
  410. }
  411. return true;
  412. }
  413. public function deInterlace()
  414. {
  415. $data = $this->m_data;
  416. for ($i = 0; $i < 4; $i++)
  417. {
  418. switch ($i)
  419. {
  420. case 0:
  421. $s = 8;
  422. $y = 0;
  423. break;
  424. case 1:
  425. $s = 8;
  426. $y = 4;
  427. break;
  428. case 2:
  429. $s = 4;
  430. $y = 2;
  431. break;
  432. case 3:
  433. $s = 2;
  434. $y = 1;
  435. break;
  436. }
  437. for (; $y < $this->m_gih->m_nHeight; $y += $s)
  438. {
  439. $lne = substr($this->m_data, 0, $this->m_gih->m_nWidth);
  440. $this->m_data = substr($this->m_data, $this->m_gih->m_nWidth);
  441. $data =
  442. substr($data, 0, $y * $this->m_gih->m_nWidth) .
  443. $lne .
  444. substr($data, ($y + 1) * $this->m_gih->m_nWidth);
  445. }
  446. }
  447. $this->m_data = $data;
  448. }
  449. }
  450. class gif_file
  451. {
  452. public $header, $image, $data, $loaded;
  453. public function __construct()
  454. {
  455. $this->data = '';
  456. $this->loaded = false;
  457. $this->header = new gif_file_header();
  458. $this->image = new gif_image();
  459. }
  460. public function loadFile($filename, $iIndex)
  461. {
  462. if ($iIndex < 0)
  463. return false;
  464. $this->data = @file_get_contents($filename);
  465. if ($this->data === false)
  466. return false;
  467. // Tell the header to load up....
  468. $len = 0;
  469. if (!$this->header->load($this->data, $len))
  470. return false;
  471. $this->data = substr($this->data, $len);
  472. // Keep reading (at least once) so we get to the actual image we're looking for.
  473. for ($j = 0; $j <= $iIndex; $j++)
  474. {
  475. $imgLen = 0;
  476. if (!$this->image->load($this->data, $imgLen))
  477. return false;
  478. $this->data = substr($this->data, $imgLen);
  479. }
  480. $this->loaded = true;
  481. return true;
  482. }
  483. public function get_png_data($background_color)
  484. {
  485. if (!$this->loaded)
  486. return false;
  487. // Prepare the color table.
  488. if ($this->image->m_gih->m_bLocalClr)
  489. {
  490. $colors = $this->image->m_gih->m_nTableSize;
  491. $pal = $this->image->m_gih->m_colorTable->toString();
  492. if ($background_color != -1)
  493. $background_color = $this->image->m_gih->m_colorTable->colorIndex($background_color);
  494. }
  495. elseif ($this->header->m_bGlobalClr)
  496. {
  497. $colors = $this->header->m_nTableSize;
  498. $pal = $this->header->m_colorTable->toString();
  499. if ($background_color != -1)
  500. $background_color = $this->header->m_colorTable->colorIndex($background_color);
  501. }
  502. else
  503. {
  504. $colors = 0;
  505. $background_color = -1;
  506. }
  507. if ($background_color == -1)
  508. $background_color = $this->header->m_nBgColor;
  509. $data = &$this->image->m_data;
  510. $header = &$this->image->m_gih;
  511. $i = 0;
  512. $bmp = '';
  513. // Prepare the bitmap itself.
  514. for ($y = 0; $y < $this->header->m_nHeight; $y++)
  515. {
  516. $bmp .= "\x00";
  517. for ($x = 0; $x < $this->header->m_nWidth; $x++, $i++)
  518. {
  519. // Is this in the proper range? If so, get the specific pixel data...
  520. if ($x >= $header->m_nLeft && $y >= $header->m_nTop && $x < ($header->m_nLeft + $header->m_nWidth) && $y < ($header->m_nTop + $header->m_nHeight))
  521. $bmp .= $data{$i};
  522. // Otherwise, this is background...
  523. else
  524. $bmp .= chr($background_color);
  525. }
  526. }
  527. $bmp = gzcompress($bmp, 9);
  528. // Output the basic signature first of all.
  529. $out = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
  530. // Now, we want the header...
  531. $out .= "\x00\x00\x00\x0D";
  532. $tmp = 'IHDR' . pack('N', (int) $this->header->m_nWidth) . pack('N', (int) $this->header->m_nHeight) . "\x08\x03\x00\x00\x00";
  533. $out .= $tmp . pack('N', smf_crc32($tmp));
  534. // The palette, assuming we have one to speak of...
  535. if ($colors > 0)
  536. {
  537. $out .= pack('N', (int) $colors * 3);
  538. $tmp = 'PLTE' . $pal;
  539. $out .= $tmp . pack('N', smf_crc32($tmp));
  540. }
  541. // Do we have any transparency we want to make available?
  542. if ($this->image->m_bTrans && $colors > 0)
  543. {
  544. $out .= pack('N', (int) $colors);
  545. $tmp = 'tRNS';
  546. // Stick each color on - full transparency or none.
  547. for ($i = 0; $i < $colors; $i++)
  548. $tmp .= $i == $this->image->m_nTrans ? "\x00" : "\xFF";
  549. $out .= $tmp . pack('N', smf_crc32($tmp));
  550. }
  551. // Here's the data itself!
  552. $out .= pack('N', strlen($bmp));
  553. $tmp = 'IDAT' . $bmp;
  554. $out .= $tmp . pack('N', smf_crc32($tmp));
  555. // EOF marker...
  556. $out .= "\x00\x00\x00\x00" . 'IEND' . "\xAE\x42\x60\x82";
  557. return $out;
  558. }
  559. }
  560. // 64-bit only functions?
  561. if (!function_exists('smf_crc32'))
  562. {
  563. require_once $sourcedir . '/Subs-Compat.php';
  564. }
  565. ?>