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 = "
Image | Reason | Action | 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 = "
Links | Image Only (Backup Server) |
---|