/
githubmirror
/
framework
Обзор
Документация
Войти
/
githubmirror
/
framework
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
13.x
tests/Image/Drivers/ImagickDriverTest.php
595 строк
18 KB
Zac Hiler
[13.x] Fix incorrect image dimensions for HEIC (#61010)
05 авг 2026, 17:41
Не верифицирован
05 авг 2026, 17:41
eade0a1
Код
Авторство
О чём код?
<?php namespace Illuminate\Tests\Image\Drivers; use Illuminate\Config\Repository; use Illuminate\Container\Container; use Illuminate\Contracts\Image\Transformation; use Illuminate\Http\UploadedFile; use Illuminate\Image\Drivers\ImagickDriver; use Illuminate\Image\Image; use Illuminate\Image\ImageManager; use Illuminate\Image\ImagePipeline; use Illuminate\Image\Transformations\Blur; use Illuminate\Image\Transformations\Contain; use Illuminate\Image\Transformations\Cover; use Illuminate\Image\Transformations\Crop; use Illuminate\Image\Transformations\FlipHorizontally; use Illuminate\Image\Transformations\FlipVertically; use Illuminate\Image\Transformations\Grayscale; use Illuminate\Image\Transformations\Orient; use Illuminate\Image\Transformations\Resize; use Illuminate\Image\Transformations\Rotate; use Illuminate\Image\Transformations\Scale; use Illuminate\Image\Transformations\Sharpen; use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; #[RequiresPhpExtension('imagick')] class ImagickDriverTest extends TestCase { public function test_processes_cover() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(200, 200); $pipeline = $this->pipeline(new Cover(100, 50)); $result = $driver->process($contents, $pipeline); [$width, $height] = getimagesizefromstring($result); $this->assertSame(100, $width); $this->assertSame(50, $height); } public function test_processes_optimize_to_webp() { $driver = new ImagickDriver; $pipeline = $this->pipeline(format: 'webp'); $result = $driver->process($this->fakeImageContents(), $pipeline); $this->assertSame(IMAGETYPE_WEBP, getimagesizefromstring($result)[2]); } public function test_processes_optimize_to_jpeg() { $driver = new ImagickDriver; $pipeline = $this->pipeline(format: 'jpg'); $result = $driver->process($this->fakeImageContents(), $pipeline); $this->assertSame(IMAGETYPE_JPEG, getimagesizefromstring($result)[2]); } public function test_processes_optimize_to_png() { $driver = new ImagickDriver; $pipeline = $this->pipeline(format: 'png'); $result = $driver->process($this->fakeImageContents(), $pipeline); $this->assertSame(IMAGETYPE_PNG, getimagesizefromstring($result)[2]); } public function test_processes_optimize_to_gif() { $driver = new ImagickDriver; $pipeline = $this->pipeline(format: 'gif'); $result = $driver->process($this->fakeImageContents(), $pipeline); $this->assertSame(IMAGETYPE_GIF, getimagesizefromstring($result)[2]); } public function test_processes_optimize_to_avif() { $this->ensureImageFormatCanBeEncoded('avif'); $driver = new ImagickDriver; $pipeline = $this->pipeline(format: 'avif'); $result = $driver->process($this->fakeImageContents(), $pipeline); // PHP's getimagesizefromstring()/finfo AVIF detection varies by the // libavif/libmagic versions installed, so we assert on the ISOBMFF // "ftyp" box and brand directly instead of relying on either. $this->assertStringContainsString('ftyp', substr($result, 0, 16)); $this->assertMatchesRegularExpression('/avif|avis|mif1/', substr($result, 0, 32)); } public function test_processes_avif_input() { $this->ensureImageFormatCanBeEncoded('avif'); $driver = new ImagickDriver; $contents = $driver->process($this->fakeImageContents(), $this->pipeline(format: 'avif')); $result = $driver->process($contents, $this->pipeline(new Cover(50, 25), format: 'jpg')); $this->assertSame([50, 25], array_slice(getimagesizefromstring($result), 0, 2)); } public function test_processes_optimize_to_heic() { $this->ensureImageFormatCanBeEncoded('heic'); $driver = new ImagickDriver; $result = $driver->process($this->fakeImageContents(), $this->pipeline(format: 'heic')); $this->assertStringContainsString('ftyp', substr($result, 0, 16)); $this->assertMatchesRegularExpression('/heic|heix|hevc|hevx|mif1/', substr($result, 0, 32)); } public function test_processes_heic_input() { $this->ensureImageFormatCanBeEncoded('heic'); $driver = new ImagickDriver; $contents = $driver->process($this->fakeImageContents(), $this->pipeline(format: 'heic')); $result = $driver->process($contents, $this->pipeline(new Cover(50, 25))); $this->assertStringContainsString('ftyp', substr($result, 0, 16)); $this->assertMatchesRegularExpression('/heic|heix|hevc|hevx|mif1/', substr($result, 0, 32)); $result = $driver->process($result, $this->pipeline(format: 'jpg')); $this->assertSame([50, 25], array_slice(getimagesizefromstring($result), 0, 2)); } public function test_dimensions_returns_the_decoded_size() { $driver = new ImagickDriver; $contents = $driver->process($this->fakeImageContents(320, 240), $this->pipeline(new Cover(200, 150), format: 'png')); $this->assertSame([200, 150], $driver->dimensions($contents)); } public function test_dimensions_returns_the_display_size_for_heic() { $this->ensureImageFormatCanBeEncoded('heic'); $driver = new ImagickDriver; // 137x73 is a size where HEIC's coded/padded frame differs from the display size, which // getimagesize() misreports; the driver must return the true display size. $contents = $driver->process($this->fakeImageContents(400, 300), $this->pipeline(new Cover(137, 73), format: 'heic')); $this->assertSame([137, 73], $driver->dimensions($contents)); } public function test_image_dimensions_are_correct_for_real_heic_through_public_api() { $this->ensureImageFormatCanBeEncoded('heic'); // End-to-end through the public Image API on a real HEIC; fails pre-fix (getimagesize returns 138x74). $heic = (new ImagickDriver)->process( $this->fakeImageContents(400, 300), $this->pipeline(new Cover(137, 73), format: 'heic') ); $container = new Container; $container->instance('config', new Repository(['images' => ['default' => 'imagick']])); $container->instance('image', new ImageManager($container)); Container::setInstance($container); try { $this->assertSame([137, 73], (new Image($heic))->dimensions()); } finally { Container::setInstance(null); } } public function test_processes_optimize_to_bmp() { $driver = new ImagickDriver; $pipeline = $this->pipeline(format: 'bmp'); $result = $driver->process($this->fakeImageContents(), $pipeline); $this->assertSame(IMAGETYPE_BMP, getimagesizefromstring($result)[2]); } public function test_processes_cover_and_optimize_together() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(300, 300); $pipeline = $this->pipeline(new Cover(75, 75), format: 'webp'); $result = $driver->process($contents, $pipeline); [$width, $height, $type] = getimagesizefromstring($result); $this->assertSame(75, $width); $this->assertSame(75, $height); $this->assertSame(IMAGETYPE_WEBP, $type); } public function test_processes_contain() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(400, 200); $pipeline = $this->pipeline(new Contain(200, 200, '#ffffff')); $result = $driver->process($contents, $pipeline); [$width, $height] = getimagesizefromstring($result); $this->assertSame(200, $width); $this->assertSame(200, $height); } public function test_processes_contain_with_dominant_background() { $driver = new ImagickDriver; $contents = $this->solidColorImageContents(255, 0, 0, 400, 200); $pipeline = $this->pipeline(new Contain(200, 200, 'dominant')); $result = $driver->process($contents, $pipeline); [$width, $height] = getimagesizefromstring($result); $this->assertSame(200, $width); $this->assertSame(200, $height); } public function test_dominant_color_returns_hex_for_solid_image() { $driver = new ImagickDriver; $contents = $this->solidColorImageContents(0, 128, 255); $this->assertSame('#0080ff', $driver->dominantColor($contents)); } public function test_dominant_color_ignores_alpha_channel(): void { $driver = new ImagickDriver; $contents = $this->semiTransparentColorImageContents(0, 128, 255, 128); $this->assertSame('#0080ff', $driver->dominantColor($contents)); } public function test_processes_crop() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(400, 200); $pipeline = $this->pipeline(new Crop(100, 50, 10, 20)); $result = $driver->process($contents, $pipeline); [$width, $height] = getimagesizefromstring($result); $this->assertSame(100, $width); $this->assertSame(50, $height); } public function test_processes_resize() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(400, 200); $pipeline = $this->pipeline(new Resize(200, 200)); $result = $driver->process($contents, $pipeline); [$width, $height] = getimagesizefromstring($result); $this->assertSame(200, $width); $this->assertSame(200, $height); } public function test_processes_rotate() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(100, 50); $pipeline = $this->pipeline(new Rotate(90)); $result = $driver->process($contents, $pipeline); [$width, $height] = getimagesizefromstring($result); $this->assertSame(50, $width); $this->assertSame(100, $height); } public function test_processes_rotate_with_dominant_background() { $driver = new ImagickDriver; $contents = $this->solidColorImageContents(0, 255, 0, 100, 50); $pipeline = $this->pipeline(new Rotate(45, 'dominant')); $result = $driver->process($contents, $pipeline); $this->assertNotEmpty($result); $this->assertNotFalse(getimagesizefromstring($result)); } public function test_processes_scale() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(400, 200); $pipeline = $this->pipeline(new Scale(200, 200)); $result = $driver->process($contents, $pipeline); [$width, $height] = getimagesizefromstring($result); $this->assertSame(200, $width); $this->assertSame(100, $height); } public function test_processes_scale_width_only() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(400, 200); $pipeline = $this->pipeline(new Scale(200, null)); $result = $driver->process($contents, $pipeline); [$width, $height] = getimagesizefromstring($result); $this->assertSame(200, $width); $this->assertSame(100, $height); } public function test_processes_scale_height_only() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(400, 200); $pipeline = $this->pipeline(new Scale(null, 100)); $result = $driver->process($contents, $pipeline); [$width, $height] = getimagesizefromstring($result); $this->assertSame(200, $width); $this->assertSame(100, $height); } public function test_scale_does_not_upscale() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(100, 80); $pipeline = $this->pipeline(new Scale(800, 600)); $result = $driver->process($contents, $pipeline); [$width, $height] = getimagesizefromstring($result); $this->assertSame(100, $width); $this->assertSame(80, $height); } public function test_format_conversion_preserves_dimensions() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(300, 200); $pipeline = $this->pipeline(format: 'webp'); $result = $driver->process($contents, $pipeline); [$width, $height] = getimagesizefromstring($result); $this->assertSame(300, $width); $this->assertSame(200, $height); } public function test_quality_preserves_dimensions() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(300, 200); $pipeline = $this->pipeline(quality: 50); $result = $driver->process($contents, $pipeline); [$width, $height] = getimagesizefromstring($result); $this->assertSame(300, $width); $this->assertSame(200, $height); } public function test_processes_orient() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(100, 100); $pipeline = $this->pipeline(new Orient); $result = $driver->process($contents, $pipeline); [$width, $height] = getimagesizefromstring($result); $this->assertSame(100, $width); $this->assertSame(100, $height); } public function test_processes_blur() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(100, 100); $pipeline = $this->pipeline(new Blur(10)); $result = $driver->process($contents, $pipeline); $this->assertNotEmpty($result); $this->assertNotSame($contents, $result); } public function test_processes_grayscale() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(100, 100); $pipeline = $this->pipeline(new Grayscale); $result = $driver->process($contents, $pipeline); $this->assertNotEmpty($result); $this->assertNotSame($contents, $result); } public function test_processes_sharpen() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(100, 100); $pipeline = $this->pipeline(new Sharpen(10)); $result = $driver->process($contents, $pipeline); $this->assertNotEmpty($result); $this->assertNotSame($contents, $result); } public function test_processes_flip_vertically() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(100, 100); $pipeline = $this->pipeline(new FlipVertically); $result = $driver->process($contents, $pipeline); $this->assertNotEmpty($result); } public function test_processes_flip_horizontally() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(100, 100); $pipeline = $this->pipeline(new FlipHorizontally); $result = $driver->process($contents, $pipeline); $this->assertNotEmpty($result); } public function test_returns_image_without_options() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(100, 100); $result = $driver->process($contents, new ImagePipeline); [$width, $height] = getimagesizefromstring($result); $this->assertSame(100, $width); $this->assertSame(100, $height); } public function test_quality_affects_file_size() { $driver = new ImagickDriver; $contents = $this->fakeImageContents(100, 100); $lowQuality = $this->pipeline(format: 'jpg', quality: 1); $highQuality = $this->pipeline(format: 'jpg', quality: 100); $lowResult = $driver->process($contents, $lowQuality); $highResult = $driver->process($contents, $highQuality); $this->assertLessThan(strlen($highResult), strlen($lowResult)); } public function test_ensure_requirements_passes() { $driver = new ImagickDriver; $driver->ensureRequirementsAreMet(); $this->assertTrue(true); } protected function fakeImageContents(int $width = 100, int $height = 100): string { $file = UploadedFile::fake()->image('test.jpg', $width, $height); return file_get_contents($file->getRealPath()); } protected function ensureImageFormatCanBeEncoded(string $format): void { if (\Imagick::queryFormats(strtoupper($format)) === []) { $this->markTestSkipped("The Imagick extension was not compiled with {$format} support."); } try { $imagick = new \Imagick; $imagick->newImage(1, 1, 'white'); $imagick->setImageFormat($format); $encoded = $imagick->getImageBlob(); } catch (\ImagickException) { // Some builds ship the HEIC decode delegate but no encoder, in which case encoding // raises instead of returning an empty blob. Either way, encoding is unavailable. $encoded = ''; } if ($encoded === '') { $this->markTestSkipped("The Imagick extension cannot encode {$format} images."); } } protected function solidColorImageContents(int $red, int $green, int $blue, int $width = 100, int $height = 100): string { $imagick = new \Imagick; $imagick->newImage($width, $height, new \ImagickPixel(sprintf('rgb(%d,%d,%d)', $red, $green, $blue))); $imagick->setImageAlphaChannel(\Imagick::ALPHACHANNEL_OPAQUE); $imagick->setImageFormat('png'); $contents = $imagick->getImageBlob(); $imagick->clear(); $imagick->destroy(); return $contents; } protected function semiTransparentColorImageContents(int $red, int $green, int $blue, int $alpha, int $width = 100, int $height = 100): string { $imagick = new \Imagick; $imagick->newImage($width, $height, new \ImagickPixel(sprintf('rgba(%d,%d,%d,%.2f)', $red, $green, $blue, $alpha / 255))); $imagick->setImageFormat('png'); $contents = $imagick->getImageBlob(); $imagick->clear(); $imagick->destroy(); return $contents; } protected function pipeline(?Transformation $transformation = null, ?string $format = null, ?int $quality = null): ImagePipeline { $pipeline = new ImagePipeline; if ($transformation) { $pipeline->add($transformation); } $pipeline->output->format = $format; $pipeline->output->quality = $quality; return $pipeline; } }