Changeset View
Changeset View
Standalone View
Standalone View
branches/5.2.x/core/units/helpers/image_helper.php
Show All 29 Lines | |||||
ini_set('gd.jpeg_ignore_warning', 1); | ini_set('gd.jpeg_ignore_warning', 1); | ||||
$this->fileHelper = $this->Application->recallObject('FileHelper'); | $this->fileHelper = $this->Application->recallObject('FileHelper'); | ||||
} | } | ||||
/** | /** | ||||
* Parses format string into array | * Parses format string into array | ||||
* | * | ||||
* @param string $format sample format: "resize:300x500;wm:inc/wm.png|c|-20" | * @param string $format sample format: "resize:300x500;wm:inc/wm.png|c|-20" | ||||
* @return Array sample result: Array('max_width' => 300, 'max_height' => 500, 'wm_filename' => 'inc/wm.png', 'h_margin' => 'c', 'v_margin' => -20) | * @return Array sample result: Array('max_width' => 300, 'max_height' => 500, 'wm_filename' => 'inc/wm.png', 'h_margin' => 'c', 'v_margin' => -20, 'quality' => 100, output_format => 'auto', orientation => 'manual') | ||||
* | |||||
* @throws InvalidArgumentException When requested quality is out of 0..100 range. | |||||
* @throws InvalidArgumentException When requested output_format is out of [jpg,png,gif,bmp,auto] range. | |||||
* @throws InvalidArgumentException When requested orientation is out of [auto,manual,portrait,landscape] range. | |||||
*/ | */ | ||||
function parseFormat($format) | function parseFormat($format) | ||||
{ | { | ||||
$res = Array (); | $res = array('quality' => 100, 'output_format' => 'auto', 'orientation' => 'manual'); | ||||
$format_parts = explode(';', $format); | $format_parts = explode(';', $format); | ||||
foreach ($format_parts as $format_part) { | foreach ($format_parts as $format_part) { | ||||
if (preg_match('/^resize:(\d*)x(\d*)$/', $format_part, $regs)) { | if (preg_match('/^resize:(\d*)x(\d*)$/', $format_part, $regs)) { | ||||
$res['max_width'] = $regs[1]; | $res['max_width'] = $regs[1]; | ||||
$res['max_height'] = $regs[2]; | $res['max_height'] = $regs[2]; | ||||
} | } | ||||
elseif ( preg_match('/^quality:(.*)$/', $format_part, $regs) ) { | |||||
$quality = (int)$regs[1]; | |||||
if ( $quality > 100 || $quality < 0 || (string)$quality !== $regs[1] ) { | |||||
throw new InvalidArgumentException( | |||||
'Quality value "' . $regs[1] . '" is out of 0..100 integer range.' | |||||
); | |||||
} | |||||
$res['quality'] = $quality; | |||||
} | |||||
elseif ( preg_match('/^output_format:(.*)$/', $format_part, $regs) ) { | |||||
if ( !in_array($regs[1], array('jpg', 'png', 'gif', 'bmp', 'auto')) ) { | |||||
throw new InvalidArgumentException( | |||||
'Output format value "' . $regs[1] . '" is out of [jpg,png,gif,bmp,auto] range.' | |||||
); | |||||
} | |||||
$res['output_format'] = $regs[1]; | |||||
} | |||||
elseif ( preg_match('/^orientation:(.*)$/', $format_part, $regs) ) { | |||||
if ( !in_array($regs[1], array('auto', 'manual', 'portrait', 'landscape')) ) { | |||||
throw new InvalidArgumentException( | |||||
'Orientation value "' . $regs[1] . '" is out of [auto,manual,portrait,landscape] range.' | |||||
); | |||||
} | |||||
$res['orientation'] = $regs[1]; | |||||
} | |||||
elseif (preg_match('/^wm:([^\|]*)\|([^\|]*)\|([^\|]*)$/', $format_part, $regs)) { | elseif (preg_match('/^wm:([^\|]*)\|([^\|]*)\|([^\|]*)$/', $format_part, $regs)) { | ||||
$res['wm_filename'] = FULL_PATH.THEMES_PATH.'/'.$regs[1]; | $res['wm_filename'] = FULL_PATH.THEMES_PATH.'/'.$regs[1]; | ||||
$res['h_margin'] = strtolower($regs[2]); | $res['h_margin'] = strtolower($regs[2]); | ||||
$res['v_margin'] = strtolower($regs[3]); | $res['v_margin'] = strtolower($regs[3]); | ||||
} | } | ||||
elseif (preg_match('/^crop:([^\|]*)\|([^\|]*)$/', $format_part, $regs)) { | elseif (preg_match('/^crop:([^\|]*)\|([^\|]*)$/', $format_part, $regs)) { | ||||
$res['crop_x'] = strtolower($regs[1]); | $res['crop_x'] = strtolower($regs[1]); | ||||
$res['crop_y'] = strtolower($regs[2]); | $res['crop_y'] = strtolower($regs[2]); | ||||
▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Line(s) | |||||
$src_image = $params['default']; | $src_image = $params['default']; | ||||
} | } | ||||
if ( !strlen($src_image) || !file_exists($src_image) ) { | if ( !strlen($src_image) || !file_exists($src_image) ) { | ||||
throw new RuntimeException(sprintf('Image "%s" doesn\'t exist', $src_image)); | throw new RuntimeException(sprintf('Image "%s" doesn\'t exist', $src_image)); | ||||
} | } | ||||
if ( !$this->isSVG($src_image) && ($params['max_width'] > 0 || $params['max_height'] > 0) ) { | if ( !$this->isSVG($src_image) && ($params['max_width'] > 0 || $params['max_height'] > 0) ) { | ||||
if ( $this->shouldRotateDimensions($src_image, $params['max_width'], $params['max_height'], $params) ) { | |||||
list ($params['max_width'], $params['max_height']) = array( | |||||
$params['max_height'], $params['max_width'], | |||||
); | |||||
if ( isset($params['crop_x']) && isset($params['crop_y']) ) { | |||||
list ($params['crop_x'], $params['crop_y']) = array($params['crop_y'], $params['crop_x']); | |||||
} | |||||
} | |||||
list ($params['target_width'], $params['target_height'], $needs_resize) = $this->GetImageDimensions($src_image, $params['max_width'], $params['max_height'], $params); | list ($params['target_width'], $params['target_height'], $needs_resize) = $this->GetImageDimensions($src_image, $params['max_width'], $params['max_height'], $params); | ||||
if (!is_numeric($params['max_width'])) { | if (!is_numeric($params['max_width'])) { | ||||
$params['max_width'] = $params['target_width']; | $params['max_width'] = $params['target_width']; | ||||
} | } | ||||
if (!is_numeric($params['max_height'])) { | if (!is_numeric($params['max_height'])) { | ||||
$params['max_height'] = $params['target_height']; | $params['max_height'] = $params['target_height']; | ||||
} | } | ||||
// Optimize, because when cropping from center without resize we'll get same image back. | // Optimize, because when cropping from center without resize we'll get same image back. | ||||
if ( !$needs_resize | if ( !$needs_resize | ||||
&& isset($params['crop_x']) | && isset($params['crop_x']) | ||||
&& $params['crop_x'] == 'c' | && $params['crop_x'] == 'c' | ||||
&& $params['crop_y'] == 'c' | && $params['crop_y'] == 'c' | ||||
) { | ) { | ||||
unset($params['crop_x'], $params['crop_y'], $params['fill']); | unset($params['crop_x'], $params['crop_y'], $params['fill']); | ||||
} | } | ||||
$src_path = dirname($src_image); | $src_path = dirname($src_image); | ||||
$transform_keys = Array ('crop_x', 'crop_y', 'fill', 'wm_filename'); | $transform_keys = Array ('crop_x', 'crop_y', 'fill', 'wm_filename'); | ||||
// Resize required OR watermarking required -> change resulting image name ! | // Resize required OR watermarking required -> change resulting image name ! | ||||
if ( $needs_resize || array_intersect(array_keys($params), $transform_keys) ) { | if ( $needs_resize | ||||
|| array_intersect(array_keys($params), $transform_keys) | |||||
|| $this->shouldChangeOutputFormat($src_image, $params['output_format']) | |||||
) { | |||||
// Escape replacement patterns, like "\<number>". | // Escape replacement patterns, like "\<number>". | ||||
$src_path_escaped = preg_replace('/(\\\[\d]+)/', '\\\\\1', $src_path); | $src_path_escaped = preg_replace('/(\\\[\d]+)/', '\\\\\1', $src_path); | ||||
$params_hash = kUtil::crc32(serialize($this->fileHelper->makeRelative($params))); | $params_hash = kUtil::crc32(serialize($this->fileHelper->makeRelative($params))); | ||||
$file_extension = $params['output_format'] === 'auto' ? '\\2' : $params['output_format']; | |||||
$dst_image = preg_replace( | $dst_image = preg_replace( | ||||
'/^' . preg_quote($src_path, '/') . '(.*)\.(.*)$/', | '/^' . preg_quote($src_path, '/') . '(.*)\.(.*)$/', | ||||
$src_path_escaped . '\\1_' . $params_hash . '.\\2', | $src_path_escaped . '\\1_' . $params_hash . '.' . $file_extension, | ||||
$src_image | $src_image | ||||
); | ); | ||||
// Keep resized version of all images under "/system/thumbs/" folder. | // Keep resized version of all images under "/system/thumbs/" folder. | ||||
$dst_image = preg_replace('/^' . preg_quote(FULL_PATH, '/') . '/', '', $dst_image, 1); | $dst_image = preg_replace('/^' . preg_quote(FULL_PATH, '/') . '/', '', $dst_image, 1); | ||||
$dst_image = FULL_PATH . THUMBS_PATH . $dst_image; | $dst_image = FULL_PATH . THUMBS_PATH . $dst_image; | ||||
$this->fileHelper->CheckFolder( dirname($dst_image) ); | $this->fileHelper->CheckFolder( dirname($dst_image) ); | ||||
▲ Show 20 Lines • Show All 58 Lines • ▼ Show 20 Line(s) | |||||
$mime_type = $image_info['mime']; | $mime_type = $image_info['mime']; | ||||
if (!isset($resize_map[$mime_type])) { | if (!isset($resize_map[$mime_type])) { | ||||
return false; | return false; | ||||
} | } | ||||
list ($read_function, $write_function, $file_extension) = explode(':', $resize_map[$mime_type]); | list ($read_function, $write_function, $file_extension) = explode(':', $resize_map[$mime_type]); | ||||
$output_format_map = array( | |||||
'jpg' => 'imagejpeg:jpg', | |||||
'png' => 'imagepng:png', | |||||
'gif' => 'imagegif:gif', | |||||
'bmp' => 'imagejpeg:bmp', | |||||
); | |||||
$output_format = $params['output_format']; | |||||
if ( isset($output_format_map[$output_format]) ) { | |||||
list ($write_function, $file_extension) = explode(':', $output_format_map[$output_format]); | |||||
} | |||||
// when source image has large dimensions (over 1MB filesize), then 16M is not enough | // when source image has large dimensions (over 1MB filesize), then 16M is not enough | ||||
kUtil::setResourceLimit(); | kUtil::setResourceLimit(); | ||||
$src_image_rs = @$read_function($src_image); | $src_image_rs = @$read_function($src_image); | ||||
if ($src_image_rs) { | if ($src_image_rs) { | ||||
$dst_image_rs = imagecreatetruecolor($params['target_width'], $params['target_height']); // resize target size | $dst_image_rs = imagecreatetruecolor($params['target_width'], $params['target_height']); // resize target size | ||||
$preserve_transparency = ($file_extension == 'gif') || ($file_extension == 'png'); | $preserve_transparency = ($file_extension == 'gif') || ($file_extension == 'png'); | ||||
Show All 20 Lines | |||||
// 3. apply watermark | // 3. apply watermark | ||||
$dst_image_rs =& $this->_applyWatermark($dst_image_rs, $params[$watermark_size . '_width'], $params[$watermark_size . '_height'], $params); | $dst_image_rs =& $this->_applyWatermark($dst_image_rs, $params[$watermark_size . '_width'], $params[$watermark_size . '_height'], $params); | ||||
if ($write_function == 'imagegif') { | if ($write_function == 'imagegif') { | ||||
return @$write_function($dst_image_rs, $params['dst_image']); | return @$write_function($dst_image_rs, $params['dst_image']); | ||||
} | } | ||||
return @$write_function($dst_image_rs, $params['dst_image'], $write_function == 'imagepng' ? 0 : 100); | if ( $write_function == 'imagepng' ) { | ||||
$params['quality'] = $this->convertQualityToCompression($params['quality']); | |||||
} | |||||
return @$write_function($dst_image_rs, $params['dst_image'], $params['quality']); | |||||
} | } | ||||
} | } | ||||
else { | else { | ||||
// try to resize using ImageMagick | // try to resize using ImageMagick | ||||
// TODO: implement crop and watermarking using imagemagick | // TODO: implement crop and watermarking using imagemagick | ||||
exec('/usr/bin/convert '.$src_image.' -resize '.$params['target_width'].'x'.$params['target_height'].' '.$params['dst_image'], $shell_output, $exec_status); | exec('/usr/bin/convert '.$src_image.' -resize '.$params['target_width'].'x'.$params['target_height'].' '.$params['dst_image'], $shell_output, $exec_status); | ||||
return $exec_status == 0; | return $exec_status == 0; | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
/** | /** | ||||
* Converts quality to compression | |||||
* | |||||
* @param integer $quality Quality. | |||||
* | |||||
* @return integer | |||||
*/ | |||||
protected function convertQualityToCompression($quality) | |||||
{ | |||||
return round((100 - $quality) / 10); | |||||
} | |||||
/** | |||||
* Preserve transparency for GIF and PNG images | * Preserve transparency for GIF and PNG images | ||||
* | * | ||||
* @param resource $src_image_rs | * @param resource $src_image_rs | ||||
* @param resource $dst_image_rs | * @param resource $dst_image_rs | ||||
* @param int $image_type | * @param int $image_type | ||||
* @return resource | * @return resource | ||||
*/ | */ | ||||
function _preserveTransparency($src_image_rs, $dst_image_rs, $image_type) | function _preserveTransparency($src_image_rs, $dst_image_rs, $image_type) | ||||
▲ Show 20 Lines • Show All 274 Lines • ▼ Show 20 Line(s) | |||||
* @return boolean | * @return boolean | ||||
*/ | */ | ||||
protected function isSVG($src_image) | protected function isSVG($src_image) | ||||
{ | { | ||||
return pathinfo($src_image, PATHINFO_EXTENSION) == 'svg'; | return pathinfo($src_image, PATHINFO_EXTENSION) == 'svg'; | ||||
} | } | ||||
/** | /** | ||||
* Determines when dimensions must be rotated | |||||
* | |||||
* @param string $src_image Source image path. | |||||
* @param integer $dst_width Destination width. | |||||
* @param integer $dst_height Destination height. | |||||
* @param array $params Parameters. | |||||
* | |||||
* @return boolean | |||||
* @throws InvalidArgumentException When orientation is "auto", but some of $dst_width/$dst_height is empty. | |||||
*/ | |||||
protected function shouldRotateDimensions($src_image, $dst_width, $dst_height, array $params) | |||||
{ | |||||
$orientation = $params['orientation']; | |||||
if ( $orientation === 'manual' ) { | |||||
return false; | |||||
} | |||||
if ( $orientation === 'auto' ) { | |||||
if ( $dst_width === '' || $dst_height === '' ) { | |||||
throw new InvalidArgumentException('Both width & height parameters must be specified.'); | |||||
} | |||||
$resized_orientation = $dst_width > $dst_height ? 'landscape' : 'portrait'; | |||||
} | |||||
else { | |||||
$resized_orientation = $orientation; | |||||
} | |||||
list ($src_width, $src_height) = $this->getImageInfo($src_image); | |||||
$src_image_orientation = $src_width > $src_height ? 'landscape' : 'portrait'; | |||||
return $src_image_orientation != $resized_orientation; | |||||
} | |||||
/** | |||||
* Determines when output format should be changed. | |||||
* | |||||
* @param string $src_image Source image path. | |||||
* @param string $output_format Output format. | |||||
* | |||||
* @return boolean | |||||
*/ | |||||
protected function shouldChangeOutputFormat($src_image, $output_format) | |||||
{ | |||||
if ( $output_format === 'auto' ) { | |||||
return false; | |||||
} | |||||
$image_info = $this->getImageInfo($src_image); | |||||
if ( !$image_info ) { | |||||
return false; | |||||
} | |||||
$resize_map = array( | |||||
'image/jpeg' => 'jpg', | |||||
'image/gif' => 'gif', | |||||
'image/png' => 'png', | |||||
'image/bmp' => 'bmp', | |||||
'image/x-ms-bmp' => 'bmp', | |||||
); | |||||
$current_mime = $image_info['mime']; | |||||
return $resize_map[$current_mime] !== $output_format; | |||||
} | |||||
/** | |||||
* Returns maximal image size (width & height) among fields specified | * Returns maximal image size (width & height) among fields specified | ||||
* | * | ||||
* @param kDBItem $object | * @param kDBItem $object | ||||
* @param string $fields | * @param string $fields | ||||
* @param string $format any format, that returns full url (e.g. files_resized:WxH, resize:WxH, full_url, full_urls) | * @param string $format any format, that returns full url (e.g. files_resized:WxH, resize:WxH, full_url, full_urls) | ||||
* @return string | * @return string | ||||
*/ | */ | ||||
function MaxImageSize(&$object, $fields, $format = null) | function MaxImageSize(&$object, $fields, $format = null) | ||||
▲ Show 20 Lines • Show All 194 Lines • Show Last 20 Lines |