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
This commit is contained in:
parent
8e976fb812
commit
b937ad6255
7 changed files with 123 additions and 46 deletions
|
@ -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
|
||||
));
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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, "<br>Thumbnail link: ");
|
||||
$sb->add_text_option(ImageConfig::TIP, "Image tooltip: ");
|
||||
$sb->add_choice_option(ImageConfig::UPLOAD_COLLISION_HANDLER, self::COLLISION_OPTIONS, "<br>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, "<br>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("<br>");
|
||||
$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("<br>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("<br>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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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
|
||||
]
|
||||
];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Reference in a new issue