generated from Nodarx/template
f7d3ef0155
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
225 lines
7.2 KiB
PHP
225 lines
7.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace KTXT\ProviderPreviewImageMagick\Unit;
|
|
|
|
use Imagick;
|
|
use ImagickPixel;
|
|
use KTXC\Resource\ProviderManager;
|
|
use KTXF\Preview\PreviewGenerationException;
|
|
use KTXF\Preview\PreviewRequest;
|
|
use KTXF\Preview\PreviewSource;
|
|
use KTXF\Resource\BinaryResource;
|
|
use KTXF\Resource\Provider\ProviderInterface;
|
|
use KTXM\ProviderPreviewImageMagick\Module;
|
|
use KTXM\ProviderPreviewImageMagick\Provider;
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class ProviderTest extends TestCase
|
|
{
|
|
#[Test]
|
|
public function moduleRegistersThePreviewProvider(): void
|
|
{
|
|
$providers = $this->createMock(ProviderManager::class);
|
|
$providers->expects(self::once())->method('register')->with(
|
|
ProviderInterface::TYPE_PREVIEW,
|
|
'imagemagick',
|
|
Provider::class,
|
|
);
|
|
|
|
(new Module($providers))->boot();
|
|
}
|
|
|
|
public static function inputFormats(): iterable
|
|
{
|
|
yield 'JPEG' => ['JPEG', 'image/jpeg'];
|
|
yield 'PNG' => ['PNG', 'image/png'];
|
|
yield 'WebP' => ['WEBP', 'image/webp'];
|
|
yield 'GIF' => ['GIF', 'image/gif'];
|
|
}
|
|
|
|
#[Test]
|
|
#[DataProvider('inputFormats')]
|
|
public function generatesBoundedWebpFromSupportedInputs(string $format, string $mimeType): void
|
|
{
|
|
$provider = new Provider();
|
|
$result = $provider->generate(
|
|
$this->source($this->image($format, 800, 400), $mimeType),
|
|
$this->request(),
|
|
);
|
|
$output = $this->content($result->resource);
|
|
$image = new Imagick();
|
|
$image->readImageBlob($output);
|
|
|
|
self::assertSame('image/webp', $result->resource->mimeType());
|
|
self::assertSame(640, $result->width);
|
|
self::assertSame(320, $result->height);
|
|
self::assertSame('WEBP', $image->getImageFormat());
|
|
self::assertSame(640, $image->getImageWidth());
|
|
self::assertSame(320, $image->getImageHeight());
|
|
$image->clear();
|
|
}
|
|
|
|
#[Test]
|
|
public function doesNotUpscaleSmallImages(): void
|
|
{
|
|
$result = (new Provider())->generate(
|
|
$this->source($this->image('PNG', 100, 50), 'image/png'),
|
|
$this->request(),
|
|
);
|
|
|
|
self::assertSame(100, $result->width);
|
|
self::assertSame(50, $result->height);
|
|
}
|
|
|
|
public static function outputFormats(): iterable
|
|
{
|
|
yield 'JPEG' => ['image/jpeg', 'JPEG'];
|
|
yield 'PNG' => ['image/png', 'PNG'];
|
|
yield 'WebP' => ['image/webp', 'WEBP'];
|
|
}
|
|
|
|
#[Test]
|
|
#[DataProvider('outputFormats')]
|
|
public function encodesEveryAdvertisedOutputFormat(string $mimeType, string $format): void
|
|
{
|
|
$request = new PreviewRequest(640, 440, $mimeType, 80);
|
|
$result = (new Provider())->generate(
|
|
$this->source($this->image('PNG', 20, 20, 'rgba(255, 0, 0, 0.25)'), 'image/png'),
|
|
$request,
|
|
);
|
|
$image = new Imagick();
|
|
$image->readImageBlob($this->content($result->resource));
|
|
|
|
self::assertSame($mimeType, $result->resource->mimeType());
|
|
self::assertSame($format, $image->getImageFormat());
|
|
$image->clear();
|
|
}
|
|
|
|
#[Test]
|
|
public function preservesTransparencyInPngOutput(): void
|
|
{
|
|
$request = new PreviewRequest(640, 440, 'image/png', 80);
|
|
$result = (new Provider())->generate(
|
|
$this->source($this->image('PNG', 20, 20, 'rgba(255, 0, 0, 0.25)'), 'image/png'),
|
|
$request,
|
|
);
|
|
$image = new Imagick();
|
|
$image->readImageBlob($this->content($result->resource));
|
|
$color = $image->getImagePixelColor(0, 0)->getColor(1);
|
|
|
|
self::assertSame('image/png', $result->resource->mimeType());
|
|
self::assertLessThan(0.5, $color['a']);
|
|
$image->clear();
|
|
}
|
|
|
|
#[Test]
|
|
public function usesOnlyTheFirstAnimatedFrame(): void
|
|
{
|
|
$animation = new Imagick();
|
|
foreach (['red', 'blue'] as $color) {
|
|
$frame = new Imagick();
|
|
$frame->newImage(20, 20, new ImagickPixel($color));
|
|
$frame->setImageFormat('GIF');
|
|
$frame->setImageDelay(10);
|
|
$animation->addImage($frame);
|
|
$frame->clear();
|
|
}
|
|
$content = $animation->getImagesBlob();
|
|
$animation->clear();
|
|
|
|
$result = (new Provider())->generate($this->source($content, 'image/gif'), $this->request());
|
|
$output = new Imagick();
|
|
$output->readImageBlob($this->content($result->resource));
|
|
|
|
self::assertSame(1, $output->getNumberImages());
|
|
$color = $output->getImagePixelColor(0, 0)->getColor();
|
|
self::assertGreaterThan($color['b'], $color['r']);
|
|
$output->clear();
|
|
}
|
|
|
|
#[Test]
|
|
public function rejectsMalformedOrMismatchedImages(): void
|
|
{
|
|
$provider = new Provider();
|
|
|
|
try {
|
|
$provider->generate($this->source('not an image', 'image/png'), $this->request());
|
|
self::fail('Malformed input should fail');
|
|
} catch (PreviewGenerationException) {
|
|
self::addToAssertionCount(1);
|
|
}
|
|
|
|
$this->expectException(PreviewGenerationException::class);
|
|
$this->expectExceptionMessage('does not match');
|
|
$provider->generate(
|
|
$this->source($this->image('PNG', 20, 20), 'image/jpeg'),
|
|
$this->request(),
|
|
);
|
|
}
|
|
|
|
#[Test]
|
|
public function enforcesTheSourceSizeLimitWhileReading(): void
|
|
{
|
|
$content = $this->image('PNG', 20, 20);
|
|
$request = new PreviewRequest(
|
|
640,
|
|
440,
|
|
'image/webp',
|
|
80,
|
|
maxSourceSize: strlen($content) - 1,
|
|
);
|
|
|
|
$this->expectException(PreviewGenerationException::class);
|
|
$this->expectExceptionMessage('size limit');
|
|
(new Provider())->generate($this->source($content, 'image/png', size: null), $request);
|
|
}
|
|
|
|
private function request(): PreviewRequest
|
|
{
|
|
return new PreviewRequest(640, 440, 'image/webp', 80);
|
|
}
|
|
|
|
private function source(string $content, string $mimeType, ?int $size = null): PreviewSource
|
|
{
|
|
return new PreviewSource(
|
|
'document',
|
|
'image',
|
|
hash('sha256', $content),
|
|
$mimeType,
|
|
$size ?? strlen($content),
|
|
static fn(): BinaryResource => new BinaryResource(
|
|
'source.bin',
|
|
$mimeType,
|
|
(static function () use ($content): \Generator {
|
|
$middle = intdiv(strlen($content), 2);
|
|
yield substr($content, 0, $middle);
|
|
yield substr($content, $middle);
|
|
})(),
|
|
),
|
|
);
|
|
}
|
|
|
|
private function image(string $format, int $width, int $height, string $color = 'red'): string
|
|
{
|
|
$image = new Imagick();
|
|
$image->newImage($width, $height, new ImagickPixel($color));
|
|
$image->setImageFormat($format);
|
|
$content = $image->getImageBlob();
|
|
$image->clear();
|
|
return $content;
|
|
}
|
|
|
|
private function content(BinaryResource $resource): string
|
|
{
|
|
$content = '';
|
|
foreach ($resource->stream() as $chunk) {
|
|
$content .= $chunk;
|
|
}
|
|
return $content;
|
|
}
|
|
}
|