From 6c0556d415a70253e5155595a22a00379a2c1db2 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Sun, 19 Apr 2026 13:44:30 +0100 Subject: [PATCH] fix: use native Imagick for WebP output instead of cwebp shell fallback The `cwebp` shell fallback dates back to the initial 2021 commit, when it guarded against Imagick builds without libwebp delegate support. The `utopia-base` image now ships Imagick with webp support, so the Imagick path succeeds and the catch block has effectively stopped firing. Switch to the same native pattern used for jpg, png, gif, avif, and heic: setImageCompressionQuality + setImageFormat('webp'), then let the shared getImagesBlob/writeImages path handle output. Add two tests: blob-return via output() (covers the empty-path branch) and webp-input roundtrip (covers readImageBlob on webp source). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/Image/Image.php | 73 +++------------------------------------ tests/Image/ImageTest.php | 40 +++++++++++++++++++++ 2 files changed, 44 insertions(+), 69 deletions(-) diff --git a/src/Image/Image.php b/src/Image/Image.php index aa51f56..2eddc5a 100644 --- a/src/Image/Image.php +++ b/src/Image/Image.php @@ -378,76 +378,11 @@ public function save(?string $path = null, string $type = '', int $quality = 75) break; case 'webp': - $temp = null; - $output = null; - try { - if ($quality >= 0) { - $this->image->setImageCompressionQuality($quality); - } - $this->image->setImageFormat('webp'); - - if (empty($path)) { - return $this->image->getImagesBlob(); - } else { - $this->image->writeImages($path, true); - } - } catch (\Throwable) { - $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; - - // save temp - $this->image->writeImages($temp, true); - - // convert temp - $quality = (int) $quality; - $command = \sprintf( - 'cwebp -quiet -metadata none -q %d %s -o %s', - $quality, - \escapeshellarg($temp), - \escapeshellarg($output) - ); - \exec($command, $outputArray, $returnCode); - - if ($returnCode !== 0) { - throw new Exception('Image conversion failed'); - } - - $data = \file_get_contents($output); - - // save to path - if (! empty($path)) { - \file_put_contents($path, $data, LOCK_EX); - - return; - } - - return $data; - } finally { - if (is_string($temp) && file_exists($temp)) { - \unlink($temp); - } - if (is_string($output) && file_exists($output)) { - \unlink($output); - } - - $this->image->clear(); - $this->image->destroy(); + if ($quality >= 0) { + $this->image->setImageCompressionQuality($quality); } - - return; + $this->image->setImageFormat('webp'); + break; case 'png': if ($quality >= 0) { diff --git a/tests/Image/ImageTest.php b/tests/Image/ImageTest.php index 766ffc9..a84aa07 100644 --- a/tests/Image/ImageTest.php +++ b/tests/Image/ImageTest.php @@ -412,6 +412,46 @@ public function test_crop100x100_webp_quality30(): void \unlink($target); } + public function test_webp_blob_output(): void + { + $image = new Image(\file_get_contents(__DIR__.'/../resources/disk-a/kitten-1.jpg') ?: ''); + + $image->crop(100, 100); + + $blob = $image->output('webp', 75); + + $this->assertIsString($blob); + $this->assertNotEmpty($blob); + $this->assertSame('RIFF', \substr($blob, 0, 4)); + $this->assertSame('WEBP', \substr($blob, 8, 4)); + + $probe = new \Imagick; + $probe->readImageBlob($blob); + $this->assertEquals(100, $probe->getImageWidth()); + $this->assertEquals(100, $probe->getImageHeight()); + $this->assertTrue(in_array($probe->getImageFormat(), ['PAM', 'WEBP'])); + } + + public function test_webp_from_webp_input(): void + { + $image = new Image(\file_get_contents(__DIR__.'/../resources/resize/100x100.webp') ?: ''); + $target = __DIR__.'/roundtrip.webp'; + + $image->crop(50, 50); + + $image->save($target, 'webp', 75); + + $this->assertFileExists($target); + $this->assertNotEmpty(\file_get_contents($target)); + + $probe = new \Imagick($target); + $this->assertEquals(50, $probe->getImageWidth()); + $this->assertEquals(50, $probe->getImageHeight()); + $this->assertTrue(in_array($probe->getImageFormat(), ['PAM', 'WEBP'])); + + \unlink($target); + } + public function test_crop100x100_avif(): void { $image = new Image(\file_get_contents(filename: __DIR__.'/../resources/disk-a/kitten-1.jpg') ?: '');