Last active
July 8, 2016 15:04
-
-
Save jasondavis/ab7c71fc1a007c3a31282bb9791cecff to your computer and use it in GitHub Desktop.
PHP GifFrameExtractor Demo ccomparing different Frame extraction quality results
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace GifFrameExtractor; | |
/** | |
* Extract the frames (and their duration) of a GIF | |
* | |
* @version 1.5 | |
* @link https://github.com/Sybio/GifFrameExtractor | |
* @author Sybio (Clément Guillemain / @Sybio01) | |
* @license http://opensource.org/licenses/gpl-license.php GNU Public License | |
* @copyright Clément Guillemain | |
*/ | |
class GifFrameExtractor | |
{ | |
// Properties | |
// =================================================================================== | |
/** | |
* @var resource | |
*/ | |
private $gif; | |
/** | |
* @var array | |
*/ | |
private $frames; | |
/** | |
* @var array | |
*/ | |
private $frameDurations; | |
/** | |
* @var array | |
*/ | |
private $frameImages; | |
/** | |
* @var array | |
*/ | |
private $framePositions; | |
/** | |
* @var array | |
*/ | |
private $frameDimensions; | |
/** | |
* @var integer | |
* | |
* (old: $this->index) | |
*/ | |
private $frameNumber; | |
/** | |
* @var array | |
* | |
* (old: $this->imagedata) | |
*/ | |
private $frameSources; | |
/** | |
* @var array | |
* | |
* (old: $this->fileHeader) | |
*/ | |
private $fileHeader; | |
/** | |
* @var integer The reader pointer in the file source | |
* | |
* (old: $this->pointer) | |
*/ | |
private $pointer; | |
/** | |
* @var integer | |
*/ | |
private $gifMaxWidth; | |
/** | |
* @var integer | |
*/ | |
private $gifMaxHeight; | |
/** | |
* @var integer | |
*/ | |
private $totalDuration; | |
/** | |
* @var integer | |
*/ | |
private $handle; | |
/** | |
* @var array | |
* | |
* (old: globaldata) | |
*/ | |
private $globaldata; | |
/** | |
* @var array | |
* | |
* (old: orgvars) | |
*/ | |
private $orgvars; | |
// Methods | |
// =================================================================================== | |
/** | |
* Extract frames of a GIF | |
* | |
* @param string $filename GIF filename path | |
* @param boolean $originalFrames Get original frames (with transparent background) | |
* | |
* @return array | |
*/ | |
public function extract($filename, $originalFrames = false) | |
{ | |
if (!self::isAnimatedGif($filename)) { | |
throw new \Exception('The GIF image you are trying to explode is not animated !'); | |
} | |
$this->reset(); | |
$this->parseFramesInfo($filename); | |
$prevImg = null; | |
for ($i = 0; $i < count($this->frameSources); $i++) { | |
$this->frames[$i] = array(); | |
$this->frameDurations[$i] = $this->frames[$i]['duration'] = $this->frameSources[$i]['delay_time']; | |
$img = imagecreatefromstring($this->fileHeader["gifheader"].$this->frameSources[$i]["graphicsextension"].$this->frameSources[$i]["imagedata"].chr(0x3b)); | |
if (!$originalFrames) { | |
if ($i > 0) { | |
$prevImg = $this->frames[$i - 1]['image']; | |
} else { | |
$prevImg = $img; | |
} | |
$sprite = imagecreate($this->gifMaxWidth, $this->gifMaxHeight); | |
imagesavealpha($sprite, true); | |
$transparent = imagecolortransparent($prevImg); | |
if ($transparent > -1 && imagecolorstotal($prevImg) > $transparent) { | |
$actualTrans = imagecolorsforindex($prevImg, $transparent); | |
imagecolortransparent($sprite, imagecolorallocate($sprite, $actualTrans['red'], $actualTrans['green'], $actualTrans['blue'])); | |
} | |
if ((int) $this->frameSources[$i]['disposal_method'] == 1 && $i > 0) { | |
imagecopy($sprite, $prevImg, 0, 0, 0, 0, $this->gifMaxWidth, $this->gifMaxHeight); | |
} | |
imagecopyresampled($sprite, $img, $this->frameSources[$i]["offset_left"], $this->frameSources[$i]["offset_top"], 0, 0, $this->gifMaxWidth, $this->gifMaxHeight, $this->gifMaxWidth, $this->gifMaxHeight); | |
$img = $sprite; | |
} | |
$this->frameImages[$i] = $this->frames[$i]['image'] = $img; | |
} | |
return $this->frames; | |
} | |
/** | |
* Check if a GIF file at a path is animated or not | |
* | |
* @param string $filename GIF path | |
*/ | |
public static function isAnimatedGif($filename) | |
{ | |
if (!($fh = @fopen($filename, 'rb'))) { | |
return false; | |
} | |
$count = 0; | |
while (!feof($fh) && $count < 2) { | |
$chunk = fread($fh, 1024 * 100); //read 100kb at a time | |
$count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches); | |
} | |
fclose($fh); | |
return $count > 1; | |
} | |
// Internals | |
// =================================================================================== | |
/** | |
* Parse the frame informations contained in the GIF file | |
* | |
* @param string $filename GIF filename path | |
*/ | |
private function parseFramesInfo($filename) | |
{ | |
$this->openFile($filename); | |
$this->parseGifHeader(); | |
$this->parseGraphicsExtension(0); | |
$this->getApplicationData(); | |
$this->getApplicationData(); | |
$this->getFrameString(0); | |
$this->parseGraphicsExtension(1); | |
$this->getCommentData(); | |
$this->getApplicationData(); | |
$this->getFrameString(1); | |
while (!$this->checkByte(0x3b) && !$this->checkEOF()) { | |
$this->getCommentData(1); | |
$this->parseGraphicsExtension(2); | |
$this->getFrameString(2); | |
$this->getApplicationData(); | |
} | |
} | |
/** | |
* Parse the gif header (old: get_gif_header) | |
*/ | |
private function parseGifHeader() | |
{ | |
$this->pointerForward(10); | |
if ($this->readBits(($mybyte = $this->readByteInt()), 0, 1) == 1) { | |
$this->pointerForward(2); | |
$this->pointerForward(pow(2, $this->readBits($mybyte, 5, 3) + 1) * 3); | |
} else { | |
$this->pointerForward(2); | |
} | |
$this->fileHeader["gifheader"] = $this->dataPart(0, $this->pointer); | |
// Decoding | |
$this->orgvars["gifheader"] = $this->fileHeader["gifheader"]; | |
$this->orgvars["background_color"] = $this->orgvars["gifheader"][11]; | |
} | |
/** | |
* Parse the application data of the frames (old: get_application_data) | |
*/ | |
private function getApplicationData() | |
{ | |
$startdata = $this->readByte(2); | |
if ($startdata == chr(0x21).chr(0xff)) { | |
$start = $this->pointer - 2; | |
$this->pointerForward($this->readByteInt()); | |
$this->readDataStream($this->readByteInt()); | |
$this->fileHeader["applicationdata"] = $this->dataPart($start, $this->pointer - $start); | |
} else { | |
$this->pointerRewind(2); | |
} | |
} | |
/** | |
* Parse the comment data of the frames (old: get_comment_data) | |
*/ | |
private function getCommentData() | |
{ | |
$startdata = $this->readByte(2); | |
if ($startdata == chr(0x21).chr(0xfe)) { | |
$start = $this->pointer - 2; | |
$this->readDataStream($this->readByteInt()); | |
$this->fileHeader["commentdata"] = $this->dataPart($start, $this->pointer - $start); | |
} else { | |
$this->pointerRewind(2); | |
} | |
} | |
/** | |
* Parse the graphic extension of the frames (old: get_graphics_extension) | |
* | |
* @param integer $type | |
*/ | |
private function parseGraphicsExtension($type) | |
{ | |
$startdata = $this->readByte(2); | |
if ($startdata == chr(0x21).chr(0xf9)) { | |
$start = $this->pointer - 2; | |
$this->pointerForward($this->readByteInt()); | |
$this->pointerForward(1); | |
if ($type == 2) { | |
$this->frameSources[$this->frameNumber]["graphicsextension"] = $this->dataPart($start, $this->pointer - $start); | |
} elseif ($type == 1) { | |
$this->orgvars["hasgx_type_1"] = 1; | |
$this->globaldata["graphicsextension"] = $this->dataPart($start, $this->pointer - $start); | |
} elseif ($type == 0) { | |
$this->orgvars["hasgx_type_0"] = 1; | |
$this->globaldata["graphicsextension_0"] = $this->dataPart($start, $this->pointer - $start); | |
} | |
} else { | |
$this->pointerRewind(2); | |
} | |
} | |
/** | |
* Get the full frame string block (old: get_image_block) | |
* | |
* @param integer $type | |
*/ | |
private function getFrameString($type) | |
{ | |
if ($this->checkByte(0x2c)) { | |
$start = $this->pointer; | |
$this->pointerForward(9); | |
if ($this->readBits(($mybyte = $this->readByteInt()), 0, 1) == 1) { | |
$this->pointerForward(pow(2, $this->readBits($mybyte, 5, 3) + 1) * 3); | |
} | |
$this->pointerForward(1); | |
$this->readDataStream($this->readByteInt()); | |
$this->frameSources[$this->frameNumber]["imagedata"] = $this->dataPart($start, $this->pointer - $start); | |
if ($type == 0) { | |
$this->orgvars["hasgx_type_0"] = 0; | |
if (isset($this->globaldata["graphicsextension_0"])) { | |
$this->frameSources[$this->frameNumber]["graphicsextension"] = $this->globaldata["graphicsextension_0"]; | |
} else { | |
$this->frameSources[$this->frameNumber]["graphicsextension"] = null; | |
} | |
unset($this->globaldata["graphicsextension_0"]); | |
} elseif ($type == 1) { | |
if (isset($this->orgvars["hasgx_type_1"]) && $this->orgvars["hasgx_type_1"] == 1) { | |
$this->orgvars["hasgx_type_1"] = 0; | |
$this->frameSources[$this->frameNumber]["graphicsextension"] = $this->globaldata["graphicsextension"]; | |
unset($this->globaldata["graphicsextension"]); | |
} else { | |
$this->orgvars["hasgx_type_0"] = 0; | |
$this->frameSources[$this->frameNumber]["graphicsextension"] = $this->globaldata["graphicsextension_0"]; | |
unset($this->globaldata["graphicsextension_0"]); | |
} | |
} | |
$this->parseFrameData(); | |
$this->frameNumber++; | |
} | |
} | |
/** | |
* Parse frame data string into an array (old: parse_image_data) | |
*/ | |
private function parseFrameData() | |
{ | |
$this->frameSources[$this->frameNumber]["disposal_method"] = $this->getImageDataBit("ext", 3, 3, 3); | |
$this->frameSources[$this->frameNumber]["user_input_flag"] = $this->getImageDataBit("ext", 3, 6, 1); | |
$this->frameSources[$this->frameNumber]["transparent_color_flag"] = $this->getImageDataBit("ext", 3, 7, 1); | |
$this->frameSources[$this->frameNumber]["delay_time"] = $this->dualByteVal($this->getImageDataByte("ext", 4, 2)); | |
$this->totalDuration += (int) $this->frameSources[$this->frameNumber]["delay_time"]; | |
$this->frameSources[$this->frameNumber]["transparent_color_index"] = ord($this->getImageDataByte("ext", 6, 1)); | |
$this->frameSources[$this->frameNumber]["offset_left"] = $this->dualByteVal($this->getImageDataByte("dat", 1, 2)); | |
$this->frameSources[$this->frameNumber]["offset_top"] = $this->dualByteVal($this->getImageDataByte("dat", 3, 2)); | |
$this->frameSources[$this->frameNumber]["width"] = $this->dualByteVal($this->getImageDataByte("dat", 5, 2)); | |
$this->frameSources[$this->frameNumber]["height"] = $this->dualByteVal($this->getImageDataByte("dat", 7, 2)); | |
$this->frameSources[$this->frameNumber]["local_color_table_flag"] = $this->getImageDataBit("dat", 9, 0, 1); | |
$this->frameSources[$this->frameNumber]["interlace_flag"] = $this->getImageDataBit("dat", 9, 1, 1); | |
$this->frameSources[$this->frameNumber]["sort_flag"] = $this->getImageDataBit("dat", 9, 2, 1); | |
$this->frameSources[$this->frameNumber]["color_table_size"] = pow(2, $this->getImageDataBit("dat", 9, 5, 3) + 1) * 3; | |
$this->frameSources[$this->frameNumber]["color_table"] = substr($this->frameSources[$this->frameNumber]["imagedata"], 10, $this->frameSources[$this->frameNumber]["color_table_size"]); | |
$this->frameSources[$this->frameNumber]["lzw_code_size"] = ord($this->getImageDataByte("dat", 10, 1)); | |
$this->framePositions[$this->frameNumber] = array( | |
'x' => $this->frameSources[$this->frameNumber]["offset_left"], | |
'y' => $this->frameSources[$this->frameNumber]["offset_top"], | |
); | |
$this->frameDimensions[$this->frameNumber] = array( | |
'width' => $this->frameSources[$this->frameNumber]["width"], | |
'height' => $this->frameSources[$this->frameNumber]["height"], | |
); | |
// Decoding | |
$this->orgvars[$this->frameNumber]["transparent_color_flag"] = $this->frameSources[$this->frameNumber]["transparent_color_flag"]; | |
$this->orgvars[$this->frameNumber]["transparent_color_index"] = $this->frameSources[$this->frameNumber]["transparent_color_index"]; | |
$this->orgvars[$this->frameNumber]["delay_time"] = $this->frameSources[$this->frameNumber]["delay_time"]; | |
$this->orgvars[$this->frameNumber]["disposal_method"] = $this->frameSources[$this->frameNumber]["disposal_method"]; | |
$this->orgvars[$this->frameNumber]["offset_left"] = $this->frameSources[$this->frameNumber]["offset_left"]; | |
$this->orgvars[$this->frameNumber]["offset_top"] = $this->frameSources[$this->frameNumber]["offset_top"]; | |
// Updating the max width | |
if ($this->gifMaxWidth < $this->frameSources[$this->frameNumber]["width"]) { | |
$this->gifMaxWidth = $this->frameSources[$this->frameNumber]["width"]; | |
} | |
// Updating the max height | |
if ($this->gifMaxHeight < $this->frameSources[$this->frameNumber]["height"]) { | |
$this->gifMaxHeight = $this->frameSources[$this->frameNumber]["height"]; | |
} | |
} | |
/** | |
* Get the image data byte (old: get_imagedata_byte) | |
* | |
* @param string $type | |
* @param integer $start | |
* @param integer $length | |
* | |
* @return string | |
*/ | |
private function getImageDataByte($type, $start, $length) | |
{ | |
if ($type == "ext") { | |
return substr($this->frameSources[$this->frameNumber]["graphicsextension"], $start, $length); | |
} | |
// "dat" | |
return substr($this->frameSources[$this->frameNumber]["imagedata"], $start, $length); | |
} | |
/** | |
* Get the image data bit (old: get_imagedata_bit) | |
* | |
* @param string $type | |
* @param integer $byteIndex | |
* @param integer $bitStart | |
* @param integer $bitLength | |
* | |
* @return number | |
*/ | |
private function getImageDataBit($type, $byteIndex, $bitStart, $bitLength) | |
{ | |
if ($type == "ext") { | |
return $this->readBits(ord(substr($this->frameSources[$this->frameNumber]["graphicsextension"], $byteIndex, 1)), $bitStart, $bitLength); | |
} | |
// "dat" | |
return $this->readBits(ord(substr($this->frameSources[$this->frameNumber]["imagedata"], $byteIndex, 1)), $bitStart, $bitLength); | |
} | |
/** | |
* Return the value of 2 ASCII chars (old: dualbyteval) | |
* | |
* @param string $s | |
* | |
* @return integer | |
*/ | |
private function dualByteVal($s) | |
{ | |
$i = ord($s[1]) * 256 + ord($s[0]); | |
return $i; | |
} | |
/** | |
* Read the data stream (old: read_data_stream) | |
* | |
* @param integer $firstLength | |
*/ | |
private function readDataStream($firstLength) | |
{ | |
$this->pointerForward($firstLength); | |
$length = $this->readByteInt(); | |
if ($length != 0) { | |
while ($length != 0) { | |
$this->pointerForward($length); | |
$length = $this->readByteInt(); | |
} | |
} | |
} | |
/** | |
* Open the gif file (old: loadfile) | |
* | |
* @param string $filename | |
*/ | |
private function openFile($filename) | |
{ | |
$this->handle = fopen($filename, "rb"); | |
$this->pointer = 0; | |
$imageSize = getimagesize($filename); | |
$this->gifWidth = $imageSize[0]; | |
$this->gifHeight = $imageSize[1]; | |
} | |
/** | |
* Close the read gif file (old: closefile) | |
*/ | |
private function closeFile() | |
{ | |
fclose($this->handle); | |
$this->handle = 0; | |
} | |
/** | |
* Read the file from the beginning to $byteCount in binary (old: readbyte) | |
* | |
* @param integer $byteCount | |
* | |
* @return string | |
*/ | |
private function readByte($byteCount) | |
{ | |
$data = fread($this->handle, $byteCount); | |
$this->pointer += $byteCount; | |
return $data; | |
} | |
/** | |
* Read a byte and return ASCII value (old: readbyte_int) | |
* | |
* @return integer | |
*/ | |
private function readByteInt() | |
{ | |
$data = fread($this->handle, 1); | |
$this->pointer++; | |
return ord($data); | |
} | |
/** | |
* Convert a $byte to decimal (old: readbits) | |
* | |
* @param string $byte | |
* @param integer $start | |
* @param integer $length | |
* | |
* @return number | |
*/ | |
private function readBits($byte, $start, $length) | |
{ | |
$bin = str_pad(decbin($byte), 8, "0", STR_PAD_LEFT); | |
$data = substr($bin, $start, $length); | |
return bindec($data); | |
} | |
/** | |
* Rewind the file pointer reader (old: p_rewind) | |
* | |
* @param integer $length | |
*/ | |
private function pointerRewind($length) | |
{ | |
$this->pointer -= $length; | |
fseek($this->handle, $this->pointer); | |
} | |
/** | |
* Forward the file pointer reader (old: p_forward) | |
* | |
* @param integer $length | |
*/ | |
private function pointerForward($length) | |
{ | |
$this->pointer += $length; | |
fseek($this->handle, $this->pointer); | |
} | |
/** | |
* Get a section of the data from $start to $start + $length (old: datapart) | |
* | |
* @param integer $start | |
* @param integer $length | |
* | |
* @return string | |
*/ | |
private function dataPart($start, $length) | |
{ | |
fseek($this->handle, $start); | |
$data = fread($this->handle, $length); | |
fseek($this->handle, $this->pointer); | |
return $data; | |
} | |
/** | |
* Check if a character if a byte (old: checkbyte) | |
* | |
* @param integer $byte | |
* | |
* @return boolean | |
*/ | |
private function checkByte($byte) | |
{ | |
if (fgetc($this->handle) == chr($byte)) { | |
fseek($this->handle, $this->pointer); | |
return true; | |
} | |
fseek($this->handle, $this->pointer); | |
return false; | |
} | |
/** | |
* Check the end of the file (old: checkEOF) | |
* | |
* @return boolean | |
*/ | |
private function checkEOF() | |
{ | |
if (fgetc($this->handle) === false) { | |
return true; | |
} | |
fseek($this->handle, $this->pointer); | |
return false; | |
} | |
/** | |
* Reset and clear this current object | |
*/ | |
private function reset() | |
{ | |
$this->gif = null; | |
$this->totalDuration = $this->gifMaxHeight = $this->gifMaxWidth = $this->handle = $this->pointer = $this->frameNumber = 0; | |
$this->frameDimensions = $this->framePositions = $this->frameImages = $this->frameDurations = $this->globaldata = $this->orgvars = $this->frames = $this->fileHeader = $this->frameSources = array(); | |
} | |
// Getter / Setter | |
// =================================================================================== | |
/** | |
* Get the total of all added frame duration | |
* | |
* @return integer | |
*/ | |
public function getTotalDuration() | |
{ | |
return $this->totalDuration; | |
} | |
/** | |
* Get the number of extracted frames | |
* | |
* @return integer | |
*/ | |
public function getFrameNumber() | |
{ | |
return $this->frameNumber; | |
} | |
/** | |
* Get the extracted frames (images and durations) | |
* | |
* @return array | |
*/ | |
public function getFrames() | |
{ | |
return $this->frames; | |
} | |
/** | |
* Get the extracted frame positions | |
* | |
* @return array | |
*/ | |
public function getFramePositions() | |
{ | |
return $this->framePositions; | |
} | |
/** | |
* Get the extracted frame dimensions | |
* | |
* @return array | |
*/ | |
public function getFrameDimensions() | |
{ | |
return $this->frameDimensions; | |
} | |
/** | |
* Get the extracted frame images | |
* | |
* @return array | |
*/ | |
public function getFrameImages() | |
{ | |
return $this->frameImages; | |
} | |
/** | |
* Get the extracted frame durations | |
* | |
* @return array | |
*/ | |
public function getFrameDurations() | |
{ | |
return $this->frameDurations; | |
} | |
} | |
$gifFileName = 'Ladda-loader-buttons'; | |
$gifFullFileName = $gifFileName.'.gif'; | |
$gifFrameTransparentImagesSubDirectory = 'frame_images/'.$gifFileName.'/transparent_img'; | |
$gifFrameImagesSubDirectory = 'frame_images/'.$gifFileName.'/img'; | |
if (GifFrameExtractor::isAnimatedGif($gifFullFileName)) { // check this is an animated GIF | |
$i = 0; | |
$html = ''; | |
$html .= '<style>td { border: 1px solid #A4A4A4; padding: 10px 50px 10px 50px;}</style>'; | |
$html .= '<p>The demo shows 3 different processing results from my attempt to take an Animated GIF image and extract each Frame into a usable image using PHP</p><p>Columns 1 and 2 are using the PHP library <a href="https://github.com/AbhasKr/GifFrameExtractor" target="_blank">GIFFrameExtractor</a>. The first column sets the transparency option to <code>TRUE</code> on the library and the 2nd column is without the transparency option set. If you scroll all the way down the page you can see that the images created in columns 1 and 2 are both not perfect.</p><p>Now in column 3 I used an online image processing cloud service called <a href="http://cloudinary.com/" target="_blankl">cloudinary.com</a>. I can view each Frame of my animated GIF file that I uploaded here using this format of URL <code>http://res.cloudinary.com/apollo-web-studio/image/upload/pg_[FRAME_NUMBER_HERE]/Ladda-loader-buttons_qlpxbc.jpg</code> and replace the <code>[FRAME_NUMBER_HERE]</code> part with the Frame Number and it will show the Frame as an image.</p><p>THe images generated on Cloudinary.com use some different technique as each frame generated is flawless!</p><p>MY goal is to generate images for each frame and have them turn out like the results from Cloudinary.com using PHP. I need help please?</p>'; | |
$html .= '<table>'; | |
$html .= '<thead>'; | |
$html .= '<th>Frame #</th>'; | |
$html .= '<th>PHP Class w/transparency option on</th>'; | |
$html .= '<th>PHP Class w/transparency option off</th>'; | |
$html .= '<th>cloudinary.com</th>'; | |
$html .= '</thead>'; | |
$html .= '<tbody>'; | |
// extract GIF animation frames from $gifFilePath with transparency option = true | |
$gfe1 = new GifFrameExtractor(); | |
$gfe1->extract($gifFullFileName, true); // Can get transparency orignal frames | |
$frameTransparentImages = $gfe1->getFrameImages(); | |
// extract GIF animation frames from $gifFilePath with transparency option =false | |
$gfe = new GifFrameExtractor(); | |
$gfe->extract($gifFullFileName, false); // Can get transparency orignal frames | |
// Make directories to store new frame images in if they do not exist for this GIF yet | |
if (!file_exists($gifFrameTransparentImagesSubDirectory)) { | |
mkdir($gifFrameTransparentImagesSubDirectory, 0777, true); | |
} | |
if (!file_exists($gifFrameImagesSubDirectory)) { | |
mkdir($gifFrameImagesSubDirectory, 0777, true); | |
} | |
// copy source animated GIF file into its directory | |
if (!copy($gifFullFileName, 'frame_images/'.$gifFileName.'/'.$gifFullFileName)) { | |
$html .= 'failed to copy $file...\n'; | |
} | |
// iterate frames | |
foreach ($gfe->getFrames() as $frame) { | |
$currentFrameNumber = $i+1; | |
// The frame resource image var | |
$img = $frame['image']; | |
// save transparent GIF frame images | |
imagegif($frameTransparentImages[$i], $gifFrameTransparentImagesSubDirectory.'/'.$gifFileName.'_trans_'.$i.'.gif'); | |
// // save NON-transparent GIF frame images | |
//imagejpeg($img, null, 100); | |
imagegif($img, $gifFrameImagesSubDirectory.'/'.$gifFileName.'_'.$i.'.gif'); | |
// // show GIF Frame images generated from the online cloud image processing web service cloudinary.com | |
// cloudinary.com does it perfectly how I want my frame images to look | |
// I can view each frame of a GIF image with this URL structure... | |
// Replace [FRAME_NUMBER_HERE] with the frame we want to view in the URL below: | |
// http://res.cloudinary.com/apollo-web-studio/image/upload/pg_[FRAME_NUMBER_HERE]/Ladda-loader-buttons_qlpxbc.jpg | |
$cloudinaryDotComFrameImgUrl = 'http://res.cloudinary.com/apollo-web-studio/image/upload/pg_'.$currentFrameNumber.'/Ladda-loader-buttons_qlpxbc.jpg'; | |
$html .= '<tr>'; | |
$html .= '<td>Frame #'.$currentFrameNumber.'</td>'; | |
$html .= '<td><img src="'.$gifFrameTransparentImagesSubDirectory.'/'.$gifFileName.'_trans_'.$i.'.gif'.'"></td>'; | |
$html .= '<td><img src="'.$gifFrameImagesSubDirectory.'/'.$gifFileName.'_'.$i.'.gif'.'"></td>'; | |
$html .= '<td class="cloudinary"><img src="'.$cloudinaryDotComFrameImgUrl.'"> - '.$cloudinaryDotComFrameImgUrl.'</td>'; | |
$html .= '</tr>'; | |
$i++; | |
} | |
$html .= '</tbody>'; | |
$html .= '</table>'; | |
echo $html; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment