From 8d627b928daf2c6416a8697092e955b3e8560054 Mon Sep 17 00:00:00 2001 From: averageLukas Date: Wed, 22 Jun 2022 16:39:34 +0200 Subject: [PATCH] Add AVIF support --- README.md | 4 +- fallback/.gitkeep | 0 imgboard.php | 4 + inc/functions.php | 215 +++++++++++++++++++++++-------------------- inc/html.php | 68 +++++++++----- settings.default.php | 4 +- 6 files changed, 170 insertions(+), 125 deletions(-) create mode 100644 fallback/.gitkeep diff --git a/README.md b/README.md index 41cc5d6..eb01be2 100644 --- a/README.md +++ b/README.md @@ -50,12 +50,14 @@ support in mind. ## Install 1. Verify the following are installed: - - [PHP 5.5+](https://php.net) + - [PHP 5.5+](https://php.net), or PHP 8.2.0+ for avif support - [GD Image Processing Library](https://php.net/gd) - This library is usually installed by default. - If you plan on disabling image uploads to use TinyIB as a text board only, this library is not required. - [cURL Library](https://www.php.net/manual/en/book.curl.php) - This is recommended, but is not strictly required except when `TINYIB_CAPTCHA` is set to `hcaptcha` or `recaptcha`. + - AVIF Library + - Compile PHP using `--with-avif` 2. CD to the directory you wish to install TinyIB. 3. Run the command: - `git clone https://code.rocketnine.space/tslocum/tinyib.git ./` diff --git a/fallback/.gitkeep b/fallback/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/imgboard.php b/imgboard.php index 9879159..d4e996e 100644 --- a/imgboard.php +++ b/imgboard.php @@ -462,6 +462,10 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name']) $post['thumb'] = $temp_file . '.gif'; } else if ($file_mime == "image/png") { $post['thumb'] = $temp_file . '.png'; + } else if ($file_mime == "image/avif") { + $post['thumb'] = $temp_file . '.avif'; + } else if ($file_mime == "image/heif") { + $post['thumb'] = $temp_file . '.avif'; } else { fancyDie(__('Error while processing audio/video.')); } diff --git a/inc/functions.php b/inc/functions.php index 8c760a6..60dddf2 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -613,116 +613,125 @@ function ffmpegThumbnail($file_location, $thumb_location, $new_w, $new_h) { } } +// TODO: Check whether I missed error handling somewhere during refactoring. +function readImageFromFile($path, $error_message) { + switch (pathinfo($path)["extension"]) { + case "jpg": + case "jpeg": return imagecreatefromjpeg($path); + case "png": return imagecreatefrompng($path); + case "gif": return imagecreatefromgif($path); + case "avif": return imagecreatefromavif($path); + } + fancyDie(__($error_message)); + return false; +} + +function saveImageToFile($image, $path) { + switch (pathinfo($path)["extension"]) { + case "jpg": + case "jpeg": return imagejpeg($image, $path, 90); // why only set quality for jpegs? + case "png": return imagepng($image, $path); + case "gif": return imagegif($image, $path); + case "avif": return imageavif($image, $path); + } + return false; +} + +function resizeImage($src_img, $max_width, $max_height, $file_extension) { + $src_width = imageSX($src_img); + $src_height = imageSY($src_img); + + $ratio = ($src_width > $src_height) ? ($max_width / $src_width) : ($max_height / $src_height); + + $target_width = round($src_width * $ratio); + $target_height = round($src_height * $ratio); + + // Why don't we use imagescale (PHP 5 >= 5.5.0, PHP 7, PHP 8) here? + $dst_img = imagecreatetruecolor($target_width, $target_height); + + if ($file_extension === 'png') { + imagealphablending($dst_img, false); + imagesavealpha($dst_img, true); + + $color = imagecolorallocatealpha($dst_img, 0, 0, 0, 0); + imagefilledrectangle($dst_img, 0, 0, $target_width, $target_height, $color); + imagecolortransparent($dst_img, $color); + + imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $target_width, $target_height, $src_width, $src_height); + } else { + fastimagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $target_width, $target_height, $src_width, $src_height); + } + + return $dst_img; +} + +function blurImage($image) { + $gaussian = array(array(1.0, 2.0, 1.0), array(2.0, 4.0, 2.0), array(1.0, 2.0, 1.0)); + + for ($x = 1; $x <= 149; $x++) { + imageconvolution($image, $gaussian, 16, 0); + } +} + +function createFallbackImage($source, $destination) { + $image = readImageFromFile($source, 'Unable to read the uploaded file while creating its thumbnail. A common cause for this is an incorrect extension when the file is actually of a different type.'); + saveImageToFile($image, $destination); +} + +function imageMagickThumbnail($file_location, $thumb_location, $new_w, $new_h) { + $discard = ''; + + $exit_status = 1; + exec("convert -version", $discard, $exit_status); + if ($exit_status != 0) { + fancyDie('ImageMagick is not installed, or the convert command is not in the server\'s $PATH.
Install ImageMagick, or set TINYIB_THUMBNAIL to \'gd\' or \'ffmpeg\'.'); + } + + $exit_status = 1; + exec("convert $file_location -auto-orient -thumbnail '" . $new_w . "x" . $new_h . "' -coalesce -layers OptimizeFrame -depth 4 -type palettealpha $thumb_location", $discard, $exit_status); + + if ($exit_status != 0) { + return false; + } +} + +function gdThumbnail($file_location, $file_extension, $thumb_location, $new_w, $new_h) { + $src_img = readImageFromFile($file_location, 'Unable to read the uploaded file while creating its thumbnail. A common cause for this is an incorrect extension when the file is actually of a different type.'); + $dst_img = resizeImage($src_img, $new_w, $new_h, $file_extension); + saveImageToFile($dst_img, $thumb_location); + imagedestroy($dst_img); + imagedestroy($src_img); +} + +function spoilerThumbnail($file_location, $thumb_location) { + // Why don't we resize images here? + $src_img = readImageFromFile($file_location, 'Unable to read the uploaded file while creating its thumbnail. A common cause for this is an incorrect extension when the file is actually of a different type.'); + + blurImage($src_img); + saveImageToFile($src_img, $thumb_location); + imagedestroy($src_img); +} + function createThumbnail($file_location, $thumb_location, $new_w, $new_h, $spoiler) { - $system = explode(".", $thumb_location); - $system = array_reverse($system); - if (TINYIB_THUMBNAIL == 'gd' || (TINYIB_THUMBNAIL == 'ffmpeg' && preg_match("/jpg|jpeg/", $system[0]))) { - if (preg_match("/jpg|jpeg/", $system[0])) { - $src_img = imagecreatefromjpeg($file_location); - } else if (preg_match("/png/", $system[0])) { - $src_img = imagecreatefrompng($file_location); - } else if (preg_match("/gif/", $system[0])) { - $src_img = imagecreatefromgif($file_location); - } else { - return false; - } + $path = pathinfo($file_location); + $file_extension = $path['extension']; - if (!$src_img) { - fancyDie(__('Unable to read the uploaded file while creating its thumbnail. A common cause for this is an incorrect extension when the file is actually of a different type.')); - } - - $old_x = imageSX($src_img); - $old_y = imageSY($src_img); - $percent = ($old_x > $old_y) ? ($new_w / $old_x) : ($new_h / $old_y); - $thumb_w = round($old_x * $percent); - $thumb_h = round($old_y * $percent); - - $dst_img = imagecreatetruecolor($thumb_w, $thumb_h); - if (preg_match("/png/", $system[0]) && imagepng($src_img, $thumb_location)) { - imagealphablending($dst_img, false); - imagesavealpha($dst_img, true); - - $color = imagecolorallocatealpha($dst_img, 0, 0, 0, 0); - imagefilledrectangle($dst_img, 0, 0, $thumb_w, $thumb_h, $color); - imagecolortransparent($dst_img, $color); - - imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $thumb_w, $thumb_h, $old_x, $old_y); - } else { - fastimagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $thumb_w, $thumb_h, $old_x, $old_y); - } - - if (preg_match("/png/", $system[0])) { - if (!imagepng($dst_img, $thumb_location)) { - return false; - } - } else if (preg_match("/jpg|jpeg/", $system[0])) { - if (!imagejpeg($dst_img, $thumb_location, 70)) { - return false; - } - } else if (preg_match("/gif/", $system[0])) { - if (!imagegif($dst_img, $thumb_location)) { - return false; - } - } - - imagedestroy($dst_img); - imagedestroy($src_img); + if (TINYIB_THUMBNAIL == 'gd' || (TINYIB_THUMBNAIL == 'ffmpeg' && preg_match("/jpg|jpeg/", $file_extension))) { + gdThumbnail($file_location, $file_extension, $thumb_location, $new_w, $new_h); } else if (TINYIB_THUMBNAIL == 'ffmpeg') { ffmpegThumbnail($file_location, $thumb_location, $new_w, $new_h); - } else { // ImageMagick - $discard = ''; - - $exit_status = 1; - exec("convert -version", $discard, $exit_status); - if ($exit_status != 0) { - fancyDie('ImageMagick is not installed, or the convert command is not in the server\'s $PATH.
Install ImageMagick, or set TINYIB_THUMBNAIL to \'gd\' or \'ffmpeg\'.'); - } - - $exit_status = 1; - exec("convert $file_location -auto-orient -thumbnail '" . $new_w . "x" . $new_h . "' -coalesce -layers OptimizeFrame -depth 4 -type palettealpha $thumb_location", $discard, $exit_status); - - if ($exit_status != 0) { - return false; - } - } - - if (!$spoiler) { - return true; - } - - if (preg_match("/jpg|jpeg/", $system[0])) { - $src_img = imagecreatefromjpeg($thumb_location); - } else if (preg_match("/png/", $system[0])) { - $src_img = imagecreatefrompng($thumb_location); - } else if (preg_match("/gif/", $system[0])) { - $src_img = imagecreatefromgif($thumb_location); } else { - return true; + imageMagickThumbnail($file_location, $thumb_location, $new_w, $new_h); } - if (!$src_img) { - fancyDie(__('Unable to read the uploaded file while creating its thumbnail. A common cause for this is an incorrect extension when the file is actually of a different type.')); + if ($spoiler) { + spoilerThumbnail($file_location, $thumb_location); } - $gaussian = array(array(1.0, 2.0, 1.0), array(2.0, 4.0, 2.0), array(1.0, 2.0, 1.0)); - for ($x = 1; $x <= 149; $x++) { - imageconvolution($src_img, $gaussian, 16, 0); + if ($file_extension === 'avif') { + createFallbackImage($thumb_location, 'fallback/' . $path['filename'] . 's.jpg'); } - if (preg_match("/png/", $system[0])) { - if (!imagepng($src_img, $thumb_location)) { - return false; - } - } else if (preg_match("/jpg|jpeg/", $system[0])) { - if (!imagejpeg($src_img, $thumb_location, 70)) { - return false; - } - } else if (preg_match("/gif/", $system[0])) { - if (!imagegif($src_img, $thumb_location)) { - return false; - } - } - imagedestroy($src_img); return true; } @@ -906,7 +915,7 @@ function attachFile($post, $filepath, $filename, $uploaded, $spoiler) { $post['file_size_formatted'] = convertBytes($post['file_size']); checkDuplicateFile($post['file_hex']); - if (in_array($file_mime, array('image/jpeg', 'image/pjpeg', 'image/png', 'image/gif', 'application/x-shockwave-flash'))) { + if (in_array($file_mime, array('image/jpeg', 'image/pjpeg', 'image/png', 'image/gif', 'application/x-shockwave-flash', 'image/avif', 'image/heif'))) { $file_info = getimagesize($file_src); $post['image_width'] = $file_info[0] != '' ? $file_info[0] : 0; $post['image_height'] = $file_info[1] != '' ? $file_info[1] : 0; @@ -922,7 +931,7 @@ function attachFile($post, $filepath, $filename, $uploaded, $spoiler) { if ($file_mime == 'application/x-shockwave-flash') { addVideoOverlay('thumb/' . $post['thumb']); } - } else if (in_array($file_mime, array('image/jpeg', 'image/pjpeg', 'image/png', 'image/gif'))) { + } else if (in_array($file_mime, array('image/jpeg', 'image/pjpeg', 'image/png', 'image/gif', 'image/avif', 'image/heif'))) { $post['thumb'] = $file_name_pre . 's.' . $tinyib_uploads[$file_mime][0]; list($thumb_maxwidth, $thumb_maxheight) = thumbnailDimensions($post); @@ -930,6 +939,10 @@ function attachFile($post, $filepath, $filename, $uploaded, $spoiler) { @unlink($file_src); fancyDie(__('Could not create thumbnail.')); } + + if (in_array($file_mime, array('image/avif', 'image/heif'))) { + createFallbackImage($file_src, "fallback/$file_name_pre.jpg"); + } } else if ($file_mime == 'audio/webm' || $file_mime == 'video/webm' || $file_mime == 'audio/mp4' || $file_mime == 'video/mp4') { list($post['image_width'], $post['image_height']) = videoDimensions($file_src); diff --git a/inc/html.php b/inc/html.php index c2c780b..a604622 100644 --- a/inc/html.php +++ b/inc/html.php @@ -497,17 +497,27 @@ function buildPost($post, $res, $compact=false) { EOF; - } else if (in_array(substr($post['file'], -4), array('.jpg', '.png', '.gif'))) { - $expandhtml = ""; + } else if (in_array(substr($post['file'], -4), array('.jpg', '.png', '.gif', 'avif'))) { + $fallback = "fallback/" . pathinfo($post["file"])['filename'] . ".jpg"; + $attributes = "width=\"{$post["image_width"]}\" style=\"max-width: {$w}vw;height: auto;\""; + $expandhtml .= ""; + + if (file_exists($fallback)) { + $expandhtml .= ""; + } else { + $expandhtml .= ""; + } + + $expandhtml .= ''; } - $thumblink = ""; + $thumblink = ""; $expandhtml = rawurlencode($expandhtml); if (isEmbed($post["file_hex"])) { - $filesize .= "${post['file_original']}–(${post['file_hex']})"; + $filesize .= "{$post['file_original']}–({$post['file_hex']})"; } else if ($post["file"] != '') { - $filesize .= $thumblink . "${post["file"]}–(${post["file_size_formatted"]}"; + $filesize .= $thumblink . "{$post["file"]}–({$post["file_size_formatted"]}"; if ($post["image_width"] > 0 && $post["image_height"] > 0) { $filesize .= ", " . $post["image_width"] . "x" . $post["image_height"]; } @@ -527,18 +537,32 @@ EOF; } $filehtml .= $filesize . '
'; if ($post["thumb_width"] > 0 && $post["thumb_height"] > 0) { - $filehtml .= << + + + EOF; + } else { + $filehtml .= << + +EOF; + } } $filehtml .= '
'; if ($expandhtml != '') { $filehtml .= <<