Merge pull request #5625 from BookStackApp/avif_images
AVIF image support
This commit is contained in:
commit
b29fe5c46d
|
@ -163,7 +163,7 @@ abstract class Controller extends BaseController
|
||||||
*/
|
*/
|
||||||
protected function getImageValidationRules(): array
|
protected function getImageValidationRules(): array
|
||||||
{
|
{
|
||||||
return ['image_extension', 'mimes:jpeg,png,gif,webp', 'max:' . (config('app.upload_limit') * 1000)];
|
return ['image_extension', 'mimes:jpeg,png,gif,webp,avif', 'max:' . (config('app.upload_limit') * 1000)];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,6 +6,7 @@ use BookStack\Exceptions\ImageUploadException;
|
||||||
use Exception;
|
use Exception;
|
||||||
use GuzzleHttp\Psr7\Utils;
|
use GuzzleHttp\Psr7\Utils;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Intervention\Image\Decoders\BinaryImageDecoder;
|
use Intervention\Image\Decoders\BinaryImageDecoder;
|
||||||
use Intervention\Image\Drivers\Gd\Decoders\NativeObjectDecoder;
|
use Intervention\Image\Drivers\Gd\Decoders\NativeObjectDecoder;
|
||||||
use Intervention\Image\Drivers\Gd\Driver;
|
use Intervention\Image\Drivers\Gd\Driver;
|
||||||
|
@ -93,8 +94,8 @@ class ImageResizer
|
||||||
|
|
||||||
$imageData = $disk->get($imagePath);
|
$imageData = $disk->get($imagePath);
|
||||||
|
|
||||||
// Do not resize apng images where we're not cropping
|
// Do not resize animated images where we're not cropping
|
||||||
if ($keepRatio && $this->isApngData($image, $imageData)) {
|
if ($keepRatio && $this->isAnimated($image, $imageData)) {
|
||||||
Cache::put($thumbCacheKey, $image->path, static::THUMBNAIL_CACHE_TIME);
|
Cache::put($thumbCacheKey, $image->path, static::THUMBNAIL_CACHE_TIME);
|
||||||
|
|
||||||
return $this->storage->getPublicUrl($image->path);
|
return $this->storage->getPublicUrl($image->path);
|
||||||
|
@ -240,15 +241,50 @@ class ImageResizer
|
||||||
/**
|
/**
|
||||||
* Check if the given image and image data is apng.
|
* Check if the given image and image data is apng.
|
||||||
*/
|
*/
|
||||||
protected function isApngData(Image $image, string &$imageData): bool
|
protected function isApngData(string &$imageData): bool
|
||||||
{
|
{
|
||||||
$isPng = strtolower(pathinfo($image->path, PATHINFO_EXTENSION)) === 'png';
|
|
||||||
if (!$isPng) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$initialHeader = substr($imageData, 0, strpos($imageData, 'IDAT'));
|
$initialHeader = substr($imageData, 0, strpos($imageData, 'IDAT'));
|
||||||
|
|
||||||
return str_contains($initialHeader, 'acTL');
|
return str_contains($initialHeader, 'acTL');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given avif image data represents an animated image.
|
||||||
|
* This is based up the answer here: https://stackoverflow.com/a/79457313
|
||||||
|
*/
|
||||||
|
protected function isAnimatedAvifData(string &$imageData): bool
|
||||||
|
{
|
||||||
|
$stszPos = strpos($imageData, 'stsz');
|
||||||
|
if ($stszPos === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look 12 bytes after the start of 'stsz'
|
||||||
|
$start = $stszPos + 12;
|
||||||
|
$end = $start + 4;
|
||||||
|
if ($end > strlen($imageData) - 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = substr($imageData, $start, 4);
|
||||||
|
$count = unpack('Nvalue', $data)['value'];
|
||||||
|
return $count > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given image is animated.
|
||||||
|
*/
|
||||||
|
protected function isAnimated(Image $image, string &$imageData): bool
|
||||||
|
{
|
||||||
|
$extension = strtolower(pathinfo($image->path, PATHINFO_EXTENSION));
|
||||||
|
if ($extension === 'png') {
|
||||||
|
return $this->isApngData($imageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($extension === 'avif') {
|
||||||
|
return $this->isAnimatedAvifData($imageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
|
||||||
class ImageService
|
class ImageService
|
||||||
{
|
{
|
||||||
protected static array $supportedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
protected static array $supportedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif'];
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected ImageStorage $storage,
|
protected ImageStorage $storage,
|
||||||
|
|
|
@ -68,7 +68,20 @@ class ImageTest extends TestCase
|
||||||
$this->files->deleteAtRelativePath($imgDetails['path']);
|
$this->files->deleteAtRelativePath($imgDetails['path']);
|
||||||
|
|
||||||
$this->assertStringContainsString('thumbs-', $imgDetails['response']->thumbs->gallery);
|
$this->assertStringContainsString('thumbs-', $imgDetails['response']->thumbs->gallery);
|
||||||
$this->assertStringNotContainsString('thumbs-', $imgDetails['response']->thumbs->display);
|
$this->assertStringNotContainsString('scaled-', $imgDetails['response']->thumbs->display);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_image_display_thumbnail_generation_for_animated_avif_images_uses_original_file()
|
||||||
|
{
|
||||||
|
$page = $this->entities->page();
|
||||||
|
$admin = $this->users->admin();
|
||||||
|
$this->actingAs($admin);
|
||||||
|
|
||||||
|
$imgDetails = $this->files->uploadGalleryImageToPage($this, $page, 'animated.avif');
|
||||||
|
$this->files->deleteAtRelativePath($imgDetails['path']);
|
||||||
|
|
||||||
|
$this->assertStringContainsString('thumbs-', $imgDetails['response']->thumbs->gallery);
|
||||||
|
$this->assertStringNotContainsString('scaled-', $imgDetails['response']->thumbs->display);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_image_edit()
|
public function test_image_edit()
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue