From be0cc1ccece504c0a1b41ff9544d0063d323d243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20H=C3=B6gberg?= Date: Thu, 3 Apr 2014 14:02:13 +0200 Subject: [PATCH 1/5] Update documentation: * Adapt to GifCreator namespace * Frame interval is in centiseconds, not milliseconds --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 847eb0d..485ce5b 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This class helps you to create an animated GIF image: give multiple images and t **1 - Creation:** ```php -// Create an array containing file paths, resource var (initialized with imagecreatefromXXX), +// Create an array containing file paths, resource var (initialized with imagecreatefromXXX), // image URLs or even binary code from image files. // All sorted in order to appear. $frames = array( @@ -23,11 +23,11 @@ $frames = array( 'http://thisisafakedomain.com/images/pic4.jpg', // URL ); -// Create an array containing the duration (in millisecond) of each frames (in order too) +// Create an array containing the duration (in centiseconds (hundredths of a second)) of each frames (in order too) $durations = array(40, 80, 40, 20); // Initialize and create the GIF ! -$gc = new GifCreator(); +$gc = new GifCreator\GifCreator(); $gc->create($frames, $durations, 5); ``` The 3rd parameter of create() method allows you to choose the number of loop of your animated gif before it stops. @@ -59,9 +59,9 @@ file_put_contents('/myfolder/animated_picture.gif', $gifBinary); ### Behavior - The transparency is based on the first given frame. It will be saved only if you give multiple frames with same transparent background. -- The dimensions of the generated GIF are based on the first frame. If you need to resize your frames to get the same dimension, you can use +- The dimensions of the generated GIF are based on the first frame. If you need to resize your frames to get the same dimension, you can use this class: https://github.com/Sybio/ImageWorkshop ### About -The class reuses some part of code of "GIFEncoder.class.php" by László Zsidi (thanks to him). \ No newline at end of file +The class reuses some part of code of "GIFEncoder.class.php" by László Zsidi (thanks to him). From 552034f1d24f2dcef01e16375188a42e56a5ec51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20H=C3=B6gberg?= Date: Thu, 3 Apr 2014 14:13:46 +0200 Subject: [PATCH 2/5] Consistent indentation (space instead of tabs), remove trailing whitespace. --- src/GifCreator/GifCreator.php | 448 +++++++++++++++++----------------- 1 file changed, 224 insertions(+), 224 deletions(-) diff --git a/src/GifCreator/GifCreator.php b/src/GifCreator/GifCreator.php index 0726243..b0cdfa5 100644 --- a/src/GifCreator/GifCreator.php +++ b/src/GifCreator/GifCreator.php @@ -4,7 +4,7 @@ /** * Create an animated GIF from multiple images - * + * * @version 1.0 * @link https://github.com/Sybio/GifCreator * @author Sybio (Clément Guillemain / @Sybio01) @@ -17,12 +17,12 @@ class GifCreator * @var string The gif string source (old: this->GIF) */ private $gif; - + /** * @var string Encoder version (old: this->VER) */ - private $version; - + private $version; + /** * @var boolean Check the image is build or not (old: this->IMG) */ @@ -31,304 +31,304 @@ class GifCreator /** * @var array Frames string sources (old: this->BUF) */ - private $frameSources; - + private $frameSources; + /** * @var integer Gif loop (old: this->LOP) */ - private $loop; - + private $loop; + /** * @var integer Gif dis (old: this->DIS) */ - private $dis; - + private $dis; + /** * @var integer Gif color (old: this->COL) */ - private $colour; - + private $colour; + /** * @var array (old: this->ERR) */ - private $errors; - + private $errors; + // Methods // =================================================================================== - + /** * Constructor */ public function __construct() { $this->reset(); - + // Static data $this->version = 'GifCreator: Under development'; $this->errors = array( 'ERR00' => 'Does not supported function for only one image.', - 'ERR01' => 'Source is not a GIF image.', - 'ERR02' => 'You have to give resource image variables, image URL or image binary sources in $frames array.', - 'ERR03' => 'Does not make animation from animated GIF source.', + 'ERR01' => 'Source is not a GIF image.', + 'ERR02' => 'You have to give resource image variables, image URL or image binary sources in $frames array.', + 'ERR03' => 'Does not make animation from animated GIF source.', ); } - /** + /** * Create the GIF string (old: GIFEncoder) - * + * * @param array $frames An array of frame: can be file paths, resource image variables, binary sources or image URLs * @param array $durations An array containing the duration of each frame * @param integer $loop Number of GIF loops before stopping animation (Set 0 to get an infinite loop) - * + * * @return string The GIF string source */ - public function create($frames = array(), $durations = array(), $loop = 0) + public function create($frames = array(), $durations = array(), $loop = 0) { - if (!is_array($frames) && !is_array($GIF_tim)) { - + if (!is_array($frames) && !is_array($GIF_tim)) { + throw new \Exception($this->version.': '.$this->errors['ERR00']); - } - - $this->loop = ($loop > -1) ? $loop : 0; - $this->dis = 2; - - for ($i = 0; $i < count($frames); $i++) { - - if (is_resource($frames[$i])) { // Resource var - + } + + $this->loop = ($loop > -1) ? $loop : 0; + $this->dis = 2; + + for ($i = 0; $i < count($frames); $i++) { + + if (is_resource($frames[$i])) { // Resource var + $resourceImg = $frames[$i]; - + ob_start(); imagegif($frames[$i]); $this->frameSources[] = ob_get_contents(); ob_end_clean(); - + } elseif (is_string($frames[$i])) { // File path or URL or Binary source code - + if (file_exists($frames[$i]) || filter_var($frames[$i], FILTER_VALIDATE_URL)) { // File path - - $frames[$i] = file_get_contents($frames[$i]); + + $frames[$i] = file_get_contents($frames[$i]); } - + $resourceImg = imagecreatefromstring($frames[$i]); - + ob_start(); imagegif($resourceImg); $this->frameSources[] = ob_get_contents(); ob_end_clean(); - - } else { // Fail - + + } else { // Fail + throw new \Exception($this->version.': '.$this->errors['ERR02'].' ('.$mode.')'); - } - + } + if ($i == 0) { - + $colour = imagecolortransparent($resourceImg); } - - if (substr($this->frameSources[$i], 0, 6) != 'GIF87a' && substr($this->frameSources[$i], 0, 6) != 'GIF89a') { - + + if (substr($this->frameSources[$i], 0, 6) != 'GIF87a' && substr($this->frameSources[$i], 0, 6) != 'GIF89a') { + throw new \Exception($this->version.': '.$i.' '.$this->errors['ERR01']); - } - - for ($j = (13 + 3 * (2 << (ord($this->frameSources[$i] { 10 }) & 0x07))), $k = TRUE; $k; $j++) { - - switch ($this->frameSources[$i] { $j }) { - - case '!': - - if ((substr($this->frameSources[$i], ($j + 3), 8)) == 'NETSCAPE') { - + } + + for ($j = (13 + 3 * (2 << (ord($this->frameSources[$i] { 10 }) & 0x07))), $k = TRUE; $k; $j++) { + + switch ($this->frameSources[$i] { $j }) { + + case '!': + + if ((substr($this->frameSources[$i], ($j + 3), 8)) == 'NETSCAPE') { + throw new \Exception($this->version.': '.$this->errors['ERR03'].' ('.($i + 1).' source).'); - } - - break; - - case ';': - - $k = false; - break; - } - } - + } + + break; + + case ';': + + $k = false; + break; + } + } + unset($resourceImg); - } - + } + if (isset($colour)) { - + $this->colour = $colour; - + } else { - + $red = $green = $blue = 0; $this->colour = ($red > -1 && $green > -1 && $blue > -1) ? ($red | ($green << 8) | ($blue << 16)) : -1; } - - $this->gifAddHeader(); - - for ($i = 0; $i < count($this->frameSources); $i++) { - - $this->addGifFrames($i, $durations[$i]); - } - - $this->gifAddFooter(); - + + $this->gifAddHeader(); + + for ($i = 0; $i < count($this->frameSources); $i++) { + + $this->addGifFrames($i, $durations[$i]); + } + + $this->gifAddFooter(); + return $this->gif; - } - + } + // Internals // =================================================================================== - - /** + + /** * Add the header gif string in its source (old: GIFAddHeader) */ - public function gifAddHeader() + public function gifAddHeader() { - $cmap = 0; - - if (ord($this->frameSources[0] { 10 }) & 0x80) { - - $cmap = 3 * (2 << (ord($this->frameSources[0] { 10 }) & 0x07)); - - $this->gif .= substr($this->frameSources[0], 6, 7); - $this->gif .= substr($this->frameSources[0], 13, $cmap); - $this->gif .= "!\377\13NETSCAPE2.0\3\1".$this->encodeAsciiToChar($this->loop)."\0"; - } - } - - /** + $cmap = 0; + + if (ord($this->frameSources[0] { 10 }) & 0x80) { + + $cmap = 3 * (2 << (ord($this->frameSources[0] { 10 }) & 0x07)); + + $this->gif .= substr($this->frameSources[0], 6, 7); + $this->gif .= substr($this->frameSources[0], 13, $cmap); + $this->gif .= "!\377\13NETSCAPE2.0\3\1".$this->encodeAsciiToChar($this->loop)."\0"; + } + } + + /** * Add the frame sources to the GIF string (old: GIFAddFrames) - * + * * @param integer $i * @param integer $d */ - public function addGifFrames($i, $d) + public function addGifFrames($i, $d) { - $Locals_str = 13 + 3 * (2 << (ord($this->frameSources[ $i ] { 10 }) & 0x07)); - - $Locals_end = strlen($this->frameSources[$i]) - $Locals_str - 1; - $Locals_tmp = substr($this->frameSources[$i], $Locals_str, $Locals_end); - - $Global_len = 2 << (ord($this->frameSources[0 ] { 10 }) & 0x07); - $Locals_len = 2 << (ord($this->frameSources[$i] { 10 }) & 0x07); - - $Global_rgb = substr($this->frameSources[0], 13, 3 * (2 << (ord($this->frameSources[0] { 10 }) & 0x07))); - $Locals_rgb = substr($this->frameSources[$i], 13, 3 * (2 << (ord($this->frameSources[$i] { 10 }) & 0x07))); - - $Locals_ext = "!\xF9\x04".chr(($this->dis << 2) + 0).chr(($d >> 0 ) & 0xFF).chr(($d >> 8) & 0xFF)."\x0\x0"; - - if ($this->colour > -1 && ord($this->frameSources[$i] { 10 }) & 0x80) { - - for ($j = 0; $j < (2 << (ord($this->frameSources[$i] { 10 } ) & 0x07)); $j++) { - - if (ord($Locals_rgb { 3 * $j + 0 }) == (($this->colour >> 16) & 0xFF) && - ord($Locals_rgb { 3 * $j + 1 }) == (($this->colour >> 8) & 0xFF) && - ord($Locals_rgb { 3 * $j + 2 }) == (($this->colour >> 0) & 0xFF) - ) { - $Locals_ext = "!\xF9\x04".chr(($this->dis << 2) + 1).chr(($d >> 0) & 0xFF).chr(($d >> 8) & 0xFF).chr($j)."\x0"; - break; - } - } - } - - switch ($Locals_tmp { 0 }) { - - case '!': - - $Locals_img = substr($Locals_tmp, 8, 10); - $Locals_tmp = substr($Locals_tmp, 18, strlen($Locals_tmp) - 18); - - break; - - case ',': - - $Locals_img = substr($Locals_tmp, 0, 10); - $Locals_tmp = substr($Locals_tmp, 10, strlen($Locals_tmp) - 10); - - break; - } - - if (ord($this->frameSources[$i] { 10 }) & 0x80 && $this->imgBuilt) { - - if ($Global_len == $Locals_len) { - - if ($this->gifBlockCompare($Global_rgb, $Locals_rgb, $Global_len)) { - - $this->gif .= $Locals_ext.$Locals_img.$Locals_tmp; - - } else { - - $byte = ord($Locals_img { 9 }); - $byte |= 0x80; - $byte &= 0xF8; - $byte |= (ord($this->frameSources[0] { 10 }) & 0x07); - $Locals_img { 9 } = chr($byte); - $this->gif .= $Locals_ext.$Locals_img.$Locals_rgb.$Locals_tmp; - } - - } else { - - $byte = ord($Locals_img { 9 }); - $byte |= 0x80; - $byte &= 0xF8; - $byte |= (ord($this->frameSources[$i] { 10 }) & 0x07); - $Locals_img { 9 } = chr($byte); - $this->gif .= $Locals_ext.$Locals_img.$Locals_rgb.$Locals_tmp; - } - - } else { - - $this->gif .= $Locals_ext.$Locals_img.$Locals_tmp; - } - - $this->imgBuilt = true; - } - - /** + $Locals_str = 13 + 3 * (2 << (ord($this->frameSources[ $i ] { 10 }) & 0x07)); + + $Locals_end = strlen($this->frameSources[$i]) - $Locals_str - 1; + $Locals_tmp = substr($this->frameSources[$i], $Locals_str, $Locals_end); + + $Global_len = 2 << (ord($this->frameSources[0 ] { 10 }) & 0x07); + $Locals_len = 2 << (ord($this->frameSources[$i] { 10 }) & 0x07); + + $Global_rgb = substr($this->frameSources[0], 13, 3 * (2 << (ord($this->frameSources[0] { 10 }) & 0x07))); + $Locals_rgb = substr($this->frameSources[$i], 13, 3 * (2 << (ord($this->frameSources[$i] { 10 }) & 0x07))); + + $Locals_ext = "!\xF9\x04".chr(($this->dis << 2) + 0).chr(($d >> 0 ) & 0xFF).chr(($d >> 8) & 0xFF)."\x0\x0"; + + if ($this->colour > -1 && ord($this->frameSources[$i] { 10 }) & 0x80) { + + for ($j = 0; $j < (2 << (ord($this->frameSources[$i] { 10 } ) & 0x07)); $j++) { + + if (ord($Locals_rgb { 3 * $j + 0 }) == (($this->colour >> 16) & 0xFF) && + ord($Locals_rgb { 3 * $j + 1 }) == (($this->colour >> 8) & 0xFF) && + ord($Locals_rgb { 3 * $j + 2 }) == (($this->colour >> 0) & 0xFF) + ) { + $Locals_ext = "!\xF9\x04".chr(($this->dis << 2) + 1).chr(($d >> 0) & 0xFF).chr(($d >> 8) & 0xFF).chr($j)."\x0"; + break; + } + } + } + + switch ($Locals_tmp { 0 }) { + + case '!': + + $Locals_img = substr($Locals_tmp, 8, 10); + $Locals_tmp = substr($Locals_tmp, 18, strlen($Locals_tmp) - 18); + + break; + + case ',': + + $Locals_img = substr($Locals_tmp, 0, 10); + $Locals_tmp = substr($Locals_tmp, 10, strlen($Locals_tmp) - 10); + + break; + } + + if (ord($this->frameSources[$i] { 10 }) & 0x80 && $this->imgBuilt) { + + if ($Global_len == $Locals_len) { + + if ($this->gifBlockCompare($Global_rgb, $Locals_rgb, $Global_len)) { + + $this->gif .= $Locals_ext.$Locals_img.$Locals_tmp; + + } else { + + $byte = ord($Locals_img { 9 }); + $byte |= 0x80; + $byte &= 0xF8; + $byte |= (ord($this->frameSources[0] { 10 }) & 0x07); + $Locals_img { 9 } = chr($byte); + $this->gif .= $Locals_ext.$Locals_img.$Locals_rgb.$Locals_tmp; + } + + } else { + + $byte = ord($Locals_img { 9 }); + $byte |= 0x80; + $byte &= 0xF8; + $byte |= (ord($this->frameSources[$i] { 10 }) & 0x07); + $Locals_img { 9 } = chr($byte); + $this->gif .= $Locals_ext.$Locals_img.$Locals_rgb.$Locals_tmp; + } + + } else { + + $this->gif .= $Locals_ext.$Locals_img.$Locals_tmp; + } + + $this->imgBuilt = true; + } + + /** * Add the gif string footer char (old: GIFAddFooter) */ - public function gifAddFooter() + public function gifAddFooter() { - $this->gif .= ';'; - } - - /** + $this->gif .= ';'; + } + + /** * Compare two block and return the version (old: GIFBlockCompare) - * + * * @param string $globalBlock * @param string $localBlock * @param integer $length - * + * * @return integer - */ - public function gifBlockCompare($globalBlock, $localBlock, $length) + */ + public function gifBlockCompare($globalBlock, $localBlock, $length) { - for ($i = 0; $i < $length; $i++) { - - if ($globalBlock { 3 * $i + 0 } != $localBlock { 3 * $i + 0 } || - $globalBlock { 3 * $i + 1 } != $localBlock { 3 * $i + 1 } || - $globalBlock { 3 * $i + 2 } != $localBlock { 3 * $i + 2 }) { - + for ($i = 0; $i < $length; $i++) { + + if ($globalBlock { 3 * $i + 0 } != $localBlock { 3 * $i + 0 } || + $globalBlock { 3 * $i + 1 } != $localBlock { 3 * $i + 1 } || + $globalBlock { 3 * $i + 2 } != $localBlock { 3 * $i + 2 }) { + return 0; - } - } + } + } + + return 1; + } - return 1; - } - - /** + /** * Encode an ASCII char into a string char (old: GIFWord) - * + * * $param integer $char ASCII char - * + * * @return string - */ - public function encodeAsciiToChar($char) + */ + public function encodeAsciiToChar($char) { - return (chr($char & 0xFF).chr(($char >> 8) & 0xFF)); - } - + return (chr($char & 0xFF).chr(($char >> 8) & 0xFF)); + } + /** * Reset and clean the current object */ @@ -341,17 +341,17 @@ public function reset() $this->dis = 2; $this->colour = -1; } - + // Getter / Setter // =================================================================================== - - /** + + /** * Get the final GIF image string (old: GetAnimation) - * + * * @return string - */ - public function getGif() + */ + public function getGif() { - return $this->gif; - } -} \ No newline at end of file + return $this->gif; + } +} From 433f9a43eac213c6d80e742a51cfbd49c912d9a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20H=C3=B6gberg?= Date: Thu, 3 Apr 2014 14:14:46 +0200 Subject: [PATCH 3/5] Fix invalid variable name --- src/GifCreator/GifCreator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GifCreator/GifCreator.php b/src/GifCreator/GifCreator.php index b0cdfa5..dfc2eca 100644 --- a/src/GifCreator/GifCreator.php +++ b/src/GifCreator/GifCreator.php @@ -84,7 +84,7 @@ public function __construct() */ public function create($frames = array(), $durations = array(), $loop = 0) { - if (!is_array($frames) && !is_array($GIF_tim)) { + if (!is_array($frames) && !is_array($durations)) { throw new \Exception($this->version.': '.$this->errors['ERR00']); } From 893f295278b8b435c9c9ad275b0f335372c1bb6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20H=C3=B6gberg?= Date: Thu, 3 Apr 2014 14:19:29 +0200 Subject: [PATCH 4/5] Update documentation. --- src/GifCreator/GifCreator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GifCreator/GifCreator.php b/src/GifCreator/GifCreator.php index dfc2eca..2bc4a1f 100644 --- a/src/GifCreator/GifCreator.php +++ b/src/GifCreator/GifCreator.php @@ -66,7 +66,7 @@ public function __construct() // Static data $this->version = 'GifCreator: Under development'; $this->errors = array( - 'ERR00' => 'Does not supported function for only one image.', + 'ERR00' => 'Does not support single image/duration input.', 'ERR01' => 'Source is not a GIF image.', 'ERR02' => 'You have to give resource image variables, image URL or image binary sources in $frames array.', 'ERR03' => 'Does not make animation from animated GIF source.', @@ -77,7 +77,7 @@ public function __construct() * Create the GIF string (old: GIFEncoder) * * @param array $frames An array of frame: can be file paths, resource image variables, binary sources or image URLs - * @param array $durations An array containing the duration of each frame + * @param array $durations An array containing the duration of each frame (in centiseconds) * @param integer $loop Number of GIF loops before stopping animation (Set 0 to get an infinite loop) * * @return string The GIF string source From e5356411bc09527e2d4494f2a1a69c23e673ab63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20H=C3=B6gberg?= Date: Thu, 24 Apr 2014 15:40:48 +0200 Subject: [PATCH 5/5] When the user wants the animated GIF to not loop ($loop == 1) then browsers seem to interpret this differently. Chrome will play animation twice while Firefox will play animation once. To work around this strangeness we can remove the loop count completely from the GIF and this seems to be interpreted as "play once only" by all browsers. --- src/GifCreator/GifCreator.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/GifCreator/GifCreator.php b/src/GifCreator/GifCreator.php index 2bc4a1f..67a8f4c 100644 --- a/src/GifCreator/GifCreator.php +++ b/src/GifCreator/GifCreator.php @@ -193,7 +193,13 @@ public function gifAddHeader() $this->gif .= substr($this->frameSources[0], 6, 7); $this->gif .= substr($this->frameSources[0], 13, $cmap); - $this->gif .= "!\377\13NETSCAPE2.0\3\1".$this->encodeAsciiToChar($this->loop)."\0"; + if ($this->loop != 1) { + // Browsers interpret loop count differently, but for loop == 1 + // we can work around this by excluding loop count completely + // since all browsers seem to handle "no loop count" as + // "play once only". + $this->gif .= "!\377\13NETSCAPE2.0\3\1".$this->encodeAsciiToChar($this->loop)."\0"; + } } }