1
0
mirror of https://github.com/Intervention/image.git synced 2025-08-01 11:30:16 +02:00

Merge branch 'feature/slice-animation' into next

This commit is contained in:
Oliver Vogel
2024-01-15 09:37:33 +01:00
11 changed files with 292 additions and 3 deletions

View File

@@ -185,10 +185,31 @@ class Collection implements CollectionInterface, IteratorAggregate, Countable
return $this;
}
/**
* {@inheritdoc}
*
* @see CollectionInterface::empty()
*/
public function empty(): CollectionInterface
{
$this->items = [];
return $this;
}
/**
* {@inheritdoc}
*
* @see CollectionInterface::slice()
*/
public function slice(int $offset, ?int $length = null): CollectionInterface
{
if ($offset >= count($this->items)) {
throw new RuntimeException('Offset exceeds the maximum value.');
}
$this->items = array_slice($this->items, $offset, $length);
return $this;
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Intervention\Image\Drivers\Gd\Modifiers;
use Intervention\Image\Drivers\DriverSpecializedModifier;
use Intervention\Image\Interfaces\ImageInterface;
/**
* @property int $offset
* @property null|int $length
*/
class SliceAnimationModifier extends DriverSpecializedModifier
{
public function apply(ImageInterface $image): ImageInterface
{
$image->core()->slice($this->offset, $this->length);
return $image;
}
}

View File

@@ -7,6 +7,7 @@ use ImagickException;
use Iterator;
use Intervention\Image\Interfaces\CoreInterface;
use Intervention\Image\Exceptions\AnimationException;
use Intervention\Image\Interfaces\CollectionInterface;
use Intervention\Image\Interfaces\FrameInterface;
class Core implements CoreInterface, Iterator
@@ -17,6 +18,98 @@ class Core implements CoreInterface, Iterator
{
}
/**
* {@inheritdoc}
*
* @see CollectionInterface::has()
*/
public function has(int|string $key): bool
{
try {
$result = $this->imagick->setIteratorIndex($key);
} catch (ImagickException) {
return false;
}
return $result;
}
/**
* {@inheritdoc}
*
* @see CollectionInterface::push()
*/
public function push($item): CollectionInterface
{
return $this->add($item);
}
/**
* {@inheritdoc}
*
* @see CollectionInterface::get()
*/
public function get(int|string $key, $default = null): mixed
{
try {
$this->imagick->setIteratorIndex($key);
} catch (ImagickException) {
return $default;
}
return new Frame($this->imagick->current());
}
/**
* {@inheritdoc}
*
* @see CollectionInterface::getAtPosition()
*/
public function getAtPosition(int $key = 0, $default = null): mixed
{
return $this->get($key, $default);
}
/**
* {@inheritdoc}
*
* @see CollectionInterface::empty()
*/
public function empty(): CollectionInterface
{
$this->imagick->clear();
return $this;
}
/**
* {@inheritdoc}
*
* @see CollectionInterface::slice()
*/
public function slice(int $offset, ?int $length = null): CollectionInterface
{
$allowed_indexes = [];
$length = is_null($length) ? $this->count() : $length;
for ($i = $offset; $i < $offset + $length; $i++) {
$allowed_indexes[] = $i;
}
$sliced = new Imagick();
foreach ($this->imagick as $key => $native) {
if (in_array($key, $allowed_indexes)) {
$sliced->addImage($native->getImage());
}
}
$sliced = $sliced->coalesceImages();
$sliced->setImageIterations($this->imagick->getImageIterations());
$this->imagick = $sliced;
return $this;
}
public function add(FrameInterface $frame): CoreInterface
{
$imagick = $frame->native();

View File

@@ -0,0 +1,20 @@
<?php
namespace Intervention\Image\Drivers\Imagick\Modifiers;
use Intervention\Image\Drivers\DriverSpecializedModifier;
use Intervention\Image\Interfaces\ImageInterface;
/**
* @property int $offset
* @property null|int $length
*/
class SliceAnimationModifier extends DriverSpecializedModifier
{
public function apply(ImageInterface $image): ImageInterface
{
$image->core()->slice($this->offset, $this->length);
return $image;
}
}

View File

@@ -83,6 +83,7 @@ use Intervention\Image\Modifiers\RotateModifier;
use Intervention\Image\Modifiers\ScaleDownModifier;
use Intervention\Image\Modifiers\ScaleModifier;
use Intervention\Image\Modifiers\SharpenModifier;
use Intervention\Image\Modifiers\SliceAnimationModifier;
use Intervention\Image\Modifiers\TextModifier;
use Intervention\Image\Typography\FontFactory;
@@ -204,6 +205,11 @@ final class Image implements ImageInterface
return $this->modify(new RemoveAnimationModifier($position));
}
public function sliceAnimation(int $offset = 0, ?int $length = null): ImageInterface
{
return $this->modify(new SliceAnimationModifier($offset, $length));
}
/**
* {@inheritdoc}
*

View File

@@ -67,4 +67,13 @@ interface CollectionInterface extends Traversable
* @return CollectionInterface
*/
public function empty(): CollectionInterface;
/**
* Extract items based on given values and discard the rest.
*
* @param int $offset
* @param null|int $length
* @return CollectionInterface
*/
public function slice(int $offset, ?int $length = 0): CollectionInterface;
}

View File

@@ -2,9 +2,7 @@
namespace Intervention\Image\Interfaces;
use Traversable;
interface CoreInterface extends Traversable
interface CoreInterface extends CollectionInterface
{
/**
* return driver's representation of the image core.
@@ -60,5 +58,10 @@ interface CoreInterface extends Traversable
*/
public function setLoops(int $loops): CoreInterface;
/**
* Get first frame in core
*
* @return FrameInterface
*/
public function first(): FrameInterface;
}

View File

@@ -112,6 +112,15 @@ interface ImageInterface extends IteratorAggregate, Countable
*/
public function removeAnimation(int|string $position = 0): ImageInterface;
/**
* Extract animation frames based on given values and discard the rest
*
* @param int $offset
* @param null|int $length
* @return ImageInterface
*/
public function sliceAnimation(int $offset = 0, ?int $length = null): ImageInterface;
/**
* Return loop count of animated image
*

View File

@@ -0,0 +1,10 @@
<?php
namespace Intervention\Image\Modifiers;
class SliceAnimationModifier extends AbstractModifier
{
public function __construct(public int $offset = 0, public ?int $length = null)
{
}
}

View File

@@ -3,6 +3,7 @@
namespace Intervention\Image\Tests;
use Intervention\Image\Collection;
use Intervention\Image\Exceptions\RuntimeException;
/**
* @covers \Intervention\Image\Collection
@@ -144,4 +145,28 @@ class CollectionTest extends TestCase
$this->assertEquals(0, $collection->count());
$this->assertEquals(0, $result->count());
}
public function testSlice(): void
{
$collection = new Collection(['a', 'b', 'c', 'd', 'e', 'f']);
$this->assertEquals(6, $collection->count());
$result = $collection->slice(0, 3);
$this->assertEquals(['a', 'b', 'c'], $collection->toArray());
$this->assertEquals(['a', 'b', 'c'], $result->toArray());
$this->assertEquals('a', $result->get(0));
$this->assertEquals('b', $result->get(1));
$this->assertEquals('c', $result->get(2));
$result = $collection->slice(2, 1);
$this->assertEquals(['c'], $collection->toArray());
$this->assertEquals(['c'], $result->toArray());
$this->assertEquals('c', $result->get(0));
}
public function testSliceOutOfBounds(): void
{
$this->expectException(RuntimeException::class);
$collection = new Collection(['a', 'b', 'c']);
$collection->slice(6);
}
}

View File

@@ -66,4 +66,77 @@ class CoreTest extends TestCase
$this->assertEquals(12, $core->loops());
$this->assertInstanceOf(Core::class, $result);
}
public function testHas(): void
{
$imagick = new Imagick();
$imagick->newImage(100, 100, new ImagickPixel('red'));
$imagick->addImage(clone $imagick);
$core = new Core($imagick);
$this->assertTrue($core->has(0));
$this->assertTrue($core->has(1));
$this->assertFalse($core->has(2));
}
public function testPush(): void
{
$imagick = new Imagick();
$imagick->newImage(100, 100, new ImagickPixel('red'));
$imagick->addImage(clone $imagick);
$core = new Core($imagick);
$this->assertEquals(2, $core->count());
$im = new Imagick();
$im->newImage(100, 100, new ImagickPixel('green'));
$result = $core->push(new Frame($im));
$this->assertEquals(3, $core->count());
$this->assertEquals(3, $result->count());
}
public function testGet(): void
{
$imagick = new Imagick();
$imagick->newImage(100, 100, new ImagickPixel('red'));
$imagick->addImage(clone $imagick);
$core = new Core($imagick);
$this->assertInstanceOf(Frame::class, $core->get(0));
$this->assertInstanceOf(Frame::class, $core->get(1));
$this->assertNull($core->get(2));
$this->assertEquals('foo', $core->get(2, 'foo'));
}
public function testEmpty(): void
{
$imagick = new Imagick();
$imagick->newImage(100, 100, new ImagickPixel('red'));
$imagick->addImage(clone $imagick);
$core = new Core($imagick);
$this->assertEquals(2, $core->count());
$result = $core->empty();
$this->assertEquals(0, $core->count());
$this->assertEquals(0, $result->count());
}
public function testSlice(): void
{
$imagick = new Imagick();
$im = new Imagick();
$im->newImage(10, 10, new ImagickPixel('red'));
$imagick->addImage($im);
$im = new Imagick();
$im->newImage(10, 10, new ImagickPixel('green'));
$imagick->addImage($im);
$im = new Imagick();
$im->newImage(10, 10, new ImagickPixel('blue'));
$imagick->addImage($im);
$core = new Core($imagick);
$this->assertEquals(3, $core->count());
$result = $core->slice(1, 2);
$this->assertEquals(2, $core->count());
$this->assertEquals(2, $result->count());
}
}