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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
20
src/Drivers/Gd/Modifiers/SliceAnimationModifier.php
Normal file
20
src/Drivers/Gd/Modifiers/SliceAnimationModifier.php
Normal 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;
|
||||
}
|
||||
}
|
@@ -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();
|
||||
|
20
src/Drivers/Imagick/Modifiers/SliceAnimationModifier.php
Normal file
20
src/Drivers/Imagick/Modifiers/SliceAnimationModifier.php
Normal 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;
|
||||
}
|
||||
}
|
@@ -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}
|
||||
*
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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
|
||||
*
|
||||
|
10
src/Modifiers/SliceAnimationModifier.php
Normal file
10
src/Modifiers/SliceAnimationModifier.php
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user