Add AVIF support

This commit is contained in:
averageLukas 2022-06-22 16:39:34 +02:00
parent 735ad801ab
commit 8d627b928d
No known key found for this signature in database
GPG Key ID: 1C47C14ABF8A7680
6 changed files with 170 additions and 125 deletions

View File

@ -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 ./`

0
fallback/.gitkeep Normal file
View File

View File

@ -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.'));
}

View File

@ -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.<br>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.<br>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);

View File

@ -497,17 +497,27 @@ function buildPost($post, $res, $compact=false) {
<source src="$direct_link"></source>
</video>
EOF;
} else if (in_array(substr($post['file'], -4), array('.jpg', '.png', '.gif'))) {
$expandhtml = "<a href=\"$direct_link\" onclick=\"return expandFile(event, '${post['id']}');\"><img src=\"" . ($res == TINYIB_RESPAGE ? "../" : "") . "src/${post["file"]}\" width=\"${post["image_width"]}\" style=\"max-width: {$w}vw;height: auto;\"></a>";
} 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 .= "<a href=\"$direct_link\" onclick=\"return expandFile(event, '{$post['id']}');\">";
if (file_exists($fallback)) {
$expandhtml .= "<object data=\"" . ($res == TINYIB_RESPAGE ? "../" : "") . "src/{$post["file"]}\" {$attributes}><img src=\"{$fallback}\"/></object>";
} else {
$expandhtml .= "<img src=\"" . ($res == TINYIB_RESPAGE ? "../" : "") . "src/{$post["file"]}\" {$attributes}/>";
}
$expandhtml .= '</a>';
}
$thumblink = "<a href=\"$direct_link\" target=\"_blank\"" . ((isEmbed($post["file_hex"]) || in_array(substr($post['file'], -4), array('.jpg', '.png', '.gif', 'webm', '.mp4'))) ? " onclick=\"return expandFile(event, '${post['id']}');\"" : "") . ">";
$thumblink = "<a href=\"$direct_link\" target=\"_blank\"" . ((isEmbed($post["file_hex"]) || in_array(substr($post['file'], -4), array('.jpg', '.png', '.gif', 'avif', 'webm', '.mp4'))) ? " onclick=\"return expandFile(event, '{$post['id']}');\"" : "") . ">";
$expandhtml = rawurlencode($expandhtml);
if (isEmbed($post["file_hex"])) {
$filesize .= "<a href=\"$direct_link\" onclick=\"return expandFile(event, '${post['id']}');\">${post['file_original']}</a>&ndash;(${post['file_hex']})";
$filesize .= "<a href=\"$direct_link\" onclick=\"return expandFile(event, '{$post['id']}');\">{$post['file_original']}</a>&ndash;({$post['file_hex']})";
} else if ($post["file"] != '') {
$filesize .= $thumblink . "${post["file"]}</a>&ndash;(${post["file_size_formatted"]}";
$filesize .= $thumblink . "{$post["file"]}</a>&ndash;({$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 . '<br><div id="thumbfile' . $post['id'] . '">';
if ($post["thumb_width"] > 0 && $post["thumb_height"] > 0) {
$filehtml .= <<<EOF
$attributes = <<<EOF
alt="{$post["id"]}" class="thumb" id="thumbnail{$post['id']}" width="{$post["thumb_width"]}" height="{$post["thumb_height"]}"
EOF;
$fallback = "fallback/" . pathinfo($post["thumb"])['filename'] . ".jpg";
if (file_exists($fallback)) {
$filehtml .= <<<EOF
$thumblink
<img src="thumb/${post["thumb"]}" alt="${post["id"]}" class="thumb" id="thumbnail${post['id']}" width="${post["thumb_width"]}" height="${post["thumb_height"]}">
<object data="thumb/{$post["thumb"]}" $attributes>
<img src="$fallback">
</object>
</a>
EOF;
} else {
$filehtml .= <<<EOF
$thumblink
<img src="thumb/{$post["thumb"]}" $attributes>
</a>
EOF;
}
}
$filehtml .= '</div>';
if ($expandhtml != '') {
$filehtml .= <<<EOF
<div id="expand${post['id']}" style="display: none;">$expandhtml</div>
<div id="file${post['id']}" class="thumb" style="display: none;"></div>
<div id="expand{$post['id']}" style="display: none;">$expandhtml</div>
<div id="file{$post['id']}" class="thumb" style="display: none;"></div>
EOF;
}
}
@ -556,15 +580,15 @@ EOF;
<td class="doubledash">
&#0168;
</td>
<td class="reply" id="post${post["id"]}">
<td class="reply" id="post{$post["id"]}">
EOF;
}
}
$return .= <<<EOF
<a id="${post['id']}"></a>
<a id="{$post['id']}"></a>
<label>
<input type="checkbox" name="delete[]" value="${post['id']}">
<input type="checkbox" name="delete[]" value="{$post['id']}">
EOF;
if ($post['subject'] != '') {
@ -572,7 +596,7 @@ EOF;
}
$return .= <<<EOF
${post["nameblock"]}
{$post["nameblock"]}
</label>
<span class="reflink">
$reflink
@ -589,7 +613,7 @@ EOF;
if ($post['parent'] == TINYIB_NEWTHREAD) {
if ($res == TINYIB_INDEXPAGE) {
$return .= "&nbsp;[<a href=\"res/${post["id"]}.html\">" . __("Reply") . "</a>]";
$return .= "&nbsp;[<a href=\"res/{$post["id"]}.html\">" . __("Reply") . "</a>]";
}
$return .= backlinks($post);
}
@ -601,7 +625,7 @@ EOF;
}
$return .= <<<EOF
<div class="message">
${post["message"]}
{$post["message"]}
</div>
EOF;
@ -1228,7 +1252,7 @@ function manageBanForm() {
<fieldset>
<legend>$txt_ban</legend>
<table border="0">
<tr><td><label for="ip">$txt_ban_ip</label></td><td><input type="text" name="ip" id="ip" value="${_GET['bans']}"></td><td><input type="submit" value="$txt_submit" class="managebutton"></td></tr>
<tr><td><label for="ip">$txt_ban_ip</label></td><td><input type="text" name="ip" id="ip" value="{$_GET['bans']}"></td><td><input type="submit" value="$txt_submit" class="managebutton"></td></tr>
<tr><td><label for="expire">$txt_ban_expire</label></td><td><input type="text" name="expire" id="expire" value="0"></td><td><small><a href="#" onclick="document.tinyib.expire.value='3600';return false;">$txt_1h</a>&nbsp;<a href="#" onclick="document.tinyib.expire.value='86400';return false;">$txt_1d</a>&nbsp;<a href="#" onclick="document.tinyib.expire.value='172800';return false;">$txt_2d</a>&nbsp;<a href="#" onclick="document.tinyib.expire.value='604800';return false;">$txt_1w</a>&nbsp;<a href="#" onclick="document.tinyib.expire.value='1209600';return false;">$txt_2w</a>&nbsp;<a href="#" onclick="document.tinyib.expire.value='2592000';return false;">$txt_1m</a>&nbsp;<a href="#" onclick="document.tinyib.expire.value='0';return false;">$txt_ban_never</a></small></td></tr>
<tr><td><label for="reason">$txt_ban_reason</label></td><td><input type="text" name="reason" id="reason"></td><td><small>$txt_ban_optional</small></td></tr>
$banmessage_html
@ -1381,7 +1405,7 @@ function manageModeratePost($post, $compact=false) {
<tr><td>
<form method="get" action="?">
<input type="hidden" name="manage" value="">
<input type="hidden" name="sticky" value="${post['id']}">
<input type="hidden" name="sticky" value="{$post['id']}">
<input type="hidden" name="setsticky" value="$sticky_set">
<input type="submit" value="$sticky_unsticky" class="managebutton">
</form>
@ -1395,7 +1419,7 @@ EOF;
<tr><td>
<form method="get" action="?">
<input type="hidden" name="manage" value="">
<input type="hidden" name="lock" value="${post['id']}">
<input type="hidden" name="lock" value="{$post['id']}">
<input type="hidden" name="setlock" value="$lock_set">
<input type="submit" value="$lock_label" class="managebutton">
</form>
@ -1423,7 +1447,7 @@ EOF;
<form method="get" action="?">
<input type="hidden" name="manage" value="">
<input type="hidden" name="clearreports" value="${post['id']}">
<input type="hidden" name="clearreports" value="{$post['id']}">
<input type="submit" value="$txt_clear_reports" class="managebutton">
</form>
@ -1452,7 +1476,7 @@ EOF;
<form method="get" action="?">
<input type="hidden" name="manage" value="">
<input type="hidden" name="delete" value="${post['id']}">
<input type="hidden" name="delete" value="{$post['id']}">
<input type="submit" value="$txt_delete" class="managebutton">
</form>
@ -1461,8 +1485,8 @@ EOF;
<form method="get" action="?">
<input type="hidden" name="manage" value="">
<input type="hidden" name="bans" value="${post['ip']}">
<input type="hidden" name="posts" value="${post['id']}">
<input type="hidden" name="bans" value="{$post['ip']}">
<input type="hidden" name="posts" value="{$post['id']}">
<input type="submit" value="$txt_ban" class="managebutton" $ban_disabled>
</form>

View File

@ -80,7 +80,9 @@ define('TINYIB_MAXMESSAGE', 8000); // Maximum message length [0 to disable]
$tinyib_uploads = array('image/jpeg' => array('jpg'),
'image/pjpeg' => array('jpg'),
'image/png' => array('png'),
'image/gif' => array('gif'));
'image/gif' => array('gif'),
// 'image/avif' => array('avif'),
// 'image/heif' => array('avif'));
// 'application/x-shockwave-flash' => array('swf', 'swf_thumbnail.png');
// 'audio/aac' => array('aac');
// 'audio/flac' => array('flac');