From 753f58d6a966a1051dcd62a3eeab8fc18798bcac Mon Sep 17 00:00:00 2001 From: rvelices Date: Tue, 27 Dec 2011 05:26:44 +0000 Subject: feature 2541 multisize - core implementation + usage on most public/admin pages - still to do: sync process, upload, gui/persistence for size parameters, migration script, center of interest ... git-svn-id: http://piwigo.org/svn/trunk@12796 68402e56-0260-453c-a942-63ccdbb3a9ee --- include/calendar_monthly.class.php | 24 +-- include/category_cats.inc.php | 4 +- include/category_default.inc.php | 2 +- include/common.inc.php | 4 +- include/config_default.inc.php | 16 +- include/constants.php | 3 +- include/derivative.inc.php | 239 +++++++++++++++++++++++++ include/derivative_params.inc.php | 315 +++++++++++++++++++++++++++++++++ include/derivative_std_params.inc.php | 116 ++++++++++++ include/functions.inc.php | 6 +- include/functions_notification.inc.php | 4 +- include/picture_metadata.inc.php | 22 +-- include/ws_functions.inc.php | 65 ++++--- 13 files changed, 731 insertions(+), 89 deletions(-) create mode 100644 include/derivative.inc.php create mode 100644 include/derivative_params.inc.php create mode 100644 include/derivative_std_params.inc.php (limited to 'include') diff --git a/include/calendar_monthly.class.php b/include/calendar_monthly.class.php index 2c14088c9..556fd055c 100644 --- a/include/calendar_monthly.class.php +++ b/include/calendar_monthly.class.php @@ -350,7 +350,7 @@ function build_month_calendar(&$tpl_var) { $page['chronology_date'][CDAY]=$day; $query = ' -SELECT id, file,tn_ext,path, width, height, '.pwg_db_get_dayofweek($this->date_field).'-1 as dow'; +SELECT id, file,representative_ext,path,width, height, '.pwg_db_get_dayofweek($this->date_field).'-1 as dow'; $query.= $this->inner_sql; $query.= $this->get_date_where(); $query.= ' @@ -359,18 +359,13 @@ SELECT id, file,tn_ext,path, width, height, '.pwg_db_get_dayofweek($this->date_f unset ( $page['chronology_date'][CDAY] ); $row = pwg_db_fetch_assoc(pwg_query($query)); - $items[$day]['tn_url'] = get_thumbnail_url($row); + $derivative = new DerivativeImage(IMG_SQUARE, new SrcImage($row)); + $items[$day]['derivative'] = $derivative; $items[$day]['file'] = $row['file']; - $items[$day]['path'] = $row['path']; - $items[$day]['tn_ext'] = @$row['tn_ext']; - $items[$day]['width'] = $row['width']; - $items[$day]['height'] = $row['height']; $items[$day]['dow'] = $row['dow']; } - if ( !empty($items) - and $conf['calendar_month_cell_width']>0 - and $conf['calendar_month_cell_height']>0) + if ( !empty($items) ) { list($known_day) = array_keys($items); $known_dow = $items[$known_day]['dow']; @@ -396,8 +391,7 @@ SELECT id, file,tn_ext,path, width, height, '.pwg_db_get_dayofweek($this->date_f array_push( $wday_labels, array_shift($wday_labels) ); } - $cell_width = $conf['calendar_month_cell_width']; - $cell_height = $conf['calendar_month_cell_height']; + list($cell_width, $cell_height) = ImageStdParams::get_by_type(IMG_SQUARE)->sizing->ideal_size; $tpl_weeks = array(); $tpl_crt_week = array(); @@ -430,11 +424,7 @@ SELECT id, file,tn_ext,path, width, height, '.pwg_db_get_dayofweek($this->date_f } else { - $thumb = get_thumbnail_path($items[$day]); - $tn_size = @getimagesize($thumb); - - $tn_width = $tn_size[0]; - $tn_height = $tn_size[1]; + list($tn_width,$tn_height) = $items[$day]['derivative']->get_size(); // now need to fit the thumbnail of size tn_size within // a cell of size cell_size by playing with CSS position (left/top) @@ -491,7 +481,7 @@ SELECT id, file,tn_ext,path, width, height, '.pwg_db_get_dayofweek($this->date_f 'DAY' => $day, 'DOW' => $dow, 'NB_ELEMENTS' => $items[$day]['nb_images'], - 'IMAGE' => $items[$day]['tn_url'], + 'IMAGE' => $items[$day]['derivative']->get_url(), 'U_IMG_LINK' => $url, 'IMAGE_STYLE' => $css_style, 'IMAGE_ALT' => $items[$day]['file'], diff --git a/include/category_cats.inc.php b/include/category_cats.inc.php index a6a686477..2ecdf5ec2 100644 --- a/include/category_cats.inc.php +++ b/include/category_cats.inc.php @@ -189,7 +189,7 @@ SELECT * { if ($row['level'] <= $user['level']) { - $row['tn_src'] = get_thumbnail_url($row); + $row['tn_src'] = DerivativeImage::thumb_url($row); $infos_of_image[$row['id']] = $row; } else @@ -236,7 +236,7 @@ SELECT * $result = pwg_query($query); while ($row = pwg_db_fetch_assoc($result)) { - $row['tn_src'] = get_thumbnail_url($row); + $row['tn_src'] = DerivativeImage::thumb_url($row); $infos_of_image[$row['id']] = $row; } } diff --git a/include/category_default.inc.php b/include/category_default.inc.php index 7204e1b4b..61c81aa9e 100644 --- a/include/category_default.inc.php +++ b/include/category_default.inc.php @@ -113,7 +113,7 @@ foreach ($pictures as $row) $tpl_var = array( 'ID' => $row['id'], - 'TN_SRC' => get_thumbnail_url($row), + 'TN_SRC' => DerivativeImage::thumb_url($row), 'TN_ALT' => htmlspecialchars(strip_tags($name)), 'TN_TITLE' => get_thumbnail_title($row), 'URL' => $url, diff --git a/include/common.inc.php b/include/common.inc.php index 724fc83d3..5cc3317e7 100644 --- a/include/common.inc.php +++ b/include/common.inc.php @@ -107,7 +107,7 @@ if(isset($conf['show_php_errors']) && !empty($conf['show_php_errors'])) include(PHPWG_ROOT_PATH . 'include/constants.php'); include(PHPWG_ROOT_PATH . 'include/functions.inc.php'); -include( PHPWG_ROOT_PATH .'include/template.class.php'); +include(PHPWG_ROOT_PATH .'include/template.class.php'); // Database connection try @@ -132,6 +132,8 @@ if (!$conf['check_upgrade_feed']) } } +ImageStdParams::load_from_db(); + load_plugins(); // users can have defined a custom order pattern, incompatible with GUI form diff --git a/include/config_default.inc.php b/include/config_default.inc.php index 883018ed5..e01f3a7c8 100644 --- a/include/config_default.inc.php +++ b/include/config_default.inc.php @@ -89,14 +89,6 @@ $conf['calendar_show_any'] = true; //no elements for these $conf['calendar_show_empty'] = true; -// calendar_month_cell_width, calendar_month_cell_height : define the -// width and the height of a cell in the monthly calendar when viewing a -// given month. a value of 0 means that the pretty view is not shown. -// a good suggestion would be to have the width and the height equal -// and smaller than upload thumbnails configuration size. -$conf['calendar_month_cell_width'] =80; -$conf['calendar_month_cell_height']=80; - // newcat_default_commentable : at creation, must a category be commentable // or not ? $conf['newcat_default_commentable'] = true; @@ -760,4 +752,10 @@ $conf['alternative_pem_url'] = ''; // based on the EXIF "orientation" tag, should we rotate photos added in the // upload form or through pwg.images.addSimple web API method? $conf['upload_form_automatic_rotation'] = true; -?> + +// 0-'auto', 1-'derivative' 2-'script' +$conf['derivative_url_style']=0; + +$conf['chmod_value']=0777; + +?> \ No newline at end of file diff --git a/include/constants.php b/include/constants.php index cfc7e9c87..f77bd739e 100644 --- a/include/constants.php +++ b/include/constants.php @@ -27,7 +27,8 @@ define('PHPWG_DEFAULT_LANGUAGE', 'en_UK'); define('PHPWG_DEFAULT_TEMPLATE', 'Sylvia'); define('PHPWG_THEMES_PATH', $conf['themes_dir'].'/'); -define('PWG_COMBINED_DIR', PWG_LOCAL_DIR.'combined/'); +defined('PWG_COMBINED_DIR') or define('PWG_COMBINED_DIR', PWG_LOCAL_DIR.'combined/'); +defined('PWG_DERIVATIVE_DIR') or define('PWG_DERIVATIVE_DIR', PWG_LOCAL_DIR.'i/'); // Required versions define('REQUIRED_PHP_VERSION', '5.0.0'); diff --git a/include/derivative.inc.php b/include/derivative.inc.php new file mode 100644 index 000000000..11f1ebb76 --- /dev/null +++ b/include/derivative.inc.php @@ -0,0 +1,239 @@ +rel_path = $infos['path']; + } + elseif (!empty($infos['representative_ext'])) + { + $pi = pathinfo($infos['path']); + $file_wo_ext = get_filename_wo_extension($pi['basename']); + $this->rel_path = $pi['dirname'].'/pwg_representative/' + .$file_wo_ext.'.'.$infos['representative_ext']; + } + else + { + $this->rel_path = get_themeconf('mime_icon_dir').strtolower($ext).'.png'; + } + + $this->coi = @$infos['coi']; + if (isset($infos['width']) && isset($infos['height'])) + { + $this->size = array($infos['width'], $infos['height']); + } + } + + function has_size() + { + return $this->size != null; + } + + function get_size() + { + if ($this->size == null) + not_implemented(); // get size from file + return $this->size; + } +} + + + +final class DerivativeImage +{ + const SAME_AS_SRC = 0x10; + + public $src_image; + + private $requested_type; + + private $flags = 0; + private $params; + private $rel_path, $rel_url; + + function __construct($type, $src_image) + { + $this->src_image = $src_image; + if (is_string($type)) + { + $this->requested_type = $type; + $this->params = ImageStdParams::get_by_type($type); + } + else + { + $this->requested_type = IMG_CUSTOM; + $this->params = $type; + } + + self::build($src_image, $this->params, $this->rel_path, $this->rel_url, $this->flags); + } + + static function thumb_url($infos) + { + $src_image = new SrcImage($infos); + self::build($src_image, ImageStdParams::get_by_type(IMG_THUMB), $rel_path, $rel_url); + return get_root_url().$rel_url; + } + + static function url($type, $infos) + { + $src_image = new SrcImage($infos); + $params = is_string($type) ? ImageStdParams::get_by_type($type) : $type; + self::build($src_image, $params, $rel_path, $rel_url); + return get_root_url().$rel_url; + } + + static function get_all($infos) + { + $src_image = new SrcImage($infos); + $ret = array(); + foreach (ImageStdParams::get_defined_type_map() as $type => $params) + { + $derivative = new DerivativeImage($params, $src_image); + $ret[$type] = $derivative; + } + foreach (ImageStdParams::get_undefined_type_map() as $type => $type2) + { + $ret[$type] = $ret[$type2]; + } + + return $ret; + } + + private static function build($src, &$params, &$rel_path, &$rel_url, &$flags = null) + { + if ( $src->has_size() && $params->is_identity( $src->get_size() ) ) + { + // todo - what if we have a watermark maybe return a smaller size? + $flags |= self::SAME_AS_SRC; + $params = null; + $rel_path = $rel_url = $src->rel_path; + return; + } + + $tokens=array(); + $tokens[] = substr($params->type,0,2); + + if (!empty($src->coi)) + { + $tokens[] = 'ci'.$src->coi; + } + + if ($params->type==IMG_CUSTOM) + { + $params->add_url_tokens($tokens); + } + + $loc = $src->rel_path; + if (substr_compare($loc, './', 0, 2)==0) + { + $loc = substr($loc, 2); + } + elseif (substr_compare($loc, '../', 0, 3)==0) + { + $loc = substr($loc, 3); + } + $loc = substr_replace($loc, '-'.implode('_', $tokens), strrpos($loc, '.'), 0 ); + + $rel_path = PWG_DERIVATIVE_DIR.$loc; + + global $conf; + $url_style=$conf['derivative_url_style']; + if (!$url_style) + { + $mtime = @filemtime(PHPWG_ROOT_PATH.$rel_path); + if ($mtime===false or $mtime < $params->last_mod_time) + { + $url_style = 2; + } + else + { + $url_style = 1; + } + } + + if ($url_style == 2) + { + $rel_url = 'i'; + if ($conf['php_extension_in_urls']) $rel_url .= '.php'; + if ($conf['question_mark_in_urls']) $rel_url .= '?'; + $rel_url .= '/'.$loc; + } + else + { + $rel_url = $rel_path; + } + } + + function get_url() + { + return get_root_url().$this->rel_url; + } + + function same_as_source() + { + return $this->flags & self::SAME_AS_SRC; + } + + + /* returns the size of the derivative image*/ + function get_size() + { + if ($this->flags & self::SAME_AS_SRC) + { + return $this->src_image->get_size(); + } + return $this->params->compute_final_size($this->src_image->get_size(), $this->src_image->coi); + } + + function get_size_htm() + { + $size = $this->get_size(); + if ($size) + { + return 'width="'.$size[0].'" height="'.$size[1].'"'; + } + } + + function get_size_hr() + { + $size = $this->get_size(); + if ($size) + { + return $size[0].' x '.$size[1]; + } + } + +} + +?> \ No newline at end of file diff --git a/include/derivative_params.inc.php b/include/derivative_params.inc.php new file mode 100644 index 000000000..9fe76cd43 --- /dev/null +++ b/include/derivative_params.inc.php @@ -0,0 +1,315 @@ +l = $this->t = 0; + $this->r = $l[0]; + $this->b = $l[1]; + } + + function width() + { + return $this->r - $this->l; + } + + function height() + { + return $this->b - $this->t; + } + + function crop_h($pixels, $coi, $force) + { + if ($this->width() <= $pixels) + return; + $tlcrop = floor($pixels/2); + + if (!empty($coi)) + { + $coil = floor($this->r * (ord($coi[0]) - ord('a'))/25); + $coir = ceil($this->r * (ord($coi[2]) - ord('a'))/25); + $availableL = $coil > $this->l ? $coil - $this->l : 0; + $availableR = $coir < $this->r ? $this->r - $coir : 0; + if ($availableL + $availableR <= $pixels) + { + if (!$force) + { + $pixels = $availableL + $availableR; + $tlcrop = $availableL; + } + } + else + { + if ($availableL < $tlcrop) + { + $tlcrop = $availableL; + } + elseif ($availableR < $tlcrop) + { + $tlcrop = $pixels - $availableR; + } + } + } + $this->l += $tlcrop; + $this->r -= $pixels - $tlcrop; + } + + function crop_v($pixels, $coi, $force) + { + if ($this->height() <= $pixels) + return; + $tlcrop = floor($pixels/2); + + if (!empty($coi)) + { + $coit = floor($this->b * (ord($coi[1]) - ord('a'))/25); + $coib = ceil($this->b * (ord($coi[3]) - ord('a'))/25); + $availableT = $coit > $this->t ? $coit - $this->t : 0; + $availableB = $coib < $this->b ? $this->b - $coib : 0; + if ($availableT + $availableB <= $pixels) + { + if (!$force) + { + $pixels = $availableT + $availableB; + $tlcrop = $availableT; + } + } + else + { + if ($availableT < $tlcrop) + { + $tlcrop = $availableT; + } + elseif ($availableB < $tlcrop) + { + $tlcrop = $pixels - $availableB; + } + } + } + $this->t += $tlcrop; + $this->b -= $pixels - $tlcrop; + } + +} + + +/*how we crop and/or resize an image*/ +final class SizingParams +{ + function __construct($ideal_size, $max_crop = 0, $min_size = null) + { + $this->ideal_size = $ideal_size; + $this->max_crop = $max_crop; + $this->min_size = $min_size; + } + + static function classic($w, $h) + { + return new SizingParams( array($w,$h) ); + } + + static function square($w) + { + return new SizingParams( array($w,$w), 1, array($w,$w) ); + } + + function add_url_tokens(&$tokens) + { + if ($this->max_crop == 0) + { + $tokens[] = 's'.size_to_url($this->ideal_size); + } + elseif ($this->max_crop == 1 && size_equals($this->ideal_size, $this->min_size) ) + { + $tokens[] = 'e'.size_to_url($this->ideal_size); + } + else + { + $tokens[] = size_to_url($this->ideal_size); + $tokens[] = sprintf('%02x', round(100*$this->max_crop) ); + $tokens[] = size_to_url($this->min_size); + } + } + + static function from_url_tokens($tokens) + { + if (count($tokens)<1) + throw new Exception('Empty array while parsing Sizing'); + $token = array_shift($tokens); + if ($token[0]=='s') + { + return new SizingParams( url_to_size( substr($token,1) ) ); + } + if ($token[0]=='e') + { + $s = url_to_size( substr($token,1) ); + return new SizingParams($s, 1, $s); + } + + $ideal_size = url_to_size( $token ); + if (count($tokens)<2) + throw new Exception('Sizing arr'); + + $token = array_shift($tokens); + $crop = sscanf('%02x' , $token) / 100; + + $token = array_shift($tokens); + $min_size = url_to_size( $token ); + return new SizingParams($ideal_size, $crop, $min_size); + } + + + function compute($in_size, $coi, &$crop_rect, &$scale_size) + { + $destCrop = new ImageRect($in_size); + + if ($this->max_crop > 0) + { + $ratio_w = $destCrop->width() / $this->ideal_size[0]; + $ratio_h = $destCrop->height() / $this->ideal_size[1]; + if ($ratio_w>1 || $ratio_h>1) + { + if ($ratio_w > $ratio_h) + { + $h = $destCrop->height() / $ratio_w; + if ($h < $this->min_size[1]) + { + $idealCropPx = $destCrop->width() - round($destCrop->height() * $this->ideal_size[0] / $this->min_size[1], 0); + $maxCropPx = round($this->max_crop * $destCrop->width()); + $destCrop->crop_h( min($idealCropPx, $maxCropPx), $coi, false); + } + } + else + { + $w = $destCrop->width() / $ratio_h; + if ($w < $this->min_size[0]) + { + $idealCropPx = $destCrop->height() - round($destCrop->width() * $this->ideal_size[1] / $this->min_size[0], 0); + $maxCropPx = round($this->max_crop * $destCrop->height()); + $destCrop->crop_v( min($idealCropPx, $maxCropPx), $coi, false); + } + } + } + } + + $scale_size = array($destCrop->width(), $destCrop->height()); + + $ratio_w = $destCrop->width() / $this->ideal_size[0]; + $ratio_h = $destCrop->height() / $this->ideal_size[1]; + if ($ratio_w>1 || $ratio_h>1) + { + if ($ratio_w > $ratio_h) + { + $scale_size[0] = $this->ideal_size[0]; + $scale_size[1] = floor($scale_size[1] / $ratio_w); + } + else + { + $scale_size[0] = floor($scale_size[0] / $ratio_h); + $scale_size[1] = $this->ideal_size[1]; + } + } + else + { + $scale_size = null; + } + + $crop_rect = null; + if ($destCrop->width()!=$in_size[0] || $destCrop->height()!=$in_size[1] ) + { + $crop_rect = $destCrop; + } + } + +} + + +/*how we generate a derivative image*/ +final class ImageParams +{ + public $type = IMG_CUSTOM; + public $last_mod_time = 0; // used for non-custom images to regenerate the cached files + public $sizing; + + function __construct($sizing) + { + $this->sizing = $sizing; + } + + function add_url_tokens(&$tokens) + { + $this->sizing->add_url_tokens($tokens); + } + + static function from_url_tokens($tokens) + { + $sizing = SizingParams::from_url_tokens($tokens); + $ret = new ImageParams($sizing); + return $ret; + } + + function compute_final_size($in_size, $coi) + { + $this->sizing->compute( $in_size, $coi, $crop_rect, $scale_size ); + return $scale_size != null ? $scale_size : $in_size; + } + + function is_identity($in_size) + { + if ($in_size[0] > $this->sizing->ideal_size[0] or + $in_size[1] > $this->sizing->ideal_size[1] ) + { + return false; + } + return true; + } +} +?> \ No newline at end of file diff --git a/include/derivative_std_params.inc.php b/include/derivative_std_params.inc.php new file mode 100644 index 000000000..3a6394886 --- /dev/null +++ b/include/derivative_std_params.inc.php @@ -0,0 +1,116 @@ +$params) + { + $params->type = $type; + } + self::$all_type_map = self::$type_map; + + for ($i=0; $i=0; $j--) + { + $target = self::$all_types[$j]; + if (isset(self::$type_map[$target])) + { + self::$all_type_map[$tocheck] = self::$type_map[$target]; + self::$undefined_type_map[$tocheck] = $target; + break; + } + } + } + } + } + +} + +?> \ No newline at end of file diff --git a/include/functions.inc.php b/include/functions.inc.php index e69d5ced6..f0a83d197 100644 --- a/include/functions.inc.php +++ b/include/functions.inc.php @@ -30,6 +30,9 @@ include_once( PHPWG_ROOT_PATH .'include/functions_html.inc.php' ); include_once( PHPWG_ROOT_PATH .'include/functions_tag.inc.php' ); include_once( PHPWG_ROOT_PATH .'include/functions_url.inc.php' ); include_once( PHPWG_ROOT_PATH .'include/functions_plugins.inc.php' ); +include_once( PHPWG_ROOT_PATH .'/include/derivative_params.inc.php'); +include_once( PHPWG_ROOT_PATH .'/include/derivative_std_params.inc.php'); +include_once( PHPWG_ROOT_PATH .'/include/derivative.inc.php'); //----------------------------------------------------------- generic functions @@ -167,12 +170,13 @@ function mkgetdir($dir, $flags=MKGETDIR_DEFAULT) { if ( !is_dir($dir) ) { + global $conf; if (substr(PHP_OS, 0, 3) == 'WIN') { $dir = str_replace('/', DIRECTORY_SEPARATOR, $dir); } $umask = umask(0); - $mkd = @mkdir($dir, 0755, ($flags&MKGETDIR_RECURSIVE) ? true:false ); + $mkd = @mkdir($dir, $conf['chmod_value'], ($flags&MKGETDIR_RECURSIVE) ? true:false ); umask($umask); if ($mkd==false) { diff --git a/include/functions_notification.inc.php b/include/functions_notification.inc.php index d7f7ea26e..06b9ce543 100644 --- a/include/functions_notification.inc.php +++ b/include/functions_notification.inc.php @@ -458,7 +458,7 @@ SELECT date_available, if ($max_elements>0) { // get some thumbnails ... $query = ' -SELECT DISTINCT id, path, name, tn_ext, file +SELECT DISTINCT id, path, name, representative_ext, file FROM '.IMAGES_TABLE.' i INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON id=image_id '.$where_sql.' AND date_available=\''.$dates[$i]['date_available'].'\' @@ -535,7 +535,7 @@ function get_html_description_recent_post_date($date_detail) foreach($date_detail['elements'] as $element) { - $tn_src = get_thumbnail_url($element); + $tn_src = DerivativeImage::thumb_url($element); $description .= '