Your IP : 216.73.216.240


Current Path : /home/j/u/v/juvelize/www/administrator/components/com_joomgallery/helpers/
Upload File :
Current File : /home/j/u/v/juvelize/www/administrator/components/com_joomgallery/helpers/gifframeextractor.php

<?php
/****************************************************************************************\
**   JoomGallery 3                                                                      **
**   By: JoomGallery::ProjectTeam                                                       **
**   Copyright (C) 2008 - 2021  JoomGallery::ProjectTeam                                **
**   Based on: JoomGallery 1.0.0 by JoomGallery::ProjectTeam                            **
**   Released under GNU GPL Public License                                              **
**   License: http://www.gnu.org/copyleft/gpl.html or have a look                       **
**   at administrator/components/com_joomgallery/LICENSE.TXT                            **
\****************************************************************************************/

defined('_JEXEC') or die('Direct Access to this location is not allowed.');

/**
 * 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, modified by JoomGallery::ProjectTeam
 */
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 int     $memory_needed_frame Memory requirement for manipultion of one frame (byte)
   * @param boolean $originalFrames      Get original frames (with transparent background)
   *
   * @return array
   */
  public function extract($filename, $originalFrames = false)
  {
      if (!self::isAnimatedGif($filename))
      {
          return array(false);
          //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'];

             // Create empty GD-Object
             if(function_exists('imagecreatetruecolor'))
             {
               // Needs at least php v4.0.6
               $sprite = imagecreatetruecolor($this->gifMaxWidth, $this->gifMaxHeight);
             }
             else
             {
               $sprite = imagecreate($this->gifMaxWidth, $this->gifMaxHeight);
             }

             if(function_exists('imagecolorallocatealpha'))
             {
               // Needs at least php v4.0.6
               $actualTrans = imagecolorallocatealpha($sprite, 0, 0, 0, 127);
               imagefill($sprite, 0, 0, $actualTrans);
               imagecolortransparent($sprite, $actualTrans);
             }
             else
             {
               $transparent = imagecolortransparent($prevImg);

               if($transparent > -1 && imagecolorstotal($prevImg) > $transparent)
               {
                 $actualTrans = imagecolorsforindex($prevImg, $transparent);
                 $actualTransIndx = imagecolorallocate($sprite, $actualTrans['red'], $actualTrans['green'], $actualTrans['blue']);
                 imagefill($sprite, 0, 0, $actualTransIndx);
                 imagecolortransparent($sprite, $actualTransIndx);
               }
             }

             if(((int) $this->frameSources[$i]['disposal_method'] == 1 || (int) $this->frameSources[$i]['disposal_method'] == 3) && $i > 0)
             {
               if(function_exists('imagecopyresampled'))
               {
                 // Needs at least php v4.0.6
                 imagecopyresampled($sprite, $prevImg, 0, 0, 0, 0, $this->gifMaxWidth, $this->gifMaxHeight, $this->gifMaxWidth, $this->gifMaxHeight);
               }
               else
               {
                 imagecopy($sprite, $prevImg, 0, 0, 0, 0, $this->gifMaxWidth, $this->gifMaxHeight);
               }
             }

             $w = imagesx($img);
             $h = imagesy($img);

             if(function_exists('imagecopyresampled'))
             {
               // Needs at least php v4.3.2
               imagecopyresampled($sprite, $img, $this->frameSources[$i]["offset_left"], $this->frameSources[$i]["offset_top"], 0, 0, $w, $h, $w, $h);
             }
             else
             {
               imagecopy($sprite, $img, $this->frameSources[$i]["offset_left"], $this->frameSources[$i]["offset_top"], 0, 0, $w, $h);
             }

             $img = $sprite;
           }
           else
           {
             $prevImg = $img;
           }
        }

        $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;
  }

  /**
   * Parse the frame informations contained in the GIF file
   *
   * @param string $filename GIF filename path
   */
  public 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();
    }
  }


  // Internals
  // ===================================================================================

  /**
   * 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;
  }

  /**
   * Get the number of frames
   *
   * @return array
   */
  public function getNumberFramens()
  {
    return count($this->frameSources);
  }
}