This repository has been archived on 2024-09-05. You can view files and clone it, but cannot push or open issues or pull requests.
shimmie2/ext/resize/main.php
2016-06-19 23:05:57 +01:00

333 lines
12 KiB
PHP

<?php
/*
* Name: Resize Image
* Author: jgen <jgen.tech@gmail.com>
* Description: Allows admins to resize images.
* License: GPLv2
* Version: 0.1
* Notice:
* The image resize and resample code is based off of the "smart_resize_image"
* function copyright 2008 Maxim Chernyak, released under a MIT-style license.
* Documentation:
* This extension allows admins to resize images.
*/
/**
* This class is just a wrapper around SCoreException.
*/
class ImageResizeException extends SCoreException {
public $error;
/** @param string $error */
public function __construct($error) {
$this->error = $error;
}
}
/**
* This class handles image resize requests.
*/
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);
}
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) {
global $user, $config;
if($user->is_admin() && $config->get_bool("resize_enabled")) {
/* Add a link to resize the image */
$event->add_part($this->theme->get_resize_html($event->image));
}
}
public function onSetupBuilding(SetupBuildingEvent $event) {
$sb = new SetupBlock("Image Resize");
$sb->add_bool_option("resize_enabled", "Allow resizing images: ");
$sb->add_bool_option("resize_upload", "<br>Resize on upload: ");
$sb->add_label("<br>Preset/Default Width: ");
$sb->add_int_option("resize_default_width");
$sb->add_label(" px");
$sb->add_label("<br>Preset/Default Height: ");
$sb->add_int_option("resize_default_height");
$sb->add_label(" px");
$sb->add_label("<br>(enter 0 for no default)");
$event->panel->add_block($sb);
}
public function onDataUpload(DataUploadEvent $event) {
global $config, $page;
$image_obj = Image::by_id($event->image_id);
if($config->get_bool("resize_upload") == true && ($image_obj->ext == "jpg" || $image_obj->ext == "png" || $image_obj->ext == "gif")){
$width = $height = 0;
if ($config->get_int("resize_default_width") !== 0) {
$height = $config->get_int("resize_default_width");
}
if ($config->get_int("resize_default_height") !== 0) {
$height = $config->get_int("resize_default_height");
}
$isanigif = 0;
if($image_obj->ext == "gif"){
$image_filename = warehouse_path("images", $image_obj->hash);
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) && $isanigif < 2) {
$chunk = fread($fh, 1024 * 100);
$isanigif += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches);
}
}
}
if($isanigif == 0){
try {
$this->resize_image($image_obj, $width, $height);
} catch (ImageResizeException $e) {
$this->theme->display_resize_error($page, "Error Resizing", $e->error);
}
//Need to generate thumbnail again...
//This only seems to be an issue if one of the sizes was set to 0.
$image_obj = Image::by_id($event->image_id); //Must be a better way to grab the new hash than setting this again..
send_event(new ThumbnailGenerationEvent($image_obj->hash, $image_obj->ext, true));
log_info("resize", "Image #{$event->image_id} has been resized to: ".$width."x".$height);
//TODO: Notify user that image has been resized.
}
}
}
public function onPageRequest(PageRequestEvent $event) {
global $page, $user;
if ( $event->page_matches("resize") && $user->is_admin() ) {
// Try to get the image ID
$image_id = int_escape($event->get_arg(0));
if (empty($image_id)) {
$image_id = isset($_POST['image_id']) ? int_escape($_POST['image_id']) : null;
}
if (empty($image_id)) {
throw new ImageResizeException("Can not resize Image: No valid Image ID given.");
}
$image = Image::by_id($image_id);
if(is_null($image)) {
$this->theme->display_error(404, "Image not found", "No image in the database has the ID #$image_id");
} else {
/* Check if options were given to resize an image. */
if (isset($_POST['resize_width']) || isset($_POST['resize_height'])) {
/* get options */
$width = $height = 0;
if (isset($_POST['resize_width'])) {
$width = int_escape($_POST['resize_width']);
}
if (isset($_POST['resize_height'])) {
$height = int_escape($_POST['resize_height']);
}
/* Attempt to resize the image */
try {
$this->resize_image($image, $width, $height);
//$this->theme->display_resize_page($page, $image_id);
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/".$image_id));
} catch (ImageResizeException $e) {
$this->theme->display_resize_error($page, "Error Resizing", $e->error);
}
}
}
}
}
// Private functions
/* ----------------------------- */
/**
* This function could be made much smaller by using the ImageReplaceEvent
* ie: Pretend that we are replacing the image with a resized copy.
*
* @param Image $image_obj
* @param int $width
* @param int $height
* @throws ImageResizeException
*/
private function resize_image(Image $image_obj, /*int*/ $width, /*int*/ $height) {
global $database;
if ( ($height <= 0) && ($width <= 0) ) {
throw new ImageResizeException("Invalid options for height and width. ($width x $height)");
}
$hash = $image_obj->hash;
$image_filename = warehouse_path("images", $hash);
$info = getimagesize($image_filename);
/* Get the image file type */
$pathinfo = pathinfo($image_obj->filename);
$filetype = strtolower($pathinfo['extension']);
if (($image_obj->width != $info[0] ) || ($image_obj->height != $info[1])) {
throw new ImageResizeException("The current image size does not match what is set in the database! - Aborting Resize.");
}
$memory_use = $this->calc_memory_use($info);
$memory_limit = get_memory_limit();
if ($memory_use > $memory_limit) {
throw new ImageResizeException("The image is too large to resize given the memory limits. ($memory_use > $memory_limit)");
}
list($new_height, $new_width) = $this->calc_new_size($image_obj, $width, $height);
/* Attempt to load the image */
switch ( $info[2] ) {
case IMAGETYPE_GIF: $image = imagecreatefromgif($image_filename); break;
case IMAGETYPE_JPEG: $image = imagecreatefromjpeg($image_filename); break;
case IMAGETYPE_PNG: $image = imagecreatefrompng($image_filename); break;
default:
throw new ImageResizeException("Unsupported image type (Only GIF, JPEG, and PNG are supported).");
}
// Handle transparent images
$image_resized = imagecreatetruecolor( $new_width, $new_height );
if ($info[2] == IMAGETYPE_GIF) {
$transparency = imagecolortransparent($image);
// If we have a specific transparent color
if ($transparency >= 0) {
// 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']);
// Completely fill the background of the new image with allocated color.
imagefill($image_resized, 0, 0, $transparency);
// Set the background color for new image to transparent
imagecolortransparent($image_resized, $transparency);
}
} elseif ($info[2] == IMAGETYPE_PNG) {
//
// More info here: http://stackoverflow.com/questions/279236/how-do-i-resize-pngs-with-transparency-in-php
//
imagealphablending($image_resized, false);
imagesavealpha($image_resized, true);
$transparent_color = imagecolorallocatealpha($image_resized, 255, 255, 255, 127);
imagefilledrectangle($image_resized, 0, 0, $new_width, $new_height, $transparent_color);
}
// Actually resize the image.
imagecopyresampled($image_resized, $image, 0, 0, 0, 0, $new_width, $new_height, $image_obj->width, $image_obj->height);
/* Temp storage while we resize */
$tmp_filename = tempnam("/tmp", 'shimmie_resize');
if (empty($tmp_filename)) {
throw new ImageResizeException("Unable to save temporary image file.");
}
/* Output to the same format as the original image */
switch ( $info[2] ) {
case IMAGETYPE_GIF: imagegif($image_resized, $tmp_filename); break;
case IMAGETYPE_JPEG: imagejpeg($image_resized, $tmp_filename); break;
case IMAGETYPE_PNG: imagepng($image_resized, $tmp_filename); break;
default:
throw new ImageResizeException("Failed to save the new image - Unsupported image type.");
}
/* Move the new image into the main storage location */
$new_hash = md5_file($tmp_filename);
$new_size = filesize($tmp_filename);
$target = warehouse_path("images", $new_hash);
if(!@copy($tmp_filename, $target)) {
throw new ImageResizeException("Failed to copy new image file from temporary location ({$tmp_filename}) to archive ($target)");
}
$new_filename = 'resized-'.$image_obj->filename;
/* Remove temporary file */
@unlink($tmp_filename);
/* Delete original image and thumbnail */
log_debug("image", "Removing image with hash ".$hash);
$image_obj->remove_image_only();
/* Generate new thumbnail */
send_event(new ThumbnailGenerationEvent($new_hash, $filetype));
/* Update the database */
$database->Execute("
UPDATE images SET filename = :filename, filesize = :filesize, hash = :hash, width = :width, height = :height
WHERE id = :id
", array(
"filename"=>$new_filename, "filesize"=>$new_size, "hash"=>$new_hash,
"width"=>$new_width, "height"=>$new_height, "id"=>$image_obj->id
));
log_info("resize", "Resized Image #{$image_obj->id} - New hash: {$new_hash}");
}
/**
* 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 mixed[] $info
* @return int
*/
private function calc_memory_use($info) {
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;
}
/**
* @param Image $image_obj
* @param int $width
* @param int $height
* @return int[]
*/
private function calc_new_size(Image $image_obj, $width, $height) {
/* Calculate the new size of the image */
if ($height > 0 && $width > 0) {
$new_height = $height;
$new_width = $width;
return array($new_height, $new_width);
} else {
// Scale the new image
if ($width == 0) $factor = $height / $image_obj->height;
elseif ($height == 0) $factor = $width / $image_obj->width;
else $factor = min($width / $image_obj->width, $height / $image_obj->height);
$new_width = round($image_obj->width * $factor);
$new_height = round($image_obj->height * $factor);
return array($new_height, $new_width);
}
}
}