From 6e8bbadc961f329522aae9f0cc48478748e72014 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:24:24 +0100 Subject: [PATCH 1/3] fix: use native Imagick for AVIF and HEIC output instead of magick convert The shell-based `magick convert` path is unreliable in containerised environments where the binary may not be on PATH, and is unnecessary because ImageMagick 7 (with the heic/avif delegates built in) exposes AVIF and HEIC encoding directly through the Imagick PHP extension. Switch the avif/heic case to the same native pattern used by jpg, png, and gif: setImageCompressionQuality + setImageFormat, then let the shared getImagesBlob/writeImages path handle output. Co-Authored-By: Claude Sonnet 4.6 --- src/Image/Image.php | 62 +++------------------------------------------ 1 file changed, 4 insertions(+), 58 deletions(-) diff --git a/src/Image/Image.php b/src/Image/Image.php index a9ca35a..84dab5a 100644 --- a/src/Image/Image.php +++ b/src/Image/Image.php @@ -369,65 +369,11 @@ public function save(?string $path = null, string $type = '', int $quality = 75) case 'avif': case 'heic': - $signature = $this->image->getImageSignature(); - - $temp = tempnam(sys_get_temp_dir(), 'temp-'.$signature); - if ($temp === false) { - throw new Exception('Failed to create temporary file'); - } - - $output = tempnam(sys_get_temp_dir(), 'output-'.$signature); - if ($output === false) { - \unlink($temp); - throw new Exception('Failed to create output file'); - } - - $temp .= '.'.\strtolower($this->image->getImageFormat()); - $output .= '.'.$type; - - try { - // save temp - $this->image->writeImages($temp, true); - - // convert temp - $command = ['magick convert', \escapeshellarg($temp)]; - - $quality = (int) $quality; - if ($quality >= 0) { - $command = [...$command, '-quality', $quality]; - } - - $command = [ - ...$command, \escapeshellarg($output), '2>&1', // 2>&1 redirect stderr to stdout - ]; - - \exec(implode(' ', $command), $outputArray, $returnCode); - - if ($returnCode !== 0) { - throw new Exception("Image conversion failed with status {$returnCode}: ".implode("\n", $outputArray)); - } - - $data = \file_get_contents($output); - - // save to path - if (! empty($path)) { - \file_put_contents($path, $data, LOCK_EX); - - return; - } - - return $data; - } finally { - if (file_exists($temp)) { - \unlink($temp); - } - if (file_exists($output)) { - \unlink($output); - } - - $this->image->clear(); - $this->image->destroy(); + if ($quality >= 0) { + $this->image->setImageCompressionQuality($quality); } + $this->image->setImageFormat($type); + break; case 'webp': $temp = null; From 9e2d4e5f61c7a8f5032149b6e36af6392a414800 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:28:34 +0100 Subject: [PATCH 2/3] style: fix pint linting issues Co-Authored-By: Claude Sonnet 4.6 --- src/Image/Image.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Image/Image.php b/src/Image/Image.php index 84dab5a..5431c72 100644 --- a/src/Image/Image.php +++ b/src/Image/Image.php @@ -97,8 +97,6 @@ public static function getGravityTypes(): array } /** - * @return Image - * * @throws \Throwable */ public function crop(int $width, int $height, string $gravity = Image::GRAVITY_CENTER): self From 1365d47a8f0535c33f0a865e701cc0297194f19d Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:50:19 +0100 Subject: [PATCH 3/3] fix: use setCompressionQuality for AVIF/HEIC instead of setImageCompressionQuality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setImageCompressionQuality() is silently ignored by the libheif coder in the Imagick PHP extension — quality had no effect and all output was encoded at the default level regardless of the requested value. The fix is to call setCompressionQuality() (object-level) after setImageFormat(), which correctly passes quality through to libheif. See: https://github.com/Imagick/imagick/issues/711 Also updates the HEIC q100 expected file size in tests (8426 → 8490) to reflect the minor encoding difference between native Imagick and the old magick convert path at the same quality level. Co-Authored-By: Claude Sonnet 4.6 --- src/Image/Image.php | 8 ++++++-- tests/Image/ImageTest.php | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Image/Image.php b/src/Image/Image.php index 5431c72..aa51f56 100644 --- a/src/Image/Image.php +++ b/src/Image/Image.php @@ -367,10 +367,14 @@ public function save(?string $path = null, string $type = '', int $quality = 75) case 'avif': case 'heic': + $this->image->setImageFormat($type); if ($quality >= 0) { - $this->image->setImageCompressionQuality($quality); + // setImageCompressionQuality() is silently ignored by the libheif coder — + // setCompressionQuality() (object-level, not image-level) must be called + // after setImageFormat() for quality to take effect on AVIF/HEIC output. + // See: https://github.com/Imagick/imagick/issues/711 + $this->image->setCompressionQuality($quality); } - $this->image->setImageFormat($type); break; case 'webp': diff --git a/tests/Image/ImageTest.php b/tests/Image/ImageTest.php index 583c7a8..766ffc9 100644 --- a/tests/Image/ImageTest.php +++ b/tests/Image/ImageTest.php @@ -471,7 +471,7 @@ public function test_crop100x100_heic(): void $this->assertEquals(\is_readable($target), true); $this->assertGreaterThan(500, \filesize($target)); - $this->assertEquals(8426, \filesize($target)); + $this->assertEquals(8490, \filesize($target)); $this->assertEquals(\mime_content_type($target), \mime_content_type($original)); $this->assertFileExists($target); $this->assertNotEmpty(\file_get_contents($target));