LICENSE000075500000002066150731645450005572 0ustar00The MIT License (MIT) Copyright (c) 2020 Oliver Vogel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.README.md000075500000005160150731645450006042 0ustar00# Intervention GIF ## Native PHP GIF Encoder/Decoder [![Latest Version](https://img.shields.io/packagist/v/intervention/gif.svg)](https://packagist.org/packages/intervention/gif) ![build](https://github.com/Intervention/gif/actions/workflows/build.yml/badge.svg) [![Monthly Downloads](https://img.shields.io/packagist/dm/intervention/gif.svg)](https://packagist.org/packages/intervention/gif/stats) Intervention GIF is a PHP encoder and decoder for the GIF image format that does not depend on any image processing extension. Only the special `Splitter::class` class divides the data stream of an animated GIF into individual `GDImage` objects for each frame and is therefore dependent on the GD library. The library is the main component of [Intervention Image](https://github.com/Intervention/image) for processing animated GIF files with the GD library, but also works independently. ## Installation You can easily install this package using [Composer](https://getcomposer.org). Just request the package with the following command: ```bash composer require intervention/gif ``` ## Code Examples ### Decoding ```php use Intervention\Gif\Decoder; // Decode filepath to Intervention\Gif\GifDataStream::class $gif = Decoder::decode('images/animation.gif'); // Decoder can also handle binary content directly $gif = Decoder::decode($contents); ``` ### Encoding Use the Builder class to create a new GIF image. ```php use Intervention\Gif\Builder; // create new gif canvas $gif = Builder::canvas(width: 32, height: 32); // add animation frames to canvas $delay = .25; // delay in seconds after next frame is displayed $left = 0; // position offset (left) $top = 0; // position offset (top) // add animation frames with optional delay in seconds // and optional position offset for each frame $gif->addFrame('images/frame01.gif', $delay, $left, $top); $gif->addFrame('images/frame02.gif', $delay, $left); $gif->addFrame('images/frame03.gif', $delay); $gif->addFrame('images/frame04.gif'); // set loop count; 0 for infinite looping $gif->setLoops(12); // encode $data = $gif->encode(); ``` ## Requirements - PHP >= 8.1 ## Development & Testing With this package comes a Docker image to build a test suite and analysis container. To build this container you have to have Docker installed on your system. You can run all tests with this command. ```bash docker-compose run --rm --build tests ``` Run the static analyzer on the code base. ```bash docker-compose run --rm --build analysis ``` ## License Intervention GIF is licensed under the [MIT License](http://opensource.org/licenses/MIT). Copyright 2024 [Oliver Vogel](http://intervention.io/) composer.json000075500000001314150731645450007302 0ustar00{ "name": "intervention/gif", "description": "Native PHP GIF Encoder/Decoder", "homepage": "https://github.com/intervention/gif", "keywords": ["image", "gd", "gif", "animation"], "license": "MIT", "authors": [ { "name": "Oliver Vogel", "email": "oliver@intervention.io", "homepage": "https://intervention.io/" } ], "require": { "php": "^8.1" }, "require-dev": { "phpunit/phpunit": "^9", "phpstan/phpstan": "^1", "squizlabs/php_codesniffer": "^3.8" }, "autoload": { "psr-4": { "Intervention\\Gif\\": "src" } }, "autoload-dev": { "psr-4": { "Intervention\\Gif\\Tests\\": "tests" } }, "minimum-stability": "stable" } src/AbstractEntity.php000064400000001233150731645450011015 0ustar00getShortName(); } /** * Cast object to string * * @return string */ public function __toString(): string { return $this->encode(); } } src/AbstractExtension.php000064400000000234150731645450011515 0ustar00application); } public function setApplication(string $value): self { $this->application = $value; return $this; } public function getApplication(): string { return $this->application; } public function addBlock(DataSubBlock $block): self { $this->blocks[] = $block; return $this; } public function setBlocks(array $blocks): self { $this->blocks = $blocks; return $this; } public function getBlocks(): array { return $this->blocks; } } src/Blocks/Color.php000064400000002756150731645450010363 0ustar00r; } /** * Set red value * * @param int $value */ public function setRed(int $value): self { $this->r = $value; return $this; } /** * Get green value * * @return int */ public function getGreen() { return $this->g; } /** * Set green value * * @param int $value */ public function setGreen(int $value): self { $this->g = $value; return $this; } /** * Get blue value * * @return int */ public function getBlue() { return $this->b; } /** * Set blue value * * @param int $value */ public function setBlue(int $value): self { $this->b = $value; return $this; } /** * Return hash value of current color * * @return string */ public function getHash(): string { return md5($this->r . $this->g . $this->b); } } src/Blocks/ColorTable.php000064400000005140150731645450011321 0ustar00colors); } /** * Add color to table * * @param int $r * @param int $g * @param int $b */ public function addRgb(int $r, int $g, int $b): self { $this->addColor(new Color($r, $g, $b)); return $this; } /** * Add color to table * * @param Color $color */ public function addColor(Color $color): self { $this->colors[] = $color; return $this; } /** * Reset colors to array of color objects * * @param array $colors */ public function setColors(array $colors): self { $this->empty(); foreach ($colors as $color) { $this->addColor($color); } return $this; } /** * Count colors of current instance * * @return int */ public function countColors(): int { return count($this->colors); } /** * Determine if any colors are present on the current table * * @return bool */ public function hasColors(): bool { return $this->countColors() >= 1; } /** * Empty color table * * @return self */ public function empty(): self { $this->colors = []; return $this; } /** * Get size of color table in logical screen descriptor * * @return int */ public function getLogicalSize(): int { switch ($this->countColors()) { case 4: return 1; case 8: return 2; case 16: return 3; case 32: return 4; case 64: return 5; case 128: return 6; case 256: return 7; default: return 0; } } /** * Calculate the number of bytes contained by the current table * * @return int */ public function getByteSize(): int { if (! $this->hasColors()) { return 0; } return 3 * pow(2, $this->getLogicalSize() + 1); } } src/Blocks/CommentExtension.php000064400000001576150731645450012603 0ustar00comments; } /** * Get one comment by key * * @param int $key * @return mixed */ public function getComment(int $key): mixed { return array_key_exists($key, $this->comments) ? $this->comments[$key] : null; } /** * Set comment text * * @param string $value */ public function addComment(string $value): self { $this->comments[] = $value; return $this; } } src/Blocks/DataSubBlock.php000064400000001155150731645450011573 0ustar00getSize() > 255) { throw new FormatException( 'Data Sub-Block can not have a block size larger than 255 bytes.' ); } } public function getSize(): int { return strlen($this->value); } public function getValue(): string { return $this->value; } } src/Blocks/FrameBlock.php000064400000010741150731645450011303 0ustar00setTableBasedImage($entity); break; case $entity instanceof GraphicControlExtension: $this->setGraphicControlExtension($entity); break; case $entity instanceof ImageDescriptor: $this->setImageDescriptor($entity); break; case $entity instanceof ColorTable: $this->setColorTable($entity); break; case $entity instanceof ImageData: $this->setImageData($entity); break; case $entity instanceof PlainTextExtension: $this->setPlainTextExtension($entity); break; case $entity instanceof NetscapeApplicationExtension: $this->addApplicationExtension($entity); break; case $entity instanceof ApplicationExtension: $this->addApplicationExtension($entity); break; case $entity instanceof CommentExtension: $this->addCommentExtension($entity); break; } return $this; } public function getApplicationExtensions(): array { return $this->applicationExtensions; } public function getCommentExtensions(): array { return $this->commentExtensions; } public function setGraphicControlExtension(GraphicControlExtension $extension): self { $this->graphicControlExtension = $extension; return $this; } public function getGraphicControlExtension(): ?GraphicControlExtension { return $this->graphicControlExtension; } public function setImageDescriptor(ImageDescriptor $descriptor): self { $this->imageDescriptor = $descriptor; return $this; } public function getImageDescriptor(): ImageDescriptor { return $this->imageDescriptor; } public function setColorTable(ColorTable $table): self { $this->colorTable = $table; return $this; } public function getColorTable(): ?ColorTable { return $this->colorTable; } public function hasColorTable(): bool { return !is_null($this->colorTable); } public function setImageData(ImageData $data): self { $this->imageData = $data; return $this; } public function getImageData(): ImageData { return $this->imageData; } public function setPlainTextExtension(PlainTextExtension $extension): self { $this->plainTextExtension = $extension; return $this; } public function getPlainTextExtension(): ?PlainTextExtension { return $this->plainTextExtension; } public function addApplicationExtension(ApplicationExtension $extension): self { $this->applicationExtensions[] = $extension; return $this; } public function addCommentExtension(CommentExtension $extension): self { $this->commentExtensions[] = $extension; return $this; } public function getNetscapeExtension(): ?NetscapeApplicationExtension { $extensions = array_filter($this->applicationExtensions, function ($extension) { return $extension instanceof NetscapeApplicationExtension; }); return count($extensions) ? reset($extensions) : null; } public function setTableBasedImage(TableBasedImage $tableBasedImage): self { $this->setImageDescriptor($tableBasedImage->getImageDescriptor()); if ($colorTable = $tableBasedImage->getColorTable()) { $this->setColorTable($colorTable); } $this->setImageData($tableBasedImage->getImageData()); return $this; } } src/Blocks/GraphicControlExtension.php000064400000005660150731645450014115 0ustar00delay = $value; return $this; } /** * Return delay time (1/100 second) * * @return int */ public function getDelay(): int { return $this->delay; } /** * Set disposal method * * @param DisposalMethod $method * @return self */ public function setDisposalMethod(DisposalMethod $method): self { $this->disposalMethod = $method; return $this; } /** * Get disposal method * * @return DisposalMethod */ public function getDisposalMethod(): DisposalMethod { return $this->disposalMethod; } /** * Get transparent color index * * @return int */ public function getTransparentColorIndex(): int { return $this->transparentColorIndex; } /** * Set transparent color index * * @param int $index */ public function setTransparentColorIndex(int $index): self { $this->transparentColorIndex = $index; return $this; } /** * Get current transparent color existance * * @return bool */ public function getTransparentColorExistance(): bool { return $this->transparentColorExistance; } /** * Set existance flag of transparent color * * @param bool $existance */ public function setTransparentColorExistance(bool $existance = true): self { $this->transparentColorExistance = $existance; return $this; } /** * Get user input flag * * @return bool */ public function getUserInput(): bool { return $this->userInput; } /** * Set user input flag * * @param bool $value */ public function setUserInput(bool $value = true): self { $this->userInput = $value; return $this; } } src/Blocks/Header.php000064400000001225150731645450010463 0ustar00version = $value; return $this; } /** * Return current version * * @return string */ public function getVersion(): string { return $this->version; } } src/Blocks/ImageData.php000064400000002460150731645450011111 0ustar00lzw_min_code_size; } /** * Set lzw min. code size * * @param int $size * @return ImageData */ public function setLzwMinCodeSize(int $size): self { $this->lzw_min_code_size = $size; return $this; } /** * Get current data sub blocks * * @return array */ public function getBlocks(): array { return $this->blocks; } /** * Addd sub block * * @param DataSubBlock $block * @return ImageData */ public function addBlock(DataSubBlock $block): self { $this->blocks[] = $block; return $this; } /** * Determine if data sub blocks are present * * @return bool */ public function hasBlocks(): bool { return count($this->blocks) >= 1; } } src/Blocks/ImageDescriptor.php000064400000010413150731645450012353 0ustar00width); } /** * Get current width * * @return int */ public function getHeight(): int { return intval($this->height); } /** * Get current Top * * @return int */ public function getTop(): int { return intval($this->top); } /** * Get current Left * * @return int */ public function getLeft(): int { return intval($this->left); } /** * Set size of current instance * * @param int $width * @param int $height */ public function setSize(int $width, int $height): self { $this->width = $width; $this->height = $height; return $this; } /** * Set position of current instance * * @param int $left * @param int $top */ public function setPosition(int $left, int $top): self { $this->left = $left; $this->top = $top; return $this; } /** * Determine if frame is interlaced * * @return bool */ public function isInterlaced(): bool { return $this->interlaced === true; } /** * Set or unset interlaced value * * @param bool $value */ public function setInterlaced(bool $value = true): self { $this->interlaced = $value; return $this; } /** * Determine if local color table is present * * @return bool */ public function getLocalColorTableExistance(): bool { return $this->localColorTableExistance; } /** * Alias for getLocalColorTableExistance * * @return bool */ public function hasLocalColorTable(): bool { return $this->getLocalColorTableExistance(); } /** * Set local color table flag * * @param bool $existance * @return self */ public function setLocalColorTableExistance($existance = true): self { $this->localColorTableExistance = $existance; return $this; } /** * Get local color table sorted flag * * @return bool */ public function getLocalColorTableSorted(): bool { return $this->localColorTableSorted; } /** * Set local color table sorted flag * * @param bool $sorted * @return self */ public function setLocalColorTableSorted($sorted = true): self { $this->localColorTableSorted = $sorted; return $this; } /** * Get size of local color table * * @return int */ public function getLocalColorTableSize(): int { return $this->localColorTableSize; } /** * Get byte size of global color table * * @return int */ public function getLocalColorTableByteSize(): int { return 3 * pow(2, $this->getLocalColorTableSize() + 1); } /** * Set size of local color table * * @param int $size */ public function setLocalColorTableSize(int $size): self { $this->localColorTableSize = $size; return $this; } } src/Blocks/LogicalScreenDescriptor.php000064400000010715150731645450014050 0ustar00width = $width; $this->height = $height; return $this; } /** * Get width of current instance * * @return int */ public function getWidth(): int { return intval($this->width); } /** * Get height of current instance * * @return int */ public function getHeight(): int { return intval($this->height); } /** * Determine if global color table is present * * @return bool */ public function getGlobalColorTableExistance(): bool { return $this->globalColorTableExistance; } /** * Alias of getGlobalColorTableExistance * * @return bool */ public function hasGlobalColorTable(): bool { return $this->getGlobalColorTableExistance(); } /** * Set global color table flag * * @param bool $existance * @return self */ public function setGlobalColorTableExistance($existance = true): self { $this->globalColorTableExistance = $existance; return $this; } /** * Get global color table sorted flag * * @return bool */ public function getGlobalColorTableSorted(): bool { return $this->globalColorTableSorted; } /** * Set global color table sorted flag * * @param bool $sorted * @return self */ public function setGlobalColorTableSorted($sorted = true): self { $this->globalColorTableSorted = $sorted; return $this; } /** * Get size of global color table * * @return int */ public function getGlobalColorTableSize(): int { return $this->globalColorTableSize; } /** * Get byte size of global color table * * @return int */ public function getGlobalColorTableByteSize(): int { return 3 * pow(2, $this->getGlobalColorTableSize() + 1); } /** * Set size of global color table * * @param int $size */ public function setGlobalColorTableSize(int $size): self { $this->globalColorTableSize = $size; return $this; } /** * Get background color index * * @return int */ public function getBackgroundColorIndex(): int { return $this->backgroundColorIndex; } /** * Set background color index * * @param int $index */ public function setBackgroundColorIndex(int $index): self { $this->backgroundColorIndex = $index; return $this; } /** * Get current pixel aspect ration * * @return int */ public function getPixelAspectRatio(): int { return $this->pixelAspectRatio; } /** * Set pixel aspect ratio * * @param int $ratio */ public function setPixelAspectRatio(int $ratio): self { $this->pixelAspectRatio = $ratio; return $this; } /** * Get color resolution * * @return int */ public function getBitsPerPixel() { return $this->bitsPerPixel; } /** * Set color resolution * * @param int $value */ public function setBitsPerPixel(int $value): self { $this->bitsPerPixel = $value; return $this; } } src/Blocks/NetscapeApplicationExtension.php000064400000001413150731645450015115 0ustar00setApplication(self::IDENTIFIER . self::AUTH_CODE); $this->setBlocks([new DataSubBlock(self::SUB_BLOCK_PREFIX . "\x00\x00")]); } public function getLoops(): int { return unpack('v*', substr($this->getBlocks()[0]->getValue(), 1))[1]; } public function setLoops(int $loops): self { $this->setBlocks([ new DataSubBlock(self::SUB_BLOCK_PREFIX . pack('v*', $loops)) ]); return $this; } } src/Blocks/PlainTextExtension.php000064400000001733150731645450013104 0ustar00text; } /** * Add text * * @param string $text */ public function addText(string $text): self { $this->text[] = $text; return $this; } /** * Set text array of extension * * @param array $text */ public function setText(array $text): self { $this->text = $text; return $this; } /** * Determine if any text is present * * @return bool */ public function hasText(): bool { return count($this->text) > 0; } } src/Blocks/TableBasedImage.php000064400000001745150731645450012233 0ustar00imageDescriptor; } public function setImageDescriptor(ImageDescriptor $descriptor): self { $this->imageDescriptor = $descriptor; return $this; } public function getImageData(): ImageData { return $this->imageData; } public function setImageData(ImageData $data): self { $this->imageData = $data; return $this; } public function getColorTable(): ?ColorTable { return $this->colorTable; } public function setColorTable(ColorTable $table): self { $this->colorTable = $table; return $this; } } src/Blocks/Trailer.php000064400000000266150731645450010701 0ustar00gif; } /** * Set canvas size of gif * * @param int $width * @param int $height * @return Builder */ public function setSize(int $width, int $height): self { $this->gif->getLogicalScreenDescriptor()->setSize($width, $height); return $this; } /** * Create new canvas * * @param int $width * @param int $height * @return self */ public static function canvas(int $width, int $height): self { return (new self())->setSize($width, $height); } /** * Set loop count * * @param int $loops * @return Builder * @throws Exception */ public function setLoops(int $loops): self { if (count($this->gif->getFrames()) === 0) { throw new Exception('Add at least one frame before setting the loop count'); } if ($loops >= 0) { // add frame count to existing or new netscape extension on first frame if (!$this->gif->getFirstFrame()->getNetscapeExtension()) { $this->gif->getFirstFrame()->addApplicationExtension( new NetscapeApplicationExtension() ); } $this->gif->getFirstFrame()->getNetscapeExtension()->setLoops($loops); } return $this; } /** * Create new animation frame from given source * which can be path to a file or GIF image data * * @param string $source * @param float $delay time delay in seconds * @param int $left position offset in pixels from left * @param int $top position offset in pixels from top * @return Builder */ public function addFrame(string $source, float $delay = 0, int $left = 0, int $top = 0): self { $frame = new FrameBlock(); $source = Decoder::decode($source); // store delay $frame->setGraphicControlExtension( $this->buildGraphicControlExtension( $source, intval($delay * 100) ) ); // store image $frame->setTableBasedImage( $this->buildTableBasedImage($source, $left, $top) ); // add frame $this->gif->addFrame($frame); return $this; } /** * Build new graphic control extension with given delay & disposal method * * @param GifDataStream $source * @param int $delay * @param DisposalMethod $disposalMethod * @return GraphicControlExtension */ protected function buildGraphicControlExtension( GifDataStream $source, int $delay, DisposalMethod $disposalMethod = DisposalMethod::BACKGROUND ): GraphicControlExtension { // create extension $extension = new GraphicControlExtension($delay, $disposalMethod); // set transparency index $control = $source->getFirstFrame()->getGraphicControlExtension(); if ($control && $control->getTransparentColorExistance()) { $extension->setTransparentColorExistance(); $extension->setTransparentColorIndex( $control->getTransparentColorIndex() ); } return $extension; } /** * Build table based image object from given source * * @param GifDataStream $source * @param int $left * @param int $top * @return TableBasedImage */ protected function buildTableBasedImage(GifDataStream $source, int $left, int $top): TableBasedImage { $block = new TableBasedImage(); $block->setImageDescriptor(new ImageDescriptor()); // set global color table from source as local color table $block->getImageDescriptor()->setLocalColorTableExistance(); $block->setColorTable($source->getGlobalColorTable()); $block->getImageDescriptor()->setLocalColorTableSorted( $source->getLogicalScreenDescriptor()->getGlobalColorTableSorted() ); $block->getImageDescriptor()->setLocalColorTableSize( $source->getLogicalScreenDescriptor()->getGlobalColorTableSize() ); $block->getImageDescriptor()->setSize( $source->getLogicalScreenDescriptor()->getWidth(), $source->getLogicalScreenDescriptor()->getHeight() ); // set position $block->getImageDescriptor()->setPosition($left, $top); // add image data from source $block->setImageData($source->getFirstFrame()->getImageData()); return $block; } /** * Encode the current build * * @return string */ public function encode(): string { return $this->gif->encode(); } } src/Decoder.php000064400000001336150731645450007426 0ustar00 self::getHandleFromFilePath($input), is_string($input) => self::getHandleFromData($input), default => throw new DecoderException('Decoder input must be either file path or binary data.') } ); } } src/Decoders/AbstractDecoder.php000064400000005500150731645450012637 0ustar00handle = $handle; return $this; } /** * Read given number of bytes and move file pointer * * @param int $length * @return string */ protected function getNextBytes(int $length): string { return fread($this->handle, $length); } /** * Read given number of bytes and move pointer back to previous position * * @param int $length * @return string */ protected function viewNextBytes(int $length): string { $bytes = $this->getNextBytes($length); $this->movePointer($length * -1); return $bytes; } /** * Read next byte and move pointer back to previous position * * @return string */ protected function viewNextByte(): string { return $this->viewNextBytes(1); } /** * Read all remaining bytes from file handler * * @return string */ protected function getRemainingBytes(): string { $all = ''; do { $byte = fread($this->handle, 1); $all .= $byte; } while (! feof($this->handle)); return $all; } /** * Get next byte in stream and move file pointer * * @return string */ protected function getNextByte(): string { return $this->getNextBytes(1); } /** * Move file pointer on handle by given offset * * @param int $offset * @return self */ protected function movePointer(int $offset): self { fseek($this->handle, $offset, SEEK_CUR); return $this; } /** * Decode multi byte value * * @return int */ protected function decodeMultiByte(string $bytes): int { return unpack('v*', $bytes)[1]; } /** * Set length * * @param int $length */ public function setLength(int $length): self { $this->length = $length; return $this; } /** * Get length * * @return null|int */ public function getLength(): ?int { return $this->length; } /** * Get current handle position * * @return int */ public function getPosition(): int { return ftell($this->handle); } } src/Decoders/AbstractPackedBitDecoder.php000064400000001673150731645450014415 0ustar00getPackedBits($byte)[$num]; } /** * Get packed bits * * @param int $start * @param int $length * @return string */ protected function getPackedBits(string $byte, int $start = 0, int $length = 8): string { $bits = str_pad(decbin($this->decodePackedByte($byte)), 8, '0', STR_PAD_LEFT); return substr($bits, $start, $length); } } src/Decoders/ApplicationExtensionDecoder.php000064400000003377150731645450015246 0ustar00getNextByte(); // marker $this->getNextByte(); // label $blocksize = $this->decodeBlockSize($this->getNextByte()); $application = $this->getNextBytes($blocksize); if ($application === NetscapeApplicationExtension::IDENTIFIER . NetscapeApplicationExtension::AUTH_CODE) { $result = new NetscapeApplicationExtension(); // skip length $this->getNextByte(); $result->setBlocks([ new DataSubBlock( $this->getNextBytes(3) ) ]); // skip terminator $this->getNextByte(); return $result; } $result->setApplication($application); // decode data sub blocks $blocksize = $this->decodeBlockSize($this->getNextByte()); while ($blocksize > 0) { $result->addBlock(new DataSubBlock($this->getNextBytes($blocksize))); $blocksize = $this->decodeBlockSize($this->getNextByte()); } return $result; } /** * Decode block size of ApplicationExtension from given byte * * @param string $byte * @return int */ protected function decodeBlockSize(string $byte): int { return (int) @unpack('C', $byte)[1]; } } src/Decoders/ColorDecoder.php000064400000001347150731645450012157 0ustar00setRed($this->decodeColorValue($this->getNextByte())); $color->setGreen($this->decodeColorValue($this->getNextByte())); $color->setBlue($this->decodeColorValue($this->getNextByte())); return $color; } /** * Decode red value from source * * @return int */ protected function decodeColorValue(string $byte): int { return unpack('C', $byte)[1]; } } src/Decoders/ColorTableDecoder.php000064400000001023150731645450013116 0ustar00getLength() / 3); $i++) { $table->addColor(Color::decode($this->handle)); } return $table; } } src/Decoders/CommentExtensionDecoder.php000064400000002346150731645450014400 0ustar00getNextBytes(2); // skip marker & label $extension = new CommentExtension(); foreach ($this->decodeComments() as $comment) { $extension->addComment($comment); } return $extension; } /** * Decode comment from current source * * @return array */ protected function decodeComments(): array { $comments = []; do { $byte = $this->getNextByte(); $size = $this->decodeBlocksize($byte); if ($size > 0) { $comments[] = $this->getNextBytes($size); } } while ($byte !== CommentExtension::TERMINATOR); return $comments; } /** * Decode blocksize of following comment * * @param string $byte * @return int */ protected function decodeBlocksize(string $byte): int { return (int) @unpack('C', $byte)[1]; } } src/Decoders/DataSubBlockDecoder.php000064400000000677150731645450013404 0ustar00getNextByte(); $size = (int) unpack('C', $char)[1]; return new DataSubBlock($this->getNextBytes($size)); } } src/Decoders/FrameBlockDecoder.php000064400000003456150731645450013111 0ustar00viewNextBytes(2)) { AbstractExtension::MARKER . GraphicControlExtension::LABEL => GraphicControlExtension::decode($this->handle), AbstractExtension::MARKER . NetscapeApplicationExtension::LABEL => NetscapeApplicationExtension::decode($this->handle), AbstractExtension::MARKER . ApplicationExtension::LABEL => ApplicationExtension::decode($this->handle), AbstractExtension::MARKER . PlainTextExtension::LABEL => PlainTextExtension::decode($this->handle), AbstractExtension::MARKER . CommentExtension::LABEL => CommentExtension::decode($this->handle), default => match ($this->viewNextByte()) { ImageDescriptor::SEPARATOR => TableBasedImage::decode($this->handle), default => throw new DecoderException('Unable to decode Data Block'), } }; $frame->addEntity($block); } while (!($block instanceof TableBasedImage)); return $frame; } } src/Decoders/GifDataStreamDecoder.php000064400000002252150731645450013550 0ustar00setHeader( Header::decode($this->handle), ); $gif->setLogicalScreenDescriptor( LogicalScreenDescriptor::decode($this->handle), ); if ($gif->getLogicalScreenDescriptor()->hasGlobalColorTable()) { $length = $gif->getLogicalScreenDescriptor()->getGlobalColorTableByteSize(); $gif->setGlobalColorTable( ColorTable::decode($this->handle, $length) ); } while ($this->viewNextByte() != Trailer::MARKER) { $gif->addFrame(FrameBlock::decode($this->handle)); } return $gif; } } src/Decoders/GraphicControlExtensionDecoder.php000064400000004304150731645450015710 0ustar00getNextBytes(3); // skip marker, label & bytesize // byte #4 $packedField = $this->getNextByte(); $result->setDisposalMethod($this->decodeDisposalMethod($packedField)); $result->setUserInput($this->decodeUserInput($packedField)); $result->setTransparentColorExistance($this->decodeTransparentColorExistance($packedField)); // bytes 5-6 $result->setDelay($this->decodeDelay($this->getNextBytes(2))); // byte #7 $result->setTransparentColorIndex($this->decodeTransparentColorIndex( $this->getNextByte() )); // byte #8 (terminator) $this->getNextByte(); return $result; } /** * Decode disposal method * * @return DisposalMethod */ protected function decodeDisposalMethod(string $byte): DisposalMethod { return DisposalMethod::from( bindec($this->getPackedBits($byte, 3, 3)) ); } /** * Decode user input flag * * @return bool */ protected function decodeUserInput(string $byte): bool { return $this->hasPackedBit($byte, 6); } /** * Decode transparent color existance * * @return bool */ protected function decodeTransparentColorExistance(string $byte): bool { return $this->hasPackedBit($byte, 7); } /** * Decode delay value * * @return int */ protected function decodeDelay(string $bytes): int { return unpack('v*', $bytes)[1]; } /** * Decode transparent color index * * @return int */ protected function decodeTransparentColorIndex(string $byte): int { return unpack('C', $byte)[1]; } } src/Decoders/HeaderDecoder.php000064400000001501150731645450012261 0ustar00setVersion($this->decodeVersion()); return $header; } /** * Decode version string * * @return string */ protected function decodeVersion(): string { $parsed = (bool) preg_match("/^GIF(?P[0-9]{2}[a-z])$/", $this->getNextBytes(6), $matches); if ($parsed === false) { throw new DecoderException('Unable to parse file header.'); } return $matches['version']; } } src/Decoders/ImageDataDecoder.php000064400000001601150731645450012706 0ustar00getNextByte(); $size = (int) unpack('C', $char)[1]; $data->setLzwMinCodeSize($size); do { // decode sub blocks $char = $this->getNextByte(); $size = (int) unpack('C', $char)[1]; if ($size > 0) { $data->addBlock(new DataSubBlock($this->getNextBytes($size))); } } while ($char !== AbstractEntity::TERMINATOR); return $data; } } src/Decoders/ImageDescriptorDecoder.php000064400000004136150731645450014161 0ustar00getNextByte(); // skip separator $descriptor->setPosition( $this->decodeMultiByte($this->getNextBytes(2)), $this->decodeMultiByte($this->getNextBytes(2)) ); $descriptor->setSize( $this->decodeMultiByte($this->getNextBytes(2)), $this->decodeMultiByte($this->getNextBytes(2)) ); $packedField = $this->getNextByte(); $descriptor->setLocalColorTableExistance( $this->decodeLocalColorTableExistance($packedField) ); $descriptor->setLocalColorTableSorted( $this->decodeLocalColorTableSorted($packedField) ); $descriptor->setLocalColorTableSize( $this->decodeLocalColorTableSize($packedField) ); $descriptor->setInterlaced( $this->decodeInterlaced($packedField) ); return $descriptor; } /** * Decode local color table existance * * @return bool */ protected function decodeLocalColorTableExistance(string $byte): bool { return $this->hasPackedBit($byte, 0); } /** * Decode local color table sort method * * @return bool */ protected function decodeLocalColorTableSorted(string $byte): bool { return $this->hasPackedBit($byte, 2); } /** * Decode local color table size * * @return int */ protected function decodeLocalColorTableSize(string $byte): int { return bindec($this->getPackedBits($byte, 5, 3)); } /** * Decode interlaced flag * * @return bool */ protected function decodeInterlaced(string $byte): bool { return $this->hasPackedBit($byte, 1); } } src/Decoders/LogicalScreenDescriptorDecoder.php000064400000006200150731645450015643 0ustar00setSize( $this->decodeWidth($this->getNextBytes(2)), $this->decodeHeight($this->getNextBytes(2)) ); // byte 5 $packedField = $this->getNextByte(); $logicalScreenDescriptor->setGlobalColorTableExistance( $this->decodeGlobalColorTableExistance($packedField) ); $logicalScreenDescriptor->setBitsPerPixel( $this->decodeBitsPerPixel($packedField) ); $logicalScreenDescriptor->setGlobalColorTableSorted( $this->decodeGlobalColorTableSorted($packedField) ); $logicalScreenDescriptor->setGlobalColorTableSize( $this->decodeGlobalColorTableSize($packedField) ); // byte 6 $logicalScreenDescriptor->setBackgroundColorIndex( $this->decodeBackgroundColorIndex($this->getNextByte()) ); // byte 7 $logicalScreenDescriptor->setPixelAspectRatio( $this->decodePixelAspectRatio($this->getNextByte()) ); return $logicalScreenDescriptor; } /** * Decode width * * @return int */ protected function decodeWidth(string $source): int { return unpack('v*', $source)[1]; } /** * Decode height * * @return int */ protected function decodeHeight(string $source): int { return unpack('v*', $source)[1]; } /** * Decode existance of global color table * * @return bool */ protected function decodeGlobalColorTableExistance(string $byte): bool { return $this->hasPackedBit($byte, 0); } /** * Decode color resolution in bits per pixel * * @return int */ protected function decodeBitsPerPixel(string $byte): int { return bindec($this->getPackedBits($byte, 1, 3)) + 1; } /** * Decode global color table sorted status * * @return bool */ protected function decodeGlobalColorTableSorted(string $byte): bool { return $this->hasPackedBit($byte, 4); } /** * Decode size of global color table * * @return int */ protected function decodeGlobalColorTableSize(string $byte): int { return bindec($this->getPackedBits($byte, 5, 3)); } /** * Decode background color index * * @return int */ protected function decodeBackgroundColorIndex(string $source): int { return unpack('C', $source)[1]; } /** * Decode pixel aspect ratio * * @return int */ protected function decodePixelAspectRatio(string $source): int { return unpack('C', $source)[1]; } } src/Decoders/NetscapeApplicationExtensionDecoder.php000064400000000325150731645450016717 0ustar00getNextBytes(2); // skip info block $this->getNextBytes($this->getInfoBlockSize()); // text blocks $extension->setText($this->decodeTextBlocks()); return $extension; } /** * Get number of bytes in header block * * @return int */ protected function getInfoBlockSize(): int { return unpack('C', $this->getNextByte())[1]; } /** * Decode text sub blocks * * @return array */ protected function decodeTextBlocks(): array { $blocks = []; do { $char = $this->getNextByte(); $size = (int) unpack('C', $char)[1]; if ($size > 0) { $blocks[] = $this->getNextBytes($size); } } while ($char !== PlainTextExtension::TERMINATOR); return $blocks; } } src/Decoders/TableBasedImageDecoder.php000064400000001557150731645450014035 0ustar00setImageDescriptor(ImageDescriptor::decode($this->handle)); if ($block->getImageDescriptor()->hasLocalColorTable()) { $block->setColorTable( ColorTable::decode( $this->handle, $block->getImageDescriptor()->getLocalColorTableByteSize() ) ); } $block->setImageData( ImageData::decode($this->handle) ); return $block; } } src/DisposalMethod.php000064400000000513150731645450010774 0ustar00source = $source; } /** * Encode current source * * @return string */ public function encode(): string { return implode('', [ ApplicationExtension::MARKER, ApplicationExtension::LABEL, pack('C', $this->source->getBlockSize()), $this->source->getApplication(), implode('', array_map(function ($block) { return $block->encode(); }, $this->source->getBlocks())), ApplicationExtension::TERMINATOR, ]); } } src/Encoders/ColorEncoder.php000064400000001530150731645450012175 0ustar00source = $source; } /** * Encode current source * * @return string */ public function encode(): string { return implode('', [ $this->encodeColorValue($this->source->getRed()), $this->encodeColorValue($this->source->getGreen()), $this->encodeColorValue($this->source->getBlue()), ]); } /** * Encode color value * * @return string */ protected function encodeColorValue(int $value): string { return pack('C', $value); } } src/Encoders/ColorTableEncoder.php000064400000001133150731645450013144 0ustar00source = $source; } /** * Encode current source * * @return string */ public function encode(): string { return implode('', array_map(function ($color) { return $color->encode(); }, $this->source->getColors())); } } src/Encoders/CommentExtensionEncoder.php000064400000001737150731645450014427 0ustar00source = $source; } /** * Encode current source * * @return string */ public function encode(): string { return implode('', [ CommentExtension::MARKER, CommentExtension::LABEL, $this->encodeComments(), CommentExtension::TERMINATOR, ]); } /** * Encode comment blocks * * @return string */ protected function encodeComments(): string { return implode('', array_map(function ($comment) { return pack('C', strlen($comment)) . $comment; }, $this->source->getComments())); } } src/Encoders/DataSubBlockEncoder.php000064400000001054150731645450013416 0ustar00source = $source; } /** * Encode current source * * @return string */ public function encode(): string { return pack('C', $this->source->getSize()) . $this->source->getValue(); } } src/Encoders/FrameBlockEncoder.php000064400000002406150731645450013127 0ustar00source = $source; } public function encode(): string { $graphicControlExtension = $this->source->getGraphicControlExtension(); $colorTable = $this->source->getColorTable(); $plainTextExtension = $this->source->getPlainTextExtension(); return implode('', [ implode('', array_map(function ($extension) { return $extension->encode(); }, $this->source->getApplicationExtensions())), implode('', array_map(function ($extension) { return $extension->encode(); }, $this->source->getCommentExtensions())), $plainTextExtension ? $plainTextExtension->encode() : '', $graphicControlExtension ? $graphicControlExtension->encode() : '', $this->source->getImageDescriptor()->encode(), $colorTable ? $colorTable->encode() : '', $this->source->getImageData()->encode(), ]); } } src/Encoders/GifDataStreamEncoder.php000064400000002362150731645450013576 0ustar00source = $source; } /** * Encode current source * * @return string */ public function encode(): string { return implode('', [ $this->source->getHeader()->encode(), $this->source->getLogicalScreenDescriptor()->encode(), $this->maybeEncodeGlobalColorTable(), $this->encodeFrames(), $this->source->getTrailer()->encode(), ]); } protected function maybeEncodeGlobalColorTable(): string { if (!$this->source->hasGlobalColorTable()) { return ''; } return $this->source->getGlobalColorTable()->encode(); } /** * Encode data blocks of source * * @return string */ protected function encodeFrames(): string { return implode('', array_map(function ($frame) { return $frame->encode(); }, $this->source->getFrames())); } } src/Encoders/GraphicControlExtensionEncoder.php000064400000003303150731645450015732 0ustar00source = $source; } /** * Encode current source * * @return string */ public function encode(): string { return implode('', [ GraphicControlExtension::MARKER, GraphicControlExtension::LABEL, GraphicControlExtension::BLOCKSIZE, $this->encodePackedField(), $this->encodeDelay(), $this->encodeTransparentColorIndex(), GraphicControlExtension::TERMINATOR, ]); } /** * Encode delay time * * @return string */ protected function encodeDelay(): string { return pack('v*', $this->source->getDelay()); } /** * Encode transparent color index * * @return string */ protected function encodeTransparentColorIndex(): string { return pack('C', $this->source->getTransparentColorIndex()); } /** * Encode packed field * * @return string */ protected function encodePackedField(): string { return pack('C', bindec(implode('', [ str_pad('0', 3, '0', STR_PAD_LEFT), str_pad(decbin($this->source->getDisposalMethod()->value), 3, '0', STR_PAD_LEFT), (int) $this->source->getUserInput(), (int) $this->source->getTransparentColorExistance(), ]))); } } src/Encoders/HeaderEncoder.php000064400000001004150731645450012303 0ustar00source = $source; } /** * Encode current source * * @return string */ public function encode(): string { return Header::SIGNATURE . $this->source->getVersion(); } } src/Encoders/ImageDataEncoder.php000064400000001675150731645450012745 0ustar00source = $source; } /** * Encode current source * * @return string */ public function encode(): string { if (!$this->source->hasBlocks()) { throw new EncoderException("No data blocks in ImageData."); } return implode('', [ pack('C', $this->source->getLzwMinCodeSize()), implode('', array_map(function ($block) { return $block->encode(); }, $this->source->getBlocks())), AbstractEntity::TERMINATOR, ]); } } src/Encoders/ImageDescriptorEncoder.php000064400000004504150731645450014204 0ustar00source = $source; } /** * Encode current source * * @return string */ public function encode(): string { return implode('', [ ImageDescriptor::SEPARATOR, $this->encodeLeft(), $this->encodeTop(), $this->encodeWidth(), $this->encodeHeight(), $this->encodePackedField(), ]); } /** * Encode left value * * @return string */ protected function encodeLeft(): string { return pack('v*', $this->source->getLeft()); } /** * Encode top value * * @return string */ protected function encodeTop(): string { return pack('v*', $this->source->getTop()); } /** * Encode width value * * @return string */ protected function encodeWidth(): string { return pack('v*', $this->source->getWidth()); } /** * Encode height value * * @return string */ protected function encodeHeight(): string { return pack('v*', $this->source->getHeight()); } /** * Encode size of local color table * * @return string */ protected function encodeLocalColorTableSize(): string { return str_pad(decbin($this->source->getLocalColorTableSize()), 3, '0', STR_PAD_LEFT); } /** * Encode reserved field * * @return string */ protected function encodeReservedField(): string { return str_pad('0', 2, '0', STR_PAD_LEFT); } /** * Encode packed field * * @return string */ protected function encodePackedField(): string { return pack('C', bindec(implode('', [ (int) $this->source->getLocalColorTableExistance(), (int) $this->source->isInterlaced(), (int) $this->source->getLocalColorTableSorted(), $this->encodeReservedField(), $this->encodeLocalColorTableSize(), ]))); } } src/Encoders/LogicalScreenDescriptorEncoder.php000064400000004775150731645450015706 0ustar00source = $source; } /** * Encode current source * * @return string */ public function encode(): string { return implode('', [ $this->encodeWidth(), $this->encodeHeight(), $this->encodePackedField(), $this->encodeBackgroundColorIndex(), $this->encodePixelAspectRatio(), ]); } /** * Encode width of current instance * * @return string */ protected function encodeWidth(): string { return pack('v*', $this->source->getWidth()); } /** * Encode height of current instance * * @return string */ protected function encodeHeight(): string { return pack('v*', $this->source->getHeight()); } /** * Encode background color index of global color table * * @return string */ protected function encodeBackgroundColorIndex(): string { return pack('C', $this->source->getBackgroundColorIndex()); } /** * Encode pixel aspect ratio * * @return string */ protected function encodePixelAspectRatio(): string { return pack('C', $this->source->getPixelAspectRatio()); } /** * Return color resolution for encoding * * @return string */ protected function encodeColorResolution(): string { return str_pad(decbin($this->source->getBitsPerPixel() - 1), 3, '0', STR_PAD_LEFT); } /** * Encode size of global color table * * @return string */ protected function encodeGlobalColorTableSize(): string { return str_pad(decbin($this->source->getGlobalColorTableSize()), 3, '0', STR_PAD_LEFT); } /** * Encode packed field of current instance * * @return string */ protected function encodePackedField(): string { return pack('C', bindec(implode('', [ (int) $this->source->getGlobalColorTableExistance(), $this->encodeColorResolution(), (int) $this->source->getGlobalColorTableSorted(), $this->encodeGlobalColorTableSize(), ]))); } } src/Encoders/NetscapeApplicationExtensionEncoder.php000064400000001773150731645450016753 0ustar00source = $source; } /** * Encode current source * * @return string */ public function encode(): string { return implode('', [ ApplicationExtension::MARKER, ApplicationExtension::LABEL, pack('C', $this->source->getBlockSize()), $this->source->getApplication(), implode('', array_map(function ($block) { return $block->encode(); }, $this->source->getBlocks())), ApplicationExtension::TERMINATOR, ]); } } src/Encoders/PlainTextExtensionEncoder.php000064400000002402150731645450014723 0ustar00source = $source; } /** * Encode current source * * @return string */ public function encode(): string { if (! $this->source->hasText()) { return ''; } return implode('', [ PlainTextExtension::MARKER, PlainTextExtension::LABEL, $this->encodeHead(), $this->encodeTexts(), PlainTextExtension::TERMINATOR, ]); } /** * Encode head block * * @return string */ protected function encodeHead(): string { return "\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; } /** * Encode text chunks * * @return string */ protected function encodeTexts(): string { return implode('', array_map(function ($text) { return pack('C', strlen($text)) . $text; }, $this->source->getText())); } } src/Encoders/TableBasedImageEncoder.php000064400000001223150731645450014047 0ustar00source = $source; } public function encode(): string { return implode('', [ $this->source->getImageDescriptor()->encode(), $this->source->getColorTable() ? $this->source->getColorTable()->encode() : '', $this->source->getImageData()->encode(), ]); } } src/Encoders/TrailerEncoder.php000064400000000750150731645450012524 0ustar00source = $source; } /** * Encode current source * * @return string */ public function encode(): string { return Trailer::MARKER; } } src/Exceptions/DecoderException.php000064400000000176150731645450013427 0ustar00header; } /** * Set header * * @param Header $header */ public function setHeader(Header $header): self { $this->header = $header; return $this; } /** * Get logical screen descriptor * * @return LogicalScreenDescriptor */ public function getLogicalScreenDescriptor(): LogicalScreenDescriptor { return $this->logicalScreenDescriptor; } /** * Set logical screen descriptor * * @param LogicalScreenDescriptor $descriptor * @return GifDataStream */ public function setLogicalScreenDescriptor(LogicalScreenDescriptor $descriptor): self { $this->logicalScreenDescriptor = $descriptor; return $this; } /** * Return global color table if available else null * * @return null|ColorTable */ public function getGlobalColorTable(): ?ColorTable { return $this->globalColorTable; } /** * Set global color table * * @param ColorTable $table * @return GifDataStream */ public function setGlobalColorTable(ColorTable $table): self { $this->globalColorTable = $table; $this->logicalScreenDescriptor->setGlobalColorTableExistance(true); $this->logicalScreenDescriptor->setGlobalColorTableSize( $table->getLogicalSize() ); return $this; } /** * Get main graphic control extension * * @return NetscapeApplicationExtension */ public function getMainApplicationExtension(): ?NetscapeApplicationExtension { foreach ($this->frames as $frame) { if ($extension = $frame->getNetscapeExtension()) { return $extension; } } return null; } /** * Get array of frames * * @return array */ public function getFrames(): array { return $this->frames; } /** * Return first frame * * @return null|FrameBlock */ public function getFirstFrame(): ?FrameBlock { if (!array_key_exists(0, $this->frames)) { return null; } return $this->frames[0]; } /** * Add frame * * @param FrameBlock $frame * @return GifDataStream */ public function addFrame(FrameBlock $frame): self { $this->frames[] = $frame; return $this; } /** * Set the current data * * @param array $frames */ public function setFrames(array $frames): self { $this->frames = $frames; return $this; } /** * Get trailer * * @return Trailer */ public function getTrailer(): Trailer { return new Trailer(); } /** * Determine if gif is animated * * @return bool */ public function isAnimated(): bool { return count($this->getFrames()) > 1; } /** * Determine if global color table is set * * @return bool */ public function hasGlobalColorTable(): bool { return !is_null($this->globalColorTable); } } src/Splitter.php000064400000017326150731645450007675 0ustar00frames); } /** * Get frames * * @return array */ public function getFrames(): array { return $this->frames; } /** * Get delays * * @return array */ public function getDelays(): array { return $this->delays; } /** * Set stream of instance * * @param GifDataStream $stream */ public function setStream(GifDataStream $stream): Splitter { $this->stream = $stream; return $this; } /** * Static constructor method * * @param GifDataStream $stream * @return Splitter */ public static function create(GifDataStream $stream): Splitter { return new self($stream); } /** * Split current stream into array of seperate streams for each frame * * @return Splitter */ public function split(): self { $this->frames = []; foreach ($this->stream->getFrames() as $frame) { // create separate stream for each frame $gif = Builder::canvas( $this->stream->getLogicalScreenDescriptor()->getWidth(), $this->stream->getLogicalScreenDescriptor()->getHeight() )->getGifDataStream(); // check if working stream has global color table if ($this->stream->hasGlobalColorTable()) { $gif->setGlobalColorTable($this->stream->getGlobalColorTable()); $gif->getLogicalScreenDescriptor()->setGlobalColorTableExistance(true); $gif->getLogicalScreenDescriptor()->setGlobalColorTableSorted( $this->stream->getLogicalScreenDescriptor()->getGlobalColorTableSorted() ); $gif->getLogicalScreenDescriptor()->setGlobalColorTableSize( $this->stream->getLogicalScreenDescriptor()->getGlobalColorTableSize() ); $gif->getLogicalScreenDescriptor()->setBackgroundColorIndex( $this->stream->getLogicalScreenDescriptor()->getBackgroundColorIndex() ); $gif->getLogicalScreenDescriptor()->setPixelAspectRatio( $this->stream->getLogicalScreenDescriptor()->getPixelAspectRatio() ); $gif->getLogicalScreenDescriptor()->setBitsPerPixel( $this->stream->getLogicalScreenDescriptor()->getBitsPerPixel() ); } // copy original frame $gif->addFrame($frame); $this->frames[] = $gif; $this->delays[] = match (is_object($frame->getGraphicControlExtension())) { true => $frame->getGraphicControlExtension()->getDelay(), default => 0, }; } return $this; } /** * Return array of GD library resources for each frame * * @return array */ public function toResources(): array { $resources = []; foreach ($this->frames as $frame) { if (is_a($frame, GifDataStream::class)) { $resource = imagecreatefromstring($frame->encode()); imagepalettetotruecolor($resource); imagesavealpha($resource, true); $resources[] = $resource; } } return $resources; } /** * Return array of coalesced GD library resources for each frame * * @return array */ public function coalesceToResources(): array { $resources = $this->toResources(); // static gif files don't need to be coalesced if (count($resources) === 1) { return $resources; } $width = imagesx($resources[0]); $height = imagesy($resources[0]); $transparent = imagecolortransparent($resources[0]); foreach ($resources as $key => $resource) { // get meta data $gif = $this->frames[$key]; $descriptor = $gif->getFirstFrame()->getImageDescriptor(); $offset_x = $descriptor->getLeft(); $offset_y = $descriptor->getTop(); $w = $descriptor->getWidth(); $h = $descriptor->getHeight(); if (in_array($this->getDisposalMethod($gif), [DisposalMethod::NONE, DisposalMethod::PREVIOUS])) { if ($key >= 1) { // create normalized gd image $canvas = imagecreatetruecolor($width, $height); if (imagecolortransparent($resource) != -1) { $transparent = imagecolortransparent($resource); } else { $transparent = imagecolorallocatealpha($resource, 255, 0, 255, 127); } // fill with transparent imagefill($canvas, 0, 0, $transparent); imagecolortransparent($canvas, $transparent); imagealphablending($canvas, true); // insert last as base imagecopy( $canvas, $resources[$key - 1], 0, 0, 0, 0, $width, $height ); // insert resource imagecopy( $canvas, $resource, $offset_x, $offset_y, 0, 0, $w, $h ); } else { imagealphablending($resource, true); $canvas = $resource; } } else { // create normalized gd image $canvas = imagecreatetruecolor($width, $height); if (imagecolortransparent($resource) != -1) { $transparent = imagecolortransparent($resource); } else { $transparent = imagecolorallocatealpha($resource, 255, 0, 255, 127); } // fill with transparent imagefill($canvas, 0, 0, $transparent); imagecolortransparent($canvas, $transparent); imagealphablending($canvas, true); // insert frame resource imagecopy( $canvas, $resource, $offset_x, $offset_y, 0, 0, $w, $h ); } $resources[$key] = $canvas; } return $resources; } /** * Find and return disposal method of given gif data stream * * @param GifDataStream $gif * @return DisposalMethod */ private function getDisposalMethod(GifDataStream $gif): DisposalMethod { return $gif->getFirstFrame()->getGraphicControlExtension()->getDisposalMethod(); } } src/Traits/CanDecode.php000064400000002326150731645450011134 0ustar00decode(); } /** * Get decoder for current instance * * @param resource $source * @param null|int $length * @return AbstractDecoder */ protected static function getDecoder($source, ?int $length = null): AbstractDecoder { $classname = self::getDecoderClassname(); if (!class_exists($classname)) { throw new DecoderException("Decoder for '" . get_called_class() . "' not found."); } return new $classname($source, $length); } /** * Get classname of decoder for current classname * * @return string */ protected static function getDecoderClassname(): string { return sprintf('Intervention\Gif\Decoders\%sDecoder', self::getShortClassname()); } } src/Traits/CanEncode.php000064400000001756150731645450011154 0ustar00getEncoder()->encode(); } /** * Get encoder object for current entity * * @return AbstractEncoder */ protected function getEncoder(): AbstractEncoder { $classname = $this->getEncoderClassname(); if (!class_exists($classname)) { throw new EncoderException("Encoder for '" . get_class($this) . "' not found."); } return new $classname($this); } /** * Get encoder classname for current entity * * @return string */ protected function getEncoderClassname(): string { return sprintf('Intervention\Gif\Encoders\%sEncoder', $this->getShortClassname()); } } src/Traits/CanHandleFiles.php000064400000002154150731645450012126 0ustar00