diff --git a/core/basethemelet.php b/core/basethemelet.php index 897a1f33..b3a2baeb 100644 --- a/core/basethemelet.php +++ b/core/basethemelet.php @@ -58,7 +58,7 @@ class BaseThemelet $tsize = get_thumbnail_size($image->width, $image->height); } else { //Use max thumbnail size if using thumbless filetype - $tsize = get_thumbnail_size($config->get_int('thumb_width'), $config->get_int('thumb_height')); + $tsize = get_thumbnail_size($config->get_int(ImageConfig::THUMB_WIDTH), $config->get_int(ImageConfig::THUMB_WIDTH)); } $custom_classes = ""; diff --git a/core/imageboard/image.php b/core/imageboard/image.php index 10590854..d6b63371 100644 --- a/core/imageboard/image.php +++ b/core/imageboard/image.php @@ -463,7 +463,7 @@ class Image */ public function get_image_link(): string { - return $this->get_link('image_ilink', '_images/$hash/$id%20-%20$tags.$ext', 'image/$id.$ext'); + return $this->get_link(ImageConfig::ILINK, '_images/$hash/$id%20-%20$tags.$ext', 'image/$id.$ext'); } /** @@ -480,8 +480,8 @@ class Image public function get_thumb_link(): string { global $config; - $ext = $config->get_string("thumb_type"); - return $this->get_link('image_tlink', '_thumbs/$hash/thumb.'.$ext, 'thumb/$id.'.$ext); + $ext = $config->get_string(ImageConfig::THUMB_TYPE); + return $this->get_link(ImageConfig::TLINK, '_thumbs/$hash/thumb.'.$ext, 'thumb/$id.'.$ext); } /** @@ -512,7 +512,7 @@ class Image public function get_tooltip(): string { global $config; - $tt = $this->parse_link_template($config->get_string('image_tip'), "no_escape"); + $tt = $this->parse_link_template($config->get_string(ImageConfig::TIP), "no_escape"); // Removes the size tag if the file is an mp3 if ($this->ext === 'mp3') { diff --git a/core/imageboard/misc.php b/core/imageboard/misc.php index 1614ca70..2a349c80 100644 --- a/core/imageboard/misc.php +++ b/core/imageboard/misc.php @@ -125,24 +125,31 @@ function get_thumbnail_size(int $orig_width, int $orig_height, bool $use_dpi_sca } - if ($use_dpi_scaling) { - $max_size = get_thumbnail_max_size_scaled(); - $max_width = $max_size[0]; - $max_height = $max_size[1]; + if($use_dpi_scaling) { + list($max_width, $max_height) = get_thumbnail_max_size_scaled(); } else { - $max_width = $config->get_int('thumb_width'); - $max_height = $config->get_int('thumb_height'); + $max_width = $config->get_int(ImageConfig::THUMB_WIDTH); + $max_height = $config->get_int(ImageConfig::THUMB_HEIGHT); } - $xscale = ($max_height / $orig_height); - $yscale = ($max_width / $orig_width); - $scale = ($xscale < $yscale) ? $xscale : $yscale; + $output = get_scaled_by_aspect_ratio($orig_width, $orig_height, $max_width, $max_height); - if ($scale > 1 && $config->get_bool('thumb_upscale')) { + if ($output[2] > 1 && $config->get_bool('thumb_upscale')) { return [(int)$orig_width, (int)$orig_height]; } else { - return [(int)($orig_width*$scale), (int)($orig_height*$scale)]; + return $output; } + +} + +function get_scaled_by_aspect_ratio(int $original_width, int $original_height, int $max_width, int $max_height) : array +{ + $xscale = ($max_width/ $original_width); + $yscale = ($max_height/ $original_height); + + $scale = ($yscale < $xscale) ? $yscale : $xscale ; + + return [(int)($original_width*$scale), (int)($original_height*$scale), $scale]; } /** @@ -154,355 +161,37 @@ function get_thumbnail_max_size_scaled(): array { global $config; - $scaling = $config->get_int("thumb_scaling"); - $max_width = $config->get_int('thumb_width') * ($scaling/100); - $max_height = $config->get_int('thumb_height') * ($scaling/100); + $scaling = $config->get_int(ImageConfig::THUMB_SCALING); + $max_width = $config->get_int(ImageConfig::THUMB_WIDTH) * ($scaling/100); + $max_height = $config->get_int(ImageConfig::THUMB_HEIGHT) * ($scaling/100); return [$max_width, $max_height]; } -/** - * Creates a thumbnail file using ImageMagick's convert command. - * - * @param $hash - * @param string $input_type Optional, allows specifying the input format. Usually not necessary. - * @return bool true is successful, false if not. - */ -function create_thumbnail_convert($hash, $input_type = ""): bool -{ + +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(); - $q = $config->get_int("thumb_quality"); - $convert = $config->get_string("thumb_convert_path"); - - if ($convert==null||$convert=="") { - return false; + if(empty($engine)) { + $engine = $config->get_string(ImageConfig::THUMB_ENGINE); } - // ffff imagemagick fails sometimes, not sure why - //$format = "'%s' '%s[0]' -format '%%[fx:w] %%[fx:h]' info:"; - //$cmd = sprintf($format, $convert, $inname); - //$size = shell_exec($cmd); - //$size = explode(" ", trim($size)); - list($w, $h) = get_thumbnail_max_size_scaled(); - - - // running the call with cmd.exe requires quoting for our paths - $type = $config->get_string('thumb_type'); - - $options = ""; - if (!$config->get_bool('thumb_upscale')) { - $options .= "\>"; - } - - $bg = "black"; - if ($type=="webp") { - $bg = "none"; - } - if (!empty($input_type)) { - $input_type = $input_type.":"; - } - $format = '"%s" -flatten -strip -thumbnail %ux%u%s -quality %u -background %s %s"%s[0]" %s:"%s" 2>&1'; - $cmd = sprintf($format, $convert, $w, $h, $options, $q, $bg, $input_type, $inname, $type, $outname); - $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) { - log_warning('imageboard/misc', "Generating thumbnail with command `$cmd`, returns $ret, outputting ".implode("\r\n", $output)); - } else { - log_debug('imageboard/misc', "Generating thumbnail with command `$cmd`, returns $ret"); - } - - if ($config->get_bool("thumb_optim", false)) { - exec("jpegoptim $outname", $output, $ret); - } - - return true; + send_event(new GraphicResizeEvent( + $engine, + $inname, + $type, + $outname, + $tsize[0], + $tsize[1], + false, + $config->get_string(ImageConfig::THUMB_TYPE), + $config->get_int(ImageConfig::THUMB_QUALITY), + true, + $config->get_bool('thumb_upscale', false) + )); } -/** - * Creates a thumbnail using ffmpeg. - * - * @param $hash - * @return bool true if successful, false if not. - */ -function create_thumbnail_ffmpeg($hash): bool -{ - global $config; - $ffmpeg = $config->get_string("thumb_ffmpeg_path"); - if ($ffmpeg==null||$ffmpeg=="") { - return false; - } - - $inname = warehouse_path(Image::IMAGE_DIR, $hash); - $outname = warehouse_path(Image::THUMBNAIL_DIR, $hash); - - $orig_size = video_size($inname); - $scaled_size = get_thumbnail_size($orig_size[0], $orig_size[1], true); - - $codec = "mjpeg"; - $quality = $config->get_int("thumb_quality"); - if ($config->get_string("thumb_type")=="webp") { - $codec = "libwebp"; - } else { - // mjpeg quality ranges from 2-31, with 2 being the best quality. - $quality = floor(31 - (31 * ($quality/100))); - if ($quality<2) { - $quality = 2; - } - } - - $args = [ - escapeshellarg($ffmpeg), - "-y", "-i", escapeshellarg($inname), - "-vf", "thumbnail,scale={$scaled_size[0]}:{$scaled_size[1]}", - "-f", "image2", - "-vframes", "1", - "-c:v", $codec, - "-q:v", $quality, - escapeshellarg($outname), - ]; - - $cmd = escapeshellcmd(implode(" ", $args)); - - exec($cmd, $output, $ret); - - if ((int)$ret == (int)0) { - log_debug('imageboard/misc', "Generating thumbnail with command `$cmd`, returns $ret"); - return true; - } else { - log_error('imageboard/misc', "Generating thumbnail with command `$cmd`, returns $ret"); - return false; - } -} - -/** - * Determines the dimensions of a video file using ffmpeg. - * - * @param string $filename - * @return array [width, height] - */ -function video_size(string $filename): array -{ - global $config; - $ffmpeg = $config->get_string("thumb_ffmpeg_path"); - $cmd = escapeshellcmd(implode(" ", [ - escapeshellarg($ffmpeg), - "-y", "-i", escapeshellarg($filename), - "-vstats" - ])); - $output = shell_exec($cmd . " 2>&1"); - // error_log("Getting size with `$cmd`"); - - $regex_sizes = "/Video: .* ([0-9]{1,4})x([0-9]{1,4})/"; - if (preg_match($regex_sizes, $output, $regs)) { - if (preg_match("/displaymatrix: rotation of (90|270).00 degrees/", $output)) { - $size = [$regs[2], $regs[1]]; - } else { - $size = [$regs[1], $regs[2]]; - } - } else { - $size = [1, 1]; - } - log_debug('imageboard/misc', "Getting video size with `$cmd`, returns $output -- $size[0], $size[1]"); - return $size; -} - -/** - * Check Memory usage limits - * - * Old check: $memory_use = (filesize($image_filename)*2) + ($width*$height*4) + (4*1024*1024); - * New check: $memory_use = $width * $height * ($bits_per_channel) * channels * 2.5 - * - * It didn't make sense to compute the memory usage based on the NEW size for the image. ($width*$height*4) - * We need to consider the size that we are GOING TO instead. - * - * The factor of 2.5 is simply a rough guideline. - * http://stackoverflow.com/questions/527532/reasonable-php-memory-limit-for-image-resize - * - * @param array $info The output of getimagesize() for the source file in question. - * @return int The number of bytes an image resize operation is estimated to use. - */ -function calc_memory_use(array $info): int -{ - if (isset($info['bits']) && isset($info['channels'])) { - $memory_use = ($info[0] * $info[1] * ($info['bits'] / 8) * $info['channels'] * 2.5) / 1024; - } else { - // If we don't have bits and channel info from the image then assume default values - // of 8 bits per color and 4 channels (R,G,B,A) -- ie: regular 24-bit color - $memory_use = ($info[0] * $info[1] * 1 * 4 * 2.5) / 1024; - } - return (int)$memory_use; -} - -/** - * Performs a resize operation on an image file using GD. - * - * @param String $image_filename The source file to be resized. - * @param array $info The output of getimagesize() for the source file. - * @param int $new_width - * @param int $new_height - * @param string $output_filename - * @param string|null $output_type If set to null, the output file type will be automatically determined via the $info parameter. Otherwise an exception will be thrown. - * @param int $output_quality Defaults to 80. - * @throws ImageResizeException - * @throws InsufficientMemoryException if the estimated memory usage exceeds the memory limit. - */ -function image_resize_gd( - String $image_filename, - array $info, - int $new_width, - int $new_height, - string $output_filename, - string $output_type=null, - int $output_quality = 80 -) { - $width = $info[0]; - $height = $info[1]; - - if ($output_type==null) { - /* If not specified, output to the same format as the original image */ - switch ($info[2]) { - case IMAGETYPE_GIF: $output_type = "gif"; break; - case IMAGETYPE_JPEG: $output_type = "jpeg"; break; - case IMAGETYPE_PNG: $output_type = "png"; break; - case IMAGETYPE_WEBP: $output_type = "webp"; break; - case IMAGETYPE_BMP: $output_type = "bmp"; break; - default: throw new ImageResizeException("Failed to save the new image - Unsupported image type."); - } - } - - $memory_use = calc_memory_use($info); - $memory_limit = get_memory_limit(); - if ($memory_use > $memory_limit) { - throw new InsufficientMemoryException("The image is too large to resize given the memory limits. ($memory_use > $memory_limit)"); - } - - $image = imagecreatefromstring(file_get_contents($image_filename)); - $image_resized = imagecreatetruecolor($new_width, $new_height); - try { - if ($image===false) { - throw new ImageResizeException("Could not load image: ".$image_filename); - } - if ($image_resized===false) { - throw new ImageResizeException("Could not create output image with dimensions $new_width c $new_height "); - } - - // Handle transparent images - switch ($info[2]) { - case IMAGETYPE_GIF: - $transparency = imagecolortransparent($image); - $palletsize = imagecolorstotal($image); - - // If we have a specific transparent color - if ($transparency >= 0 && $transparency < $palletsize) { - // Get the original image's transparent color's RGB values - $transparent_color = imagecolorsforindex($image, $transparency); - - // Allocate the same color in the new image resource - $transparency = imagecolorallocate($image_resized, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']); - if ($transparency===false) { - throw new ImageResizeException("Unable to allocate transparent color"); - } - - // Completely fill the background of the new image with allocated color. - if (imagefill($image_resized, 0, 0, $transparency)===false) { - throw new ImageResizeException("Unable to fill new image with transparent color"); - } - - // Set the background color for new image to transparent - imagecolortransparent($image_resized, $transparency); - } - break; - case IMAGETYPE_PNG: - case IMAGETYPE_WEBP: - // - // More info here: http://stackoverflow.com/questions/279236/how-do-i-resize-pngs-with-transparency-in-php - // - if (imagealphablending($image_resized, false)===false) { - throw new ImageResizeException("Unable to disable image alpha blending"); - } - if (imagesavealpha($image_resized, true)===false) { - throw new ImageResizeException("Unable to enable image save alpha"); - } - $transparent_color = imagecolorallocatealpha($image_resized, 255, 255, 255, 127); - if ($transparent_color===false) { - throw new ImageResizeException("Unable to allocate transparent color"); - } - if (imagefilledrectangle($image_resized, 0, 0, $new_width, $new_height, $transparent_color)===false) { - throw new ImageResizeException("Unable to fill new image with transparent color"); - } - break; - } - - // Actually resize the image. - if (imagecopyresampled( - $image_resized, - $image, - 0, - 0, - 0, - 0, - $new_width, - $new_height, - $width, - $height - )===false) { - throw new ImageResizeException("Unable to copy resized image data to new image"); - } - - - switch ($output_type) { - case "bmp": - $result = imagebmp($image_resized, $output_filename, true); - break; - case "webp": - $result = imagewebp($image_resized, $output_filename, $output_quality); - break; - case "jpg": - case "jpeg": - $result = imagejpeg($image_resized, $output_filename, $output_quality); - break; - case "png": - $result = imagepng($image_resized, $output_filename, 9); - break; - case "gif": - $result = imagegif($image_resized, $output_filename); - break; - default: - throw new ImageResizeException("Failed to save the new image - Unsupported image type: $output_type"); - } - if ($result==false) { - throw new ImageResizeException("Failed to save the new image, function returned false when saving type: $output_type"); - } - } finally { - imagedestroy($image); - imagedestroy($image_resized); - } -} - -/** - * Determines if a file is an animated gif. - * - * @param String $image_filename The path of the file to check. - * @return bool true if the file is an animated gif, false if it is not. - */ -function is_animated_gif(String $image_filename) -{ - $is_anim_gif = 0; - if (($fh = @fopen($image_filename, 'rb'))) { - //check if gif is animated (via http://www.php.net/manual/en/function.imagecreatefromgif.php#104473) - while (!feof($fh) && $is_anim_gif < 2) { - $chunk = fread($fh, 1024 * 100); - $is_anim_gif += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches); - } - } - return ($is_anim_gif == 0); -} - -function image_to_id(Image $image): int -{ - return $image->id; -} diff --git a/core/sys_config.php b/core/sys_config.php index d3ed2761..a489a96f 100644 --- a/core/sys_config.php +++ b/core/sys_config.php @@ -40,7 +40,7 @@ _d("SEARCH_ACCEL", false); // boolean use search accelerator _d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse _d("VERSION", '2.7-beta'); // string shimmie version _d("TIMEZONE", null); // string timezone -_d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,handle_static,comment,tag_list,index,tag_edit,alias_editor"); // extensions to always enable +_d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,handle_static,comment,tag_list,index,tag_edit,alias_editor,graphics"); // extensions to always enable _d("EXTRA_EXTS", ""); // string optional extra extensions _d("BASE_URL", null); // string force a specific base URL (default is auto-detect) _d("MIN_PHP_VERSION", '7.1');// string minimum supported PHP version diff --git a/core/util.php b/core/util.php index 007822d5..24bda493 100644 --- a/core/util.php +++ b/core/util.php @@ -79,7 +79,7 @@ function get_memory_limit(): int // thumbnail generation requires lots of memory $default_limit = 8*1024*1024; // 8 MB of memory is PHP's default. - $shimmie_limit = parse_shorthand_int($config->get_int("thumb_mem_limit")); + $shimmie_limit = parse_shorthand_int($config->get_int(GraphicsConfig::MEM_LIMIT)); if ($shimmie_limit < 3*1024*1024) { // we aren't going to fit, override diff --git a/ext/et/main.php b/ext/et/main.php index 702c9c9c..50a639e1 100644 --- a/ext/et/main.php +++ b/ext/et/main.php @@ -52,14 +52,17 @@ class ET extends Extension $info['sys_disk'] = to_shorthand_int(disk_total_space("./") - disk_free_space("./")) . " / " . to_shorthand_int(disk_total_space("./")); $info['sys_server'] = isset($_SERVER["SERVER_SOFTWARE"]) ? $_SERVER["SERVER_SOFTWARE"] : 'unknown'; - - $info['thumb_engine'] = $config->get_string("thumb_engine"); - $info['thumb_quality'] = $config->get_int('thumb_quality'); - $info['thumb_width'] = $config->get_int('thumb_width'); - $info['thumb_height'] = $config->get_int('thumb_height'); - $info['thumb_scaling'] = $config->get_int('thumb_scaling'); - $info['thumb_type'] = $config->get_string('thumb_type'); - $info['thumb_mem'] = $config->get_int("thumb_mem_limit"); + + $info[GraphicsConfig::FFMPEG_PATH] = $config->get_string(GraphicsConfig::FFMPEG_PATH); + $info[GraphicsConfig::CONVERT_PATH] = $config->get_string(GraphicsConfig::CONVERT_PATH); + $info[GraphicsConfig::MEM_LIMIT] = $config->get_int(GraphicsConfig::MEM_LIMIT); + + $info[ImageConfig::THUMB_ENGINE] = $config->get_string(ImageConfig::THUMB_ENGINE); + $info[ImageConfig::THUMB_QUALITY] = $config->get_int(ImageConfig::THUMB_QUALITY); + $info[ImageConfig::THUMB_WIDTH] = $config->get_int(ImageConfig::THUMB_WIDTH); + $info[ImageConfig::THUMB_HEIGHT] = $config->get_int(ImageConfig::THUMB_HEIGHT); + $info[ImageConfig::THUMB_SCALING] = $config->get_int(ImageConfig::THUMB_SCALING); + $info[ImageConfig::THUMB_TYPE] = $config->get_string(ImageConfig::THUMB_TYPE); $info['stat_images'] = $database->get_one("SELECT COUNT(*) FROM images"); $info['stat_comments'] = $database->get_one("SELECT COUNT(*) FROM comments"); diff --git a/ext/et/theme.php b/ext/et/theme.php index cb55ffb9..a0778dff 100644 --- a/ext/et/theme.php +++ b/ext/et/theme.php @@ -35,14 +35,16 @@ Database: {$info['sys_db']} Server: {$info['sys_server']} Disk use: {$info['sys_disk']} +Graphics System: +Memory Limit: {$info[GraphicsConfig::MEM_LIMIT]} + Thumbnail Generation: -Engine: {$info['thumb_engine']} -Type: {$info['thumb_type']} -Memory: {$info['thumb_mem']} -Quality: {$info['thumb_quality']} -Width: {$info['thumb_width']} -Height: {$info['thumb_height']} -Scaling: {$info['thumb_scaling']} +Engine: {$info[ImageConfig::THUMB_ENGINE]} +Type: {$info[ImageConfig::THUMB_TYPE]} +Quality: {$info[ImageConfig::THUMB_QUALITY]} +Width: {$info[ImageConfig::THUMB_WIDTH]} +Height: {$info[ImageConfig::THUMB_HEIGHT]} +Scaling: {$info[ImageConfig::THUMB_SCALING]} Shimmie stats: Images: {$info['stat_images']} diff --git a/ext/graphics/main.php b/ext/graphics/main.php new file mode 100644 index 00000000..e46d470d --- /dev/null +++ b/ext/graphics/main.php @@ -0,0 +1,808 @@ + + * Description: Provides common functions and settings used for graphic operations. + * License: MIT + * Version: 1.0 + */ + +/* +* This is used by the graphics code when there is an error +*/ + +use FFMpeg\FFMpeg; + +abstract class GraphicsConfig +{ + const FFMPEG_PATH = "graphics_ffmpeg_path"; + const FFPROBE_PATH = "graphics_ffprobe_path"; + const CONVERT_PATH = "graphics_convert_path"; + const VERSION = "ext_graphics_version"; + const MEM_LIMIT = 'graphics_mem_limit'; + +} + +class GraphicsException extends SCoreException +{ +} + +class GraphicResizeEvent extends Event +{ + public $engine; + public $input_path; + public $input_type; + public $output_path; + public $target_format; + public $target_width; + public $target_height; + public $target_quality; + public $minimize; + public $ignore_aspect_ratio; + public $allow_upscale; + + public function __construct(String $engine, string $input_path, string $input_type, string $output_path, + int $target_width, int $target_height, + bool $ignore_aspect_ratio = false, + string $target_format = null, + int $target_quality = 80, + bool $minimize = false, + bool $allow_upscale = true) + { + assert(in_array($engine, Graphics::GRAPHICS_ENGINES)); + $this->engine = $engine; + $this->input_path = $input_path; + $this->input_type = $input_type; + $this->output_path = $output_path; + $this->target_height = $target_height; + $this->target_width = $target_width; + $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; + } +} + +class Graphics extends Extension +{ + const WEBP_LOSSY = "webp-lossy"; + const WEBP_LOSSLESS = "webp-lossless"; + + const FFMPEG_ENGINE = "ffmpeg"; + const GD_ENGINE = "gd"; + const IMAGICK_ENGINE = "convert"; + + const GRAPHICS_ENGINES = [ + self::GD_ENGINE, + self::FFMPEG_ENGINE, + self::IMAGICK_ENGINE + ]; + + const IMAGE_GRAPHICS_ENGINES = [ + "GD" => self::GD_ENGINE, + "ImageMagick" => self::IMAGICK_ENGINE, + ]; + + const ENGINE_INPUT_SUPPORT = [ + self::GD_ENGINE => [ + "bmp", + "gif", + "jpg", + "png", + "webp", + ], + self::IMAGICK_ENGINE => [ + "bmp", + "gif", + "jpg", + "png", + "psd", + "tiff", + "webp", + "ico", + ], + self::FFMPEG_ENGINE => [ + "avi", + "mkv", + "webm", + "mp4", + "mov", + "flv" + ] + ]; + + const ENGINE_OUTPUT_SUPPORT = [ + self::GD_ENGINE => [ + "gif", + "jpg", + "png", + "webp", + self::WEBP_LOSSY, + ], + self::IMAGICK_ENGINE => [ + "gif", + "jpg", + "png", + "webp", + self::WEBP_LOSSY, + self::WEBP_LOSSLESS, + ], + self::FFMPEG_ENGINE => [ + + ] + ]; + + const LOSSLESS_FORMATS = [ + self::WEBP_LOSSLESS, + "png", + ]; + + const ALPHA_FORMATS = [ + self::WEBP_LOSSLESS, + self::WEBP_LOSSY, + "png", + ]; + + const FORMAT_ALIASES = [ + "tif" => "tiff", + "jpeg" => "jpg", + ]; + + + static function imagick_available(): bool + { + return extension_loaded("imagick"); + } + + /** + * High priority just so that it can be early in the settings + */ + public function get_priority(): int + { + return 30; + } + + public function onInitExt(InitExtEvent $event) + { + global $config; + $config->set_default_string(GraphicsConfig::FFPROBE_PATH, 'ffprobe'); + + + if ($config->get_int(GraphicsConfig::VERSION) < 1) { + $current_value = $config->get_string("thumb_ffmpeg_path"); + if(!empty($current_value)) { + $config->set_string(GraphicsConfig::FFMPEG_PATH, $current_value); + } elseif ($ffmpeg = shell_exec((PHP_OS == 'WINNT' ? 'where' : 'which') . ' ffmpeg')) { + //ffmpeg exists in PATH, check if it's executable, and if so, default to it instead of static + if (is_executable(strtok($ffmpeg, PHP_EOL))) { + $config->set_default_string(GraphicsConfig::FFMPEG_PATH, 'ffmpeg'); + } + } else { + $config->set_default_string(GraphicsConfig::FFMPEG_PATH, ''); + } + + $current_value = $config->get_string("thumb_convert_path"); + if(!empty($current_value)) { + $config->set_string(GraphicsConfig::CONVERT_PATH, $current_value); + } elseif ($convert = shell_exec((PHP_OS == 'WINNT' ? 'where' : 'which') . ' convert')) { + //ffmpeg exists in PATH, check if it's executable, and if so, default to it instead of static + if (is_executable(strtok($convert, PHP_EOL))) { + $config->set_default_string(GraphicsConfig::CONVERT_PATH, 'convert'); + } + } else { + $config->set_default_string(GraphicsConfig::CONVERT_PATH, ''); + } + + $current_value = $config->get_int("thumb_mem_limit"); + if(!empty($current_value)) { + $config->set_int(GraphicsConfig::MEM_LIMIT, $current_value); + } + + + + + $config->set_int(GraphicsConfig::VERSION, 1); + log_info("graphics", "extension installed"); + } + } + + public function onSetupBuilding(SetupBuildingEvent $event) + { + $sb = new SetupBlock("Graphics"); + +// if (self::imagick_available()) { +// try { +// $image = new Imagick(realpath('tests/favicon.png')); +// $image->clear(); +// $sb->add_label("ImageMagick detected"); +// } catch (ImagickException $e) { +// $sb->add_label("ImageMagick not detected"); +// } +// } else { + $sb->add_text_option(GraphicsConfig::CONVERT_PATH, "convert command: "); +// } + + $sb->add_text_option(GraphicsConfig::FFMPEG_PATH, "
ffmpeg command: "); + + $sb->add_shorthand_int_option(GraphicsConfig::MEM_LIMIT, "
Max memory use: "); + + $event->panel->add_block($sb); + + } + + /** + * @param GraphicResizeEvent $event + * @throws GraphicsException + * @throws InsufficientMemoryException + */ + public function onGraphicResize(GraphicResizeEvent $event) + { + switch ($event->engine) { + case self::GD_ENGINE: + $info = getimagesize($event->input_path); + if ($info === false) { + throw new GraphicsException("getimagesize failed for " . $event->input_path); + } + + self::image_resize_gd( + $event->input_path, + $info, + $event->target_width, + $event->target_height, + $event->output_path, + $event->target_format, + $event->ignore_aspect_ratio, + $event->target_quality, + $event->allow_upscale); + + break; + case self::IMAGICK_ENGINE: +// if (self::imagick_available()) { +// } else { + self::image_resize_convert( + $event->input_path, + $event->input_type, + $event->target_width, + $event->target_height, + $event->output_path, + $event->target_format, + $event->ignore_aspect_ratio, + $event->target_quality, + $event->minimize, + $event->allow_upscale); + //} + break; + default: + throw new GraphicsException("Engine not supported for resize: " . $event->engine); + } + + // TODO: Get output optimization tools working better +// if ($config->get_bool("thumb_optim", false)) { +// exec("jpegoptim $outname", $output, $ret); +// } + } + + /** + * Check Memory usage limits + * + * Old check: $memory_use = (filesize($image_filename)*2) + ($width*$height*4) + (4*1024*1024); + * New check: $memory_use = $width * $height * ($bits_per_channel) * channels * 2.5 + * + * It didn't make sense to compute the memory usage based on the NEW size for the image. ($width*$height*4) + * We need to consider the size that we are GOING TO instead. + * + * The factor of 2.5 is simply a rough guideline. + * http://stackoverflow.com/questions/527532/reasonable-php-memory-limit-for-image-resize + * + * @param array $info The output of getimagesize() for the source file in question. + * @return int The number of bytes an image resize operation is estimated to use. + */ + static function calc_memory_use(array $info): int + { + if (isset($info['bits']) && isset($info['channels'])) { + $memory_use = ($info[0] * $info[1] * ($info['bits'] / 8) * $info['channels'] * 2.5) / 1024; + } else { + // If we don't have bits and channel info from the image then assume default values + // of 8 bits per color and 4 channels (R,G,B,A) -- ie: regular 24-bit color + $memory_use = ($info[0] * $info[1] * 1 * 4 * 2.5) / 1024; + } + return (int)$memory_use; + } + + + /** + * Creates a thumbnail using ffmpeg. + * + * @param $hash + * @return bool true if successful, false if not. + * @throws GraphicsException + */ + static function create_thumbnail_ffmpeg($hash): bool + { + global $config; + + $ffmpeg = $config->get_string(GraphicsConfig::FFMPEG_PATH); + if ($ffmpeg == null || $ffmpeg == "") { + throw new GraphicsException("ffmpeg command configured"); + } + + $inname = warehouse_path(Image::IMAGE_DIR, $hash); + $outname = warehouse_path(Image::THUMBNAIL_DIR, $hash); + + $orig_size = self::video_size($inname); + $scaled_size = get_thumbnail_size($orig_size[0], $orig_size[1], true); + + $codec = "mjpeg"; + $quality = $config->get_int(ImageConfig::THUMB_QUALITY); + if ($config->get_string(ImageConfig::THUMB_TYPE) == "webp") { + $codec = "libwebp"; + } else { + // mjpeg quality ranges from 2-31, with 2 being the best quality. + $quality = floor(31 - (31 * ($quality / 100))); + if ($quality < 2) { + $quality = 2; + } + } + + $args = [ + escapeshellarg($ffmpeg), + "-y", "-i", escapeshellarg($inname), + "-vf", "thumbnail,scale={$scaled_size[0]}:{$scaled_size[1]}", + "-f", "image2", + "-vframes", "1", + "-c:v", $codec, + "-q:v", $quality, + escapeshellarg($outname), + ]; + + $cmd = escapeshellcmd(implode(" ", $args)); + + exec($cmd, $output, $ret); + + if ((int)$ret == (int)0) { + log_debug('graphics', "Generating thumbnail with command `$cmd`, returns $ret"); + return true; + } else { + log_error('graphics', "Generating thumbnail with command `$cmd`, returns $ret"); + return false; + } + } + + public static function determine_ext(String $format): String + { + $format = self::normalize_format($format); + switch ($format) { + case self::WEBP_LOSSLESS: + case self::WEBP_LOSSY: + return "webp"; + default: + return $format; + } + } + +// private static function image_save_imagick(Imagick $image, string $path, string $format, int $output_quality = 80, bool $minimize) +// { +// switch ($format) { +// case "png": +// $result = $image->setOption('png:compression-level', 9); +// if ($result !== true) { +// throw new GraphicsException("Could not set png compression option"); +// } +// break; +// case Graphics::WEBP_LOSSLESS: +// $result = $image->setOption('webp:lossless', true); +// if ($result !== true) { +// throw new GraphicsException("Could not set lossless webp option"); +// } +// break; +// default: +// $result = $image->setImageCompressionQuality($output_quality); +// if ($result !== true) { +// throw new GraphicsException("Could not set compression quality for $path to $output_quality"); +// } +// break; +// } +// +// if (self::supports_alpha($format)) { +// $result = $image->setImageBackgroundColor(new \ImagickPixel('transparent')); +// } else { +// $result = $image->setImageBackgroundColor(new \ImagickPixel('black')); +// } +// if ($result !== true) { +// throw new GraphicsException("Could not set background color"); +// } +// +// +// if ($minimize) { +// $profiles = $image->getImageProfiles("icc", true); +// $result = $image->stripImage(); +// if ($result !== true) { +// throw new GraphicsException("Could not strip information from image"); +// } +// if (!empty($profiles)) { +// $image->profileImage("icc", $profiles['icc']); +// } +// } +// +// $ext = self::determine_ext($format); +// +// $result = $image->writeImage($ext . ":" . $path); +// if ($result !== true) { +// throw new GraphicsException("Could not write image to $path"); +// } +// } + +// public static function image_resize_imagick( +// String $input_path, +// String $input_type, +// int $new_width, +// int $new_height, +// string $output_filename, +// string $output_type = null, +// bool $ignore_aspect_ratio = false, +// int $output_quality = 80, +// bool $minimize = false, +// bool $allow_upscale = true +// ): void +// { +// global $config; +// +// if (!empty($input_type)) { +// $input_type = self::determine_ext($input_type); +// } +// +// try { +// $image = new Imagick($input_type . ":" . $input_path); +// try { +// $result = $image->flattenImages(); +// if ($result !== true) { +// throw new GraphicsException("Could not flatten image $input_path"); +// } +// +// $height = $image->getImageHeight(); +// $width = $image->getImageWidth(); +// if (!$allow_upscale && +// ($new_width > $width || $new_height > $height)) { +// $new_height = $height; +// $new_width = $width; +// } +// +// $result = $image->resizeImage($new_width, $new_width, Imagick::FILTER_LANCZOS, 0, !$ignore_aspect_ratio); +// if ($result !== true) { +// throw new GraphicsException("Could not perform image resize on $input_path"); +// } +// +// +// if (empty($output_type)) { +// $output_type = $input_type; +// } +// +// self::image_save_imagick($image, $output_filename, $output_type, $output_quality); +// +// } finally { +// $image->destroy(); +// } +// } catch (ImagickException $e) { +// throw new GraphicsException("Error while resizing with Imagick: " . $e->getMessage(), $e->getCode(), $e); +// } +// } + + + public static function image_resize_convert( + String $input_path, + String $input_type, + int $new_width, + int $new_height, + string $output_filename, + string $output_type = null, + bool $ignore_aspect_ratio = false, + int $output_quality = 80, + bool $minimize = false, + bool $allow_upscale = true + ): void + { + global $config; + + $convert = $config->get_string(GraphicsConfig::CONVERT_PATH); + + if ($convert == null || $convert == "") { + throw new GraphicsException("convert command not configured"); + } + + if (empty($output_type)) { + $output_type = $input_type; + } + + $bg = "black"; + if (self::supports_alpha($output_type)) { + $bg = "none"; + } + if (!empty($input_type)) { + $input_type = $input_type . ":"; + } + $args = ""; + if ($minimize) { + $args = " -strip -thumbnail"; + } + + $resize_args = ""; + if (!$allow_upscale) { + $resize_args .= "\>"; + } + if ($ignore_aspect_ratio) { + $resize_args .= "\!"; + } + + $format = '"%s" -flatten %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_type, $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) { + throw new GraphicsException("Resizing image with command `$cmd`, returns $ret, outputting " . implode("\r\n", $output)); + } else { + log_debug('graphics', "Generating thumbnail with command `$cmd`, returns $ret"); + } + } + + /** + * Performs a resize operation on an image file using GD. + * + * @param String $image_filename The source file to be resized. + * @param array $info The output of getimagesize() for the source file. + * @param int $new_width + * @param int $new_height + * @param string $output_filename + * @param string|null $output_type If set to null, the output file type will be automatically determined via the $info parameter. Otherwise an exception will be thrown. + * @param int $output_quality Defaults to 80. + * @throws GraphicsException + * @throws InsufficientMemoryException if the estimated memory usage exceeds the memory limit. + */ + public static function image_resize_gd( + String $image_filename, + array $info, + int $new_width, + int $new_height, + string $output_filename, + string $output_type = null, + bool $ignore_aspect_ratio = false, + int $output_quality = 80, + bool $allow_upscale = true + ) + { + $width = $info[0]; + $height = $info[1]; + + if ($output_type == null) { + /* If not specified, output to the same format as the original image */ + switch ($info[2]) { + case IMAGETYPE_GIF: + $output_type = "gif"; + break; + case IMAGETYPE_JPEG: + $output_type = "jpeg"; + break; + case IMAGETYPE_PNG: + $output_type = "png"; + break; + case IMAGETYPE_WEBP: + $output_type = "webp"; + break; + case IMAGETYPE_BMP: + $output_type = "bmp"; + break; + default: + throw new GraphicsException("Failed to save the new image - Unsupported image type."); + } + } + + $memory_use = self::calc_memory_use($info); + $memory_limit = get_memory_limit(); + if ($memory_use > $memory_limit) { + throw new InsufficientMemoryException("The image is too large to resize given the memory limits. ($memory_use > $memory_limit)"); + } + + if (!$ignore_aspect_ratio) { + list($new_width, $new_height) = get_scaled_by_aspect_ratio($width, $height, $new_width, $new_height); + } + if (!$allow_upscale && + ($new_width > $width || $new_height > $height)) { + $new_height = $height; + $new_width = $width; + } + + $image = imagecreatefromstring(file_get_contents($image_filename)); + $image_resized = imagecreatetruecolor($new_width, $new_height); + try { + if ($image === false) { + throw new GraphicsException("Could not load image: " . $image_filename); + } + if ($image_resized === false) { + throw new GraphicsException("Could not create output image with dimensions $new_width c $new_height "); + } + + // Handle transparent images + switch ($info[2]) { + case IMAGETYPE_GIF: + $transparency = imagecolortransparent($image); + $pallet_size = imagecolorstotal($image); + + // If we have a specific transparent color + if ($transparency >= 0 && $transparency < $pallet_size) { + // Get the original image's transparent color's RGB values + $transparent_color = imagecolorsforindex($image, $transparency); + + // Allocate the same color in the new image resource + $transparency = imagecolorallocate($image_resized, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']); + if ($transparency === false) { + throw new GraphicsException("Unable to allocate transparent color"); + } + + // Completely fill the background of the new image with allocated color. + if (imagefill($image_resized, 0, 0, $transparency) === false) { + throw new GraphicsException("Unable to fill new image with transparent color"); + } + + // Set the background color for new image to transparent + imagecolortransparent($image_resized, $transparency); + } + break; + case IMAGETYPE_PNG: + case IMAGETYPE_WEBP: + // + // More info here: http://stackoverflow.com/questions/279236/how-do-i-resize-pngs-with-transparency-in-php + // + if (imagealphablending($image_resized, false) === false) { + throw new GraphicsException("Unable to disable image alpha blending"); + } + if (imagesavealpha($image_resized, true) === false) { + throw new GraphicsException("Unable to enable image save alpha"); + } + $transparent_color = imagecolorallocatealpha($image_resized, 255, 255, 255, 127); + if ($transparent_color === false) { + throw new GraphicsException("Unable to allocate transparent color"); + } + if (imagefilledrectangle($image_resized, 0, 0, $new_width, $new_height, $transparent_color) === false) { + throw new GraphicsException("Unable to fill new image with transparent color"); + } + break; + } + + // Actually resize the image. + if (imagecopyresampled( + $image_resized, + $image, + 0, + 0, + 0, + 0, + $new_width, + $new_height, + $width, + $height + ) === false) { + throw new GraphicsException("Unable to copy resized image data to new image"); + } + + switch ($output_type) { + case "bmp": + $result = imagebmp($image_resized, $output_filename, true); + break; + case "webp": + case Graphics::WEBP_LOSSY: + $result = imagewebp($image_resized, $output_filename, $output_quality); + break; + case "jpg": + case "jpeg": + $result = imagejpeg($image_resized, $output_filename, $output_quality); + break; + case "png": + $result = imagepng($image_resized, $output_filename, 9); + break; + case "gif": + $result = imagegif($image_resized, $output_filename); + break; + default: + throw new GraphicsException("Failed to save the new image - Unsupported image type: $output_type"); + } + if ($result === false) { + throw new GraphicsException("Failed to save the new image, function returned false when saving type: $output_type"); + } + } finally { + @imagedestroy($image); + @imagedestroy($image_resized); + } + } + + /** + * Determines if a file is an animated gif. + * + * @param String $image_filename The path of the file to check. + * @return bool true if the file is an animated gif, false if it is not. + */ + public static function is_animated_gif(String $image_filename) + { + $is_anim_gif = 0; + if (($fh = @fopen($image_filename, 'rb'))) { + //check if gif is animated (via http://www.php.net/manual/en/function.imagecreatefromgif.php#104473) + while (!feof($fh) && $is_anim_gif < 2) { + $chunk = fread($fh, 1024 * 100); + $is_anim_gif += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches); + } + } + return ($is_anim_gif == 0); + } + + public static function supports_alpha(string $format) + { + return in_array(self::normalize_format($format), self::ALPHA_FORMATS); + } + + public static function is_input_supported($engine, $format): bool + { + $format = self::normalize_format($format); + if (!in_array($format, Graphics::ENGINE_INPUT_SUPPORT[$engine])) { + return false; + } + return true; + } + + public static function is_output_supported($engine, $format): bool + { + $format = self::normalize_format($format); + if (!in_array($format, Graphics::ENGINE_OUTPUT_SUPPORT[$engine])) { + return false; + } + return true; + } + + /** + * Checks if a format (normally a file extension) is a variant name of another format (ie, jpg and jpeg). + * If one is found, then the maine name that the Graphics extension will recognize is returned, + * otherwise the incoming format is returned. + * + * @param $format + * @return string|null The format name that the graphics extension will recognize. + */ + static public function normalize_format($format): ?string + { + if (array_key_exists($format, Graphics::FORMAT_ALIASES)) { + return self::FORMAT_ALIASES[$format]; + } + return $format; + } + + + /** + * Determines the dimensions of a video file using ffmpeg. + * + * @param string $filename + * @return array [width, height] + */ + static public function video_size(string $filename): array + { + global $config; + $ffmpeg = $config->get_string(GraphicsConfig::FFMPEG_PATH); + $cmd = escapeshellcmd(implode(" ", [ + escapeshellarg($ffmpeg), + "-y", "-i", escapeshellarg($filename), + "-vstats" + ])); + $output = shell_exec($cmd . " 2>&1"); + // error_log("Getting size with `$cmd`"); + + $regex_sizes = "/Video: .* ([0-9]{1,4})x([0-9]{1,4})/"; + if (preg_match($regex_sizes, $output, $regs)) { + if (preg_match("/displaymatrix: rotation of (90|270).00 degrees/", $output)) { + $size = [$regs[2], $regs[1]]; + } else { + $size = [$regs[1], $regs[2]]; + } + } else { + $size = [1, 1]; + } + log_debug('graphics', "Getting video size with `$cmd`, returns $output -- $size[0], $size[1]"); + return $size; + } + +} diff --git a/ext/graphics/theme.php b/ext/graphics/theme.php new file mode 100644 index 00000000..6210644e --- /dev/null +++ b/ext/graphics/theme.php @@ -0,0 +1,6 @@ +getMessage()); + return false; + } } } diff --git a/ext/handle_pixel/main.php b/ext/handle_pixel/main.php index ac72bcc1..f5c9b3f1 100644 --- a/ext/handle_pixel/main.php +++ b/ext/handle_pixel/main.php @@ -56,24 +56,22 @@ class PixelFileHandler extends DataHandlerExtension protected function create_thumb(string $hash, string $type): bool { - global $config; - - $inname = warehouse_path(Image::IMAGE_DIR, $hash); - $outname = warehouse_path(Image::THUMBNAIL_DIR, $hash); - - $ok = false; - - switch ($config->get_string("thumb_engine")) { - default: - case 'gd': - $ok = $this->make_thumb_gd($inname, $outname); - break; - case 'convert': - $ok = create_thumbnail_convert($hash); - break; + try { + create_image_thumb($hash, $type); + return true; + } catch (InsufficientMemoryException $e) { + $tsize = get_thumbnail_max_size_scaled(); + $thumb = imagecreatetruecolor($tsize[0], min($tsize[1], 64)); + $white = imagecolorallocate($thumb, 255, 255, 255); + $black = imagecolorallocate($thumb, 0, 0, 0); + imagefill($thumb, 0, 0, $white); + log_warning("handle_pixel", "Insufficient memory while creating thumbnail: ".$e->getMessage()); + imagestring($thumb, 5, 10, 24, "Image Too Large :(", $black); + return true; + } catch (Exception $e) { + log_error("handle_pixel", "Error while creating thumbnail: ".$e->getMessage()); + return false; } - - return $ok; } public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) @@ -90,38 +88,4 @@ class PixelFileHandler extends DataHandlerExtension ", 20); } - // GD thumber {{{ - private function make_thumb_gd(string $inname, string $outname): bool - { - global $config; - - try { - $info = getimagesize($inname); - $tsize = get_thumbnail_size($info[0], $info[1], true); - $image = image_resize_gd( - $inname, - $info, - $tsize[0], - $tsize[1], - $outname, - $config->get_string('thumb_type'), - $config->get_int('thumb_quality') - ); - } catch (InsufficientMemoryException $e) { - $tsize = get_thumbnail_max_size_scaled(); - $thumb = imagecreatetruecolor($tsize[0], min($tsize[1], 64)); - $white = imagecolorallocate($thumb, 255, 255, 255); - $black = imagecolorallocate($thumb, 0, 0, 0); - imagefill($thumb, 0, 0, $white); - log_warning("handle_pixel", "Insufficient memory while creating thumbnail: ".$e->getMessage()); - imagestring($thumb, 5, 10, 24, "Image Too Large :(", $black); - return true; - } catch (Exception $e) { - log_error("handle_pixel", "Error while creating thumbnail: ".$e->getMessage()); - return false; - } - - return true; - } - // }}} } diff --git a/ext/handle_pixel/theme.php b/ext/handle_pixel/theme.php index b8ebd36d..278ed0d7 100644 --- a/ext/handle_pixel/theme.php +++ b/ext/handle_pixel/theme.php @@ -7,7 +7,7 @@ class PixelFileHandlerTheme extends Themelet global $config; $u_ilink = $image->get_image_link(); - if ($config->get_bool("image_show_meta") && function_exists("exif_read_data")) { + if ($config->get_bool(ImageConfig::SHOW_META) && function_exists(ImageIO::EXIF_READ_FUNCTION)) { # FIXME: only read from jpegs? $exif = @exif_read_data($image->get_image_filename(), 0, true); if ($exif) { diff --git a/ext/handle_svg/main.php b/ext/handle_svg/main.php index 9998a245..da4c9ff4 100644 --- a/ext/handle_svg/main.php +++ b/ext/handle_svg/main.php @@ -35,10 +35,14 @@ class SVGFileHandler extends DataHandlerExtension protected function create_thumb(string $hash, string $type): bool { - if (!create_thumbnail_convert($hash)) { + try { + create_image_thumb($hash, $type, Graphics::IMAGICK_ENGINE); + return true; + } catch (GraphicsException $e) { + log_warning("handle_svg", "Could not generate thumbnail. " . $e->getMessage()); copy("ext/handle_svg/thumb.jpg", warehouse_path(Image::THUMBNAIL_DIR, $hash)); + return false; } - return true; } public function onDisplayingImage(DisplayingImageEvent $event) diff --git a/ext/handle_video/main.php b/ext/handle_video/main.php index f4f50320..15f192cc 100644 --- a/ext/handle_video/main.php +++ b/ext/handle_video/main.php @@ -16,20 +16,21 @@ class VideoFileHandler extends DataHandlerExtension { + const SUPPORTED_MIME = [ + 'video/webm', + 'video/mp4', + 'video/ogg', + 'video/flv', + 'video/x-flv' + ]; + const SUPPORTED_EXT = ["flv", "mp4", "m4v", "ogv", "webm"]; + public function onInitExt(InitExtEvent $event) { global $config; if ($config->get_int("ext_handle_video_version") < 1) { - if ($ffmpeg = shell_exec((PHP_OS == 'WINNT' ? 'where' : 'which') . ' ffmpeg')) { - //ffmpeg exists in PATH, check if it's executable, and if so, default to it instead of static - if (is_executable(strtok($ffmpeg, PHP_EOL))) { - $config->set_default_string('thumb_ffmpeg_path', 'ffmpeg'); - } - } else { - $config->set_default_string('thumb_ffmpeg_path', ''); - } - + // This used to set the ffmpeg path. It does not do this anymore, that is now in the base graphic extension. $config->set_int("ext_handle_video_version", 1); log_info("handle_video", "extension installed"); } @@ -41,9 +42,6 @@ class VideoFileHandler extends DataHandlerExtension public function onSetupBuilding(SetupBuildingEvent $event) { $sb = new SetupBlock("Video Options"); - $sb->add_label("
Path to ffmpeg: "); - $sb->add_text_option("thumb_ffmpeg_path"); - $sb->add_label("
"); $sb->add_bool_option("video_playback_autoplay", "Autoplay: "); $sb->add_label("
"); $sb->add_bool_option("video_playback_loop", "Loop: "); @@ -55,20 +53,19 @@ class VideoFileHandler extends DataHandlerExtension */ protected function create_thumb(string $hash, string $type): bool { - return create_thumbnail_ffmpeg($hash); + return Graphics::create_thumbnail_ffmpeg($hash); } protected function supported_ext(string $ext): bool { - $exts = ["flv", "mp4", "m4v", "ogv", "webm"]; - return in_array(strtolower($ext), $exts); + return in_array(strtolower($ext), self::SUPPORTED_EXT); } protected function create_image_from_data(string $filename, array $metadata): Image { $image = new Image(); - $size = video_size($filename); + $size = Graphics::video_size($filename); $image->width = $size[0]; $image->height = $size[1]; @@ -103,13 +100,7 @@ class VideoFileHandler extends DataHandlerExtension { return ( file_exists($tmpname) && - in_array(getMimeType($tmpname), [ - 'video/webm', - 'video/mp4', - 'video/ogg', - 'video/flv', - 'video/x-flv' - ]) + in_array(getMimeType($tmpname), self::SUPPORTED_MIME) ); } } diff --git a/ext/image/main.php b/ext/image/main.php index c460660c..dc53ee7c 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -9,30 +9,64 @@ */ +abstract class ImageConfig { + const THUMB_ENGINE = 'thumb_engine'; + const THUMB_WIDTH = 'thumb_width'; + const THUMB_HEIGHT = 'thumb_height'; + const THUMB_SCALING = 'thumb_scaling'; + const THUMB_QUALITY = 'thumb_quality'; + const THUMB_TYPE = 'thumb_type'; + + const SHOW_META = 'image_show_meta'; + const ILINK = 'image_ilink'; + const TLINK = 'image_tlink'; + const TIP = 'image_tip'; + const EXPIRES = 'image_expires'; + const UPLOAD_COLLISION_HANDLER = 'upload_collision_handler'; + + const COLLISION_MERGE = 'merge'; + const COLLISION_ERROR = 'error'; + +} + /** * A class to handle adding / getting / removing image files from the disk. */ class ImageIO extends Extension { + + const COLLISION_OPTIONS = ['Error'=>ImageConfig::COLLISION_ERROR, 'Merge'=>ImageConfig::COLLISION_MERGE]; + + const EXIF_READ_FUNCTION = "exif_read_data"; + + + const THUMBNAIL_ENGINES = [ + 'Built-in GD' => Graphics::GD_ENGINE, + 'ImageMagick' => Graphics::IMAGICK_ENGINE + ]; + + const THUMBNAIL_TYPES = [ + 'JPEG' => "jpg", + 'WEBP (Not IE/Safari compatible)' => "webp" + ]; + public function onInitExt(InitExtEvent $event) { global $config; - $config->set_default_int('thumb_width', 192); - $config->set_default_int('thumb_height', 192); - $config->set_default_int('thumb_scaling', 100); - $config->set_default_int('thumb_quality', 75); - $config->set_default_string('thumb_type', 'jpg'); - $config->set_default_int('thumb_mem_limit', parse_shorthand_int('8MB')); - $config->set_default_string('thumb_convert_path', 'convert'); + $config->set_default_int(ImageConfig::THUMB_WIDTH, 192); + $config->set_default_int(ImageConfig::THUMB_HEIGHT, 192); + $config->set_default_int(ImageConfig::THUMB_SCALING, 100); + $config->set_default_int(ImageConfig::THUMB_QUALITY, 75); + $config->set_default_string(ImageConfig::THUMB_TYPE, 'jpg'); - if (function_exists("exif_read_data")) { - $config->set_default_bool('image_show_meta', false); + if (function_exists(self::EXIF_READ_FUNCTION)) { + $config->set_default_bool(ImageConfig::SHOW_META, false); } - $config->set_default_string('image_ilink', ''); - $config->set_default_string('image_tlink', ''); - $config->set_default_string('image_tip', '$tags // $size // $filesize'); - $config->set_default_string('upload_collision_handler', 'error'); - $config->set_default_int('image_expires', (60*60*24*31)); // defaults to one month + $config->set_default_string(ImageConfig::ILINK, ''); + $config->set_default_string(ImageConfig::TLINK, ''); + $config->set_default_string(ImageConfig::TIP, '$tags // $size // $filesize'); + $config->set_default_string(ImageConfig::UPLOAD_COLLISION_HANDLER, ImageConfig::COLLISION_ERROR); + $config->set_default_int(ImageConfig::EXPIRES, (60*60*24*31)); // defaults to one month } public function onPageRequest(PageRequestEvent $event) @@ -125,50 +159,36 @@ class ImageIO extends Extension $sb = new SetupBlock("Image Options"); $sb->position = 30; // advanced only - //$sb->add_text_option("image_ilink", "Image link: "); - //$sb->add_text_option("image_tlink", "
Thumbnail link: "); - $sb->add_text_option("image_tip", "Image tooltip: "); - $sb->add_choice_option("upload_collision_handler", ['Error'=>'error', 'Merge'=>'merge'], "
Upload collision handler: "); - if (function_exists("exif_read_data")) { - $sb->add_bool_option("image_show_meta", "
Show metadata: "); + //$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: "); + if (function_exists(self::EXIF_READ_FUNCTION)) { + $sb->add_bool_option(ImageConfig::SHOW_META, "
Show metadata: "); } $event->panel->add_block($sb); - $thumbers = []; - $thumbers['Built-in GD'] = "gd"; - $thumbers['ImageMagick'] = "convert"; - $thumb_types = []; - $thumb_types['JPEG'] = "jpg"; - $thumb_types['WEBP (Not IE/Safari compatible)'] = "webp"; $sb = new SetupBlock("Thumbnailing"); - $sb->add_choice_option("thumb_engine", $thumbers, "Engine: "); + $sb->add_choice_option(ImageConfig::THUMB_ENGINE, self::THUMBNAIL_ENGINES, "Engine: "); $sb->add_label("
"); - $sb->add_choice_option("thumb_type", $thumb_types, "Filetype: "); + $sb->add_choice_option(ImageConfig::THUMB_TYPE, self::THUMBNAIL_TYPES, "Filetype: "); $sb->add_label("
Size "); - $sb->add_int_option("thumb_width"); + $sb->add_int_option(ImageConfig::THUMB_WIDTH); $sb->add_label(" x "); - $sb->add_int_option("thumb_height"); + $sb->add_int_option(ImageConfig::THUMB_HEIGHT); $sb->add_label(" px at "); - $sb->add_int_option("thumb_quality"); + $sb->add_int_option(ImageConfig::THUMB_QUALITY); $sb->add_label(" % quality "); $sb->add_label("
High-DPI scaling "); - $sb->add_int_option("thumb_scaling"); + $sb->add_int_option(ImageConfig::THUMB_SCALING); $sb->add_label("%"); - if ($config->get_string("thumb_engine") == "convert") { - $sb->add_label("
ImageMagick Binary: "); - $sb->add_text_option("thumb_convert_path"); - } - - if ($config->get_string("thumb_engine") == "gd") { - $sb->add_shorthand_int_option("thumb_mem_limit", "
Max memory use: "); - } $event->panel->add_block($sb); } @@ -193,8 +213,8 @@ class ImageIO extends Extension */ $existing = Image::by_hash($image->hash); if (!is_null($existing)) { - $handler = $config->get_string("upload_collision_handler"); - if ($handler == "merge" || isset($_GET['update'])) { + $handler = $config->get_string(ImageConfig::UPLOAD_COLLISION_HANDLER); + if ($handler == ImageConfig::COLLISION_MERGE || isset($_GET['update'])) { $merged = array_merge($image->get_tag_array(), $existing->get_tag_array()); send_event(new TagSetEvent($existing, $merged)); if (isset($_GET['rating']) && isset($_GET['update']) && ext_is_live("Ratings")) { @@ -256,7 +276,7 @@ class ImageIO extends Extension global $page; if (!is_null($image)) { if ($type == "thumb") { - $ext = $config->get_string("thumb_type"); + $ext = $config->get_string(ImageConfig::THUMB_TYPE); if (array_key_exists($ext, MIME_TYPE_MAP)) { $page->set_type(MIME_TYPE_MAP[$ext]); } else { @@ -289,8 +309,8 @@ class ImageIO extends Extension $page->set_file($file); - if ($config->get_int("image_expires")) { - $expires = date(DATE_RFC1123, time() + $config->get_int("image_expires")); + if ($config->get_int(ImageConfig::EXPIRES)) { + $expires = date(DATE_RFC1123, time() + $config->get_int(ImageConfig::EXPIRES)); } else { $expires = 'Fri, 2 Sep 2101 12:42:42 GMT'; // War was beginning } diff --git a/ext/ouroboros_api/main.php b/ext/ouroboros_api/main.php index 91fd85ef..d701fbea 100644 --- a/ext/ouroboros_api/main.php +++ b/ext/ouroboros_api/main.php @@ -231,8 +231,8 @@ class _SafeOuroborosImage $this->has_notes = false; // thumb - $this->preview_height = $config->get_int('thumb_height'); - $this->preview_width = $config->get_int('thumb_width'); + $this->preview_height = $config->get_int(ImageConfig::THUMB_HEIGHT); + $this->preview_width = $config->get_int(ImageConfig::THUMB_WIDTH); $this->preview_url = make_http($img->get_thumb_link()); // sample (use the full image here) @@ -481,8 +481,8 @@ class OuroborosAPI extends Extension protected function postCreate(OuroborosPost $post, string $md5 = '') { global $config; - $handler = $config->get_string("upload_collision_handler"); - if (!empty($md5) && !($handler == 'merge')) { + $handler = $config->get_string(ImageConfig::UPLOAD_COLLISION_HANDLER); + if (!empty($md5) && !($handler == ImageConfig::COLLISION_MERGE)) { $img = Image::by_hash($md5); if (!is_null($img)) { $this->sendResponse(420, self::ERROR_POST_CREATE_DUPE); @@ -524,8 +524,8 @@ class OuroborosAPI extends Extension if (!empty($meta['hash'])) { $img = Image::by_hash($meta['hash']); if (!is_null($img)) { - $handler = $config->get_string("upload_collision_handler"); - if ($handler == "merge") { + $handler = $config->get_string(ImageConfig::UPLOAD_COLLISION_HANDLER); + if ($handler == ImageConfig::COLLISION_MERGE) { $postTags = is_array($post->tags) ? $post->tags : Tag::explode($post->tags); $merged = array_merge($postTags, $img->get_tag_array()); send_event(new TagSetEvent($img, $merged)); diff --git a/ext/report_image/theme.php b/ext/report_image/theme.php index a2a80f91..f4de5934 100644 --- a/ext/report_image/theme.php +++ b/ext/report_image/theme.php @@ -46,7 +46,7 @@ class ReportImageTheme extends Themelet "; } - $thumb_width = $config->get_int("thumb_width"); + $thumb_width = $config->get_int(ImageConfig::THUMB_WIDTH); $html = " diff --git a/ext/resize/main.php b/ext/resize/main.php index 08036260..8cbb7dd6 100644 --- a/ext/resize/main.php +++ b/ext/resize/main.php @@ -26,8 +26,6 @@ abstract class ResizeConfig */ class ResizeImage extends Extension { - const SUPPORTED_EXT = ["jpg","jpeg","png","gif","webp"]; - /** * Needs to be after the data processing extensions */ @@ -40,17 +38,18 @@ class ResizeImage extends Extension public function onInitExt(InitExtEvent $event) { global $config; - $config->set_default_bool('resize_enabled', true); - $config->set_default_bool('resize_upload', false); - $config->set_default_int('resize_default_width', 0); - $config->set_default_int('resize_default_height', 0); + $config->set_default_bool(ResizeConfig::ENABLED, true); + $config->set_default_bool(ResizeConfig::UPLOAD, false); + $config->set_default_string(ResizeConfig::ENGINE, Graphics::GD_ENGINE); + $config->set_default_int(ResizeConfig::DEFAULT_WIDTH, 0); + $config->set_default_int(ResizeConfig::DEFAULT_HEIGHT, 0); } public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) { global $user, $config; - if ($user->is_admin() && $config->get_bool("resize_enabled") - && in_array($event->image->ext, self::SUPPORTED_EXT)) { + if ($user->is_admin() && $config->get_bool(ResizeConfig::ENABLED) + && $this->can_resize_format($event->image->ext)) { /* Add a link to resize the image */ $event->add_part($this->theme->get_resize_html($event->image)); } @@ -60,6 +59,7 @@ class ResizeImage extends Extension { $sb = new SetupBlock("Image Resize"); $sb->start_table(); + $sb->add_choice_option(ResizeConfig::ENGINE, Media::IMAGE_MEDIA_ENGINES, "Engine: ", true); $sb->add_bool_option(ResizeConfig::ENABLED, "Allow resizing images: ", true); $sb->add_bool_option(ResizeConfig::UPLOAD, "Resize on upload: ", true); $sb->end_table(); @@ -82,15 +82,15 @@ class ResizeImage extends Extension $image_obj = Image::by_id($event->image_id); - if ($config->get_bool("resize_upload") == true - && in_array($event->type, self::SUPPORTED_EXT)) { + if ($config->get_bool(ResizeConfig::UPLOAD) == true + && $this->can_resize_format($event->type)) { $width = $height = 0; - if ($config->get_int("resize_default_width") !== 0) { - $height = $config->get_int("resize_default_width"); + if ($config->get_int(ResizeConfig::DEFAULT_WIDTH) !== 0) { + $height = $config->get_int(ResizeConfig::DEFAULT_WIDTH); } - if ($config->get_int("resize_default_height") !== 0) { - $height = $config->get_int("resize_default_height"); + if ($config->get_int(ResizeConfig::DEFAULT_HEIGHT) !== 0) { + $height = $config->get_int(ResizeConfig::DEFAULT_HEIGHT); } $isanigif = 0; if ($image_obj->ext == "gif") { @@ -169,8 +169,16 @@ class ResizeImage extends Extension } } } - - + + private function can_resize_format($format): bool + { + global $config; + $engine = $config->get_string(ResizeConfig::ENGINE); + return Graphics::is_input_supported($engine, $format) + && Graphics::is_output_supported($engine, $format); + } + + // Private functions /* ----------------------------- */ private function resize_image(Image $image_obj, int $width, int $height) @@ -197,7 +205,15 @@ class ResizeImage extends Extension throw new ImageResizeException("Unable to save temporary image file."); } - image_resize_gd($image_filename, $info, $new_width, $new_height, $tmp_filename); + send_event(new GraphicResizeEvent( + Graphics::GD_ENGINE, + $image_filename, + $image_obj->ext, + $tmp_filename, + $new_width, + $new_height, + true + )); $new_image = new Image(); $new_image->hash = md5_file($tmp_filename); diff --git a/ext/resize/theme.php b/ext/resize/theme.php index 0cc86777..302af40f 100644 --- a/ext/resize/theme.php +++ b/ext/resize/theme.php @@ -9,8 +9,8 @@ class ResizeImageTheme extends Themelet { global $config; - $default_width = $config->get_int('resize_default_width'); - $default_height = $config->get_int('resize_default_height'); + $default_width = $config->get_int(ResizeConfig::DEFAULT_WIDTH); + $default_height = $config->get_int(ResizeConfig::DEFAULT_HEIGHT); if (!$default_width) { $default_width = $image->width; diff --git a/ext/rotate/main.php b/ext/rotate/main.php index 65194bbc..bf539ba8 100644 --- a/ext/rotate/main.php +++ b/ext/rotate/main.php @@ -130,7 +130,7 @@ class RotateImage extends Extension $info = getimagesize($image_filename); - $memory_use =calc_memory_use($info); + $memory_use = Graphics::calc_memory_use($info); $memory_limit = get_memory_limit(); if ($memory_use > $memory_limit) { diff --git a/ext/rule34/main.php b/ext/rule34/main.php index e1e6f5a3..b26fb166 100644 --- a/ext/rule34/main.php +++ b/ext/rule34/main.php @@ -30,7 +30,7 @@ class Rule34 extends Extension public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event) { global $config; - $image_link = $config->get_string('image_ilink'); + $image_link = $config->get_string(ImageConfig::ILINK); $url0 = $event->image->parse_link_template($image_link, "url_escape", 0); $url1 = $event->image->parse_link_template($image_link, "url_escape", 1); $html = ""; diff --git a/ext/setup/test.php b/ext/setup/test.php index c2bb2874..9f55a090 100644 --- a/ext/setup/test.php +++ b/ext/setup/test.php @@ -39,6 +39,6 @@ class SetupTest extends ShimmiePHPUnitTestCase $this->log_in_as_admin(); $this->get_page('setup/advanced'); $this->assert_title("Shimmie Setup"); - $this->assert_text("thumb_quality"); + $this->assert_text(ImageConfig::THUMB_QUALITY); } } diff --git a/ext/transcode/main.php b/ext/transcode/main.php index 9c8d3185..d5562e8c 100644 --- a/ext/transcode/main.php +++ b/ext/transcode/main.php @@ -33,50 +33,6 @@ class TranscodeImage extends Extension { const ACTION_BULK_TRANSCODE = "bulk_transcode"; - const CONVERSION_ENGINES = [ - "GD" => "gd", - "ImageMagick" => "convert", - ]; - - const ENGINE_INPUT_SUPPORT = [ - "gd" => [ - "bmp", - "gif", - "jpg", - "png", - "webp", - ], - "convert" => [ - "bmp", - "gif", - "jpg", - "png", - "psd", - "tiff", - "webp", - "ico", - ] - ]; - - const ENGINE_OUTPUT_SUPPORT = [ - "gd" => [ - "jpg", - "png", - "webp-lossy", - ], - "convert" => [ - "jpg", - "png", - "webp-lossy", - "webp-lossless", - ] - ]; - - const LOSSLESS_FORMATS = [ - "webp-lossless", - "png", - ]; - const INPUT_FORMATS = [ "BMP" => "bmp", "GIF" => "gif", @@ -88,17 +44,12 @@ class TranscodeImage extends Extension "WEBP" => "webp", ]; - const FORMAT_ALIASES = [ - "tif" => "tiff", - "jpeg" => "jpg", - ]; - const OUTPUT_FORMATS = [ "" => "", "JPEG (lossy)" => "jpg", "PNG (lossless)" => "png", - "WEBP (lossy)" => "webp-lossy", - "WEBP (lossless)" => "webp-lossless", + "WEBP (lossy)" => Graphics::WEBP_LOSSY, + "WEBP (lossless)" => Graphics::WEBP_LOSSLESS, ]; /** @@ -113,13 +64,13 @@ class TranscodeImage extends Extension public function onInitExt(InitExtEvent $event) { global $config; - $config->set_default_bool('transcode_enabled', true); - $config->set_default_bool('transcode_upload', false); - $config->set_default_string('transcode_engine', "gd"); - $config->set_default_int('transcode_quality', 80); + $config->set_default_bool(TranscodeConfig::ENABLED, true); + $config->set_default_bool(TranscodeConfig::UPLOAD, false); + $config->set_default_string(TranscodeConfig::ENGINE, Graphics::GD_ENGINE); + $config->set_default_int(TranscodeConfig::QUALITY, 80); foreach (array_values(self::INPUT_FORMATS) as $format) { - $config->set_default_string('transcode_upload_'.$format, ""); + $config->set_default_string(TranscodeConfig::UPLOAD_PREFIX.$format, ""); } } @@ -127,8 +78,8 @@ class TranscodeImage extends Extension { global $user, $config; - if ($user->is_admin() && $config->get_bool("resize_enabled")) { - $engine = $config->get_string("transcode_engine"); + if ($user->is_admin()) { + $engine = $config->get_string(TranscodeConfig::ENGINE); if ($this->can_convert_format($engine, $event->image->ext)) { $options = $this->get_supported_output_formats($engine, $event->image->ext); $event->add_part($this->theme->get_transcode_html($event->image, $options)); @@ -140,16 +91,16 @@ class TranscodeImage extends Extension { global $config; - $engine = $config->get_string("transcode_engine"); + $engine = $config->get_string(TranscodeConfig::ENGINE); $sb = new SetupBlock("Image Transcode"); $sb->start_table(); $sb->add_bool_option(TranscodeConfig::ENABLED, "Allow transcoding images: ", true); $sb->add_bool_option(TranscodeConfig::UPLOAD, "Transcode on upload: ", true); - $sb->add_choice_option(TranscodeConfig::ENGINE, self::CONVERSION_ENGINES, "Engine", true); + $sb->add_choice_option(TranscodeConfig::ENGINE, Graphics::IMAGE_GRAPHICS_ENGINES, "Engine", true); foreach (self::INPUT_FORMATS as $display=>$format) { - if (in_array($format, self::ENGINE_INPUT_SUPPORT[$engine])) { + if (in_array($format, Graphics::ENGINE_INPUT_SUPPORT[$engine])) { $outputs = $this->get_supported_output_formats($engine, $format); $sb->add_choice_option(TranscodeConfig::UPLOAD_PREFIX.$format, $outputs, "$display", true); } @@ -163,23 +114,23 @@ class TranscodeImage extends Extension { global $config, $page; - if ($config->get_bool("transcode_upload") == true) { + if ($config->get_bool(TranscodeConfig::UPLOAD) == true) { $ext = strtolower($event->type); - $ext = $this->clean_format($ext); + $ext = Graphics::normalize_format($ext); - if ($event->type=="gif"&&is_animated_gif($event->tmpname)) { + if ($event->type=="gif"&&Graphics::is_animated_gif($event->tmpname)) { return; } if (in_array($ext, array_values(self::INPUT_FORMATS))) { - $target_format = $config->get_string("transcode_upload_".$ext); + $target_format = $config->get_string(TranscodeConfig::UPLOAD_PREFIX.$ext); if (empty($target_format)) { return; } try { $new_image = $this->transcode_image($event->tmpname, $ext, $target_format); - $event->set_type($this->determine_ext($target_format)); + $event->set_type(Graphics::determine_ext($target_format)); $event->set_tmpname($new_image); } catch (Exception $e) { log_error("transcode", "Error while performing upload transcode: ".$e->getMessage()); @@ -227,7 +178,7 @@ class TranscodeImage extends Extension { global $user, $config; - $engine = $config->get_string("transcode_engine"); + $engine = $config->get_string(TranscodeConfig::ENGINE); if ($user->is_admin()) { $event->add_action(self::ACTION_BULK_TRANSCODE, "Transcode", null,"", $this->theme->get_transcode_picker_html($this->get_supported_output_formats($engine))); @@ -239,7 +190,7 @@ class TranscodeImage extends Extension global $user, $database; switch ($event->action) { - case "bulk_transcode": + case self::ACTION_BULK_TRANSCODE: if (!isset($_POST['transcode_format'])) { return; } @@ -251,8 +202,9 @@ class TranscodeImage extends Extension $database->beginTransaction(); $this->transcode_and_replace_image($image, $format); - // If a subsequent transcode fails, the database need to have everything about the previous transcodes recorded already, - // otherwise the image entries will be stuck pointing to missing image files + // If a subsequent transcode fails, the database needs to have everything about the previous + // transcodes recorded already, otherwise the image entries will be stuck pointing to + // missing image files $database->commit(); $total++; } catch (Exception $e) { @@ -269,54 +221,34 @@ class TranscodeImage extends Extension } } - private function clean_format($format): ?string - { - if (array_key_exists($format, self::FORMAT_ALIASES)) { - return self::FORMAT_ALIASES[$format]; - } - return $format; - } private function can_convert_format($engine, $format): bool { - $format = $this->clean_format($format); - if (!in_array($format, self::ENGINE_INPUT_SUPPORT[$engine])) { - return false; - } - return true; + return Graphics::is_input_supported($engine, $format); } + private function get_supported_output_formats($engine, ?String $omit_format = null): array { - $omit_format = $this->clean_format($omit_format); + $omit_format = Graphics::normalize_format($omit_format); $output = []; foreach (self::OUTPUT_FORMATS as $key=>$value) { if ($value=="") { $output[$key] = $value; continue; } - if (in_array($value, self::ENGINE_OUTPUT_SUPPORT[$engine]) - &&(empty($omit_format)||$omit_format!=$this->determine_ext($value))) { + if(Graphics::is_output_supported($engine, $value) + &&(empty($omit_format)||$omit_format!=Graphics::determine_ext($value))) { $output[$key] = $value; } } return $output; } - private function determine_ext(String $format): String - { - switch ($format) { - case "webp-lossless": - case "webp-lossy": - return "webp"; - default: - return $format; - } - } + private function transcode_and_replace_image(Image $image_obj, String $target_format) { - $target_format = $this->clean_format($target_format); $original_file = warehouse_path(Image::IMAGE_DIR, $image_obj->hash); $tmp_filename = $this->transcode_image($original_file, $image_obj->ext, $target_format); @@ -327,7 +259,7 @@ class TranscodeImage extends Extension $new_image->filename = $image_obj->filename; $new_image->width = $image_obj->width; $new_image->height = $image_obj->height; - $new_image->ext = $this->determine_ext($target_format); + $new_image->ext = Graphics::determine_ext($target_format); /* Move the new image into the main storage location */ $target = warehouse_path(Image::IMAGE_DIR, $new_image->hash); @@ -346,7 +278,7 @@ class TranscodeImage extends Extension { global $config; - if ($source_format==$this->determine_ext($target_format)) { + if ($source_format==Graphics::determine_ext($target_format)) { throw new ImageTranscodeException("Source and target formats are the same: ".$source_format); } @@ -357,7 +289,7 @@ class TranscodeImage extends Extension if (!$this->can_convert_format($engine, $source_format)) { throw new ImageTranscodeException("Engine $engine does not support input format $source_format"); } - if (!in_array($target_format, self::ENGINE_OUTPUT_SUPPORT[$engine])) { + if (!in_array($target_format, Graphics::ENGINE_OUTPUT_SUPPORT[$engine])) { throw new ImageTranscodeException("Engine $engine does not support output format $target_format"); } @@ -426,20 +358,20 @@ class TranscodeImage extends Extension global $config; $q = $config->get_int("transcode_quality"); - $convert = $config->get_string("thumb_convert_path"); + $convert = $config->get_string(GraphicsConfig::CONVERT_PATH); if ($convert==null||$convert=="") { throw new ImageTranscodeException("ImageMagick path not configured"); } - $ext = $this->determine_ext($target_format); + $ext = Graphics::determine_ext($target_format); $args = " -flatten "; $bg = "none"; switch ($target_format) { - case "webp-lossless": + case Graphics::WEBP_LOSSLESS: $args .= '-define webp:lossless=true'; break; - case "webp-lossy": + case Graphics::WEBP_LOSSY: $args .= ''; break; case "png":
ImageReasonAction
LinksImage Only (Backup Server)