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

/src/GifCreator/GifCreator.php

https://github.com/govaniso/GifCreator
PHP | 357 lines | 181 code | 88 blank | 88 comment | 44 complexity | 1ecf6809f8d7117316343fd281e60fae MD5 | raw file
  1. <?php
  2. namespace GifCreator;
  3. /**
  4. * Create an animated GIF from multiple images
  5. *
  6. * @version 1.0
  7. * @link https://github.com/Sybio/GifCreator
  8. * @author Sybio (ClĂŠment Guillemain / @Sybio01)
  9. * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  10. * @copyright ClĂŠment Guillemain
  11. */
  12. class GifCreator
  13. {
  14. /**
  15. * @var string The gif string source (old: this->GIF)
  16. */
  17. private $gif;
  18. /**
  19. * @var string Encoder version (old: this->VER)
  20. */
  21. private $version;
  22. /**
  23. * @var boolean Check the image is build or not (old: this->IMG)
  24. */
  25. private $imgBuilt;
  26. /**
  27. * @var array Frames string sources (old: this->BUF)
  28. */
  29. private $frameSources;
  30. /**
  31. * @var integer Gif loop (old: this->LOP)
  32. */
  33. private $loop;
  34. /**
  35. * @var integer Gif dis (old: this->DIS)
  36. */
  37. private $dis;
  38. /**
  39. * @var integer Gif color (old: this->COL)
  40. */
  41. private $colour;
  42. /**
  43. * @var array (old: this->ERR)
  44. */
  45. private $errors;
  46. // Methods
  47. // ===================================================================================
  48. /**
  49. * Constructor
  50. */
  51. public function __construct()
  52. {
  53. $this->reset();
  54. // Static data
  55. $this->version = 'GifCreator: Under development';
  56. $this->errors = array(
  57. 'ERR00' => 'Does not supported function for only one image.',
  58. 'ERR01' => 'Source is not a GIF image.',
  59. 'ERR02' => 'You have to give resource image variables, image URL or image binary sources in $frames array.',
  60. 'ERR03' => 'Does not make animation from animated GIF source.',
  61. );
  62. }
  63. /**
  64. * Create the GIF string (old: GIFEncoder)
  65. *
  66. * @param array $frames An array of frame: can be file paths, resource image variables, binary sources or image URLs
  67. * @param array $durations An array containing the duration of each frame
  68. * @param integer $loop Number of GIF loops before stopping animation (Set 0 to get an infinite loop)
  69. *
  70. * @return string The GIF string source
  71. */
  72. public function create($frames = array(), $durations = array(), $loop = 0)
  73. {
  74. if (!is_array($frames) && !is_array($GIF_tim)) {
  75. throw new \Exception($this->version.': '.$this->errors['ERR00']);
  76. }
  77. $this->loop = ($loop > -1) ? $loop : 0;
  78. $this->dis = 2;
  79. for ($i = 0; $i < count($frames); $i++) {
  80. if (is_resource($frames[$i])) { // Resource var
  81. $resourceImg = $frames[$i];
  82. ob_start();
  83. imagegif($frames[$i]);
  84. $this->frameSources[] = ob_get_contents();
  85. ob_end_clean();
  86. } elseif (is_string($frames[$i])) { // File path or URL or Binary source code
  87. if (file_exists($frames[$i]) || filter_var($frames[$i], FILTER_VALIDATE_URL)) { // File path
  88. $frames[$i] = file_get_contents($frames[$i]);
  89. }
  90. $resourceImg = imagecreatefromstring($frames[$i]);
  91. ob_start();
  92. imagegif($resourceImg);
  93. $this->frameSources[] = ob_get_contents();
  94. ob_end_clean();
  95. } else { // Fail
  96. throw new \Exception($this->version.': '.$this->errors['ERR02'].' ('.$mode.')');
  97. }
  98. if ($i == 0) {
  99. $colour = imagecolortransparent($resourceImg);
  100. }
  101. if (substr($this->frameSources[$i], 0, 6) != 'GIF87a' && substr($this->frameSources[$i], 0, 6) != 'GIF89a') {
  102. throw new \Exception($this->version.': '.$i.' '.$this->errors['ERR01']);
  103. }
  104. for ($j = (13 + 3 * (2 << (ord($this->frameSources[$i] { 10 }) & 0x07))), $k = TRUE; $k; $j++) {
  105. switch ($this->frameSources[$i] { $j }) {
  106. case '!':
  107. if ((substr($this->frameSources[$i], ($j + 3), 8)) == 'NETSCAPE') {
  108. throw new \Exception($this->version.': '.$this->errors['ERR03'].' ('.($i + 1).' source).');
  109. }
  110. break;
  111. case ';':
  112. $k = false;
  113. break;
  114. }
  115. }
  116. unset($resourceImg);
  117. }
  118. if (isset($colour)) {
  119. $this->colour = $colour;
  120. } else {
  121. $red = $green = $blue = 0;
  122. $this->colour = ($red > -1 && $green > -1 && $blue > -1) ? ($red | ($green << 8) | ($blue << 16)) : -1;
  123. }
  124. $this->gifAddHeader();
  125. for ($i = 0; $i < count($this->frameSources); $i++) {
  126. $this->addGifFrames($i, $durations[$i]);
  127. }
  128. $this->gifAddFooter();
  129. return $this->gif;
  130. }
  131. // Internals
  132. // ===================================================================================
  133. /**
  134. * Add the header gif string in its source (old: GIFAddHeader)
  135. */
  136. public function gifAddHeader()
  137. {
  138. $cmap = 0;
  139. if (ord($this->frameSources[0] { 10 }) & 0x80) {
  140. $cmap = 3 * (2 << (ord($this->frameSources[0] { 10 }) & 0x07));
  141. $this->gif .= substr($this->frameSources[0], 6, 7);
  142. $this->gif .= substr($this->frameSources[0], 13, $cmap);
  143. $this->gif .= "!\377\13NETSCAPE2.0\3\1".$this->encodeAsciiToChar($this->loop)."\0";
  144. }
  145. }
  146. /**
  147. * Add the frame sources to the GIF string (old: GIFAddFrames)
  148. *
  149. * @param integer $i
  150. * @param integer $d
  151. */
  152. public function addGifFrames($i, $d)
  153. {
  154. $Locals_str = 13 + 3 * (2 << (ord($this->frameSources[ $i ] { 10 }) & 0x07));
  155. $Locals_end = strlen($this->frameSources[$i]) - $Locals_str - 1;
  156. $Locals_tmp = substr($this->frameSources[$i], $Locals_str, $Locals_end);
  157. $Global_len = 2 << (ord($this->frameSources[0 ] { 10 }) & 0x07);
  158. $Locals_len = 2 << (ord($this->frameSources[$i] { 10 }) & 0x07);
  159. $Global_rgb = substr($this->frameSources[0], 13, 3 * (2 << (ord($this->frameSources[0] { 10 }) & 0x07)));
  160. $Locals_rgb = substr($this->frameSources[$i], 13, 3 * (2 << (ord($this->frameSources[$i] { 10 }) & 0x07)));
  161. $Locals_ext = "!\xF9\x04".chr(($this->dis << 2) + 0).chr(($d >> 0 ) & 0xFF).chr(($d >> 8) & 0xFF)."\x0\x0";
  162. if ($this->colour > -1 && ord($this->frameSources[$i] { 10 }) & 0x80) {
  163. for ($j = 0; $j < (2 << (ord($this->frameSources[$i] { 10 } ) & 0x07)); $j++) {
  164. if (ord($Locals_rgb { 3 * $j + 0 }) == (($this->colour >> 16) & 0xFF) &&
  165. ord($Locals_rgb { 3 * $j + 1 }) == (($this->colour >> 8) & 0xFF) &&
  166. ord($Locals_rgb { 3 * $j + 2 }) == (($this->colour >> 0) & 0xFF)
  167. ) {
  168. $Locals_ext = "!\xF9\x04".chr(($this->dis << 2) + 1).chr(($d >> 0) & 0xFF).chr(($d >> 8) & 0xFF).chr($j)."\x0";
  169. break;
  170. }
  171. }
  172. }
  173. switch ($Locals_tmp { 0 }) {
  174. case '!':
  175. $Locals_img = substr($Locals_tmp, 8, 10);
  176. $Locals_tmp = substr($Locals_tmp, 18, strlen($Locals_tmp) - 18);
  177. break;
  178. case ',':
  179. $Locals_img = substr($Locals_tmp, 0, 10);
  180. $Locals_tmp = substr($Locals_tmp, 10, strlen($Locals_tmp) - 10);
  181. break;
  182. }
  183. if (ord($this->frameSources[$i] { 10 }) & 0x80 && $this->imgBuilt) {
  184. if ($Global_len == $Locals_len) {
  185. if ($this->gifBlockCompare($Global_rgb, $Locals_rgb, $Global_len)) {
  186. $this->gif .= $Locals_ext.$Locals_img.$Locals_tmp;
  187. } else {
  188. $byte = ord($Locals_img { 9 });
  189. $byte |= 0x80;
  190. $byte &= 0xF8;
  191. $byte |= (ord($this->frameSources[0] { 10 }) & 0x07);
  192. $Locals_img { 9 } = chr($byte);
  193. $this->gif .= $Locals_ext.$Locals_img.$Locals_rgb.$Locals_tmp;
  194. }
  195. } else {
  196. $byte = ord($Locals_img { 9 });
  197. $byte |= 0x80;
  198. $byte &= 0xF8;
  199. $byte |= (ord($this->frameSources[$i] { 10 }) & 0x07);
  200. $Locals_img { 9 } = chr($byte);
  201. $this->gif .= $Locals_ext.$Locals_img.$Locals_rgb.$Locals_tmp;
  202. }
  203. } else {
  204. $this->gif .= $Locals_ext.$Locals_img.$Locals_tmp;
  205. }
  206. $this->imgBuilt = true;
  207. }
  208. /**
  209. * Add the gif string footer char (old: GIFAddFooter)
  210. */
  211. public function gifAddFooter()
  212. {
  213. $this->gif .= ';';
  214. }
  215. /**
  216. * Compare two block and return the version (old: GIFBlockCompare)
  217. *
  218. * @param string $globalBlock
  219. * @param string $localBlock
  220. * @param integer $length
  221. *
  222. * @return integer
  223. */
  224. public function gifBlockCompare($globalBlock, $localBlock, $length)
  225. {
  226. for ($i = 0; $i < $length; $i++) {
  227. if ($globalBlock { 3 * $i + 0 } != $localBlock { 3 * $i + 0 } ||
  228. $globalBlock { 3 * $i + 1 } != $localBlock { 3 * $i + 1 } ||
  229. $globalBlock { 3 * $i + 2 } != $localBlock { 3 * $i + 2 }) {
  230. return 0;
  231. }
  232. }
  233. return 1;
  234. }
  235. /**
  236. * Encode an ASCII char into a string char (old: GIFWord)
  237. *
  238. * $param integer $char ASCII char
  239. *
  240. * @return string
  241. */
  242. public function encodeAsciiToChar($char)
  243. {
  244. return (chr($char & 0xFF).chr(($char >> 8) & 0xFF));
  245. }
  246. /**
  247. * Reset and clean the current object
  248. */
  249. public function reset()
  250. {
  251. $this->frameSources;
  252. $this->gif = 'GIF89a'; // the GIF header
  253. $this->imgBuilt = false;
  254. $this->loop = 0;
  255. $this->dis = 2;
  256. $this->colour = -1;
  257. }
  258. // Getter / Setter
  259. // ===================================================================================
  260. /**
  261. * Get the final GIF image string (old: GetAnimation)
  262. *
  263. * @return string
  264. */
  265. public function getGif()
  266. {
  267. return $this->gif;
  268. }
  269. }