Skip to content

Commit b29fe5c

Browse files
authored
Merge pull request #5625 from BookStackApp/avif_images
AVIF image support
2 parents 59e2c5e + 131ac29 commit b29fe5c

File tree

5 files changed

+59
-10
lines changed

5 files changed

+59
-10
lines changed

app/Http/Controller.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ protected function logActivity(string $type, string|Loggable $detail = ''): void
163163
*/
164164
protected function getImageValidationRules(): array
165165
{
166-
return ['image_extension', 'mimes:jpeg,png,gif,webp', 'max:' . (config('app.upload_limit') * 1000)];
166+
return ['image_extension', 'mimes:jpeg,png,gif,webp,avif', 'max:' . (config('app.upload_limit') * 1000)];
167167
}
168168

169169
/**

app/Uploads/ImageResizer.php

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Exception;
77
use GuzzleHttp\Psr7\Utils;
88
use Illuminate\Support\Facades\Cache;
9+
use Illuminate\Support\Facades\Log;
910
use Intervention\Image\Decoders\BinaryImageDecoder;
1011
use Intervention\Image\Drivers\Gd\Decoders\NativeObjectDecoder;
1112
use Intervention\Image\Drivers\Gd\Driver;
@@ -93,8 +94,8 @@ public function resizeToThumbnailUrl(
9394

9495
$imageData = $disk->get($imagePath);
9596

96-
// Do not resize apng images where we're not cropping
97-
if ($keepRatio && $this->isApngData($image, $imageData)) {
97+
// Do not resize animated images where we're not cropping
98+
if ($keepRatio && $this->isAnimated($image, $imageData)) {
9899
Cache::put($thumbCacheKey, $image->path, static::THUMBNAIL_CACHE_TIME);
99100

100101
return $this->storage->getPublicUrl($image->path);
@@ -240,15 +241,50 @@ protected function getExtension(Image $image): string
240241
/**
241242
* Check if the given image and image data is apng.
242243
*/
243-
protected function isApngData(Image $image, string &$imageData): bool
244+
protected function isApngData(string &$imageData): bool
244245
{
245-
$isPng = strtolower(pathinfo($image->path, PATHINFO_EXTENSION)) === 'png';
246-
if (!$isPng) {
246+
$initialHeader = substr($imageData, 0, strpos($imageData, 'IDAT'));
247+
248+
return str_contains($initialHeader, 'acTL');
249+
}
250+
251+
/**
252+
* Check if the given avif image data represents an animated image.
253+
* This is based up the answer here: https://stackoverflow.com/a/79457313
254+
*/
255+
protected function isAnimatedAvifData(string &$imageData): bool
256+
{
257+
$stszPos = strpos($imageData, 'stsz');
258+
if ($stszPos === false) {
247259
return false;
248260
}
249261

250-
$initialHeader = substr($imageData, 0, strpos($imageData, 'IDAT'));
262+
// Look 12 bytes after the start of 'stsz'
263+
$start = $stszPos + 12;
264+
$end = $start + 4;
265+
if ($end > strlen($imageData) - 1) {
266+
return false;
267+
}
251268

252-
return str_contains($initialHeader, 'acTL');
269+
$data = substr($imageData, $start, 4);
270+
$count = unpack('Nvalue', $data)['value'];
271+
return $count > 1;
272+
}
273+
274+
/**
275+
* Check if the given image is animated.
276+
*/
277+
protected function isAnimated(Image $image, string &$imageData): bool
278+
{
279+
$extension = strtolower(pathinfo($image->path, PATHINFO_EXTENSION));
280+
if ($extension === 'png') {
281+
return $this->isApngData($imageData);
282+
}
283+
284+
if ($extension === 'avif') {
285+
return $this->isAnimatedAvifData($imageData);
286+
}
287+
288+
return false;
253289
}
254290
}

app/Uploads/ImageService.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
class ImageService
1515
{
16-
protected static array $supportedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
16+
protected static array $supportedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif'];
1717

1818
public function __construct(
1919
protected ImageStorage $storage,

tests/Uploads/ImageTest.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,20 @@ public function test_image_display_thumbnail_generation_for_apng_images_uses_ori
6868
$this->files->deleteAtRelativePath($imgDetails['path']);
6969

7070
$this->assertStringContainsString('thumbs-', $imgDetails['response']->thumbs->gallery);
71-
$this->assertStringNotContainsString('thumbs-', $imgDetails['response']->thumbs->display);
71+
$this->assertStringNotContainsString('scaled-', $imgDetails['response']->thumbs->display);
72+
}
73+
74+
public function test_image_display_thumbnail_generation_for_animated_avif_images_uses_original_file()
75+
{
76+
$page = $this->entities->page();
77+
$admin = $this->users->admin();
78+
$this->actingAs($admin);
79+
80+
$imgDetails = $this->files->uploadGalleryImageToPage($this, $page, 'animated.avif');
81+
$this->files->deleteAtRelativePath($imgDetails['path']);
82+
83+
$this->assertStringContainsString('thumbs-', $imgDetails['response']->thumbs->gallery);
84+
$this->assertStringNotContainsString('scaled-', $imgDetails['response']->thumbs->display);
7285
}
7386

7487
public function test_image_edit()

tests/test-data/animated.avif

1.14 KB
Binary file not shown.

0 commit comments

Comments
 (0)