From b937ad6255a5e8c1ae14b5ad4c9225b619eff572 Mon Sep 17 00:00:00 2001 From: Matthew Barbour Date: Thu, 11 Jun 2020 16:58:19 -0500 Subject: [PATCH] Added thumbnail scaling options Changed ffmpeg thumbnailer to instead output a full-size png which is forwarded to the image thumbnailer, to allow it to take advantage of all available scaling options --- core/imageboard/misc.php | 28 ++++++++++++-- ext/image/config.php | 1 + ext/image/main.php | 41 +++++++++++--------- ext/media/events.php | 6 +-- ext/media/main.php | 76 +++++++++++++++++++++++++++----------- ext/media/media_engine.php | 15 ++++++++ ext/transcode/main.php | 2 +- 7 files changed, 123 insertions(+), 46 deletions(-) diff --git a/core/imageboard/misc.php b/core/imageboard/misc.php index 88b36d7a..7d588955 100644 --- a/core/imageboard/misc.php +++ b/core/imageboard/misc.php @@ -73,6 +73,12 @@ function get_thumbnail_size(int $orig_width, int $orig_height, bool $use_dpi_sca { global $config; + $fit = $config->get_string(ImageConfig::THUMB_FIT); + + if (in_array($fit, [Media::RESIZE_TYPE_FILL, Media::RESIZE_TYPE_STRETCH, Media::RESIZE_TYPE_FIT_BLUR])) { + return [$config->get_int(ImageConfig::THUMB_WIDTH), $config->get_int(ImageConfig::THUMB_HEIGHT)]; + } + if ($orig_width === 0) { $orig_width = 192; } @@ -132,18 +138,32 @@ function get_thumbnail_max_size_scaled(): array function create_image_thumb(string $hash, string $type, string $engine = null) { + global $config; + $inname = warehouse_path(Image::IMAGE_DIR, $hash); $outname = warehouse_path(Image::THUMBNAIL_DIR, $hash); $tsize = get_thumbnail_max_size_scaled(); - create_scaled_image($inname, $outname, $tsize, $type, $engine); + create_scaled_image( + $inname, + $outname, + $tsize, + $type, + $engine, + $config->get_string(ImageConfig::THUMB_FIT) + ); } -function create_scaled_image(string $inname, string $outname, array $tsize, string $type, ?string $engine) + + +function create_scaled_image(string $inname, string $outname, array $tsize, string $type, ?string $engine = null, ?string $resize_type = null) { global $config; if (empty($engine)) { $engine = $config->get_string(ImageConfig::THUMB_ENGINE); } + if (empty($resize_type)) { + $resize_type = $config->get_string(ImageConfig::THUMB_FIT); + } $output_format = $config->get_string(ImageConfig::THUMB_TYPE); if ($output_format==EXTENSION_WEBP) { @@ -157,10 +177,10 @@ function create_scaled_image(string $inname, string $outname, array $tsize, stri $outname, $tsize[0], $tsize[1], - false, + $resize_type, $output_format, $config->get_int(ImageConfig::THUMB_QUALITY), true, - $config->get_bool('thumb_upscale', false) + true )); } diff --git a/ext/image/config.php b/ext/image/config.php index b54452fe..2b078113 100644 --- a/ext/image/config.php +++ b/ext/image/config.php @@ -8,6 +8,7 @@ abstract class ImageConfig const THUMB_SCALING = 'thumb_scaling'; const THUMB_QUALITY = 'thumb_quality'; const THUMB_TYPE = 'thumb_type'; + const THUMB_FIT = 'thumb_fit'; const SHOW_META = 'image_show_meta'; const ILINK = 'image_ilink'; diff --git a/ext/image/main.php b/ext/image/main.php index 2d873ec2..bba0e551 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -33,6 +33,7 @@ class ImageIO extends Extension $config->set_default_int(ImageConfig::THUMB_SCALING, 100); $config->set_default_int(ImageConfig::THUMB_QUALITY, 75); $config->set_default_string(ImageConfig::THUMB_TYPE, EXTENSION_JPG); + $config->set_default_string(ImageConfig::THUMB_FIT, Media::RESIZE_TYPE_FIT); if (function_exists(self::EXIF_READ_FUNCTION)) { $config->set_default_bool(ImageConfig::SHOW_META, false); @@ -215,35 +216,41 @@ class ImageIO extends Extension public function onSetupBuilding(SetupBuildingEvent $event) { + global $config; + $sb = new SetupBlock("Image Options"); + $sb->start_table(); $sb->position = 30; // advanced only //$sb->add_text_option(ImageConfig::ILINK, "Image link: "); //$sb->add_text_option(ImageConfig::TLINK, "
Thumbnail link: "); - $sb->add_text_option(ImageConfig::TIP, "Image tooltip: "); - $sb->add_choice_option(ImageConfig::UPLOAD_COLLISION_HANDLER, self::COLLISION_OPTIONS, "
Upload collision handler: "); + $sb->add_text_option(ImageConfig::TIP, "Image tooltip", true); + $sb->add_choice_option(ImageConfig::UPLOAD_COLLISION_HANDLER, self::COLLISION_OPTIONS, "Upload collision handler", true); if (function_exists(self::EXIF_READ_FUNCTION)) { - $sb->add_bool_option(ImageConfig::SHOW_META, "
Show metadata: "); + $sb->add_bool_option(ImageConfig::SHOW_META, "Show metadata", true); } - + $sb->end_table(); $event->panel->add_block($sb); $sb = new SetupBlock("Thumbnailing"); - $sb->add_choice_option(ImageConfig::THUMB_ENGINE, self::THUMBNAIL_ENGINES, "Engine: "); - $sb->add_label("
"); - $sb->add_choice_option(ImageConfig::THUMB_TYPE, self::THUMBNAIL_TYPES, "Filetype: "); + $sb->start_table(); + $sb->add_choice_option(ImageConfig::THUMB_ENGINE, self::THUMBNAIL_ENGINES, "Engine", true); + $sb->add_choice_option(ImageConfig::THUMB_TYPE, self::THUMBNAIL_TYPES, "Filetype", true); - $sb->add_label("
Size "); - $sb->add_int_option(ImageConfig::THUMB_WIDTH); - $sb->add_label(" x "); - $sb->add_int_option(ImageConfig::THUMB_HEIGHT); - $sb->add_label(" px at "); - $sb->add_int_option(ImageConfig::THUMB_QUALITY); - $sb->add_label(" % quality "); + $sb->add_int_option(ImageConfig::THUMB_WIDTH, "Max Width", true); + $sb->add_int_option(ImageConfig::THUMB_HEIGHT, "Max Height", true); - $sb->add_label("
High-DPI scaling "); - $sb->add_int_option(ImageConfig::THUMB_SCALING); - $sb->add_label("%"); + $options = []; + foreach (MediaEngine::RESIZE_TYPE_SUPPORT[$config->get_string(ImageConfig::THUMB_ENGINE)] as $type) { + $options[$type] = $type; + } + + $sb->add_choice_option(ImageConfig::THUMB_FIT, $options, "Fit", true); + + $sb->add_int_option(ImageConfig::THUMB_QUALITY, "Quality", true); + $sb->add_int_option(ImageConfig::THUMB_SCALING, "High-DPI Scale %", true); + + $sb->end_table(); $event->panel->add_block($sb); } diff --git a/ext/media/events.php b/ext/media/events.php index b8cd984a..ffa69a49 100644 --- a/ext/media/events.php +++ b/ext/media/events.php @@ -11,8 +11,8 @@ class MediaResizeEvent extends Event public $target_height; public $target_quality; public $minimize; - public $ignore_aspect_ratio; public $allow_upscale; + public $resize_type; public function __construct( String $engine, @@ -21,7 +21,7 @@ class MediaResizeEvent extends Event string $output_path, int $target_width, int $target_height, - bool $ignore_aspect_ratio = false, + string $resize_type = Media::RESIZE_TYPE_FIT, string $target_format = null, int $target_quality = 80, bool $minimize = false, @@ -38,8 +38,8 @@ class MediaResizeEvent extends Event $this->target_format = $target_format; $this->target_quality = $target_quality; $this->minimize = $minimize; - $this->ignore_aspect_ratio = $ignore_aspect_ratio; $this->allow_upscale = $allow_upscale; + $this->resize_type = $resize_type; } } diff --git a/ext/media/main.php b/ext/media/main.php index 814af2da..50d548ad 100644 --- a/ext/media/main.php +++ b/ext/media/main.php @@ -48,6 +48,10 @@ class Media extends Extension EXTENSION_JPEG => EXTENSION_JPG, ]; + const RESIZE_TYPE_FIT = "Fit"; + const RESIZE_TYPE_FIT_BLUR = "Fit Blur"; + const RESIZE_TYPE_FILL = "Fill"; + const RESIZE_TYPE_STRETCH = "Stretch"; //RIFF####WEBPVP8?..............ANIM private const WEBP_ANIMATION_HEADER = @@ -206,6 +210,10 @@ class Media extends Extension */ public function onMediaResize(MediaResizeEvent $event) { + if (!in_array($event->resize_type, MediaEngine::RESIZE_TYPE_SUPPORT[MediaEngine::IMAGICK])) { + throw new MediaException("Resize type $event->resize_type not supported by selected media engine $event->engine"); + } + switch ($event->engine) { case MediaEngine::GD: $info = getimagesize($event->input_path); @@ -220,7 +228,7 @@ class Media extends Extension $event->target_height, $event->output_path, $event->target_format, - $event->ignore_aspect_ratio, + $event->resize_type, $event->target_quality, $event->allow_upscale ); @@ -236,7 +244,7 @@ class Media extends Extension $event->target_height, $event->output_path, $event->target_format, - $event->ignore_aspect_ratio, + $event->resize_type, $event->target_quality, $event->minimize, $event->allow_upscale @@ -356,6 +364,7 @@ class Media extends Extension } $inname = warehouse_path(Image::IMAGE_DIR, $hash); + $tmpname = tempnam("/tmp", "shimmie_ffmpeg_thumb"); $outname = warehouse_path(Image::THUMBNAIL_DIR, $hash); $orig_size = self::video_size($inname); @@ -376,12 +385,11 @@ class Media extends Extension $args = [ escapeshellarg($ffmpeg), "-y", "-i", escapeshellarg($inname), - "-vf", "thumbnail,scale={$scaled_size[0]}:{$scaled_size[1]}", + "-vf", "thumbnail", "-f", "image2", "-vframes", "1", - "-c:v", $codec, - "-q:v", $quality, - escapeshellarg($outname), + "-c:v", "png", + escapeshellarg($tmpname), ]; $cmd = escapeshellcmd(implode(" ", $args)); @@ -390,6 +398,10 @@ class Media extends Extension if ((int)$ret == (int)0) { log_debug('media', "Generating thumbnail with command `$cmd`, returns $ret"); + + create_scaled_image($tmpname, $outname, $scaled_size, "png"); + + return true; } else { log_error('media', "Generating thumbnail with command `$cmd`, returns $ret"); @@ -569,7 +581,7 @@ class Media extends Extension int $new_height, string $output_filename, string $output_type = null, - bool $ignore_aspect_ratio = false, + string $resize_type = self::RESIZE_TYPE_FIT, int $output_quality = 80, bool $minimize = false, bool $allow_upscale = true @@ -598,16 +610,41 @@ class Media extends Extension $input_type = $input_type . ":"; } - - $resize_args = ""; + $resize_suffix = ""; if (!$allow_upscale) { - $resize_args .= "\>"; + $resize_suffix .= "\>"; } - if ($ignore_aspect_ratio) { - $resize_args .= "\!"; + if ($resize_type==Media::RESIZE_TYPE_STRETCH) { + $resize_suffix .= "\!"; } $args = ""; + $resize_arg = "-resize"; + if ($minimize) { + $args .= "-strip "; + $resize_arg = "-thumbnail"; + } + + $file_arg = "${input_type}\"${input_path}[0]\""; + + switch ($resize_type) { + case Media::RESIZE_TYPE_FIT: + case Media::RESIZE_TYPE_STRETCH: + $args .= "${resize_arg} ${new_width}x${new_height}${resize_suffix} ${file_arg}"; + break; + case Media::RESIZE_TYPE_FILL: + $args .= "${resize_arg} ${new_width}x${new_height}\^ -gravity center -extent ${new_width}x${new_height} ${file_arg}"; + break; + case Media::RESIZE_TYPE_FIT_BLUR: + $blur_size = max(ceil(max($new_width, $new_height) / 25), 5); + $args .= "${file_arg} ". + "\( -clone 0 -resize ${new_width}x${new_height}\^ -gravity center -fill black -colorize 50% -extent ${new_width}x${new_height} -blur 0x${blur_size} \) ". + "\( -clone 0 -resize ${new_width}x${new_height} \) ". + "-delete 0 -gravity center -compose over -composite"; + break; + } + + switch ($output_type) { case Media::WEBP_LOSSLESS: $args .= '-define webp:lossless=true'; @@ -617,17 +654,14 @@ class Media extends Extension break; } - if ($minimize) { - $args .= " -strip -thumbnail"; - } else { - $args .= " -resize"; - } + + $args .= " -quality ${output_quality} -background ${bg}"; $output_ext = self::determine_ext($output_type); - $format = '"%s" %s %ux%u%s -quality %u -background %s %s"%s[0]" %s:"%s" 2>&1'; - $cmd = sprintf($format, $convert, $args, $new_width, $new_height, $resize_args, $output_quality, $bg, $input_type, $input_path, $output_ext, $output_filename); + $format = '"%s" %s %s:"%s" 2>&1'; + $cmd = sprintf($format, $convert, $args, $output_ext, $output_filename); $cmd = str_replace("\"convert\"", "convert", $cmd); // quotes are only needed if the path to convert contains a space; some other times, quotes break things, see github bug #27 exec($cmd, $output, $ret); if ($ret != 0) { @@ -657,7 +691,7 @@ class Media extends Extension int $new_height, string $output_filename, string $output_type = null, - bool $ignore_aspect_ratio = false, + string $resize_type = self::RESIZE_TYPE_FIT, int $output_quality = 80, bool $allow_upscale = true ) { @@ -693,7 +727,7 @@ class Media extends Extension throw new InsufficientMemoryException("The image is too large to resize given the memory limits. ($memory_use > $memory_limit)"); } - if (!$ignore_aspect_ratio) { + if ($resize_type==Media::RESIZE_TYPE_FIT) { list($new_width, $new_height) = get_scaled_by_aspect_ratio($width, $height, $new_width, $new_height); } if (!$allow_upscale && diff --git a/ext/media/media_engine.php b/ext/media/media_engine.php index 43dc74f4..3a43b14d 100644 --- a/ext/media/media_engine.php +++ b/ext/media/media_engine.php @@ -74,4 +74,19 @@ abstract class MediaEngine EXTENSION_PNG, ], ]; + public const RESIZE_TYPE_SUPPORT = [ + MediaEngine::GD => [ + Media::RESIZE_TYPE_FIT, + Media::RESIZE_TYPE_STRETCH + ], + MediaEngine::IMAGICK => [ + Media::RESIZE_TYPE_FIT, + Media::RESIZE_TYPE_FIT_BLUR, + Media::RESIZE_TYPE_FILL, + Media::RESIZE_TYPE_STRETCH, + ], + MediaEngine::FFMPEG => [ + Media::RESIZE_TYPE_FIT + ] + ]; } diff --git a/ext/transcode/main.php b/ext/transcode/main.php index 27c1eb8c..42f7b8c3 100644 --- a/ext/transcode/main.php +++ b/ext/transcode/main.php @@ -88,7 +88,7 @@ class TranscodeImage extends Extension $sb->add_choice_option(TranscodeConfig::UPLOAD_PREFIX.$format, $outputs, "$display", true); } } - $sb->add_int_option(TranscodeConfig::QUALITY, "Lossy format quality: "); + $sb->add_int_option(TranscodeConfig::QUALITY, "Lossy Format Quality", true); $sb->end_table(); $event->panel->add_block($sb); }