diff --git a/CRM/Mosaico/Form/MosaicoAdmin.php b/CRM/Mosaico/Form/MosaicoAdmin.php index 18b348cea9..754e2ff6cd 100644 --- a/CRM/Mosaico/Form/MosaicoAdmin.php +++ b/CRM/Mosaico/Form/MosaicoAdmin.php @@ -12,6 +12,10 @@ class CRM_Mosaico_Form_MosaicoAdmin extends CRM_Admin_Form_Setting { protected $_settings = [ 'mosaico_layout' => 'Mosaico Preferences', 'mosaico_graphics' => 'Mosaico Preferences', + 'mosaico_scale_factor1' => 'Mosaico Preferences', + 'mosaico_scale_factor2' => 'Mosaico Preferences', + 'mosaico_scale_width_limit1' => 'Mosaico Preferences', + 'mosaico_scale_width_limit2' => 'Mosaico Preferences', 'mosaico_custom_templates_dir' => 'Mosaico Custom Templates Directory', 'mosaico_custom_templates_url' => 'Mosaico Custom Templates URL' ]; diff --git a/CRM/Mosaico/Graphics/Imagick.php b/CRM/Mosaico/Graphics/Imagick.php index b31792fea8..d91e6049d8 100644 --- a/CRM/Mosaico/Graphics/Imagick.php +++ b/CRM/Mosaico/Graphics/Imagick.php @@ -10,7 +10,7 @@ * * @see https://github.com/voidlabs/mosaico/blob/master/backend/README.txt */ -class CRM_Mosaico_Graphics_Imagick implements CRM_Mosaico_Graphics_Interface { +class CRM_Mosaico_Graphics_Imagick extends CRM_Mosaico_Graphics_Interface { /** * CRM_Mosaico_Graphics_Imagick constructor. @@ -79,6 +79,7 @@ public function createResizedImage($srcFile, $destFile, $width, $height) { $mobileMinWidth = $config['MOBILE_MIN_WIDTH']; $image = new Imagick($srcFile); + $this->adjustResizeDimensions($image->getImageWidth(), $image->getImageHeight(), $width, $height); $resize_width = $width; $resize_height = $image->getImageHeight(); @@ -109,6 +110,7 @@ public function createCoveredImage($srcFile, $destFile, $width, $height) { $image = new Imagick($srcFile); $image_geometry = $image->getImageGeometry(); + $this->adjustResizeDimensions($image_geometry["width"], $image_geometry["height"], $width, $height); $width_ratio = $image_geometry["width"] / $width; $height_ratio = $image_geometry["height"] / $height; diff --git a/CRM/Mosaico/Graphics/Interface.php b/CRM/Mosaico/Graphics/Interface.php index f93e8fc8de..f1296e7e65 100644 --- a/CRM/Mosaico/Graphics/Interface.php +++ b/CRM/Mosaico/Graphics/Interface.php @@ -10,7 +10,7 @@ * probably be better. As long as it remains internal, we have some * flexibility to clean it up. */ -interface CRM_Mosaico_Graphics_Interface { +abstract class CRM_Mosaico_Graphics_Interface { /** * Generate a placeholder image. @@ -19,7 +19,7 @@ interface CRM_Mosaico_Graphics_Interface { * @param int $height * @return mixed */ - public function sendPlaceholder($width, $height); + abstract public function sendPlaceholder($width, $height); /** * Generate a scaled version of the image. @@ -40,7 +40,7 @@ public function sendPlaceholder($width, $height); * NOTE: NULL or 0 are interpreted "auto-scaled". * @return mixed */ - public function createResizedImage($src, $dest, $width, $height); + abstract public function createResizedImage($src, $dest, $width, $height); /** * Generate a "cover" version of the image. @@ -61,6 +61,56 @@ public function createResizedImage($src, $dest, $width, $height); * NOTE: NULL or 0 are interpreted "auto-scaled". * @return mixed */ - public function createCoveredImage($src, $dest, $width, $height); + abstract public function createCoveredImage($src, $dest, $width, $height); + + /** + * Adjust resize dimensions in order to preserve the best possible resolution for the image. + * + * @param int $imgWidth + * Image width in pixels. + * @param int $imgHeight + * Image height in pixels. + * @param int|NULL $resizeWidth + * Resize width in pixels. + * @param int|NULL $resizeHeight + * Resize height in pixels. + * @return float|null + */ + public function adjustResizeDimensions($imgWidth, $imgHeight, &$resizeWidth, &$resizeHeight) { + $scaleFactor = NULL; + $scales[Civi::settings()->get('mosaico_scale_width_limit1')] = Civi::settings()->get('mosaico_scale_factor1'); + $scales[Civi::settings()->get('mosaico_scale_width_limit2')] = Civi::settings()->get('mosaico_scale_factor2'); + $scales = array_filter($scales); + ksort($scales, SORT_NUMERIC); + if (!empty($scales) && $resizeWidth) { + foreach ($scales as $width => $slevel) { + if ($resizeWidth <= $width) { + $scaleFactor = $slevel; + break; + } + } + } + if (empty($scaleFactor)) { + return NULL; + } + // If scale-factor make new width bigger than that of image itself, re-compute scale-factor to + // maximum possible. + if ($scaleFactor && $resizeWidth && $imgWidth && ($imgWidth < ($resizeWidth * $scaleFactor))) { + $possibleLevels[] = $imgWidth / $resizeWidth; + } + if ($scaleFactor && $resizeHeight && $imgHeight && ($imgHeight < ($resizeHeight * $scaleFactor))) { + $possibleLevels[] = $imgHeight / $resizeHeight; + } + if (!empty($possibleLevels)) { + $scaleFactor = max($possibleLevels); + } + if ($scaleFactor && $resizeWidth) { + $resizeWidth = round($resizeWidth * $scaleFactor); + } + if ($scaleFactor && $resizeHeight) { + $resizeHeight = round($resizeHeight * $scaleFactor); + } + return $scaleFactor; + } } diff --git a/CRM/Mosaico/Graphics/Intervention.php b/CRM/Mosaico/Graphics/Intervention.php index e04749ec57..c75bf1aae2 100644 --- a/CRM/Mosaico/Graphics/Intervention.php +++ b/CRM/Mosaico/Graphics/Intervention.php @@ -9,7 +9,7 @@ * @see https://github.com/voidlabs/mosaico/blob/master/backend/README.txt * @see http://image.intervention.io/getting_started/introduction */ -class CRM_Mosaico_Graphics_Intervention implements CRM_Mosaico_Graphics_Interface { +class CRM_Mosaico_Graphics_Intervention extends CRM_Mosaico_Graphics_Interface { const FONT_PATH = 'packages/mosaico/dist/vendor/notoregular/NotoSans-Regular-webfont.ttf'; @@ -96,6 +96,7 @@ protected static function flattenPoints($points) { public function createResizedImage($srcFile, $destFile, $width, $height) { $config = CRM_Mosaico_Utils::getConfig(); $img = Image::make($srcFile); + $this->adjustResizeDimensions($img->width(), $img->height(), $width, $height); if ($width && $height) { $img->resize($width, $height); @@ -114,6 +115,7 @@ public function createResizedImage($srcFile, $destFile, $width, $height) { public function createCoveredImage($srcFile, $destFile, $width, $height) { $img = Image::make($srcFile); + $this->adjustResizeDimensions($img->width(), $img->height(), $width, $height); $ratios = []; if ($width) { diff --git a/CRM/Mosaico/Utils.php b/CRM/Mosaico/Utils.php index a449c326ed..6e89ad9ff8 100644 --- a/CRM/Mosaico/Utils.php +++ b/CRM/Mosaico/Utils.php @@ -51,6 +51,36 @@ public static function getGraphicsOptions() { ]; } + /** + * Get a list of image resize scale factors + * + * @return array + * Array (int $machineName => string $label). + */ + public static function getResizeScaleFactor() { + return [ + '' => E::ts('None'), + 3 => E::ts('3x'), + 2 => E::ts('2x'), + ]; + } + + /** + * Get a list of image resize scale width limits + * + * @return array + * Array (int $machineName => string $label). + */ + public static function getResizeScaleWidthLimit() { + return [ + '' => E::ts('None'), + 190 => E::ts('Upto 190 pixels (e.g 3 column blocks)'), + 285 => E::ts('Upto 285 pixels (e.g 2 column blocks)'), + 999 => E::ts('Upto 570 pixels (e.g 1 column blocks)'), + 9999 => E::ts('All (other) sizes'), + ]; + } + /** * Get the path to the Mosaico layout file. * @@ -178,7 +208,6 @@ public static function getConfig() { return $mConfig; } - /** * handler for upload requests */ diff --git a/docs/images/scaling-factor-config.png b/docs/images/scaling-factor-config.png new file mode 100644 index 0000000000..4996baffe1 Binary files /dev/null and b/docs/images/scaling-factor-config.png differ diff --git a/docs/images/scaling-factor-resolution-diff.png b/docs/images/scaling-factor-resolution-diff.png new file mode 100644 index 0000000000..9b8c6ea6bc Binary files /dev/null and b/docs/images/scaling-factor-resolution-diff.png differ diff --git a/docs/setup.md b/docs/setup.md new file mode 100644 index 0000000000..94675c88fc --- /dev/null +++ b/docs/setup.md @@ -0,0 +1,24 @@ +# Setup + +## Support for High Resolution Images + +Images when uploaded in 2 columns (e.g 258 x 100) or 3 (e.g 166 x 90) column blocks when upscaled to render on mobile devices, ends up with stretched out and lower resolution image. + +Introduced `Image resize scale factor` mosaico setting attempts to solve the problem by scaling uploaded images to 2x or 3x of their block size specially for 2 and 3 column layouts so upscale doesn't look distorted or low resolution. The solution also work for single column images. + +![](images/scaling-factor-config.png) + +Following example shows how a correctly configured scaling factor can improve the resolution of image rendered. + +![](images/scaling-factor-resolution-diff.png) + +__Scaling factor config example__: +3x => Upto 285 pixels (covers both 2 and 3 column block images) +2x => All other sizes (single column block images) + +Which means when an image is uploaded in a block with 285 pixels (or less), image gets trimmed to 285 * 3 pixels instead of 285 pixels. +For any image larger than that, size is reduced to 2x size of the block. + +__Note__: higher the scaling factor, higher the resolution but lower the compression. + +Scaling is not tied to any particular type of image. Png format supports lossless compression and therefore compression appears less than jpg images which support lossy compression. diff --git a/mkdocs.yml b/mkdocs.yml index 70af7b9274..f927730d68 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -17,6 +17,7 @@ markdown_extensions: nav: - About: index.md +- Setup: setup.md - API: api.md - Development: develop.md - Testing: testing.md diff --git a/settings/Mosaico.setting.php b/settings/Mosaico.setting.php index 3fab1e4acf..1c02218967 100644 --- a/settings/Mosaico.setting.php +++ b/settings/Mosaico.setting.php @@ -42,6 +42,90 @@ 'description' => NULL, 'help_text' => NULL, ], + 'mosaico_scale_factor1' => [ + 'group_name' => 'Mosaico Preferences', + 'group' => 'mosaico', + 'name' => 'mosaico_scale_factor1', + 'quick_form_type' => 'Select', + 'type' => 'String', + 'html_type' => 'select', + 'html_attributes' => [ + 'class' => 'crm-select2', + ], + 'pseudoconstant' => [ + 'callback' => 'CRM_Mosaico_Utils::getResizeScaleFactor', + ], + 'default' => '', + 'add' => '5.24', + 'title' => 'Image resize scale factor', + 'is_domain' => 1, + 'is_contact' => 0, + 'description' => NULL, + 'help_text' => NULL, + ], + 'mosaico_scale_factor2' => [ + 'group_name' => 'Mosaico Preferences', + 'group' => 'mosaico', + 'name' => 'mosaico_scale_factor2', + 'quick_form_type' => 'Select', + 'type' => 'String', + 'html_type' => 'select', + 'html_attributes' => [ + 'class' => 'crm-select2', + ], + 'pseudoconstant' => [ + 'callback' => 'CRM_Mosaico_Utils::getResizeScaleFactor', + ], + 'default' => '', + 'add' => '5.24', + 'title' => 'Image resize scale factor', + 'is_domain' => 1, + 'is_contact' => 0, + 'description' => NULL, + 'help_text' => NULL, + ], + 'mosaico_scale_width_limit1' => [ + 'group_name' => 'Mosaico Preferences', + 'group' => 'mosaico', + 'name' => 'mosaico_scale_width_limit1', + 'quick_form_type' => 'Select', + 'type' => 'String', + 'html_type' => 'select', + 'html_attributes' => [ + 'class' => 'crm-select2', + ], + 'pseudoconstant' => [ + 'callback' => 'CRM_Mosaico_Utils::getResizeScaleWidthLimit', + ], + 'default' => '', + 'add' => '5.24', + 'title' => 'Image resize scale factor width limit', + 'is_domain' => 1, + 'is_contact' => 0, + 'description' => NULL, + 'help_text' => NULL, + ], + 'mosaico_scale_width_limit2' => [ + 'group_name' => 'Mosaico Preferences', + 'group' => 'mosaico', + 'name' => 'mosaico_scale_width_limit2', + 'quick_form_type' => 'Select', + 'type' => 'String', + 'html_type' => 'select', + 'html_attributes' => [ + 'class' => 'crm-select2', + ], + 'pseudoconstant' => [ + 'callback' => 'CRM_Mosaico_Utils::getResizeScaleWidthLimit', + ], + 'default' => '', + 'add' => '5.24', + 'title' => 'Image resize scale factor width limit', + 'is_domain' => 1, + 'is_contact' => 0, + 'description' => NULL, + 'help_text' => NULL, + ], 'mosaico_custom_templates_dir' => [ 'group_name' => 'Mosaico Preferences', 'group' => 'mosaico', diff --git a/templates/CRM/Mosaico/Form/MosaicoAdmin.tpl b/templates/CRM/Mosaico/Form/MosaicoAdmin.tpl index 4a2230199c..ab894e1f85 100644 --- a/templates/CRM/Mosaico/Form/MosaicoAdmin.tpl +++ b/templates/CRM/Mosaico/Form/MosaicoAdmin.tpl @@ -34,6 +34,16 @@ {$form.mosaico_custom_templates_url.html|crmAddClass:'huge40'} +