diff --git a/README.md b/README.md index d3e6973..3b732ea 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,35 @@ Usage: $wt = $wt->crop(true); $conf = [WebThumbnailer::CROP => true]; ``` + +### Resize Mode + +This setting choose whether to use `imagecopyresized` (RESIZE mode) or `imagecopyresampled` (RESAMPLE mode): + + * `RESAMPLE`: higher CPI usage, with better resized image rendering. + * `RESIZE`: faster and lower CPU usage, but the resized image might not look as good. +By default, this library uses `RESAMPLE` setting since the setting was introduced. + +Example: + +|---------------------RESIZE---------------------|---------------------RESAMPLE---------------------| + +![](https://user-images.githubusercontent.com/28786873/195634626-ec80e2ac-43f1-4f00-a507-8ba30dc5dda7.jpg) +![](https://user-images.githubusercontent.com/28786873/195634635-87f84476-e923-461c-accf-d3e5988e33a5.jpg) + +Usage: + +```php +// Resize +$wt = $wt->resize(); +$conf = [WebThumbnailer::RESIZE_MODE => WebThumbnailer::RESIZE]; + +// Resample +$wt = $wt->resample(); +$conf = [WebThumbnailer::RESIZE_MODE => WebThumbnailer::RESAMPLE]; +``` + ### Miscellaneous * **NOCACHE**: Force the thumbnail to be resolved and downloaded instead of using cache files. diff --git a/src/Application/Thumbnailer.php b/src/Application/Thumbnailer.php index 31adc63..3e3d465 100644 --- a/src/Application/Thumbnailer.php +++ b/src/Application/Thumbnailer.php @@ -55,7 +55,7 @@ class Thumbnailer public function __construct(string $url, array $options, ?array $server) { ApplicationUtils::checkExtensionRequirements(['gd']); - ApplicationUtils::checkPHPVersion('5.6', PHP_VERSION); + ApplicationUtils::checkPHPVersion('7.1', PHP_VERSION); $this->url = $url; $this->server = $server; @@ -128,6 +128,13 @@ protected function setOptions(array $options): void $this->options[WebThumbnailer::DEBUG] = false; } + // Resize mode, defaults to resample + if (isset($options[WebThumbnailer::RESIZE_MODE])) { + $this->options[WebThumbnailer::RESIZE_MODE] = $options[WebThumbnailer::RESIZE_MODE]; + } else { + $this->options[WebThumbnailer::RESIZE_MODE] = WebThumbnailer::RESAMPLE; + } + // Image size $this->setSizeOptions($options); } @@ -354,7 +361,8 @@ protected function thumbnailDownload(string $thumbUrl) $thumbPath, $this->options[WebThumbnailer::MAX_WIDTH], $this->options[WebThumbnailer::MAX_HEIGHT], - $this->options[WebThumbnailer::CROP] + $this->options[WebThumbnailer::CROP], + $this->options[WebThumbnailer::RESIZE_MODE] ); if (!is_file($thumbPath)) { diff --git a/src/Utils/ImageUtils.php b/src/Utils/ImageUtils.php index 1490e4d..f83f278 100644 --- a/src/Utils/ImageUtils.php +++ b/src/Utils/ImageUtils.php @@ -6,6 +6,7 @@ use WebThumbnailer\Exception\ImageConvertException; use WebThumbnailer\Exception\NotAnImageException; +use WebThumbnailer\WebThumbnailer; /** * Util class to manipulate GD images. @@ -33,7 +34,8 @@ public static function generateThumbnail( string $target, int $maxWidth, int $maxHeight, - bool $crop = false + bool $crop = false, + string $resizeMode = WebThumbnailer::RESAMPLE ): void { if (!touch($target)) { throw new ImageConvertException('Target file is not writable.'); @@ -74,8 +76,9 @@ public static function generateThumbnail( throw new ImageConvertException('Could not generate the thumbnail from source image.'); } + $resizeFunction = $resizeMode === WebThumbnailer::RESIZE ? 'imagecopyresized' : 'imagecopyresampled'; if ( - !imagecopyresized( + !$resizeFunction( $targetImg, $sourceImg, 0, diff --git a/src/WebThumbnailer.php b/src/WebThumbnailer.php index 29900ec..3567bf9 100644 --- a/src/WebThumbnailer.php +++ b/src/WebThumbnailer.php @@ -52,6 +52,15 @@ class WebThumbnailer /** Debug mode. Throw exceptions. */ public const DEBUG = 'DEBUG'; + /** Setting to define resize mode */ + public const RESIZE_MODE = 'RESIZE_MODE'; + + /** Resize mode: less CPU usage but could end up pixellized */ + public const RESIZE = 'RESIZE'; + + /** Resample mode: more CPU usage but smoother rendering */ + public const RESAMPLE = 'RESAMPLE'; + /** @var int|null */ protected $maxWidth = null; @@ -79,6 +88,9 @@ class WebThumbnailer /** @var string|null */ protected $downloadMode = null; + /** @var string|null */ + protected $resizeMode = null; + /** * Get the thumbnail for the given URL> * @@ -106,6 +118,7 @@ public function thumbnail(string $url, array $options = []) static::DOWNLOAD_TIMEOUT => $this->downloadTimeout, static::DOWNLOAD_MAX_SIZE => $this->downloadMaxSize, static::CROP => $this->crop, + static::RESIZE_MODE => $this->resizeMode, $this->downloadMode ], $options @@ -262,4 +275,30 @@ public function modeHotlinkStrict(): self return $this; } + + /** + * Apply resize mode during resizing: + * lower CPU usage but sometimes rougher rendering. + * + * @return WebThumbnailer $this + */ + public function resize(): self + { + $this->resizeMode = static::RESIZE; + + return $this; + } + + /** + * Apply resample mode during resizing: + * slightly more CPU usage but smoother rendering. + * + * @return WebThumbnailer $this + */ + public function resample(): self + { + $this->resizeMode = static::RESAMPLE; + + return $this; + } } diff --git a/tests/WebThumbnailerTest.php b/tests/WebThumbnailerTest.php index 3ba3389..b665fba 100644 --- a/tests/WebThumbnailerTest.php +++ b/tests/WebThumbnailerTest.php @@ -90,14 +90,28 @@ public function testDirectImageWithoutExtension(): void /** * URL which contains an opengraph image. */ - public function testOpenGraphImage(): void + public function testOpenGraphImageResample(): void + { + $image = 'default/le-monde-full.jpg'; + $this->regenerate($image, true, [0, 0, 160, 80]); + $expected = self::$regenerated . $image; + $url = self::LOCAL_SERVER . 'default/le-monde.html'; + $wt = new WebThumbnailer(); + $thumb = $wt->resample()->thumbnail($url); + $this->assertFileEquals($expected, $thumb); + } + + /** + * URL which contains an opengraph image. + */ + public function testOpenGraphImageResize(): void { $image = 'default/le-monde.jpg'; - $this->regenerate($image); + $this->regenerate($image, false, [], 'imagecopyresized'); $expected = self::$regenerated . $image; $url = self::LOCAL_SERVER . 'default/le-monde.html'; $wt = new WebThumbnailer(); - $thumb = $wt->thumbnail($url); + $thumb = $wt->resize()->thumbnail($url); $this->assertFileEquals($expected, $thumb); } @@ -243,11 +257,11 @@ public function testDownloadDirectImageResizeHeightCrop(): void public function testDownloadDirectImageResizeWidthHeightCrop(): void { $image = 'default/image-crop-341-341.png'; - $this->regenerate($image, true); + $this->regenerate($image, true, [], 'imagecopyresized'); $expected = self::$regenerated . $image; $url = self::LOCAL_SERVER . 'default/image-crop.png'; $wt = new WebThumbnailer(); - $wt = $wt->maxHeight(341)->maxWidth(341)->crop(true); + $wt = $wt->maxHeight(341)->maxWidth(341)->resize()->crop(true); $thumb = $wt->thumbnail($url); $this->assertEquals(base64_encode(file_get_contents($expected)), base64_encode(file_get_contents($thumb))); $this->assertFileEquals($expected, $thumb); @@ -260,11 +274,11 @@ public function testDownloadDirectImageResizeWidthHeightCrop(): void public function testDownloadDirectImageResizeWidthHeightCropOverride(): void { $image = 'default/image-crop-120-160.png'; - $this->regenerate($image, true); + $this->regenerate($image, true, [], 'imagecopyresized'); $expected = self::$regenerated . $image; $url = self::LOCAL_SERVER . 'default/image-crop.png'; $wt = new WebThumbnailer(); - $wt = $wt->maxHeight(341)->maxWidth(341)->crop(true); + $wt = $wt->maxHeight(341)->maxWidth(341)->crop(true)->resize(); $thumb = $wt->thumbnail( $url, [ @@ -336,8 +350,12 @@ public function testHotlinkOpenGraphJsonConfig(): void * * @throws \Exception couldn't create the image. */ - public function regenerate(string $image, bool $crop = false, array $cropParameters = []): void - { + public function regenerate( + string $image, + bool $crop = false, + array $cropParameters = [], + string $resizeFunc = 'imagecopyresampled' + ): void { $targetFolder = dirname(self::$regenerated . $image); if (! is_dir($targetFolder)) { mkdir($targetFolder, 0755, true); @@ -350,15 +368,15 @@ public function regenerate(string $image, bool $crop = false, array $cropParamet $targetImg = imagecreatetruecolor($width, $height); if ( - !imagecopyresized( + !$resizeFunc( $targetImg, $sourceImg, 0, 0, 0, 0, - $width, - $height, + $cropParameters[2] ?? $width, + $cropParameters[3] ?? $height, $width, $height ) diff --git a/tests/resources/expected-thumbs/default/image-crop.png b/tests/resources/expected-thumbs/default/image-crop.png new file mode 100644 index 0000000..4d15492 Binary files /dev/null and b/tests/resources/expected-thumbs/default/image-crop.png differ diff --git a/tests/resources/expected-thumbs/default/le-monde-full.jpg b/tests/resources/expected-thumbs/default/le-monde-full.jpg new file mode 100644 index 0000000..91050ab Binary files /dev/null and b/tests/resources/expected-thumbs/default/le-monde-full.jpg differ