PageRenderTime 52ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/GifMagic/Encoder.php

https://bitbucket.org/webvariants/gifmagic
PHP | 258 lines | 168 code | 48 blank | 42 comment | 37 complexity | 076374f8d9b3410f59aea6ca3df3c09f MD5 | raw file
  1. <?php
  2. /*
  3. * Copyright (c) 2011, webvariants GbR, http://www.webvariants.de
  4. *
  5. * This file is released under the terms of the MIT license. You can find the
  6. * complete text in the attached LICENSE file or online at:
  7. *
  8. * http://www.opensource.org/licenses/mit-license.php
  9. *
  10. * based on GIFEncoder Version 3.0 by Lรกszlรณ Zsidi
  11. * http://www.phpclasses.org/package/3163-PHP-Generate-GIF-animations-from-a-set-of-GIF-images.html
  12. * http://www.phpclasses.org/package/3234-PHP-Split-GIF-animations-into-multiple-images.html
  13. * http://www.gifs.hu
  14. */
  15. /**
  16. * @author robert.koppsieker@webvariants.de
  17. */
  18. class GifMagic_Encoder {
  19. private $result = 'GIF89a'; // GIF header 6 bytes
  20. private $buffers = array();
  21. private $OFS = array();
  22. private $SIG = 0;
  23. private $LOP = 0;
  24. private $DIS = 2;
  25. private $COL = -1;
  26. private $IMG = -1;
  27. private $ERR = array(
  28. 'ERR01' => 'Source is not a GIF image!',
  29. 'ERR02' => 'Unintelligible flag',
  30. 'ERR03' => 'Does not make animation from animated GIF source',
  31. );
  32. public function __construct(array $sources, array $result_dly, $loop, $disposables,
  33. $red, $green, $blue, $offsets, $sourceMode) {
  34. $this->LOP = $loop === false ? false : (($loop > -1) ? $loop : 0);
  35. $this->COL = ($red > -1 && $green > -1 && $blue > -1) ?
  36. ($red | ($green << 8) | ($blue << 16)) : -1;
  37. $sourceMode = strtolower($sourceMode);
  38. if (!in_array($sourceMode, array('url', 'bin'))) {
  39. throw new GifMagic_Exception($this->ERR['ERR02']." ($sourceMode)", 2);
  40. }
  41. for ($i = 0; $i < count ($sources); $i++) {
  42. if ($sourceMode === 'url') {
  43. $this->buffers[] = file_get_contents($sources[$i]);
  44. }
  45. else {
  46. $this->buffers[] = $sources [$i];
  47. }
  48. if (strlen($this->buffers[$i]) < 6) {
  49. throw new GifMagic_Exception('Invalid file given (file smaller than GIF signature).');
  50. }
  51. $sig = substr($this->buffers[$i], 0, 6);
  52. if ($sig !== 'GIF87a' && $sig !== 'GIF89a') {
  53. throw new GifMagic_Exception($this->ERR['ERR01'], 1);
  54. }
  55. for ($j = $this->getLocalsStrLength($i), $k = true; $k; $j++) {
  56. switch ($this->buffers[$i][$j]) {
  57. case '!':
  58. if ((substr($this->buffers[$i], ($j + 3), 8)) === 'NETSCAPE') {
  59. throw new GifMagic_Exception(sprintf('%s (%d source)', $this->ERR['ERR03'], $i+1), 3);
  60. }
  61. break;
  62. case ';':
  63. $k = false;
  64. break;
  65. }
  66. }
  67. }
  68. if (!is_array($offsets)) {
  69. $offsets = array();
  70. }
  71. for ($i = 0; $i < count($this->buffers); $i++) {
  72. if (!isset($disposables[$i])) $disposables[$i] = $this->DIS;
  73. else $disposables[$i] = ($disposables[$i] > -1) ? (($disposables[$i] < 3) ? $disposables[$i] : 3) : 2;
  74. if (!isset($offsets[$i])) $offsets[$i] = 0;
  75. }
  76. if (count($offsets) > 1) {
  77. $this->SIG = 1;
  78. $this->OFS = $offsets;
  79. }
  80. $this->addHeader();
  81. for ($i = 0; $i < count($this->buffers); $i++) {
  82. $this->addFrames($i, $result_dly [$i], $disposables[$i]);
  83. }
  84. $this->addFooter();
  85. }
  86. private function addHeader() {
  87. $cmap = 0;
  88. if (ord($this->buffers[0][10]) & 0x80) {
  89. $cmap = $this->getColorTableLength(0);
  90. $this->result .= substr($this->buffers[0], 6, 7);
  91. $this->result .= substr($this->buffers[0], 13, $cmap);
  92. if ($this->LOP !== false) {
  93. $this->result .= "!\377\13NETSCAPE2.0\3\1".$this->intToWord($this->LOP)."\0";
  94. }
  95. }
  96. }
  97. /**
  98. *
  99. * @param int $delay delay in seconds to next image
  100. * @param int $disposal disposal method type (0-7)
  101. * @param int $transpColorIndex color index for transparency
  102. * @return string in hex code
  103. */
  104. private function getGraphicalControlExtension($delay, $disposal, $transpColorIndex = null) {
  105. $transpFlag = 0;
  106. if (!is_int($transpColorIndex) || $transpColorIndex < 0) $transpColorIndex = 0;
  107. else $transpFlag = 1;
  108. return "\x21" // extension introducer (always 21)
  109. . "\xF9" // graphic control label (F9 means graphic control extension)
  110. . "\x04" // block size (fixed value 4)
  111. . chr( // packed fields
  112. ($disposal << 2) // disposal method
  113. + $transpFlag // transparency flag
  114. )
  115. . chr(($delay >> 0) & 0xFF) // delay time
  116. . chr(($delay >> 8) & 0xFF) // delay time
  117. . chr($transpColorIndex) // transparent color index
  118. . "\x0"; // block terminator (always zero)
  119. }
  120. private function getColorLength($imageNumber) {
  121. return 2 << (ord($this->buffers[$imageNumber][10]) & 0x07);
  122. }
  123. private function getColorTableLength($imageNumber) {
  124. return 3 * $this->getColorLength($imageNumber);
  125. }
  126. private function getLocalsStrLength($imageNumber) {
  127. return 13 + $this->getColorTableLength($imageNumber);
  128. }
  129. private function getXYPadding($i, $Locals_ext, $Locals_img, $Locals_rgb, $Locals_tmp, $local=true) {
  130. if ($this->SIG == 1) {
  131. $Locals_img[1] = chr( $this->OFS[$i][0] & 0xFF);
  132. $Locals_img[2] = chr(($this->OFS[$i][0] & 0xFF00) >> 8);
  133. $Locals_img[3] = chr( $this->OFS[$i][1] & 0xFF);
  134. $Locals_img[4] = chr(($this->OFS[$i][1] & 0xFF00) >> 8);
  135. }
  136. $byte = ord($Locals_img[9]);
  137. $byte |= 0x80;
  138. $byte &= 0xF8;
  139. if ($local) $byte |= (ord($this->buffers[$i][10]) & 0x07);
  140. else $byte |= (ord($this->buffers[0] [10]) & 0x07);
  141. $Locals_img[9] = chr($byte);
  142. return $Locals_ext.$Locals_img.$Locals_rgb.$Locals_tmp;
  143. }
  144. private function addFrames($i, $d, $disposal) {
  145. $Locals_str = $this->getLocalsStrLength($i);
  146. $Locals_end = strlen($this->buffers[$i]) - $Locals_str - 1;
  147. $Locals_tmp = substr($this->buffers[$i], $Locals_str, $Locals_end);
  148. $Global_len = $this->getColorLength(0);
  149. $Locals_len = $this->getColorLength($i);
  150. $Global_rgb = substr($this->buffers[0], 13, $this->getColorTableLength(0));
  151. $Locals_rgb = substr($this->buffers[$i], 13, $this->getColorTableLength($i));
  152. // first frame
  153. $transpColorIndex = ord(substr($this->buffers[$i], $Locals_str+6, 1));
  154. $Locals_ext = $this->getGraphicalControlExtension($d, $disposal, $transpColorIndex);
  155. // if ($this->COL > -1 && ord($this->buffers[$i][10]) & 0x80) {
  156. // print 'maximum color index: '.$this->getColorLength($i).'<br>';
  157. // for ($j = 0; $j < $this->getColorLength($i); $j++) {
  158. //
  159. // if (ord($Locals_rgb[3 * $j + 0]) == (($this->COL >> 16) & 0xFF)
  160. // && ord($Locals_rgb[3 * $j + 1]) == (($this->COL >> 8) & 0xFF)
  161. // && ord($Locals_rgb[3 * $j + 2]) == (($this->COL >> 0) & 0xFF)) {
  162. //
  163. // $Locals_ext = $this->getGraphicalControlExtension($d, $disposal, $j);
  164. // break;
  165. // }
  166. // }
  167. // // hack to get index 1 transparent
  168. // if ($i > 0) $Locals_ext = $this->getGraphicalControlExtension($d, $disposal, 1);
  169. // }
  170. switch ($Locals_tmp[0]) {
  171. case '!':
  172. $Locals_img = substr($Locals_tmp, 8, 10);
  173. $Locals_tmp = substr($Locals_tmp, 18, strlen($Locals_tmp) - 18);
  174. break;
  175. case ',':
  176. $Locals_img = substr($Locals_tmp, 0, 10);
  177. $Locals_tmp = substr($Locals_tmp, 10, strlen($Locals_tmp) - 10);
  178. break;
  179. }
  180. if (ord($this->buffers[$i][10]) & 0x80 && $this->IMG > -1) {
  181. // global and local color table have same length
  182. if ($Global_len == $Locals_len) {
  183. // global and local color table are equal
  184. if ($Global_rgb === $Locals_rgb) {
  185. $this->result .= $Locals_ext.$Locals_img.$Locals_tmp;
  186. }
  187. else {
  188. $this->result .= $this->getXYPadding($i, $Locals_ext, $Locals_img, $Locals_rgb, $Locals_tmp, false);
  189. }
  190. }
  191. // special local color table
  192. else {
  193. $this->result .= $this->getXYPadding($i, $Locals_ext, $Locals_img, $Locals_rgb, $Locals_tmp);
  194. }
  195. }
  196. else {
  197. $this->result .= $Locals_ext.$Locals_img.$Locals_tmp;
  198. }
  199. $this->IMG = 1;
  200. }
  201. private function addFooter() {
  202. $this->result .= ';';
  203. }
  204. private function intToWord($int) {
  205. return chr($int & 0xFF).chr(($int >> 8) & 0xFF);
  206. }
  207. public function getAnimation() {
  208. return $this->result;
  209. }
  210. }