.github/workflows/build.yml 0000644 00000001204 15073164573 0011773 0 ustar 00 name: Tests
on: [push]
jobs:
run:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.0', '8.1']
name: Testing on PHP ${{ matrix.php }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mbstring,bcmath
tools: composer
- name: Install PHP dependencies
run: composer install -q
- name: Run phpunit
run: vendor/bin/phpunit
- name: Run phpstan
run: vendor/bin/phpstan analyze --no-progress --level=1 src/
.gitignore 0000755 00000000065 15073164573 0006553 0 ustar 00 .DS_Store
.phpunit.result.cache
composer.lock
vendor/ LICENSE 0000755 00000002066 15073164573 0005573 0 ustar 00 The 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.md 0000755 00000003502 15073164573 0006041 0 ustar 00 # Intervention Gif
Native PHP GIF Encoder/Decoder.
[](https://packagist.org/packages/intervention/gif)

[](https://packagist.org/packages/intervention/gif/stats)
## Installation
You can install this package easily with [Composer](https://getcomposer.org/).
Just require the package with the following command:
$ composer require intervention/gif
## Usage
### 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 an empty canvas
//
$width = 32;
$height = 32;
$loops = 0; // 0 for unlimited repetitions
// create new gif with width/height and optional
// number of repetitions of animation
$gif = Builder::canvas($width, $height, $loops);
// 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');
// encode
$data = $gif->encode();
```
## License
Intervention Gif is licensed under the [MIT License](http://opensource.org/licenses/MIT).
Copyright 2020 [Oliver Vogel](http://olivervogel.com/)
composer.json 0000755 00000001446 15073164573 0007311 0 ustar 00 {
"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.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0",
"phpstan/phpstan": "^1"
},
"autoload": {
"psr-4": {
"Intervention\\Gif\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Intervention\\Gif\\Test\\": "tests"
}
},
"minimum-stability": "stable"
}
phpunit.xml 0000755 00000001035 15073164573 0006772 0 ustar 00
./tests/
src/AbstractEntity.php 0000644 00000000741 15073164573 0011021 0 ustar 00 getShortName();
}
}
src/AbstractExtension.php 0000644 00000000202 15073164573 0011511 0 ustar 00 application);
}
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/Builder.php 0000644 00000010065 15073164573 0007447 0 ustar 00 gif;
}
/**
* Create new canvas
*
* @param int $width
* @param int $height
* @param int $loops
* @return self
*/
public static function canvas(int $width, int $height, int $loops = 0): self
{
$builder = new self();
$gif = new GifDataStream();
// set width +height
$gif->getLogicalScreen()->getDescriptor()->setSize($width, $height);
if ($loops >= 0 && $loops !== 1) {
// set loop count
$gif->addData(
(new NetscapeApplicationExtension())->setLoops($loops)
);
}
$builder->gif = $gif;
return $builder;
}
/**
* Create new animation frame from given source
* which can be path to a file or GIF image data
*
* @param string $source
* @param integer $delay time delay in seconds
* @param integer $left position offset in pixels from left
* @param integer $top position offset in pixels from top
*/
public function addFrame(string $source, float $delay = 0, int $left = 0, int $top = 0): self
{
$data = new GraphicBlock();
// store delay
$data->setGraphicControlExtension(
$this->buildGraphicControlExtension(intval($delay * 100))
);
// store image
$data->setGraphicRenderingBlock(
$this->buildTableBasedImage($source, $left, $top)
);
// add frame
$this->gif->addData($data);
return $this;
}
/**
* Build new graphic control extension object with given delay
*
* @param float $delay
* @return GraphicControlExtension
*/
protected function buildGraphicControlExtension(int $delay): GraphicControlExtension
{
$extension = new GraphicControlExtension();
$extension->setDelay($delay);
return $extension;
}
/**
* Build table based image object from given source
*
* @param string $source
* @param int $left
* @param int $top
* @return TableBasedImage
*/
protected function buildTableBasedImage(string $source, int $left, int $top): TableBasedImage
{
$block = new TableBasedImage();
$source = Decoder::decode($source);
// $name = '__' . rand(1, 1000);
// file_put_contents(__DIR__ . '/../tests/' . $name . '.gif', $source->encode());
// add global color table from source as local color table
$block->getDescriptor()->setLocalColorTableExistance();
$block->setColorTable(
$source->getLogicalScreen()->getColorTable()
);
$block->getDescriptor()->setLocalColorTableSorted(
$source->getLogicalScreen()->getDescriptor()->getGlobalColorTableSorted()
);
$block->getDescriptor()->setLocalColorTableSize(
$source->getLogicalScreen()->getDescriptor()->getGlobalColorTableSize()
);
$block->getDescriptor()->setSize(
$source->getLogicalScreen()->getDescriptor()->getWidth(),
$source->getLogicalScreen()->getDescriptor()->getHeight()
);
// set position
$block->getDescriptor()->setPosition($left, $top);
// add image data from source
$block->setData(
$source->getData()[0]->getGraphicRenderingBlock()->getData()
);
return $block;
}
public function modifyGif(callable $callback): self
{
$callback($this->gif);
return $this;
}
/**
* Encode the current build
*
* @return string
*/
public function encode(): string
{
return $this->gif->encode();
}
}
src/Color.php 0000644 00000003230 15073164573 0007133 0 ustar 00 r = $r;
$this->g = $g;
$this->b = $b;
}
/**
* Get red value
*
* @return int
*/
public function getRed()
{
return $this->r;
}
/**
* 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/ColorTable.php 0000644 00000005201 15073164573 0010103 0 ustar 00 setColors($colors);
}
/**
* Return array of current colors
*
* @return array
*/
public function getColors(): array
{
return array_values($this->colors);
}
/**
* 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 boolean
*/
public function hasColors()
{
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/CommentExtension.php 0000644 00000001474 15073164573 0011364 0 ustar 00 comments;
}
return array_key_exists($num, $this->comments) ? $this->comments[$num] : null;
}
/**
* Set comment text
*
* @param string $value
*/
public function addComment(string $value): self
{
$this->comments[] = $value;
return $this;
}
}
src/Contracts/DataBlock.php 0000644 00000000124 15073164573 0011640 0 ustar 00 value = $value;
if ($this->getSize() > 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/Decoder.php 0000644 00000001146 15073164573 0007426 0 ustar 00 handle = $handle;
if (is_callable($callback)) {
$callback($this);
}
}
/**
* Set source to decode
*
* @param resource $handle
*/
public function setHandle($handle): self
{
$this->handle = $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 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);
}
/**
* Get bytes fixed by length property
*
* @return string
*/
protected function getFixedBytes(): string
{
if (empty($this->length)) {
throw new DecoderException(
"Length must be defined, in order to call getFixedBytes(). Call setLength() first."
);
}
return $this->getNextBytes($this->getLength());
}
/**
* 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;
}
/**
* Abort decoding process with exception
*
* @param string|null $message
*/
protected function abort(string $message = null): void
{
throw new DecoderException($message);
}
}
src/Decoder/AbstractPackedBitDecoder.php 0000644 00000001653 15073164573 0014231 0 ustar 00 getPackedBits($byte)[$num];
}
/**
* Get packed bits
*
* @param integer $start
* @param integer $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/Decoder/ApplicationExtensionDecoder.php 0000644 00000003036 15073164573 0015054 0 ustar 00 getNextByte();
$label = $this->getNextByte();
$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;
}
protected function decodeBlockSize(string $byte): int
{
return (int) @unpack('C', $byte)[1];
}
}
src/Decoder/ColorDecoder.php 0000644 00000001455 15073164573 0011775 0 ustar 00 setRed($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/Decoder/ColorTableDecoder.php 0000644 00000001110 15073164573 0012731 0 ustar 00 getLength() / 3); $i++) {
$table->addColor(Color::decode($this->handle));
}
return $table;
}
}
src/Decoder/CommentExtensionDecoder.php 0000644 00000002355 15073164573 0014216 0 ustar 00 getNextBytes(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/Decoder/DataSubBlockDecoder.php 0000644 00000000767 15073164573 0013222 0 ustar 00 getNextByte();
$size = (int) unpack('C', $char)[1];
return new DataSubBlock($this->getNextBytes($size));
}
}
src/Decoder/GifDataStreamDecoder.php 0000644 00000005064 15073164573 0013372 0 ustar 00 setHeader(Header::decode($this->handle));
$gif->setLogicalScreen(LogicalScreen::decode($this->handle));
while (! feof($this->handle)) {
if ($block = $this->decodeNextDataBlock()) {
$gif->addData($block);
}
}
return $gif;
}
/**
* Decode data blocks from source into the given data stream object
*
* @return DataBlock
*/
protected function decodeNextDataBlock(): ?DataBlock
{
//graphicblock ([GraphicControlExtension] | ((ImageDescriptor [LocalColorTable] ImageData) | PlainTextExtension))
//or special purpose block (ApplicationExtension | CommentExtension)
$marker = $this->getNextByte();
$label = $this->getNextByte();
if ($marker === AbstractExtension::MARKER) {
// extension
if ($label === ApplicationExtension::LABEL) {
// special purpose block
return ApplicationExtension::decode($this->handle, function ($decoder) {
$decoder->movePointer(-2);
});
} elseif ($label === CommentExtension::LABEL) {
// special purpose block
return CommentExtension::decode($this->handle, function ($decoder) {
$decoder->movePointer(-2);
});
}
}
if ($marker === Trailer::MARKER) {
return null;
}
return GraphicBlock::decode($this->handle, function ($decoder) {
$decoder->movePointer(-2);
});
}
}
src/Decoder/GraphicBlockDecoder.php 0000644 00000003160 15073164573 0013242 0 ustar 00 getNextByte();
$label = $this->getNextByte();
$back = -2;
// plain text extension
if ($label === PlainTextExtension::LABEL) {
// graphic block is already complete
return $block->setGraphicRenderingBlock(
PlainTextExtension::decode($this->handle, function ($decoder) use ($back) {
$decoder->movePointer($back);
})
);
}
if ($label === GraphicControlExtension::LABEL) {
// graphic control extension
$block->setGraphicControlExtension(
GraphicControlExtension::decode($this->handle, function ($decoder) use ($back) {
$decoder->movePointer($back);
})
);
$back = 0;
}
// table based image
$block->setGraphicRenderingBlock(
TableBasedImage::decode($this->handle, function ($decoder) use ($back) {
$decoder->movePointer($back);
})
);
return $block;
}
}
src/Decoder/GraphicControlExtensionDecoder.php 0000644 00000004132 15073164573 0015525 0 ustar 00 getNextBytes(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 int
*/
protected function decodeDisposalMethod(string $byte): int
{
return 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/Decoder/HeaderDecoder.php 0000644 00000001523 15073164573 0012103 0 ustar 00 setVersion($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/Decoder/ImageDataDecoder.php 0000644 00000001542 15073164573 0012530 0 ustar 00 getNextByte();
$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/Decoder/ImageDescriptorDecoder.php 0000644 00000004220 15073164573 0013771 0 ustar 00 getNextByte(); // 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/Decoder/LogicalScreenDecoder.php 0000644 00000001470 15073164573 0013426 0 ustar 00 setDescriptor(LogicalScreenDescriptor::decode($this->handle));
if ($screen->getDescriptor()->hasGlobalColorTable()) {
$screen->setColorTable(ColorTable::decode($this->handle, function ($decoder) use ($screen) {
$decoder->setLength($screen->getDescriptor()->getGlobalColorTableByteSize());
}));
}
return $screen;
}
}
src/Decoder/LogicalScreenDescriptorDecoder.php 0000644 00000006161 15073164573 0015467 0 ustar 00 setSize(
$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/Decoder/NetscapeApplicationExtensionDecoder.php 0000644 00000000300 15073164573 0016526 0 ustar 00 getNextBytes(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/Decoder/TableBasedImageDecoder.php 0000644 00000001775 15073164573 0013655 0 ustar 00 setDescriptor(ImageDescriptor::decode($this->handle));
// local color table
if ($image->getDescriptor()->hasLocalColorTable()) {
$image->setColortable(ColorTable::decode($this->handle, function ($decoder) use ($image) {
$decoder->setLength($image->getDescriptor()->getLocalColorTableByteSize());
}));
}
// image data
$image->setData(ImageData::decode($this->handle));
return $image;
}
}
src/DisposalMethod.php 0000644 00000000264 15073164573 0011000 0 ustar 00 source = $source;
}
}
src/Encoder/ApplicationExtensionEncoder.php 0000644 00000001573 15073164573 0015104 0 ustar 00 source = $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/Encoder/ColorEncoder.php 0000644 00000001466 15073164573 0012023 0 ustar 00 source = $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/Encoder/ColorTableEncoder.php 0000644 00000001071 15073164573 0012763 0 ustar 00 source = $source;
}
/**
* Encode current source
*
* @return string
*/
public function encode(): string
{
return implode('', array_map(function ($color) {
return $color->encode();
}, $this->source->getColors()));
}
}
src/Encoder/CommentExtensionEncoder.php 0000644 00000001675 15073164573 0014246 0 ustar 00 source = $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/Encoder/DataSubBlockEncoder.php 0000644 00000001012 15073164573 0013226 0 ustar 00 source = $source;
}
/**
* Encode current source
*
* @return string
*/
public function encode(): string
{
return pack('C', $this->source->getSize()) . $this->source->getValue();
}
}
src/Encoder/GifDataStreamEncoder.php 0000644 00000001665 15073164573 0013421 0 ustar 00 source = $source;
}
/**
* Encode current source
*
* @return string
*/
public function encode(): string
{
return implode('', [
$this->source->getHeader()->encode(),
$this->source->getLogicalScreen()->encode(),
$this->encodeData(),
$this->source->getTrailer()->encode(),
]);
}
/**
* Encode data blocks of source
*
* @return string
*/
protected function encodeData(): string
{
return implode('', array_map(function ($block) {
return $block->encode();
}, $this->source->getData()));
}
}
src/Encoder/GraphicBlockEncoder.php 0000644 00000001627 15073164573 0013274 0 ustar 00 source = $source;
}
/**
* Encode current source
*
* @return string
*/
public function encode(): string
{
return implode('', [
$this->encodeGraphicControlExtension(),
$this->source->getGraphicRenderingBlock()->encode(),
]);
}
/**
* Encode graphic control extension from current source
*
* @return string
*/
protected function encodeGraphicControlExtension(): string
{
$extension = $this->source->getGraphicControlExtension();
return $extension ? $extension->encode() : '';
}
}
src/Encoder/GraphicControlExtensionEncoder.php 0000644 00000003232 15073164573 0015551 0 ustar 00 source = $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()), 3, '0', STR_PAD_LEFT),
(int) $this->source->getUserInput(),
(int) $this->source->getTransparentColorExistance(),
])));
}
}
src/Encoder/HeaderEncoder.php 0000644 00000000754 15073164573 0012134 0 ustar 00 source = $source;
}
/**
* Encode current source
*
* @return string
*/
public function encode(): string
{
return Header::SIGNATURE . $this->source->getVersion();
}
}
src/Encoder/ImageDataEncoder.php 0000644 00000001633 15073164573 0012555 0 ustar 00 source = $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/Encoder/ImageDescriptorEncoder.php 0000644 00000004442 15073164573 0014023 0 ustar 00 source = $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/Encoder/LogicalScreenDescriptorEncoder.php 0000644 00000004733 15073164573 0015516 0 ustar 00 source = $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/Encoder/LogicalScreenEncoder.php 0000644 00000001220 15073164573 0013443 0 ustar 00 source = $source;
}
/**
* Encode current source
*
* @return string
*/
public function encode(): string
{
$encoded = $this->source->getDescriptor()->encode();
if ($this->source->hasColorTable()) {
$encoded .= $this->source->getColorTable()->encode();
}
return $encoded;
}
}
src/Encoder/NetscapeApplicationExtensionEncoder.php 0000644 00000001712 15073164573 0016562 0 ustar 00 source = $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/Encoder/PlainTextExtensionEncoder.php 0000644 00000002366 15073164573 0014552 0 ustar 00 source = $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/Encoder/TableBasedImageEncoder.php 0000644 00000001622 15073164573 0013670 0 ustar 00 source = $source;
}
/**
* Encode current source
*
* @return string
*/
public function encode(): string
{
return implode('', [
$this->source->getDescriptor()->encode(),
$this->encodeLocalColorTable(),
$this->source->getData()->encode()
]);
}
/**
* Encode local color table if available
*
* @return string
*/
protected function encodeLocalColorTable(): string
{
$table = $this->source->getColorTable();
return $table ? $table->encode() : '';
}
}
src/Encoder/TrailerEncoder.php 0000644 00000000721 15073164573 0012340 0 ustar 00 source = $source;
}
/**
* Encode current source
*
* @return string
*/
public function encode(): string
{
return Trailer::MARKER;
}
}
src/Exception/DecoderException.php 0000644 00000000175 15073164573 0013244 0 ustar 00 header = new Header();
$this->logicalScreen = new LogicalScreen();
}
/**
* Get header
*
* @return Header
*/
public function getHeader(): Header
{
return $this->header;
}
/**
* Set header
*
* @param Header $header
*/
public function setHeader(Header $header): self
{
$this->header = $header;
return $this;
}
/**
* Get logical screen
*
* @return LogicalScreen
*/
public function getLogicalScreen(): LogicalScreen
{
return $this->logicalScreen;
}
/**
* Set logical screen
*
* @param LogicalScreen $screen
*/
public function setLogicalScreen(LogicalScreen $screen): self
{
$this->logicalScreen = $screen;
return $this;
}
/**
* Get main graphic control extension
*
* @return NetscapeApplicationExtension
*/
public function getMainApplicationExtension(): ?NetscapeApplicationExtension
{
foreach ($this->getData() as $block) {
if (is_a($block, NetscapeApplicationExtension::class)) {
return $block;
}
}
return null;
}
/**
* Get all graphic blocks
*
* @return array
*/
public function getGraphicBlocks(): array
{
return array_values(array_filter($this->getData(), function ($block) {
return is_a($block, GraphicBlock::class);
}));
}
/**
* Get all table based images
*
* @return array
*/
public function getTableBasedImages(): array
{
$blocks = array_filter($this->getGraphicBlocks(), function ($block) {
return is_a($block->getGraphicRenderingBlock(), TableBasedImage::class);
});
return array_values(array_map(function ($block) {
return $block->getGraphicRenderingBlock();
}, $blocks));
}
/**
* Get array of all image descriptors
*
* @return array
*/
public function getImageDescriptors(): array
{
return array_values(array_map(function ($tbi) {
return $tbi->getDescriptor();
}, $this->getTableBasedImages()));
}
/**
* Get array of data blocks
*
* @return array
*/
public function getData(): array
{
return $this->data;
}
/**
* Add to data
*
* @param DataBlock $block
*/
public function addData(DataBlock $block): self
{
$this->data[] = $block;
return $this;
}
/**
* Set the current data
*
* @param array $data
*/
public function setData(array $data): self
{
$this->data = $data;
return $this;
}
/**
* Get trailer
*
* @return Trailer
*/
public function getTrailer(): Trailer
{
return new Trailer();
}
/**
* Determine if gif is animated
*
* @return boolean
*/
public function isAnimated(): bool
{
return count($this->getGraphicBlocks()) > 1;
}
}
src/GraphicBlock.php 0000644 00000003530 15073164573 0010410 0 ustar 00 graphicRenderingBlock = new TableBasedImage();
}
/**
* Get graphic control extension
*
* @return GraphicControlExtension
*/
public function getGraphicControlExtension(): ?GraphicControlExtension
{
return $this->graphicControlExtension;
}
/**
* Set graphic control extension
*
* @param GraphicControlExtension $extension
*/
public function setGraphicControlExtension(GraphicControlExtension $extension): self
{
$this->graphicControlExtension = $extension;
return $this;
}
/**
* Determine if current instance has graphic control extension
*
* @return boolean
*/
public function hasGraphicControlExtension()
{
return is_a($this->graphicControlExtension, GraphicControlExtension::class);
}
/**
* Get graphic rendering block
*
* @return GraphicRenderingBlock
*/
public function getGraphicRenderingBlock(): GraphicRenderingBlock
{
return $this->graphicRenderingBlock;
}
/**
* Set graphic rendering block
*
* @param GraphicRenderingBlock $block
*/
public function setGraphicRenderingBlock(GraphicRenderingBlock $block): self
{
$this->graphicRenderingBlock = $block;
return $this;
}
}
src/GraphicControlExtension.php 0000644 00000005272 15073164573 0012700 0 ustar 00 delay = $value;
return $this;
}
/**
* Return delay time (1/100 second)
*
* @return int
*/
public function getDelay(): int
{
return $this->delay;
}
/**
* Set disposal method
*
* @param int $method
* @return self
*/
public function setDisposalMethod(int $method): self
{
$this->disposalMethod = $method;
return $this;
}
/**
* Get disposal method
*
* @return int
*/
public function getDisposalMethod(): int
{
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 boolean $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/Header.php 0000644 00000001107 15073164573 0007246 0 ustar 00 version = $value;
return $this;
}
/**
* Return current version
*
* @return string
*/
public function getVersion(): string
{
return $this->version;
}
}
src/ImageData.php 0000644 00000002331 15073164573 0007672 0 ustar 00 lzw_min_code_size;
}
/**
* Set lzw min. code size
*
* @param array $blocks
*/
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 $data
*/
public function addBlock(DataSubBlock $block): self
{
$this->blocks[] = $block;
return $this;
}
/**
* Determine if data sub blocks are present
*
* @return boolean
*/
public function hasBlocks(): bool
{
return count($this->blocks) >= 1;
}
}
src/ImageDescriptor.php 0000644 00000010257 15073164573 0011145 0 ustar 00 width);
}
/**
* 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 boolean
*/
public function isInterlaced(): bool
{
return $this->interlaced === true;
}
/**
* Set or unset interlaced value
*
* @param boolean $value
*/
public function setInterlaced(bool $value = true): self
{
$this->interlaced = $value;
return $this;
}
/**
* Determine if local color table is present
*
* @return boolean
*/
public function getLocalColorTableExistance(): bool
{
return $this->localColorTableExistance;
}
/**
* Alias for getLocalColorTableExistance
*
* @return boolean
*/
public function hasLocalColorTable(): bool
{
return $this->getLocalColorTableExistance();
}
/**
* Set local color table flag
*
* @param boolean $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 boolean $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/LogicalScreen.php 0000644 00000002674 15073164573 0010602 0 ustar 00 descriptor = new LogicalScreenDescriptor();
}
/**
* Get descriptor
*
* @return LogicalScreenDescriptor
*/
public function getDescriptor(): ?LogicalScreenDescriptor
{
return $this->descriptor;
}
/**
* Set descriptor
*
* @param LogicalScreenDescriptor $descriptor
*/
public function setDescriptor(LogicalScreenDescriptor $descriptor): self
{
$this->descriptor = $descriptor;
return $this;
}
/**
* Get color table
*
* @return ColorTable
*/
public function getColorTable(): ?ColorTable
{
return $this->colorTable;
}
/**
* Set global color table
*
* @param ColorTable $table
*/
public function setColorTable(ColorTable $table): self
{
$this->colorTable = $table;
return $this;
}
/**
* Determine if global color table is set
*
* @return boolean
*/
public function hasColorTable()
{
return ! is_null($this->colorTable);
}
}
src/LogicalScreenDescriptor.php 0000644 00000010620 15073164573 0012627 0 ustar 00 width = $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 boolean
*/
public function getGlobalColorTableExistance(): bool
{
return $this->globalColorTableExistance;
}
/**
* Alias of getGlobalColorTableExistance
*
* @return boolean
*/
public function hasGlobalColorTable(): bool
{
return $this->getGlobalColorTableExistance();
}
/**
* Set global color table flag
*
* @param boolean $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 boolean $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/NetscapeApplicationExtension.php 0000644 00000001541 15073164573 0013703 0 ustar 00 setApplication(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/PlainTextExtension.php 0000644 00000001746 15073164573 0011674 0 ustar 00 text;
}
/**
* 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 boolean
*/
public function hasText(): bool
{
return count($this->text) > 0;
}
}
src/Splitter.php 0000644 00000012635 15073164573 0007674 0 ustar 00 stream = $stream;
}
/**
* Iterator
*
* @return \Traversable
*/
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->frames);
}
public function getFrames(): array
{
return $this->frames;
}
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 array
*/
public function split(): self
{
$this->frames = [];
foreach ($this->stream->getGraphicBlocks() as $block) {
// create separate stream for each frame
$frame = Builder::canvas(
$this->stream->getLogicalScreen()->getDescriptor()->getWidth(),
$this->stream->getLogicalScreen()->getDescriptor()->getHeight()
)->getGifDataStream();
// check if working stream has global color table
if ($this->stream->getLogicalScreen()->getDescriptor()->hasGlobalColorTable()) {
$frame->getLogicalScreen()->setColorTable(
$this->stream->getLogicalScreen()->getColorTable()
);
$frame->getLogicalScreen()->getDescriptor()->setGlobalColorTableExistance(
true
);
$frame->getLogicalScreen()->getDescriptor()->setGlobalColorTableSorted(
$this->stream->getLogicalScreen()->getDescriptor()->getGlobalColorTableSorted()
);
$frame->getLogicalScreen()->getDescriptor()->setGlobalColorTableSize(
$this->stream->getLogicalScreen()->getDescriptor()->getGlobalColorTableSize()
);
$frame->getLogicalScreen()->getDescriptor()->setBackgroundColorIndex(
$this->stream->getLogicalScreen()->getDescriptor()->getBackgroundColorIndex()
);
$frame->getLogicalScreen()->getDescriptor()->setPixelAspectRatio(
$this->stream->getLogicalScreen()->getDescriptor()->getPixelAspectRatio()
);
$frame->getLogicalScreen()->getDescriptor()->setBitsPerPixel(
$this->stream->getLogicalScreen()->getDescriptor()->getBitsPerPixel()
);
}
// copy original block
$frame->addData($block);
$this->frames[] = $frame;
$this->delays[] = $block->getGraphicControlExtension()->getDelay();
}
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)) {
$resources[] = imagecreatefromstring($frame->encode());
}
}
return $resources;
}
/**
* Return array of coalesced GD library resources for each frame
*
* @return array
*/
public function coalesceToResources(): array
{
$resources = $this->toResources();
$base = $resources[0];
$width = imagesx($base);
$height = imagesy($base);
foreach ($resources as $key => $resource) {
if ($key >= 1) {
$descriptor = $this->frames[$key]->getTableBasedImages()[0]->getDescriptor();
$offset_x = $descriptor->getLeft();
$offset_y = $descriptor->getTop();
$w = $descriptor->getWidth();
$h = $descriptor->getHeight();
// create new
$new = imagecreatetruecolor($width, $height);
imagealphablending($new, true);
// insert last as base
imagecopy(
$new,
$resources[$key - 1],
0,
0,
0,
0,
$width,
$height
);
// copy over current
imagecopy(
$new,
$resource,
$offset_x,
$offset_y,
0,
0,
$w,
$h
);
$resources[$key] = $new;
}
}
return $resources;
}
}
src/TableBasedImage.php 0000644 00000003670 15073164573 0011016 0 ustar 00 descriptor = new ImageDescriptor();
$this->data = new ImageData();
}
/**
* Get descriptor
*
* @return ImageDescriptor
*/
public function getDescriptor(): ImageDescriptor
{
return $this->descriptor;
}
/**
* Set descriptor
*
* @param ImageDescriptor $descriptor
*/
public function setDescriptor(ImageDescriptor $descriptor): self
{
$this->descriptor = $descriptor;
return $this;
}
/**
* Get color table
*
* @return ColorTable
*/
public function getColorTable(): ?ColorTable
{
return $this->colorTable;
}
/**
* Set global color table
*
* @param ColorTable $table
*/
public function setColorTable(ColorTable $table): self
{
$this->colorTable = $table;
return $this;
}
/**
* Determine if current instance has color table
*
* @return boolean
*/
public function hasColorTable()
{
return is_a($this->colorTable, ColorTable::class);
}
/**
* Get image data
*
* @return ImageData
*/
public function getData(): ImageData
{
return $this->data;
}
/**
* Set image data
*
* @param ImageData $data
*/
public function setData(ImageData $data): self
{
$this->data = $data;
return $this;
}
}
src/Trailer.php 0000644 00000000157 15073164573 0007464 0 ustar 00 decode();
}
/**
* Get decoder for current instance
*
* @param resource $source
* @param null|Closure $callback
* @return AbstractDecoder
*/
protected static function getDecoder($source, Closure $callback = null): AbstractDecoder
{
$classname = self::getDecoderClassname();
if (!class_exists($classname)) {
throw new DecoderException("Decoder for '" . get_called_class() . "' not found.");
}
return new $classname($source, $callback);
}
/**
* Get classname of decoder for current classname
*
* @return string
*/
protected static function getDecoderClassname(): string
{
return sprintf('Intervention\Gif\Decoder\%sDecoder', self::getShortClassname());
}
}
src/Traits/CanEncode.php 0000644 00000001740 15073164573 0011146 0 ustar 00 getEncoder()->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\Encoder\%sEncoder', $this->getShortClassname());
}
}
src/Traits/CanHandleFiles.php 0000644 00000002150 15073164573 0012123 0 ustar 00 setLength(12);
};
$handle = $this->getTestHandle('foobarbaz');
$decoder = $this->getMockForAbstractClass(AbstractDecoder::class, [$handle, $callback]);
$this->assertEquals(12, $decoder->getLength());
}
public function testSetHandle()
{
$handle = $this->getTestHandle('foobarbaz');
$decoder = $this->getMockForAbstractClass(AbstractDecoder::class, [$handle]);
$result = $decoder->setHandle($handle);
$this->assertInstanceOf(AbstractDecoder::class, $result);
}
public function testSetGetLength()
{
$handle = $this->getTestHandle('foobarbaz');
$decoder = $this->getMockForAbstractClass(AbstractDecoder::class, [$handle]);
$this->assertNull($decoder->getLength());
$decoder->setLength(1);
$this->assertEquals(1, $decoder->getLength());
}
}
tests/AnimationDecodingTest.php 0000644 00000011357 15073164573 0012655 0 ustar 00 assertInstanceOf(GifDataStream::class, $gif);
// HEADER
$this->assertEquals('89a', $gif->getHeader()->getVersion());
// LOGICAL SCREEN DESCRIPTOR
$this->assertEquals(30, $gif->getLogicalScreen()->getDescriptor()->getWidth());
$this->assertEquals(20, $gif->getLogicalScreen()->getDescriptor()->getHeight());
$this->assertTrue($gif->getLogicalScreen()->getDescriptor()->hasGlobalColorTable());
$this->assertFalse($gif->getLogicalScreen()->getDescriptor()->getGlobalColorTableSorted());
$this->assertEquals(7, $gif->getLogicalScreen()->getDescriptor()->getGlobalColorTableSize());
$this->assertEquals(0, $gif->getLogicalScreen()->getDescriptor()->getBackgroundColorIndex());
$this->assertEquals(0, $gif->getLogicalScreen()->getDescriptor()->getPixelAspectRatio());
$this->assertEquals(1, $gif->getLogicalScreen()->getDescriptor()->getBitsPerPixel());
// GLOBAL COLOR TABLE
$this->assertInstanceOf(ColorTable::class, $gif->getLogicalScreen()->getColorTable());
$this->assertEquals(256, $gif->getLogicalScreen()->getColorTable()->countColors());
// APPLICATION EXTENSION
$this->assertInstanceOf(ApplicationExtension::class, $gif->getMainApplicationExtension());
$this->assertEquals(0, $gif->getMainApplicationExtension()->getLoops());
// TABLE BASED IMAGES
$this->assertCount(6, $gif->getGraphicBlocks());
// GRAPHICS CONTROL EXTENSIONS
$colortables = array_values(array_map(function ($block) {
return $block->getGraphicRenderingBlock()->hasColorTable();
}, $gif->getGraphicBlocks()));
$this->assertEquals(array_fill(0, 6, false), $colortables);
$delays = array_values(array_map(function ($block) {
return $block->getGraphicControlExtension()->getDelay();
}, $gif->getGraphicBlocks()));
$this->assertEquals(array_fill(0, 6, 13), $delays);
$usrinputs = array_values(array_map(function ($block) {
return $block->getGraphicControlExtension()->getUserInput();
}, $gif->getGraphicBlocks()));
$this->assertEquals(array_fill(0, 6, false), $usrinputs);
$disposals = array_values(array_map(function ($block) {
return $block->getGraphicControlExtension()->getDisposalMethod();
}, $gif->getGraphicBlocks()));
$this->assertEquals(array_fill(0, 6, DisposalMethod::LEAVE), $disposals);
$indexes = array_values(array_map(function ($block) {
return $block->getGraphicControlExtension()->getTransparentColorIndex();
}, $gif->getGraphicBlocks()));
$this->assertEquals(array_fill(0, 6, 2), $indexes);
// IMAGE DESCRIPTORS
$lefts = array_values(array_map(function ($desc) {
return $desc->getLeft();
}, $gif->getImageDescriptors()));
$this->assertEquals([0, 9, 5, 0, 9, 5], $lefts);
$tops = array_values(array_map(function ($desc) {
return $desc->getTop();
}, $gif->getImageDescriptors()));
$this->assertEquals([0, 6, 3, 0, 6, 3], $tops);
$widths = array_values(array_map(function ($desc) {
return $desc->getWidth();
}, $gif->getImageDescriptors()));
$this->assertEquals([30, 12, 20, 30, 12, 20], $widths);
$heights = array_values(array_map(function ($desc) {
return $desc->getHeight();
}, $gif->getImageDescriptors()));
$this->assertEquals([20, 8, 14, 20, 8, 14], $heights);
$localcolortables = array_values(array_map(function ($desc) {
return $desc->hasLocalColorTable();
}, $gif->getImageDescriptors()));
$this->assertEquals(array_fill(0, 6, false), $localcolortables);
$interlaces = array_values(array_map(function ($desc) {
return $desc->isInterlaced();
}, $gif->getImageDescriptors()));
$this->assertEquals(array_fill(0, 6, false), $interlaces);
$sorts = array_values(array_map(function ($desc) {
return $desc->getLocalColorTableSorted();
}, $gif->getImageDescriptors()));
$this->assertEquals(array_fill(0, 6, false), $sorts);
$sizes = array_values(array_map(function ($desc) {
return $desc->getLocalColorTableSize();
}, $gif->getImageDescriptors()));
$this->assertEquals(array_fill(0, 6, 0), $sizes);
}
}
tests/ApplicationExtensionTest.php 0000644 00000004251 15073164573 0013434 0 ustar 00 assertEquals('', $ext->getApplication());
$ext->setApplication('foo');
$this->assertEquals('foo', $ext->getApplication());
}
public function testAddBlock(): void
{
$extension = new ApplicationExtension();
$this->assertCount(0, $extension->getBlocks());
$extension->addBlock(new DataSubBlock('foo'));
$extension->addBlock(new DataSubBlock('bar'));
$this->assertCount(2, $extension->getBlocks());
}
public function testEncode()
{
$extension = new ApplicationExtension();
$extension->setApplication('foobar');
$extension->addBlock(new DataSubBlock('baz'));
$result = $extension->encode();
$this->assertEquals("\x21\xff\x06\x66\x6F\x6F\x62\x61\x72\x03\x62\x61\x7A\x00", $result);
$extension = new ApplicationExtension();
$extension->setApplication('NETSCAPE2.0');
$extension->addBlock(new DataSubBlock("\x01\x0c\x00"));
$result = $extension->encode();
$this->assertEquals("\x21\xff\x0b\x4e\x45\x54\x53\x43\x41\x50\x45\x32\x2e\x30\x03\x01\x0c\x00\x00", $result);
}
public function testDecode()
{
$source = "\x21\xff\x06\x66\x6F\x6F\x62\x61\x72\x03\x62\x61\x7A\x00";
$extension = ApplicationExtension::decode($this->getTestHandle($source));
$this->assertInstanceOf(ApplicationExtension::class, $extension);
$this->assertCount(1, $extension->getBlocks());
$this->assertEquals('foobar', $extension->getApplication());
// $source = "\x21\xff\x0b\x4e\x45\x54\x53\x43\x41\x50\x45\x32\x2e\x30\x03\x01\x0c\x00\x00";
// $extension = ApplicationExtension::decode($this->getTestHandle($source));
// $this->assertInstanceOf(ApplicationExtension::class, $extension);
// $this->assertCount(1, $extension->getBlocks());
// $this->assertEquals('NETSCAPE2.0', $extension->getApplication());
}
}
tests/BaseTestCase.php 0000644 00000010405 15073164573 0010740 0 ustar 00 addRgb(0, 0, 0);
$table->addRgb(255, 0, 0);
$table->addRgb(0, 255, 0);
$table->addRgb(0, 0, 255);
return $table;
}
protected function getTestImageData(): ImageData
{
$data = new ImageData();
$data->setLzwMinCodeSize(2);
$data->addBlock(
new DataSubBlock("\x8C\x2D\x99\x87\x2A\x1C\xDC\x33\xA0\x02\x75\xEC\x95\xFA\xA8\xDE\x60\x8C\x04\x91\x4C\x01")
);
return $data;
}
protected function getTestImageDescriptor(): ImageDescriptor
{
$descriptor = new ImageDescriptor();
$descriptor->setSize(10, 10);
$descriptor->setPosition(10, 10);
return $descriptor;
}
protected function getTestTableBasedImage(): TableBasedImage
{
$tbi = new TableBasedImage();
$tbi->setDescriptor($this->getTestImageDescriptor());
$tbi->setData($this->getTestImageData());
return $tbi;
}
protected function getTestGraphicControlExtension(): GraphicControlExtension
{
$extension = new GraphicControlExtension();
$extension->setDelay(150);
$extension->setDisposalMethod(3);
$extension->setTransparentColorExistance();
$extension->setTransparentColorIndex(1);
$extension->setUserInput();
return $extension;
}
protected function getTestGraphicBlock(): GraphicBlock
{
$block = new GraphicBlock();
$block->setGraphicControlExtension($this->getTestGraphicControlExtension());
$block->setGraphicRenderingBlock($this->getTestTableBasedImage());
return $block;
}
protected function getTestNetscapeApplicationExtension(): NetscapeApplicationExtension
{
$extension = new NetscapeApplicationExtension();
$extension->setLoops(12);
return $extension;
}
protected function getTestCommentExtension(): CommentExtension
{
$extension = new CommentExtension();
$extension->addComment('foo');
$extension->addComment('bar');
$extension->addComment('baz');
return $extension;
}
protected function getTestLogicalScreen()
{
$screen = new LogicalScreen();
$screen->setDescriptor($this->getTestLogicalScreenDescriptor());
$screen->setColorTable($this->getTestColorTable());
return $screen;
}
protected function getTestLogicalScreenDescriptor()
{
$descriptor = new LogicalScreenDescriptor();
$descriptor->setSize(81, 22);
$descriptor->setGlobalColorTableExistance(true);
$descriptor->setGlobalColorTableSorted(false);
$descriptor->setGlobalColorTableSize(1);
$descriptor->setBackgroundColorIndex(0);
return $descriptor;
}
}
tests/BuilderTest.php 0000644 00000004260 15073164573 0010662 0 ustar 00 assertInstanceOf(GifDataStream::class, $builder->getGifDataStream());
}
public function testEncode()
{
$builder = Builder::canvas(320, 240);
$this->assertMatchesRegularExpression('/^\x47\x49\x46\x38(\x37|\x39)\x61/', $builder->encode());
}
public function testCanvasDefaultLoops()
{
$builder = Builder::canvas(320, 240);
$this->assertInstanceOf(Builder::class, $builder);
$gif = $builder->getGifDataStream();
$this->assertEquals(320, $gif->getLogicalScreen()->getDescriptor()->getWidth());
$this->assertEquals(240, $gif->getLogicalScreen()->getDescriptor()->getHeight());
$this->assertEquals(0, $gif->getMainApplicationExtension()->getLoops());
}
public function testCanvasOneLoops()
{
$builder = Builder::canvas(320, 240, 1);
$gif = $builder->getGifDataStream();
$this->assertNull($gif->getMainApplicationExtension());
}
public function testCanvasMultipleLoops()
{
$builder = Builder::canvas(320, 240, 10);
$gif = $builder->getGifDataStream();
$this->assertEquals(10, $gif->getMainApplicationExtension()->getLoops());
}
public function testAddFrame()
{
$builder = Builder::canvas(320, 240);
$result = $builder->addFrame(__DIR__ . '/images/red.gif', 0.25, 1, 2);
$this->assertInstanceOf(Builder::class, $result);
$blocks = $result->getGifDataStream()->getGraphicBlocks();
$this->assertEquals(25, $blocks[0]->getGraphicControlExtension()->getDelay());
$this->assertEquals(1, $blocks[0]->getGraphicRenderingBlock()->getDescriptor()->getLeft());
$this->assertEquals(2, $blocks[0]->getGraphicRenderingBlock()->getDescriptor()->getTop());
}
}
tests/ColorTableTest.php 0000644 00000006746 15073164573 0011335 0 ustar 00 assertInstanceOf(ColorTable::class, $table);
$this->assertEquals(3, $table->countColors());
}
public function testGetColors()
{
$table = new ColorTable();
$table->addRgb(0, 0, 0);
$table->addRgb(0, 255, 0);
$this->assertIsArray($table->getColors());
$this->assertCount(2, $table->getColors());
foreach ($table->getColors() as $key => $value) {
$this->assertIsNumeric($key);
}
}
public function testSetColors()
{
$table = new ColorTable();
$this->assertEquals(0, $table->countColors());
$table->setColors([
new Color(0, 0, 0),
new Color(255, 255, 255),
new Color(255, 255, 255),
]);
$this->assertEquals(3, $table->countColors());
}
public function testAddRgb()
{
$table = new ColorTable();
$result = $table->addRgb(255, 255, 255);
$this->assertInstanceOf(ColorTable::class, $result);
}
public function testCountColors()
{
$table = new ColorTable();
$table->addRgb(0, 255, 255);
$table->addRgb(255, 0, 255);
$table->addRgb(255, 255, 0);
$this->assertEquals(3, $table->countColors());
}
public function testHasColors()
{
$table = new ColorTable();
$this->assertFalse($table->hasColors());
$table->addRgb(0, 0, 0);
$this->assertTrue($table->hasColors());
}
public function testEmpty()
{
$table = new ColorTable();
$table->addRgb(0, 0, 0);
$this->assertTrue($table->hasColors());
$table->empty();
$this->assertFalse($table->hasColors());
}
public function testGetLogicalSize()
{
$table = new ColorTable();
$this->assertEquals(0, $table->getLogicalSize());
$table->addRgb(0, 0, 0);
$table->addRgb(255, 0, 0);
$table->addRgb(255, 255, 0);
$table->addRgb(255, 255, 255);
$this->assertEquals(1, $table->getLogicalSize());
}
public function testGetByteSize()
{
$table = new ColorTable();
$this->assertEquals(0, $table->getByteSize());
$table->addRgb(0, 0, 0);
$table->addRgb(255, 0, 0);
$table->addRgb(255, 255, 0);
$table->addRgb(255, 255, 255);
$this->assertEquals(12, $table->getByteSize());
}
public function testEncode()
{
$table = new ColorTable([
new Color(0, 0, 0),
new Color(255, 0, 0),
new Color(255, 255, 0),
new Color(255, 255, 255),
]);
$result = "\x00\x00\x00\xff\x00\x00\xff\xff\x00\xff\xff\xff";
$this->assertEquals($result, $table->encode());
}
public function testDecode()
{
$source = "\x00\x00\x00\xff\x00\x00\xff\xff\x00\xff\xff\xff";
$table = ColorTable::decode($this->getTestHandle($source), function ($decoder) {
$decoder->setLength(12);
});
$this->assertInstanceOf(ColorTable::class, $table);
$this->assertEquals(4, $table->countColors());
}
}
tests/ColorTest.php 0000644 00000004767 15073164573 0010366 0 ustar 00 assertEquals(1, $color->getRed());
$this->assertEquals(2, $color->getGreen());
$this->assertEquals(3, $color->getBlue());
$color->setRed(4);
$color->setGreen(5);
$color->setBlue(6);
$this->assertEquals(4, $color->getRed());
$this->assertEquals(5, $color->getGreen());
$this->assertEquals(6, $color->getBlue());
}
public function testGetHash()
{
$color = new Color(1, 2, 3);
$this->assertEquals('202cb962ac59075b964b07152d234b70', $color->getHash());
}
public function testEncode()
{
$result = (new Color())->encode();
$this->assertEquals("\x00\x00\x00", $result);
$result = (new Color(255, 0, 255))->encode();
$this->assertEquals("\xff\x00\xff", $result);
$result = (new Color(125, 125, 125))->encode();
$this->assertEquals("\x7d\x7d\x7d", $result);
$result = (new Color(15, 43, 121))->encode();
$this->assertEquals("\x0f\x2b\x79", $result);
}
public function testDecode()
{
$source = "\x00\x00\x00";
$color = Color::decode($this->getTestHandle($source));
$this->assertInstanceOf(Color::class, $color);
$this->assertEquals(0, $color->getRed());
$this->assertEquals(0, $color->getGreen());
$this->assertEquals(0, $color->getBlue());
$source = "\xff\x00\xff";
$color = Color::decode($this->getTestHandle($source));
$this->assertInstanceOf(Color::class, $color);
$this->assertEquals(255, $color->getRed());
$this->assertEquals(0, $color->getGreen());
$this->assertEquals(255, $color->getBlue());
$source = "\x7d\x7d\x7d";
$color = Color::decode($this->getTestHandle($source));
$this->assertInstanceOf(Color::class, $color);
$this->assertEquals(125, $color->getRed());
$this->assertEquals(125, $color->getGreen());
$this->assertEquals(125, $color->getBlue());
$source = "\x0f\x2b\x79";
$color = Color::decode($this->getTestHandle($source));
$this->assertInstanceOf(Color::class, $color);
$this->assertEquals(15, $color->getRed());
$this->assertEquals(43, $color->getGreen());
$this->assertEquals(121, $color->getBlue());
}
}
tests/CommentExtensionTest.php 0000644 00000004374 15073164573 0012601 0 ustar 00 addComment('foo');
$extension->addComment('bar');
$extension->addComment('baz');
$this->assertIsArray($extension->getComments());
$this->assertCount(3, $extension->getComments());
$this->assertEquals('foo', $extension->getComments(0));
$this->assertEquals('bar', $extension->getComments(1));
$this->assertEquals('baz', $extension->getComments(2));
}
public function testEncode()
{
$extension = new CommentExtension();
$extension->addComment('blueberry');
$result = "\x21\xFE\x09\x62\x6C\x75\x65\x62\x65\x72\x72\x79\x00";
$this->assertEquals($result, $extension->encode());
$extension = new CommentExtension();
$extension->addComment('foo');
$extension->addComment('bar');
$extension->addComment('baz');
$result = "\x21\xFE\x03\x66\x6F\x6F\x03\x62\x61\x72\x03\x62\x61\x7a\x00";
$this->assertEquals($result, $extension->encode());
}
public function testDecode()
{
$sources = [
"\x21\xFE\x09\x62\x6C\x75\x65\x62\x65\x72\x72\x79\x00",
];
foreach ($sources as $source) {
$extension = CommentExtension::decode($this->getTestHandle($source));
$this->assertInstanceOf(CommentExtension::class, $extension);
$this->assertCount(1, $extension->getComments());
$this->assertEquals('blueberry', $extension->getComments(0));
}
$sources = [
"\x21\xFE\x03\x66\x6F\x6F\x03\x62\x61\x72\x03\x62\x61\x7a\x00",
];
foreach ($sources as $source) {
$extension = CommentExtension::decode($this->getTestHandle($source));
$this->assertInstanceOf(CommentExtension::class, $extension);
$this->assertCount(3, $extension->getComments());
$this->assertEquals('foo', $extension->getComments(0));
$this->assertEquals('bar', $extension->getComments(1));
$this->assertEquals('baz', $extension->getComments(2));
}
}
}
tests/DataSubBlockTest.php 0000644 00000002771 15073164573 0011577 0 ustar 00 assertInstanceOf(DataSubBlock::class, $block);
$this->assertEquals(4, $block->getSize());
}
public function testGetValue(): void
{
$block = new DataSubBlock('test');
$this->assertEquals('test', $block->getValue());
}
public function testEncode(): void
{
// 64 bytes block size
$block = new DataSubBlock('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
$result = $block->encode();
$this->assertEquals("\x40\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78", $result);
}
public function testDecode(): void
{
$source = "\x40\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78";
$block = DataSubBlock::decode($this->getTestHandle($source));
$this->assertInstanceOf(DataSubBlock::class, $block);
$this->assertEquals(64, $block->getSize());
}
}
tests/DecoderTest.php 0000644 00000001047 15073164573 0010641 0 ustar 00 assertInstanceOf(GifDataStream::class, $decoded);
}
public function testDecodeFromData()
{
$decoded = Decoder::decode(file_get_contents(__DIR__ . '/images/animation1.gif'));
$this->assertInstanceOf(GifDataStream::class, $decoded);
}
}
tests/GifDataStreamTest.php 0000644 00000016036 15073164573 0011753 0 ustar 00 setHeader(new Header());
$this->assertInstanceOf(Header::class, $gif->getHeader());
}
public function testSetGetLogicalScreen()
{
$gif = new GifDataStream();
$gif->setLogicalScreen(new LogicalScreen());
$this->assertInstanceOf(LogicalScreen::class, $gif->getLogicalScreen());
}
public function testSetGetData()
{
$gif = new GifDataStream();
$this->assertIsArray($gif->getData());
$this->assertCount(0, $gif->getData());
$gif->addData(new GraphicBlock());
$gif->addData(new GraphicBlock());
$this->assertCount(2, $gif->getData());
}
public function testGetMainApplicationExtension()
{
$gif = new GifDataStream();
$extension1 = new ApplicationExtension();
$extension2 = new NetscapeApplicationExtension();
$gif->addData(new GraphicBlock());
$gif->addData($extension1);
$gif->addData($extension2);
$gif->addData(new GraphicBlock());
$this->assertEquals($extension2, $gif->getMainApplicationExtension());
}
public function testEncode()
{
$gif = new GifDataStream();
$gif->setLogicalScreen($this->getTestLogicalScreen());
$gif->addData($this->getTestNetscapeApplicationExtension());
$gif->addData($this->getTestGraphicBlock());
$gif->addData($this->getTestCommentExtension());
$result = implode('', [
self::HEADER_SAMPLE,
self::LOGICAL_SCREEN_DESCRIPTOR_SAMPLE,
self::APPLICATION_EXTENSION_SAMPLE,
self::GRAPHIC_CONTROL_EXTENSION_SAMPLE,
self::TABLE_BASED_IMAGE_SAMPLE,
self::COMMENT_EXTENSION_SAMPLE,
Trailer::MARKER,
]);
$this->assertEquals($result, $gif->encode());
}
public function testDecode()
{
$source = file_get_contents(__DIR__ . '/images/animation1.gif');
$gif = GifDataStream::decode($this->getTestHandle($source));
$this->assertInstanceOf(GifDataStream::class, $gif);
// HEADER
$this->assertEquals('89a', $gif->getHeader()->getVersion());
// LOGICAL SCREEN DESCRIPTOR
$this->assertEquals(20, $gif->getLogicalScreen()->getDescriptor()->getWidth());
$this->assertEquals(15, $gif->getLogicalScreen()->getDescriptor()->getHeight());
$this->assertTrue($gif->getLogicalScreen()->getDescriptor()->hasGlobalColorTable());
$this->assertFalse($gif->getLogicalScreen()->getDescriptor()->getGlobalColorTableSorted());
$this->assertEquals(4, $gif->getLogicalScreen()->getDescriptor()->getGlobalColorTableSize());
$this->assertEquals(7, $gif->getLogicalScreen()->getDescriptor()->getBackgroundColorIndex());
$this->assertEquals(0, $gif->getLogicalScreen()->getDescriptor()->getPixelAspectRatio());
$this->assertEquals(8, $gif->getLogicalScreen()->getDescriptor()->getBitsPerPixel());
// GLOBAL COLOR TABLE
$this->assertInstanceOf(ColorTable::class, $gif->getLogicalScreen()->getColorTable());
$this->assertEquals(32, $gif->getLogicalScreen()->getColorTable()->countColors());
// APPLICATION EXTENSION
$this->assertInstanceOf(ApplicationExtension::class, $gif->getMainApplicationExtension());
$this->assertEquals(2, $gif->getMainApplicationExtension()->getLoops());
// TABLE BASED IMAGES
$this->assertCount(8, $gif->getGraphicBlocks());
// GRAPHICS CONTROL EXTENSIONS
$colortables = array_values(array_map(function ($block) {
return $block->getGraphicRenderingBlock()->hasColorTable();
}, $gif->getGraphicBlocks()));
$this->assertEquals(array_fill(0, 8, false), $colortables);
$delays = array_values(array_map(function ($block) {
return $block->getGraphicControlExtension()->getDelay();
}, $gif->getGraphicBlocks()));
$this->assertEquals(array_fill(0, 8, 20), $delays);
$usrinputs = array_values(array_map(function ($block) {
return $block->getGraphicControlExtension()->getUserInput();
}, $gif->getGraphicBlocks()));
$this->assertEquals(array_fill(0, 8, false), $usrinputs);
$disposals = array_values(array_map(function ($block) {
return $block->getGraphicControlExtension()->getDisposalMethod();
}, $gif->getGraphicBlocks()));
$this->assertEquals(array_fill(0, 8, DisposalMethod::LEAVE), $disposals);
$indexes = array_values(array_map(function ($block) {
return $block->getGraphicControlExtension()->getTransparentColorIndex();
}, $gif->getGraphicBlocks()));
$this->assertEquals([255, 0, 0, 0, 1, 1, 1, 1], $indexes);
// IMAGE DESCRIPTORS
$lefts = array_values(array_map(function ($desc) {
return $desc->getLeft();
}, $gif->getImageDescriptors()));
$this->assertEquals([0, 5, 1, 0, 8, 5, 1, 0], $lefts);
$tops = array_values(array_map(function ($desc) {
return $desc->getTop();
}, $gif->getImageDescriptors()));
$this->assertEquals([0, 2, 0, 0, 5, 2, 0, 0], $tops);
$widths = array_values(array_map(function ($desc) {
return $desc->getWidth();
}, $gif->getImageDescriptors()));
$this->assertEquals([20, 10, 17, 20, 5, 10, 17, 20], $widths);
$heights = array_values(array_map(function ($desc) {
return $desc->getHeight();
}, $gif->getImageDescriptors()));
$this->assertEquals([15, 10, 15, 15, 5, 10, 15, 15], $heights);
$localcolortables = array_values(array_map(function ($desc) {
return $desc->hasLocalColorTable();
}, $gif->getImageDescriptors()));
$this->assertEquals(array_fill(0, 8, false), $localcolortables);
$interlaces = array_values(array_map(function ($desc) {
return $desc->isInterlaced();
}, $gif->getImageDescriptors()));
$this->assertEquals([true, false, false, false, false, false, false, false], $interlaces);
$sorts = array_values(array_map(function ($desc) {
return $desc->getLocalColorTableSorted();
}, $gif->getImageDescriptors()));
$this->assertEquals([false, false, false, false, false, false, false, false], $sorts);
$sizes = array_values(array_map(function ($desc) {
return $desc->getLocalColorTableSize();
}, $gif->getImageDescriptors()));
$this->assertEquals([0, 0, 0, 0, 0, 0, 0, 0], $sizes);
}
}
tests/GraphicBlockTest.php 0000644 00000006265 15073164573 0011633 0 ustar 00 assertNull($block->getGraphicControlExtension());
$block->setGraphicControlExtension(new GraphicControlExtension());
$this->assertInstanceOf(GraphicControlExtension::class, $block->getGraphicControlExtension());
}
public function testSetGetGraphicRenderingBlock()
{
$block = new GraphicBlock();
$this->assertInstanceOf(GraphicRenderingBlock::class, $block->getGraphicRenderingBlock());
$block->setGraphicRenderingBlock(new TableBasedImage());
$this->assertInstanceOf(TableBasedImage::class, $block->getGraphicRenderingBlock());
$block->setGraphicRenderingBlock(new PlainTextExtension());
$this->assertInstanceOf(PlainTextExtension::class, $block->getGraphicRenderingBlock());
}
public function testEncode()
{
$block = new GraphicBlock();
$block->setGraphicControlExtension($this->getTestGraphicControlExtension());
$block->setGraphicRenderingBlock($this->getTestTableBasedImage());
$this->assertEquals(self::GRAPHIC_CONTROL_EXTENSION_SAMPLE.self::TABLE_BASED_IMAGE_SAMPLE, $block->encode());
}
public function testDecoder()
{
$sources = [
// graphic control extension + imagedescriptor + imagedata
"\x21\xF9\x04\x00\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x0A\x00\x0A\x00\x00\x02\x16\x8C\x2D\x99\x87\x2A\x1C\xDC\x33\xA0\x02\x75\xEC\x95\xFA\xA8\xDE\x60\x8C\x04\x91\x4C\x01\x00",
// graphic control extension + plaintextextension
// "\x21\xF9\x04\x00\x00\x00\x00\x00\x21\x01\x0C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x66\x6f\x6f\x03\x62\x61\x72\x00",
// graphic control extension + imagedescriptor + localcolortable + imagedata
"\x21\xF9\x04\x00\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x0A\x00\x0A\x00\x81\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\x00\x00\x00\x02\x16\x8C\x2D\x99\x87\x2A\x1C\xDC\x33\xA0\x02\x75\xEC\x95\xFA\xA8\xDE\x60\x8C\x04\x91\x4C\x01\x00",
// imagedescriptor + localcolortable + imagedata
"\x2C\x00\x00\x00\x00\x0A\x00\x0A\x00\x81\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\x00\x00\x00\x02\x16\x8C\x2D\x99\x87\x2A\x1C\xDC\x33\xA0\x02\x75\xEC\x95\xFA\xA8\xDE\x60\x8C\x04\x91\x4C\x01\x00",
// imagedescriptor + imagedata
"\x2C\x00\x00\x00\x00\x0A\x00\x0A\x00\x00\x02\x16\x8C\x2D\x99\x87\x2A\x1C\xDC\x33\xA0\x02\x75\xEC\x95\xFA\xA8\xDE\x60\x8C\x04\x91\x4C\x01\x00",
// plaintextextension
"\x21\x01\x0C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x66\x6f\x6f\x03\x62\x61\x72\x00",
];
foreach ($sources as $source) {
$block = GraphicBlock::decode($this->getTestHandle($source));
$this->assertInstanceOf(GraphicBlock::class, $block);
}
}
}
tests/GraphicControlExtensionTest.php 0000644 00000005537 15073164573 0014117 0 ustar 00 assertEquals(0, $ext->getDelay());
$ext->setDelay(100);
$this->assertEquals(100, $ext->getDelay());
}
public function testSetGetDisposalMethod()
{
$ext = new GraphicControlExtension();
$this->assertEquals(DisposalMethod::NONE, $ext->getDisposalMethod());
$ext->setDisposalMethod(DisposalMethod::BACKGROUND);
$this->assertEquals(DisposalMethod::BACKGROUND, $ext->getDisposalMethod());
}
public function testSetGetTransparentColorIndex()
{
$ext = new GraphicControlExtension();
$this->assertEquals(0, $ext->getTransparentColorIndex());
$ext->setTransparentColorIndex(100);
$this->assertEquals(100, $ext->getTransparentColorIndex());
}
public function testSetGetTransparentColorExistance()
{
$ext = new GraphicControlExtension();
$this->assertFalse($ext->getTransparentColorExistance());
$ext->setTransparentColorExistance();
$this->assertTrue($ext->getTransparentColorExistance());
$ext->setTransparentColorExistance(false);
$this->assertFalse($ext->getTransparentColorExistance());
}
public function testSetGetUserInput()
{
$ext = new GraphicControlExtension();
$this->assertFalse($ext->getUserInput());
$ext->setUserInput();
$this->assertTrue($ext->getUserInput());
$ext->setUserInput(false);
$this->assertFalse($ext->getUserInput());
}
public function testEncode()
{
$extension = new GraphicControlExtension();
$extension->setDelay(150);
$extension->setDisposalMethod(3);
$extension->setTransparentColorExistance();
$extension->setTransparentColorIndex(144);
$extension->setUserInput();
$this->assertEquals("\x21\xF9\x04\x0f\x96\x00\x90\x00", $extension->encode());
}
public function testDecode()
{
$sources = [
"\x21\xF9\x04\x0f\x96\x00\x90\x00",
];
foreach ($sources as $source) {
$extension = GraphicControlExtension::decode($this->getTestHandle($source));
$this->assertInstanceOf(GraphicControlExtension::class, $extension);
$this->assertEquals(150, $extension->getDelay());
$this->assertEquals(3, $extension->getDisposalMethod());
$this->assertTrue($extension->getTransparentColorExistance());
$this->assertEquals(144, $extension->getTransparentColorIndex());
$this->assertTrue($extension->getUserInput());
}
}
}
tests/HeaderTest.php 0000644 00000001473 15073164573 0010467 0 ustar 00 assertEquals('89a', $header->getVersion());
$header->setVersion('foo');
$this->assertEquals('foo', $header->getVersion());
}
public function testEncode()
{
$header = new Header();
$this->assertEquals('GIF89a', $header->encode());
}
public function testDecode()
{
$header = Header::decode($this->getTestHandle('GIF87a'));
$this->assertInstanceOf(Header::class, $header);
$this->assertEquals('87a', $header->getVersion());
}
}
tests/ImageDataTest.php 0000644 00000003361 15073164573 0011111 0 ustar 00 assertFalse($data->hasBlocks());
$data->addBlock(new DataSubBlock('foo'));
$this->assertCount(1, $data->getBlocks());
$this->assertTrue($data->hasBlocks());
$data->addBlock(new DataSubBlock('bar'));
$this->assertCount(2, $data->getBlocks());
$this->assertTrue($data->hasBlocks());
}
public function testEncoder()
{
$data = new ImageData();
$data->setLzwMinCodeSize(5);
$data->addBlock(new DataSubBlock("\x20\x20\x8E\x64\x69\x9E\x51\xA0\x46\x67\xEB\xBE\x70\x2C\x97\xE9\x3A\xDF\x78\xAE\xDF\x4F\xD4\x40\x8F\x9B\x43\x15\x70\xF0\x7C\xC0\x9D\xB2\x15\x02"));
$data->addBlock(new DataSubBlock("\x01\x01\x01\x01"));
$data->addBlock(new DataSubBlock("\x01\x01\x01"));
$result = "\x05\x24\x20\x20\x8E\x64\x69\x9E\x51\xA0\x46\x67\xEB\xBE\x70\x2C\x97\xE9\x3A\xDF\x78\xAE\xDF\x4F\xD4\x40\x8F\x9B\x43\x15\x70\xF0\x7C\xC0\x9D\xB2\x15\x02\x04\x01\x01\x01\x01\x03\x01\x01\x01\x00";
$this->assertEquals($result, $data->encode());
}
public function testDecode()
{
$source = "\x05\x24\x20\x20\x8E\x64\x69\x9E\x51\xA0\x46\x67\xEB\xBE\x70\x2C\x97\xE9\x3A\xDF\x78\xAE\xDF\x4F\xD4\x40\x8F\x9B\x43\x15\x70\xF0\x7C\xC0\x9D\xB2\x15\x02\x04\x01\x01\x01\x01\x03\x01\x01\x01\x00";
$data = ImageData::decode($this->getTestHandle($source));
$this->assertInstanceOf(ImageData::class, $data);
$this->assertEquals(5, $data->getLzwMinCodeSize());
$this->assertCount(3, $data->getBlocks());
}
}
tests/ImageDescriptorTest.php 0000644 00000010734 15073164573 0012360 0 ustar 00 assertEquals(0, $desc->getWidth());
$this->assertEquals(0, $desc->getHeight());
$desc->setSize(300, 200);
$this->assertEquals(300, $desc->getWidth());
$this->assertEquals(200, $desc->getHeight());
}
public function testSetGetPosition()
{
$desc = new ImageDescriptor();
$this->assertEquals(0, $desc->getTop());
$this->assertEquals(0, $desc->getLeft());
$desc->setPosition(300, 200);
$this->assertEquals(300, $desc->getLeft());
$this->assertEquals(200, $desc->getTop());
}
public function testSetGetInterlaced()
{
$desc = new ImageDescriptor();
$this->assertFalse($desc->isInterlaced());
$desc->setInterlaced();
$this->assertTrue($desc->isInterlaced());
$desc->setInterlaced(false);
$this->assertFalse($desc->isInterlaced());
}
public function testLocalColorTableExistanceFlag()
{
$descriptor = new ImageDescriptor();
$this->assertFalse($descriptor->getLocalColorTableExistance());
$descriptor->setLocalColorTableExistance();
$this->assertTrue($descriptor->getLocalColorTableExistance());
$descriptor->setLocalColorTableExistance(false);
$this->assertFalse($descriptor->getLocalColorTableExistance());
}
public function testLocalColorTableSortFlag()
{
$descriptor = new ImageDescriptor();
$this->assertFalse($descriptor->getLocalColorTableSorted());
$descriptor->setLocalColorTableSorted();
$this->assertTrue($descriptor->getLocalColorTableSorted());
$descriptor->setLocalColorTableSorted(false);
$this->assertFalse($descriptor->getLocalColorTableSorted());
}
public function testLocalColorTableSize()
{
$descriptor = new ImageDescriptor();
$this->assertEquals(0, $descriptor->getLocalColorTableSize());
$descriptor->setLocalColorTableSize(7);
$this->assertEquals(7, $descriptor->getLocalColorTableSize());
$descriptor->setLocalColorTableSize(2);
$this->assertEquals(2, $descriptor->getLocalColorTableSize());
}
public function testEncode()
{
// width: 10
// height: 10
// left: 10
// top: 10
// localColorTableExistance: false
// Interlaced: false
// localColorTableSorted: false
// localColorTableSize: 0
$descriptor = new ImageDescriptor();
$descriptor->setSize(10, 10);
$descriptor->setPosition(10, 10);
$this->assertEquals("\x2C\x0A\x00\x0A\x00\x0A\x00\x0A\x00\x00", $descriptor->encode());
// width: 300
// height: 200
// left: 1
// top: 5
// localColorTableExistance: true
// Interlaced: true
// localColorTableSorted: false
// localColorTableSize: 4
$descriptor = new ImageDescriptor();
$descriptor->setSize(300, 200);
$descriptor->setPosition(1, 5);
$descriptor->setLocalColorTableExistance();
$descriptor->setInterlaced();
$descriptor->setLocalColorTableSorted(false);
$descriptor->setLocalColorTableSize(4);
$this->assertEquals("\x2C\x01\x00\x05\x00\x2c\x01\xc8\x00\xc4", $descriptor->encode());
}
public function testDecode()
{
// width: 300
// height: 200
// top: 0
// left: 5
// localColorTableExistance: true
// Interlaced: true
// localColorTableSorted: false
// localColorTableSize: 4
$source = "\x2C\x05\x00\x00\x00\x2c\x01\xc8\x00\xc4";
$descriptor = ImageDescriptor::decode($this->getTestHandle($source));
$this->assertInstanceOf(ImageDescriptor::class, $descriptor);
$this->assertEquals(300, $descriptor->getWidth());
$this->assertEquals(200, $descriptor->getHeight());
$this->assertEquals(0, $descriptor->getTop());
$this->assertEquals(5, $descriptor->getLeft());
$this->assertTrue($descriptor->getLocalColorTableExistance());
$this->assertFalse($descriptor->getLocalColorTableSorted());
$this->assertEquals(4, $descriptor->getLocalColorTableSize());
$this->assertTrue($descriptor->isInterlaced());
}
}
tests/LogicalScreenDescriptorTest.php 0000644 00000013217 15073164573 0014047 0 ustar 00 setSize(300, 200);
$this->assertEquals(300, $descriptor->getWidth());
$this->assertEquals(200, $descriptor->getHeight());
$this->assertInstanceOf(LogicalScreenDescriptor::class, $result);
}
public function testGlobalColorTableExistanceFlag()
{
$descriptor = new LogicalScreenDescriptor();
$this->assertFalse($descriptor->getGlobalColorTableExistance());
$descriptor->setGlobalColorTableExistance();
$this->assertTrue($descriptor->getGlobalColorTableExistance());
$descriptor->setGlobalColorTableExistance(false);
$this->assertFalse($descriptor->getGlobalColorTableExistance());
}
public function testGlobalColorTableSortFlag()
{
$descriptor = new LogicalScreenDescriptor();
$this->assertFalse($descriptor->getGlobalColorTableSorted());
$descriptor->setGlobalColorTableSorted();
$this->assertTrue($descriptor->getGlobalColorTableSorted());
$descriptor->setGlobalColorTableSorted(false);
$this->assertFalse($descriptor->getGlobalColorTableSorted());
}
public function testGlobalColorTableSize()
{
$descriptor = new LogicalScreenDescriptor();
$this->assertEquals(0, $descriptor->getGlobalColorTableSize());
$descriptor->setGlobalColorTableSize(7);
$this->assertEquals(7, $descriptor->getGlobalColorTableSize());
$descriptor->setGlobalColorTableSize(2);
$this->assertEquals(2, $descriptor->getGlobalColorTableSize());
}
public function testGlobalColorTableByteSize()
{
$descriptor = new LogicalScreenDescriptor(); // default: 0
$this->assertEquals(6, $descriptor->getGlobalColorTableByteSize());
$descriptor->setGlobalColorTableSize(7);
$this->assertEquals(768, $descriptor->getGlobalColorTableByteSize());
$descriptor->setGlobalColorTableSize(2);
$this->assertEquals(24, $descriptor->getGlobalColorTableByteSize());
}
public function testBackgroundColorIndex()
{
$descriptor = new LogicalScreenDescriptor();
$this->assertEquals(0, $descriptor->getBackgroundColorIndex());
$descriptor->setBackgroundColorIndex(10);
$this->assertEquals(10, $descriptor->getBackgroundColorIndex());
$descriptor->setBackgroundColorIndex(11);
$this->assertEquals(11, $descriptor->getBackgroundColorIndex());
}
public function testPixelAspectRatio()
{
$descriptor = new LogicalScreenDescriptor();
$this->assertEquals(0, $descriptor->getPixelAspectRatio());
$descriptor->setPixelAspectRatio(10);
$this->assertEquals(10, $descriptor->getPixelAspectRatio());
$descriptor->setPixelAspectRatio(11);
$this->assertEquals(11, $descriptor->getPixelAspectRatio());
}
public function testBitsPerPixel()
{
$descriptor = new LogicalScreenDescriptor();
$this->assertEquals(8, $descriptor->getBitsPerPixel());
$descriptor->setBitsPerPixel(1);
$this->assertEquals(1, $descriptor->getBitsPerPixel());
$descriptor->setBitsPerPixel(2);
$this->assertEquals(2, $descriptor->getBitsPerPixel());
}
public function testEncode()
{
// width: 10
// height: 10
// globalColorTableExistance: false
// colorResolution: 8 bits/pixel
// globalColorTableSorted: false
// globalColorTableSize: 0
// BackgroundColorIndex: 0
// PixelAspectRatio: 0
$descriptor = new LogicalScreenDescriptor();
$descriptor->setSize(10, 10);
$this->assertEquals("\x0A\x00\x0A\x00\x70\x00\x00", $descriptor->encode());
// width: 300
// height: 200
// globalColorTableExistance: true
// colorResolution: 8 bits/pixel
// globalColorTableSorted: true
// globalColorTableSize: 7
// BackgroundColorIndex: 128
// PixelAspectRatio: 0
$descriptor = new LogicalScreenDescriptor();
$descriptor->setSize(300, 200);
$descriptor->setGlobalColorTableExistance(true);
$descriptor->setGlobalColorTableSorted(true);
$descriptor->setGlobalColorTableSize(7);
$descriptor->setBackgroundColorIndex(128);
$this->assertEquals("\x2c\x01\xc8\x00\xff\x80\x00", $descriptor->encode());
}
public function testDecode()
{
// width: 300
// height: 200
// globalColorTableExistance: true
// colorResolution: 8 bits/pixel
// globalColorTableSorted: true
// globalColorTableSize: 7
// BackgroundColorIndex: 128
// PixelAspectRatio: 0
$source = "\x2c\x01\xc8\x00\xff\x80\x00";
$descriptor = LogicalScreenDescriptor::decode($this->getTestHandle($source));
$this->assertInstanceOf(LogicalScreenDescriptor::class, $descriptor);
$this->assertEquals(300, $descriptor->getWidth());
$this->assertEquals(200, $descriptor->getHeight());
$this->assertTrue($descriptor->getGlobalColorTableExistance());
$this->assertEquals(8, $descriptor->getBitsPerPixel());
$this->assertTrue($descriptor->getGlobalColorTableSorted());
$this->assertEquals(7, $descriptor->getGlobalColorTableSize());
$this->assertEquals(128, $descriptor->getBackgroundColorIndex());
$this->assertEquals(0, $descriptor->getPixelAspectRatio());
}
}
tests/LogicalScreenTest.php 0000644 00000003453 15073164573 0012011 0 ustar 00 setDescriptor(new LogicalScreenDescriptor());
$this->assertInstanceOf(LogicalScreenDescriptor::class, $screen->getDescriptor());
}
public function testSetGetColortable()
{
$screen = new LogicalScreen();
$screen->setColorTable(new ColorTable());
$this->assertInstanceOf(ColorTable::class, $screen->getColorTable());
}
public function testEncode()
{
$screen = new LogicalScreen();
$screen->setDescriptor($this->getTestLogicalScreenDescriptor());
$screen->setColorTable($this->getTestColorTable());
$result = "\x51\x00\x16\x00\xf1\x00\x00\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff";
$this->assertEquals($result, $screen->encode());
}
public function testDecode()
{
$source = "\x14\x00\x0F\x00\xF4\x07\x00\x39\x4B\x63\xFF\xA6\x01\x42\x4F\x5F\x45\x51\x5D\x6D\x63\x49\xE6\x9B\x0D\x6A\x61\x4B\xB9\x86\x24\x3C\x4C\x61\x4B\x53\x5A\x9E\x79\x31\x98\x77\x34\xA8\x7E\x2C\xF6\xA2\x05\xF3\xA0\x07\x52\x56\x57\xCB\x8E\x1B\xCE\x90\x1A\x7D\x6A\x42\xFC\xA5\x03\x82\x6D\x3F\x8F\x73\x39\xF2\xA0\x08\xED\x9E\x0A\xF8\xA3\x04\x77\x68\x44\xD0\x90\x18\x72\x65\x47\x40\x4E\x60\x93\x74\x36\xC1\x89\x20\xC6\x8C\x1D";
$screen = LogicalScreen::decode($this->getTestHandle($source));
$this->assertInstanceOf(LogicalScreen::class, $screen);
$this->assertInstanceOf(LogicalScreenDescriptor::class, $screen->getDescriptor());
$this->assertInstanceOf(ColorTable::class, $screen->getColorTable());
}
}
tests/NetscapeApplicationExtensionTest.php 0000644 00000005222 15073164573 0015116 0 ustar 00 encode();
$this->assertEquals("\x21\xFF\x0B\x4E\x45\x54\x53\x43\x41\x50\x45\x32\x2E\x30\x03\x01\x00\x00\x00", $result);
// loops = 1
$extension = new NetscapeApplicationExtension();
$extension->setLoops(1);
$result = $extension->encode();
$this->assertEquals("\x21\xFF\x0B\x4E\x45\x54\x53\x43\x41\x50\x45\x32\x2E\x30\x03\x01\x01\x00\x00", $result);
// loops = 26
$extension = new NetscapeApplicationExtension();
$extension->setLoops(26);
$result = $extension->encode();
$this->assertEquals("\x21\xFF\x0B\x4E\x45\x54\x53\x43\x41\x50\x45\x32\x2E\x30\x03\x01\x1A\x00\x00", $result);
// loops = 11034
$extension = new NetscapeApplicationExtension();
$extension->setLoops(11034);
$result = $extension->encode();
$this->assertEquals("\x21\xFF\x0B\x4E\x45\x54\x53\x43\x41\x50\x45\x32\x2E\x30\x03\x01\x1A\x2B\x00", $result);
}
public function testDecode()
{
// loops = 0
$source = "\x21\xFF\x0B\x4E\x45\x54\x53\x43\x41\x50\x45\x32\x2E\x30\x03\x01\x00\x00\x00";
$extension = NetscapeApplicationExtension::decode($this->getTestHandle($source));
$this->assertInstanceOf(NetscapeApplicationExtension::class, $extension);
$this->assertEquals(0, $extension->getLoops());
// loops = 1
$source = "\x21\xFF\x0B\x4E\x45\x54\x53\x43\x41\x50\x45\x32\x2E\x30\x03\x01\x01\x00\x00";
$extension = NetscapeApplicationExtension::decode($this->getTestHandle($source));
$this->assertInstanceOf(NetscapeApplicationExtension::class, $extension);
$this->assertEquals(1, $extension->getLoops());
// loops = 26
$source = "\x21\xFF\x0B\x4E\x45\x54\x53\x43\x41\x50\x45\x32\x2E\x30\x03\x01\x1A\x00\x00";
$extension = NetscapeApplicationExtension::decode($this->getTestHandle($source));
$this->assertInstanceOf(NetscapeApplicationExtension::class, $extension);
$this->assertEquals(26, $extension->getLoops());
// loops = 111034
$source = "\x21\xFF\x0B\x4E\x45\x54\x53\x43\x41\x50\x45\x32\x2E\x30\x03\x01\x1A\x2B\x00";
$extension = NetscapeApplicationExtension::decode($this->getTestHandle($source));
$this->assertInstanceOf(NetscapeApplicationExtension::class, $extension);
$this->assertEquals(11034, $extension->getLoops());
}
}
tests/PipelineTest.php 0000644 00000005263 15073164573 0011045 0 ustar 00 validateGif($gif);
$splitter = Splitter::create($gif)->split();
$delays = $splitter->getDelays();
$gd_objects = $splitter->coalesceToResources();
foreach ($gd_objects as $gd) {
$this->assertEquals(30, imagesx($gd));
$this->assertEquals(20, imagesy($gd));
}
$builder = Builder::canvas(30, 20);
foreach ($gd_objects as $gd) {
$framesrc = $this->getBuffered(function () use ($gd) {
imagecolortransparent($gd);
imagegif($gd);
});
$builder->addFrame($framesrc, 13);
}
// reread
$resource = $builder->encode();
$regif = Decoder::decode($source);
$this->validateGif($regif);
}
protected function validateGif(GifDataStream $gif): void
{
$this->assertInstanceOf(GifDataStream::class, $gif);
// global color table
$this->assertEquals(true, $gif->getLogicalScreen()->getDescriptor()->hasGlobalColorTable());
$this->assertEquals(false, $gif->getLogicalScreen()->getDescriptor()->getGlobalColorTableSorted());
$this->assertEquals(7, $gif->getLogicalScreen()->getDescriptor()->getGlobalColorTableSize());
$this->assertEquals(256 * 3, $gif->getLogicalScreen()->getDescriptor()->getGlobalColorTableByteSize());
$this->assertEquals(0, $gif->getLogicalScreen()->getDescriptor()->getBackgroundColorIndex());
$this->assertEquals(0, $gif->getMainApplicationExtension()->getLoops()); // loops
// frames
$this->assertCount(6, $gif->getGraphicBlocks());
foreach ($gif->getGraphicBlocks() as $block) {
$this->assertEquals(13, $block->getGraphicControlExtension()->getDelay()); // delay
$this->assertEquals(1, $block->getGraphicControlExtension()->getDisposalMethod()); // disposal
$this->assertFalse($block->getGraphicRenderingBlock()->hasColorTable()); // local color table
$this->assertFalse($block->getGraphicRenderingBlock()->getDescriptor()->isInterlaced()); // interlaced
}
}
protected function getBuffered(callable $callback): string
{
ob_start();
$callback();
$buffer = ob_get_contents();
ob_end_clean();
return $buffer;
}
}
tests/PlainTextExtensionTest.php 0000644 00000002755 15073164573 0013110 0 ustar 00 assertCount(0, $extension->getText());
$extension->addText('foo');
$extension->addText('bar');
$this->assertCount(2, $extension->getText());
$extension->setText(['foo']);
$this->assertCount(1, $extension->getText());
}
public function testEncode()
{
$extension = new PlainTextExtension();
$this->assertEquals('', $extension->encode());
$extension->addText('foo');
$extension->addText('bar');
$result = "\x21\x01\x0C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x66\x6f\x6f\x03\x62\x61\x72\x00";
$this->assertEquals($result, $extension->encode());
}
public function testDecode()
{
$sources = [
"\x21\x01\x0C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x66\x6f\x6f\x03\x62\x61\x72\x00",
];
foreach ($sources as $source) {
$extension = PlainTextExtension::decode($this->getTestHandle($source));
$this->assertInstanceOf(PlainTextExtension::class, $extension);
$this->assertEquals(['foo', 'bar'], $extension->getText());
}
}
}
tests/SplitterTest.php 0000644 00000002777 15073164573 0011115 0 ustar 00 split();
$this->assertCount(8, $splitter->getFrames());
foreach ($splitter->getFrames() as $key => $gif) {
$this->assertInstanceOf(GifDataStream::class, $gif);
$this->assertEquals(20, $gif->getLogicalScreen()->getDescriptor()->getWidth());
$this->assertEquals(15, $gif->getLogicalScreen()->getDescriptor()->getHeight());
$this->assertInstanceOf(GifDataStream::class, Decoder::decode($gif->encode()));
}
foreach ($splitter->toResources() as $key => $gif) {
$this->assertInstanceOf(GdImage::class, $gif);
// imagegif($gif, __DIR__ . '/img_resource_' . $key . '.gif');
}
foreach ($splitter->coalesceToResources() as $key => $gif) {
$this->assertInstanceOf(GdImage::class, $gif);
// imagegif($gif, __DIR__ . '/img_coa_' . $key . '.gif');
}
}
public function testGetDelays(): void
{
$decoded = Decoder::decode(__DIR__ . '/images/animation2.gif');
$delays = Splitter::create($decoded)->split()->getDelays();
$this->assertEquals($delays, array_fill(0, 6, 13));
}
}
tests/TableBasedImageTest.php 0000644 00000005321 15073164573 0012224 0 ustar 00 assertNotNull($tbi->getDescriptor());
$this->assertNotNull($tbi->getData());
$this->assertNull($tbi->getColorTable());
$this->assertFalse($tbi->hasColorTable());
}
public function testDescriptor()
{
$tbi = new TableBasedImage();
$tbi->setDescriptor(new ImageDescriptor());
$this->assertInstanceOf(ImageDescriptor::class, $tbi->getDescriptor());
}
public function testData()
{
$tbi = new TableBasedImage();
$tbi->setData(new ImageData());
$this->assertInstanceOf(ImageData::class, $tbi->getData());
}
public function testColorTable()
{
$tbi = new TableBasedImage();
$this->assertFalse($tbi->hasColorTable());
$tbi->setColortable(new ColorTable());
$this->assertInstanceOf(ColorTable::class, $tbi->getColorTable());
$this->assertTrue($tbi->hasColorTable());
}
public function testEncoder()
{
$tbi = new TableBasedImage();
$tbi->setDescriptor($this->getTestDescriptor());
$tbi->setColortable($this->getTestColorTable());
$tbi->setData($this->getTestImageData());
$result = "\x2c\x01\x00\x05\x00\x2c\x01\xc8\x00\xc1\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x02\x16\x8C\x2D\x99\x87\x2A\x1C\xDC\x33\xA0\x02\x75\xEC\x95\xFA\xA8\xDE\x60\x8C\x04\x91\x4C\x01\x00";
$this->assertEquals($result, $tbi->encode());
}
public function testDecode()
{
$source = "\x2C\x00\x00\x00\x00\x0A\x00\x0A\x00\xe1\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\x00\x00\x00\x02\x16\x8C\x2D\x99\x87\x2A\x1C\xDC\x33\xA0\x02\x75\xEC\x95\xFA\xA8\xDE\x60\x8C\x04\x91\x4C\x01\x00";
$tbi = TableBasedImage::decode($this->getTestHandle($source));
$this->assertInstanceOf(TableBasedImage::class, $tbi);
$this->assertInstanceOf(ImageDescriptor::class, $tbi->getDescriptor());
$this->assertInstanceOf(ColorTable::class, $tbi->getColorTable());
$this->assertInstanceOf(ImageData::class, $tbi->getData());
}
private function getTestDescriptor()
{
$descriptor = new ImageDescriptor();
$descriptor->setSize(300, 200);
$descriptor->setPosition(1, 5);
$descriptor->setLocalColorTableExistance();
$descriptor->setInterlaced();
$descriptor->setLocalColorTableSorted(false);
$descriptor->setLocalColorTableSize(1);
return $descriptor;
}
}
tests/images/animation1.gif 0000644 00000001120 15073164573 0011707 0 ustar 00 GIF89a ô 9Kcÿ¦BO_EQ]mcIæ›
jaK¹†$