From ba3f450061d4daf2ca2cecb3257a623965ea682a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniels=20=C5=A0atcs?= Date: Thu, 12 Mar 2026 18:31:23 +0200 Subject: [PATCH] vips conversion --- app/Util/Media/Image.php | 73 +++++++++++--------------------- app/Util/Media/StorageFormat.php | 43 +++++++++++++++++++ composer.json | 3 +- config/pixelfed.php | 10 +++++ 4 files changed, 79 insertions(+), 50 deletions(-) create mode 100644 app/Util/Media/StorageFormat.php diff --git a/app/Util/Media/Image.php b/app/Util/Media/Image.php index 8fe72989c..b208b5f44 100644 --- a/app/Util/Media/Image.php +++ b/app/Util/Media/Image.php @@ -5,11 +5,9 @@ namespace App\Util\Media; use App\Media; use App\Services\StatusService; use Cache; -use Intervention\Image\Encoders\JpegEncoder; -use Intervention\Image\Encoders\PngEncoder; -use Intervention\Image\Encoders\WebpEncoder; use Log; use Storage; +use Jcupitt\Vips; class Image { @@ -117,6 +115,7 @@ class Image public function handleImageTransform(Media $media, $thumbnail = false) { $path = $media->media_path; + $targetFormat = StorageFormat::getConfigured(); $localFs = config('filesystems.default') === 'local'; if (! in_array($media->mime, $this->acceptedMimes)) { @@ -135,7 +134,7 @@ class Image } $fileInfo = pathinfo($path); - $extension = strtolower($fileInfo['extension'] ?? 'jpg'); + $extension = $targetFormat->extension(); $outputExtension = $extension; $metadata = null; @@ -202,69 +201,45 @@ class Image } } - $img = $this->imageManager->read($fileContents); + $img = Vips\Image::newFromBuffer($fileContents); - $ratio = $this->getAspect($img->width(), $img->height(), $thumbnail); + $ratio = $this->getAspect($img->width, $img->height, $thumbnail); $aspect = $ratio['dimensions']; $orientation = $ratio['orientation']; if ($thumbnail) { - $img = $img->coverDown( - $aspect['width'], - $aspect['height'] - ); + $img = Vips\Image::thumbnail_buffer($fileContents, $aspect['width'], [ + 'height' => $aspect['height'], + 'crop' => 'centre', + 'size' => 'down', + ]); } else { if ( ($ratio['width_original'] > $aspect['width']) || ($ratio['height_original'] > $aspect['height']) ) { - $img = $img->scaleDown( - $aspect['width'], - $aspect['height'] - ); + $img = Vips\Image::thumbnail_buffer($fileContents, $aspect['width'], [ + 'height' => $aspect['height'], + 'size' => 'down', + ]); + } else { + $img = Vips\Image::newFromBuffer($fileContents); } } $quality = config_cache('pixelfed.image_quality'); - $encoder = null; - switch ($extension) { - case 'jpeg': - case 'jpg': - $encoder = new JpegEncoder($quality); - $outputExtension = 'jpg'; - break; - case 'png': - $encoder = new PngEncoder; - $outputExtension = 'png'; - break; - case 'webp': - $encoder = new WebpEncoder($quality); - $outputExtension = 'webp'; - break; - case 'avif': - $encoder = new JpegEncoder($quality); - $outputExtension = 'jpg'; - break; - case 'heic': - $encoder = new JpegEncoder($quality); - $outputExtension = 'jpg'; - break; - default: - $encoder = new JpegEncoder($quality); - $outputExtension = 'jpg'; - } - $converted = $this->setBaseName($path, $thumbnail, $outputExtension); - $encoded = $encoder->encode($img); + + $encoded = $targetFormat->saveBuffer($img); if ($localFs) { $newPath = storage_path('app/'.$converted['path']); - file_put_contents($newPath, $encoded->toString()); + file_put_contents($newPath, $encoded); } else { Storage::disk($this->defaultDisk)->put( $converted['path'], - $encoded->toString() + $encoded ); } @@ -272,11 +247,11 @@ class Image $media->thumbnail_path = $converted['path']; $media->thumbnail_url = url(Storage::url($converted['path'])); } else { - $media->width = $img->width(); - $media->height = $img->height(); + $media->width = $img->width; + $media->height = $img->height; $media->orientation = $orientation; $media->media_path = $converted['path']; - $media->mime = 'image/'.$outputExtension; + $media->mime = $targetFormat->mimeType(); } $media->save(); @@ -305,7 +280,7 @@ class Image $filename = $pathInfo['filename']; $name = ($thumbnail == true) ? $filename . '_thumb' : $filename; $basePath = $dir . $name . '.' . $extension; - + return ['path' => $basePath, 'png' => false]; } diff --git a/app/Util/Media/StorageFormat.php b/app/Util/Media/StorageFormat.php new file mode 100644 index 000000000..5d8385c04 --- /dev/null +++ b/app/Util/Media/StorageFormat.php @@ -0,0 +1,43 @@ + 'jpg', + self::JXL => 'jxl', + }; + } + + /** + * Get the appropriate MIME type for the format. + */ + public function mimeType(): string + { + return match($this) { + self::JPEG => 'image/jpeg', + self::JXL => 'image/jxl', + }; + } + + public function saveBuffer(\Jcupitt\Vips\Image $media): string + { + return match($this) { + self::JPEG => $media->uhdrsave_buffer(), + self::JXL => $media->jxlsave_buffer(), + }; + } + + static function getConfigured(): StorageFormat + { + $key = config_cache("pixelfed.image_storage_format", 'jpeg'); + return StorageFormat::from($key); + } +} diff --git a/composer.json b/composer.json index 87ac20d98..f1bfd2d43 100644 --- a/composer.json +++ b/composer.json @@ -18,8 +18,9 @@ "buzz/laravel-h-captcha": "^1.0.4", "endroid/qr-code": "^6.0", "guzzlehttp/guzzle": "^7.10", - "intervention/image-laravel": "^1.5", "intervention/image-driver-vips": "^1.0", + "intervention/image-laravel": "^1.5", + "jcupitt/vips": "^2.6", "jenssegers/agent": "^2.6", "laravel-notification-channels/expo": "^2.0.0", "laravel-notification-channels/webpush": "^10.2", diff --git a/config/pixelfed.php b/config/pixelfed.php index a9cacf92f..fa9af4d31 100644 --- a/config/pixelfed.php +++ b/config/pixelfed.php @@ -159,6 +159,16 @@ return [ */ 'image_quality' => (int) env('IMAGE_QUALITY', 80), + /* + |-------------------------------------------------------------------------- + | Image Storage Format + |-------------------------------------------------------------------------- + | + | Set the image storage format. All uploads will be converted to this format. + | + */ + 'image_storage_format' => (string) env('IMAGE_STORAGE_FORMAT', \App\Util\Media\StorageFormat::JPEG->name), + /* |-------------------------------------------------------------------------- | Account deletion