Index: branches/5.2.x/core/units/helpers/image_helper.php =================================================================== --- branches/5.2.x/core/units/helpers/image_helper.php +++ branches/5.2.x/core/units/helpers/image_helper.php @@ -35,11 +35,15 @@ * Parses format string into array * * @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) { - $res = Array (); + $res = array('quality' => 100, 'output_format' => 'auto', 'orientation' => 'manual'); $format_parts = explode(';', $format); foreach ($format_parts as $format_part) { @@ -47,6 +51,35 @@ $res['max_width'] = $regs[1]; $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)) { $res['wm_filename'] = FULL_PATH.THEMES_PATH.'/'.$regs[1]; $res['h_margin'] = strtolower($regs[2]); @@ -113,6 +146,16 @@ } 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); if (!is_numeric($params['max_width'])) { @@ -136,13 +179,17 @@ $transform_keys = Array ('crop_x', 'crop_y', 'fill', 'wm_filename'); // 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 "\". $src_path_escaped = preg_replace('/(\\\[\d]+)/', '\\\\\1', $src_path); $params_hash = kUtil::crc32(serialize($this->fileHelper->makeRelative($params))); + $file_extension = $params['output_format'] === 'auto' ? '\\2' : $params['output_format']; $dst_image = preg_replace( '/^' . preg_quote($src_path, '/') . '(.*)\.(.*)$/', - $src_path_escaped . '\\1_' . $params_hash . '.\\2', + $src_path_escaped . '\\1_' . $params_hash . '.' . $file_extension, $src_image ); @@ -217,6 +264,18 @@ 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 kUtil::setResourceLimit(); @@ -253,7 +312,11 @@ 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 { @@ -267,6 +330,18 @@ } /** + * 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 * * @param resource $src_image_rs @@ -557,6 +632,76 @@ } /** + * 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 * * @param kDBItem $object