GIF图片生成与提取

白与黑 2023-07-28 10:01:00 449℃ 38684 0条

最好是素材图片大小尺寸一样,生成的GIF图片才好看,同样,提取的图片也要好看些。

提取类:GifFrameExtractor

<?php
namespace tools;
/**
 * 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;
    }
}

生成类:Gifcreator

<?php
namespace tools;
/**
 * Create an animated GIF from multiple images
 * 
 * @version 1.0
 * @link https://github.com/Sybio/GifCreator
 * @author Sybio (Clément Guillemain  / @Sybio01)
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
 * @copyright Clément Guillemain
 */
class Gifcreator
{
    /**
     * @var string The gif string source (old: this->GIF)
     */
    private $gif;
    /**
     * @var string Encoder version (old: this->VER)
     */
    private $version;
    /**
     * @var boolean Check the image is build or not (old: this->IMG)
     */
    private $imgBuilt;
    /**
     * @var array Frames string sources (old: this->BUF)
     */
    private $frameSources;
    /**
     * @var integer Gif loop (old: this->LOP)
     */
    private $loop;
    /**
     * @var integer Gif dis (old: this->DIS)
     */
    private $dis;
    /**
     * @var integer Gif color (old: this->COL)
     */
    private $colour;
    /**
     * @var array (old: this->ERR)
     */
    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.',
        );
    }
    /**
     * 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)
    {
        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
                $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]);                    
                }
                $resourceImg = imagecreatefromstring($frames[$i]);
                ob_start();
                imagegif($resourceImg);
                $this->frameSources[] = ob_get_contents();
                ob_end_clean();
            } 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') {
                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') {
                            throw new \Exception($this->version.': '.$this->errors['ERR03'].' ('.($i + 1).' source).');
                        }
                    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();
        //d(count($this->frameSources));
        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()
    {
        $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)
    {
        $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()
    {
        $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)
    {
        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;
    }
    /**
     * Encode an ASCII char into a string char (old: GIFWord)
     * 
     * $param integer $char ASCII char
     * 
     * @return string
     */
    public function encodeAsciiToChar($char)
    {
        return (chr($char & 0xFF).chr(($char >> 8) & 0xFF));
    }
    /**
     * Reset and clean the current object
     */
    public function reset()
    {
        $this->frameSources;
        $this->gif = 'GIF89a'; // the GIF header
        $this->imgBuilt = false;
        $this->loop = 0;
        $this->dis = 2;
        $this->colour = -1;
    }
    // Getter / Setter
    // ===================================================================================
    /**
     * Get the final GIF image string (old: GetAnimation)
     * 
     * @return string
     */
    public function getGif()
    {
        return $this->gif;
    }
}

使用方式

后端接口

<?php
namespace app\index\controller;
use think\Controller;
use tools\Gifcreator;
use tools\GifFrameExtractor;
class Gif extends Controller
{
    /**
     * @param array $file
     * @param string $savePath
     * @return array|bool
     * @desc 上传处理图片
     */
    private function upload($file = array(), $savePath = 'uploads/')
    {
        if (empty($file)) {
            return false;
        }
        $savePath = $savePath . date('Ymd') . '/';
        if (!file_exists($savePath)) {
            mkdir($savePath, 0755, true);
        }
        // 允许上传的图片后缀
        $allowedExts = array("gif", "jpeg", "jpg", "png");
        $allowedType = array("image/jpeg", "image/gif", "image/jpg", "image/x-png", "image/png");
        $maxSize = 2 * 1024 * 1024;
        $uploaded = array('code' => 200, 'data' => array(), 'error' => array());
        //名称
        foreach ($file['name'] as $key => $name) {
            $temp = explode(".", $name);
            $extension = end($temp);
            if (in_array($file["type"][$key], $allowedType) && in_array($extension, $allowedExts)) {
                if ($file["size"][$key] > $maxSize) {
                    foreach ($uploaded['data'] as $v) {
                        @unlink($v['saveName']);
                    }
                    $uploaded['error'][] = array(
                        'file' => $name,
                        'error_msg' => "Current picture size:" . $this->format_bytes($file["size"][$key]) . ". Picture size exceeds maximum limit:" . $this->format_bytes($maxSize)
                    );
                    $uploaded['code'] = 201;
                }
                if ($file["error"][$key] > 0) {
                    foreach ($uploaded['data'] as $v) {
                        @unlink($v['saveName']);
                    }
                    $uploaded['error'][] = array(
                        'file' => $name,
                        'error_msg' => "Upload error:" . $this->getError($file["error"][$key])
                    );
                    $uploaded['code'] = 201;
                } else {
                    $saveName = $savePath . uniqid() . '.' . $extension;
                    move_uploaded_file($file["tmp_name"][$key], $saveName);
                    $uploaded['data'][] = array(
                        'originName' => $name,
                        'saveName' => $saveName,
                        'size' => $file["size"][$key],
                        'type' => $file["type"][$key]
                    );
                }
            } else {
                foreach ($uploaded['data'] as $v) {
                    @unlink($v['saveName']);
                }
                $uploaded['error'][] = array(
                    'file' => $name,
                    'error_msg' => "Illegal file format:" . $extension
                );
                $uploaded['code'] = 201;
            }
        }
        return $uploaded;
    }
    /**
     * @param $size
     * @param string $delimiter
     * @return string
     * @desc 字节转换
     */
    private function format_bytes($size, $delimiter = '')
    {
        $units = array('B', 'KB', 'MB', 'GB', 'TB', 'PB');
        for ($i = 0; $size >= 1024 && $i < 5; $i++) $size /= 1024;
        return round($size, 2) . $delimiter . $units[$i];
    }
    /**
     * @param string $error
     * @return string
     * @desc 获取错误信息
     */
    private function getError($error = '')
    {
        switch ($error) {
            case 1:
                // 文件大小超出了服务器的空间大小
                $err = "The file is too large (server).";
                break;
            case 2:
                // 要上传的文件大小超出浏览器限制
                $err = "The file is too large (form).";
                break;
            case 3:
                // 文件仅部分被上传
                $err = "The file was only partially uploaded.";
                break;
            case 4:
                // 没有找到要上传的文件
                $err = "No file was uploaded.";
                break;
            case 5:
                // 服务器临时文件夹丢失
                $err = "The servers temporary folder is missing.";
                break;
            case 6:
                // 文件写入到临时文件夹出错
                $err = "Failed to write to the temporary folder.";
                break;
            default:
                $err = "";
        }
        return $err;
    }
    /**
     * @return mixed
     * @desc 视图文件
     */
    public function createGifView()
    {
        return $this->fetch();
    }
    /**
     * @return false|string
     * @throws \Exception
     * @desc  生成GIF
     */
    public function createGif()
    {
        $files = $_FILES['files'];
        $imgs = $this->upload($files);
        if (200 != $imgs['code']) {
            return json_encode($imgs['error']);
        }
        $picArr = [];
        $durations = array();
        foreach ($imgs['data'] as $k => $v) {
            $picArr[] = $v['saveName'];
            //过度时间
            $durations[] = 50;
        }
        $gitCreator = new Gifcreator();
        $gitCreator->create($picArr, $durations, 0);
        $gifBinary = $gitCreator->getGif();
        //保存路径
        $gif_path = 'uploads/' . date('Ymd') . '/';
        if (!file_exists($gif_path)) {
            mkdir($gif_path, 0755, true);
        }
        $gifName = $gif_path . uniqid() . '.gif';
        file_put_contents($gifName, $gifBinary);
        $data = ['code' => 200, 'msg' => 'success', 'data' => array('url' => $gifName)];
        //echo json_encode($data);
        echo "<img src='/$gifName'>";
    }
    /**
     * @throws \Exception
     * @desc 提取GIF
     */
    public function extractor()
    {
        $gfe = new GifFrameExtractor();
        //gif图片地址
        $gfe->extract('./uploads/20200429/5ea98e6cd8442.gif', true);
        //gif图片帧数
        $frameImages = $gfe->getFrameImages();
        //过度时间
        $frameDurations = $gfe->getFrameDurations();
        //保存路径
        $savePath = 'uploads/gifFrameExtractor/' . date('Ymd') . '/';
        if (!file_exists($savePath)) {
            mkdir($savePath, 0755, true);
        }
        //保存
        foreach ($frameImages as $key => $image) {
            $saveName = $savePath . $key . ".jpeg";
            imagejpeg($image, $saveName);
            echo "<img src='/$saveName'>";
        }
    }
}

前端代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>gif图片生成</title>
</head>
<body>
<form method="post" action="{:url('createGif')}" enctype="multipart/form-data">
    <input type="file" name="files[]" multiple>
    <button>上传</button>
</form>
</body>
</html>

效果展示

GIF生成与提取

非特殊说明,本博所有文章均为博主原创。

评论啦~