New Graphics extension

Added constants to several extensions
This commit is contained in:
Matthew Barbour 2019-06-18 13:45:59 -05:00 committed by matthew
parent 3dce134fe9
commit 3859e27839
24 changed files with 1069 additions and 628 deletions

View file

@ -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 = "";

View file

@ -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') {

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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");

View file

@ -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']}

808
ext/graphics/main.php Normal file
View file

@ -0,0 +1,808 @@
<?php
/*
* Name: Graphics
* Author: Matthew Barbour <matthew@darkholme.net>
* 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("<b style='color:red'>ImageMagick not detected</b>");
// }
// } else {
$sb->add_text_option(GraphicsConfig::CONVERT_PATH, "convert command: ");
// }
$sb->add_text_option(GraphicsConfig::FFMPEG_PATH, "<br/>ffmpeg command: ");
$sb->add_shorthand_int_option(GraphicsConfig::MEM_LIMIT, "<br />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;
}
}

6
ext/graphics/theme.php Normal file
View file

@ -0,0 +1,6 @@
<?php
class GraphicsTheme extends Themelet
{
}

View file

@ -12,7 +12,7 @@ class FlashFileHandler extends DataHandlerExtension
{
global $config;
if (!create_thumbnail_ffmpeg($hash)) {
if (!Graphics::create_thumbnail_ffmpeg($hash)) {
copy("ext/handle_flash/thumb.jpg", warehouse_path(Image::THUMBNAIL_DIR, $hash));
}
return true;

View file

@ -56,6 +56,12 @@ class IcoFileHandler extends DataHandlerExtension
protected function create_thumb(string $hash, string $type): bool
{
return create_thumbnail_convert($hash, $type);
try {
create_image_thumb($hash, $type, Graphics::IMAGICK_ENGINE);
return true;
} catch (GraphicsException $e) {
log_warning("handle_ico", "Could not generate thumbnail. " . $e->getMessage());
return false;
}
}
}

View file

@ -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;
}
// }}}
}

View file

@ -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) {

View file

@ -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)

View file

@ -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("<br>Path to ffmpeg: ");
$sb->add_text_option("thumb_ffmpeg_path");
$sb->add_label("<br>");
$sb->add_bool_option("video_playback_autoplay", "Autoplay: ");
$sb->add_label("<br>");
$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)
);
}
}

View file

@ -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", "<br>Thumbnail link: ");
$sb->add_text_option("image_tip", "Image tooltip: ");
$sb->add_choice_option("upload_collision_handler", ['Error'=>'error', 'Merge'=>'merge'], "<br>Upload collision handler: ");
if (function_exists("exif_read_data")) {
$sb->add_bool_option("image_show_meta", "<br>Show metadata: ");
//$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: ");
if (function_exists(self::EXIF_READ_FUNCTION)) {
$sb->add_bool_option(ImageConfig::SHOW_META, "<br>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("<br>");
$sb->add_choice_option("thumb_type", $thumb_types, "Filetype: ");
$sb->add_choice_option(ImageConfig::THUMB_TYPE, self::THUMBNAIL_TYPES, "Filetype: ");
$sb->add_label("<br>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("<br>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("<br>ImageMagick Binary: ");
$sb->add_text_option("thumb_convert_path");
}
if ($config->get_string("thumb_engine") == "gd") {
$sb->add_shorthand_int_option("thumb_mem_limit", "<br>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
}

View file

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

View file

@ -46,7 +46,7 @@ class ReportImageTheme extends Themelet
";
}
$thumb_width = $config->get_int("thumb_width");
$thumb_width = $config->get_int(ImageConfig::THUMB_WIDTH);
$html = "
<table id='reportedimage' class='zebra'>
<thead><td width='$thumb_width'>Image</td><td>Reason</td><td width='128'>Action</td></thead>

View file

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

View file

@ -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;

View file

@ -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) {

View file

@ -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 = "<tr><th>Links</th><td><a href='$url0'>Image Only</a> (<a href='$url1'>Backup Server</a>)</td></tr>";

View file

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

View file

@ -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":