Jump to content

Improving the Image Quality


Recommended Posts

I found this topic https://www.prestashop.com/forums/topic/340771-ps-1606-poor-quality-on-uploaded-images/ very interesting but because I can not reply or add any comment I created this new topic based on hideaki idea.

 

First steps in Ubuntu 14.04 to add imagemagick:

 

sudo apt-get update
sudo apt-get install php5-imagick
sudo php5enmod imagick
 
and don't forget restart or reload Apache
 
sudo service apache2 restart
 
Then I changed the file classes/ImageManager.php Prestashop 1.6.0.14 with the following final result:
 
class ImageManagerCore
{
	const ERROR_FILE_NOT_EXIST = 1;
	const ERROR_FILE_WIDTH     = 2;
	const ERROR_MEMORY_LIMIT   = 3;

	/**
	 * Generate a cached thumbnail for object lists (eg. carrier, order statuses...etc)
	 *
	 * @param string $image Real image filename
	 * @param string $cache_image Cached filename
	 * @param int $size Desired size
	 * @param string $image_type Image type
	 * @param bool $disable_cache When turned on a timestamp will be added to the image URI to disable the HTTP cache
	 * @param bool $regenerate When turned on and the file already exist, the file will be regenerated
	 * @return string
	 */
	public static function thumbnail($image, $cache_image, $size, $image_type = 'jpg', $disable_cache = true, $regenerate = false)
	{
		if (!file_exists($image))
			return '';

		if (file_exists(_PS_TMP_IMG_DIR_.$cache_image) && $regenerate)
			@unlink(_PS_TMP_IMG_DIR_.$cache_image);

		if ($regenerate || !file_exists(_PS_TMP_IMG_DIR_.$cache_image))
		{
			$infos = getimagesize($image);

			// Evaluate the memory required to resize the image: if it's too much, you can't resize it.
			if (!ImageManager::checkImageMemoryLimit($image))
				return false;

			$x = $infos[0];
			$y = $infos[1];
			$max_x = $size * 3;

			// Size is already ok
			if ($y < $size && $x <= $max_x)
				copy($image, _PS_TMP_IMG_DIR_.$cache_image);
			// We need to resize */
			else
			{
				$ratio_x = $x / ($y / $size);
				if ($ratio_x > $max_x)
				{
					$ratio_x = $max_x;
					$size = $y / ($x / $max_x);
				}

				ImageManager::resize($image, _PS_TMP_IMG_DIR_.$cache_image, $ratio_x, $size, $image_type);
			}
		}
		// Relative link will always work, whatever the base uri set in the admin
		if (Context::getContext()->controller->controller_type == 'admin')
			return '<img src="../img/tmp/'.$cache_image.($disable_cache ? '?time='.time() : '').'" alt="" class="imgm img-thumbnail" />';
		else
			return '<img src="'._PS_TMP_IMG_.$cache_image.($disable_cache ? '?time='.time() : '').'" alt="" class="imgm img-thumbnail" />';
	}

	/**
	 * Check if memory limit is too long or not
	 *
	 * @static
	 * @param $image
	 * @return bool
	 */
	public static function checkImageMemoryLimit($image)
	{
		$infos = @getimagesize($image);

		if (!is_array($infos) || !isset($infos['bits']))
			return true;

		$memory_limit = Tools::getMemoryLimit();
		// memory_limit == -1 => unlimited memory
		if (function_exists('memory_get_usage') && (int)$memory_limit != -1)
		{
			$current_memory = memory_get_usage();
			$channel = isset($infos['channels']) ? ($infos['channels'] / 8) : 1;

			// Evaluate the memory required to resize the image: if it's too much, you can't resize it.
			if (($infos[0] * $infos[1] * $infos['bits'] * $channel + pow(2, 16)) * 1.8 + $current_memory > $memory_limit - 1024 * 1024)
				return false;
		}

		return true;
	}

	/**
	 * Resize, cut and optimize image
	 *
	 * @param string $src_file Image object from $_FILE
	 * @param string $dst_file Destination filename
	 * @param integer $dst_width Desired width (optional)
	 * @param integer $dst_height Desired height (optional)
	 * @param string $file_type
	 * @return boolean Operation result
	 */
	public static function resize($src_file, $dst_file, $dst_width = null, $dst_height = null, $file_type = 'jpg', $force_type = false, &$error = 0)
	{
		if (PHP_VERSION_ID < 50300)
			clearstatcache();
		else
			clearstatcache(true, $src_file);
		
		if (!file_exists($src_file) || !filesize($src_file))
			return !($error = self::ERROR_FILE_NOT_EXIST);

		list($src_width, $src_height, $type) = getimagesize($src_file);

		// If PS_IMAGE_QUALITY is activated, the generated image will be a PNG with .jpg as a file extension.
		// This allow for higher quality and for transparency. JPG source files will also benefit from a higher quality
		// because JPG reencoding by GD, even with max quality setting, degrades the image.
		if (Configuration::get('PS_IMAGE_QUALITY') == 'png_all'
			|| (Configuration::get('PS_IMAGE_QUALITY') == 'png' && $type == IMAGETYPE_PNG) && !$force_type)
			$file_type = 'png';

		if (!$src_width)
			return !($error = self::ERROR_FILE_WIDTH);
		if (!$dst_width)
			$dst_width = $src_width;
		if (!$dst_height)
			$dst_height = $src_height;

		//$src_image = ImageManager::create($type, $src_file);
		
		$src_image = new Imagick($src_file);

		$width_diff = $dst_width / $src_width;
		$height_diff = $dst_height / $src_height;

		if ($width_diff > 1 && $height_diff > 1)
		{
			$next_width = $src_width;
			$next_height = $src_height;
		}
		else
		{
			if (Configuration::get('PS_IMAGE_GENERATION_METHOD') == 2 || (!Configuration::get('PS_IMAGE_GENERATION_METHOD') && $width_diff < $height_diff))
			{
				$next_height = $dst_height;
				$next_width = round(($src_width * $next_height) / $src_height);
				$dst_width = (int)(!Configuration::get('PS_IMAGE_GENERATION_METHOD') ? $dst_width : $next_width);
			}
			else
			{
				$next_width = $dst_width;
				$next_height = round($src_height * $dst_width / $src_width);
				$dst_height = (int)(!Configuration::get('PS_IMAGE_GENERATION_METHOD') ? $dst_height : $next_height);
			}
		}

		if (!ImageManager::checkImageMemoryLimit($src_file))
			return !($error = self::ERROR_MEMORY_LIMIT);
			
		$dest_image = $src_image;
		$dest_image->resizeImage($dst_width, $dst_height, Imagick::FILTER_LANCZOS, 1);
		//$dest_image->writeImage('mythumb.gif');
		
		//$dest_image->destroy(); 
		
		//$dest_image = imagecreatetruecolor($dst_width, $dst_height);

		// If image is a PNG and the output is PNG, fill with transparency. Else fill with white background.
		/*if ($file_type == 'png' && $type == IMAGETYPE_PNG)
		{
			imagealphablending($dest_image, false);
			imagesavealpha($dest_image, true);
			$transparent = imagecolorallocatealpha($dest_image, 255, 255, 255, 127);
			imagefilledrectangle($dest_image, 0, 0, $dst_width, $dst_height, $transparent);
		}
		else
		{
			$white = imagecolorallocate($dest_image, 255, 255, 255);
			imagefilledrectangle ($dest_image, 0, 0, $dst_width, $dst_height, $white);
		}*/

		/*imagecopyresampled($dest_image, $src_image, (int)(($dst_width - $next_width) / 2), (int)(($dst_height - $next_height) / 2), 0, 0, $next_width, $next_height, $src_width, $src_height);*/
		//return (ImageManager::write($file_type, $dest_image, $dst_file));
		return $dest_image->writeImage($dst_file);
	}

	/**
	 * Check if file is a real image
	 *
	 * @param string $filename File path to check
	 * @param string $file_mime_type File known mime type (generally from $_FILES)
	 * @param array $mime_type_list Allowed MIME types
	 * @return bool
	 */
	public static function isRealImage($filename, $file_mime_type = null, $mime_type_list = null)
	{
		// Detect mime content type
		$mime_type = false;
		if (!$mime_type_list)
			$mime_type_list = array('image/gif', 'image/jpg', 'image/jpeg', 'image/pjpeg', 'image/png', 'image/x-png');

		// Try 4 different methods to determine the mime type
		if (function_exists('getimagesize'))
		{
			$image_info = @getimagesize($filename);

			if ($image_info)
				$mime_type = $image_info['mime'];
			else
				$file_mime_type = false;
		}
		elseif (function_exists('finfo_open'))
		{
			$const = defined('FILEINFO_MIME_TYPE') ? FILEINFO_MIME_TYPE : FILEINFO_MIME;
			$finfo = finfo_open($const);
			$mime_type = finfo_file($finfo, $filename);
			finfo_close($finfo);
		}
		elseif (function_exists('mime_content_type'))
			$mime_type = mime_content_type($filename);
		elseif (function_exists('exec'))
		{
			$mime_type = trim(exec('file -b --mime-type '.escapeshellarg($filename)));
			if (!$mime_type)
				$mime_type = trim(exec('file --mime '.escapeshellarg($filename)));
			if (!$mime_type)
				$mime_type = trim(exec('file -bi '.escapeshellarg($filename)));
		}

		if ($file_mime_type && (empty($mime_type) || $mime_type == 'regular file' || $mime_type == 'text/plain'))
			$mime_type = $file_mime_type;

		// For each allowed MIME type, we are looking for it inside the current MIME type
		foreach ($mime_type_list as $type)
			if (strstr($mime_type, $type))
				return true;

		return false;
	}

	/**
	 * Check if image file extension is correct
	 *
	 * @static
	 * @param $filename real filename
	 * @return bool true if it's correct
	 */
	public static function isCorrectImageFileExt($filename, $authorized_extensions = null)
	{
		// Filter on file extension
		if ($authorized_extensions === null)
			$authorized_extensions = array('gif', 'jpg', 'jpeg', 'jpe', 'png');
		$name_explode = explode('.', $filename);
		if (count($name_explode) >= 2)
		{
			$current_extension = strtolower($name_explode[count($name_explode) - 1]);
			if (!in_array($current_extension, $authorized_extensions))
				return false;
		}
		else
			return false;

		return true;
	}

	/**
	 * Validate image upload (check image type and weight)
	 *
	 * @param array $file Upload $_FILE value
	 * @param integer $max_file_size Maximum upload size
	 * @return bool|string Return false if no error encountered
	 */
	public static function validateUpload($file, $max_file_size = 0, $types = null)
	{
		if ((int)$max_file_size > 0 && $file['size'] > (int)$max_file_size)
			return sprintf(Tools::displayError('Image is too large (%1$d kB). Maximum allowed: %2$d kB'), $file['size'] / 1024, $max_file_size / 1024);
		if (!ImageManager::isRealImage($file['tmp_name'], $file['type']) || !ImageManager::isCorrectImageFileExt($file['name'], $types) || preg_match('/\%00/', $file['name']))
			return Tools::displayError('Image format not recognized, allowed formats are: .gif, .jpg, .png');
		if ($file['error'])
			return sprintf(Tools::displayError('Error while uploading image; please change your server\'s settings. (Error code: %s)'), $file['error']);
		return false;
	}

	/**
	 * Validate icon upload
	 *
	 * @param array $file Upload $_FILE value
	 * @param int $max_file_size Maximum upload size
	 * @return bool|string Return false if no error encountered
	 */
	public static function validateIconUpload($file, $max_file_size = 0)
	{
		if ((int)$max_file_size > 0 && $file['size'] > $max_file_size)
			return sprintf(
				Tools::displayError('Image is too large (%1$d kB). Maximum allowed: %2$d kB'),
				$file['size'] / 1000,
				$max_file_size / 1000
			);
		if (substr($file['name'], -4) != '.ico')
			return Tools::displayError('Image format not recognized, allowed formats are: .ico');
		if ($file['error'])
			return Tools::displayError('Error while uploading image; please change your server\'s settings.');
		return false;
	}

	/**
	 * Cut image
	 *
	 * @param array $src_file Origin filename
	 * @param string $dst_file Destination filename
	 * @param integer $dst_width Desired width
	 * @param integer $dst_height Desired height
	 * @param string $file_type
	 * @param int $dst_x
	 * @param int $dst_y
	 *
	 * @return bool Operation result
	 */
	public static function cut($src_file, $dst_file, $dst_width = null, $dst_height = null, $file_type = 'jpg', $dst_x = 0, $dst_y = 0)
	{
		if (!file_exists($src_file))
			return false;

		// Source information
		$src_info = getimagesize($src_file);
		$src = array(
			'width' => $src_info[0],
			'height' => $src_info[1],
			'ressource' => ImageManager::create($src_info[2], $src_file),
		);

		// Destination information
		$dest = array();
		$dest['x'] = $dst_x;
		$dest['y'] = $dst_y;
		$dest['width'] = !is_null($dst_width) ? $dst_width : $src['width'];
		$dest['height'] = !is_null($dst_height) ? $dst_height : $src['height'];
		$dest['ressource'] = ImageManager::createWhiteImage($dest['width'], $dest['height']);

		$white = imagecolorallocate($dest['ressource'], 255, 255, 255);
		imagecopyresampled($dest['ressource'], $src['ressource'], 0, 0, $dest['x'], $dest['y'], $dest['width'], $dest['height'], $dest['width'], $dest['height']);
		imagecolortransparent($dest['ressource'], $white);
		$return = ImageManager::write($file_type, $dest['ressource'], $dst_file);
		return	$return;
	}

	/**
	 * Create an image with GD extension from a given type
	 *
	 * @param string $type
	 * @param string $filename
	 * @return resource
	 */
	public static function create($type, $filename)
	{
		switch ($type)
		{
			case IMAGETYPE_GIF :
				return imagecreatefromgif($filename);
			break;

			case IMAGETYPE_PNG :
				return imagecreatefrompng($filename);
			break;

			case IMAGETYPE_JPEG :
			default:
				return imagecreatefromjpeg($filename);
			break;
		}
	}

	/**
	 * Create an empty image with white background
	 *
	 * @param int $width
	 * @param int $height
	 * @return resource
	 */
	public static function createWhiteImage($width, $height)
	{
		$image = imagecreatetruecolor($width, $height);
		$white = imagecolorallocate($image, 255, 255, 255);
		imagefill($image, 0, 0, $white);
		return $image;
	}

	/**
	 * Generate and write image
	 *
	 * @param string $type
	 * @param resource $resource
	 * @param string $filename
	 * @return bool
	 */
	public static function write($type, $resource, $filename)
	{
		switch ($type)
		{
			case 'gif':
				$success = imagegif($resource, $filename);
			break;

			case 'png':
				$quality = (Configuration::get('PS_PNG_QUALITY') === false ? 7 : Configuration::get('PS_PNG_QUALITY'));
				$success = imagepng($resource, $filename, (int)$quality);
			break;

			case 'jpg':
			case 'jpeg':
			default:
				$quality = (Configuration::get('PS_JPEG_QUALITY') === false ? 90 : Configuration::get('PS_JPEG_QUALITY'));
				imageinterlace($resource, 1); /// make it PROGRESSIVE
				$success = imagejpeg($resource, $filename, (int)$quality);
			break;
		}
		imagedestroy($resource);
		@chmod($filename, 0664);
		return $success;
	}

	/**
	 * Return the mime type by the file extension
	 *
	 * @param string $file_name
	 * @return string
	 */
	public static function getMimeTypeByExtension($file_name)
	{
		$types = array(
						'image/gif' => array('gif'),
						'image/jpeg' => array('jpg', 'jpeg'),
						'image/png' => array('png')
					);
		$extension = substr($file_name, strrpos($file_name, '.') + 1);

		$mime_type = null;
		foreach ($types as $mime => $exts)
			if (in_array($extension, $exts))
			{
				$mime_type = $mime;
				break;
			}

		if ($mime_type === null)
			$mime_type = 'image/jpeg';

		return $mime_type;
	}
}

The image quality it's significantly improved and the files sizes are very small, producing great results but I have a issue, instead of producing images with the correct size ratio, the images are deformed. I'm I putting this in wrong order? Or something it's missing to calculate the correct ratio?

 

Thank you very much. 

Edited by pedroserapio (see edit history)
  • Like 1
Link to comment
Share on other sites

  • 4 months later...

In Prestashop 1.6.2 I created the following override:

 

override/classes/ImageManager.php

 

with the following code:

<?php

class ImageManager extends ImageManagerCore
{

    /**
     * Resize, cut and optimize image
     *
     * @param string $src_file   Image object from $_FILE
     * @param string $dst_file   Destination filename
     * @param int    $dst_width  Desired width (optional)
     * @param int    $dst_height Desired height (optional)
     * @param string $file_type
     * @param bool   $force_type
     * @param int    $error
     * @param int    $tgt_width
     * @param int    $tgt_height
     * @param int    $quality
     * @param int    $src_width
     * @param int    $src_height
     * @return bool Operation result
     */
    public static function resize($src_file, $dst_file, $dst_width = null, $dst_height = null, $file_type = 'jpg',
                                $force_type = false, &$error = 0, &$tgt_width = null, &$tgt_height = null, $quality = 5,
                                &$src_width = null, &$src_height = null)
    {
        if (PHP_VERSION_ID < 50300) {
            clearstatcache();
        } else {
            clearstatcache(true, $src_file);
        }

        if (!file_exists($src_file) || !filesize($src_file)) {
            return !($error = self::ERROR_FILE_NOT_EXIST);
        }

		list($src_width, $src_height, $type) = getimagesize($src_file);

        // If PS_IMAGE_QUALITY is activated, the generated image will be a PNG with .jpg as a file extension.
        // This allow for higher quality and for transparency. JPG source files will also benefit from a higher quality
        // because JPG reencoding by GD, even with max quality setting, degrades the image.
        if (Configuration::get('PS_IMAGE_QUALITY') == 'png_all'
            || (Configuration::get('PS_IMAGE_QUALITY') == 'png' && $type == IMAGETYPE_PNG) && !$force_type) {
            $file_type = 'png';
        }

        if (!$src_width) {
            return !($error = self::ERROR_FILE_WIDTH);
        }
        if (!$dst_width) {
            $dst_width = $src_width;
        }
        if (!$dst_height) {
            $dst_height = $src_height;
        }
		
        $width_diff = $dst_width / $src_width;
        $height_diff = $dst_height / $src_height;

        $ps_image_generation_method = Configuration::get('PS_IMAGE_GENERATION_METHOD');
        if ($width_diff > 1 && $height_diff > 1) {
            $next_width = $src_width;
            $next_height = $src_height;
        } else {
            if ($ps_image_generation_method == 2 || (!$ps_image_generation_method && $width_diff > $height_diff)) {
                $next_height = $dst_height;
                $next_width = round(($src_width * $next_height) / $src_height);
                $dst_width = (int)(!$ps_image_generation_method ? $dst_width : $next_width);
            } else {
                $next_width = $dst_width;
                $next_height = round($src_height * $dst_width / $src_width);
                $dst_height = (int)(!$ps_image_generation_method ? $dst_height : $next_height);
            }
        }

        if (!ImageManager::checkImageMemoryLimit($src_file)) {
            return !($error = self::ERROR_MEMORY_LIMIT);
        }
		
		//Set Imagick Object values
		$src_image = new Imagick();
		$src_image->readImage($src_file);
		$src_image->setImageCompression(Imagick::COMPRESSION_JPEG);
		$src_image->setInterlaceScheme(Imagick::INTERLACE_PLANE);
		$src_image->setImageCompressionQuality(82);
		$src_image->gaussianBlurImage(0.05,0.05);
		$src_image->stripImage();
		$src_image->thumbnailImage($dst_width, $dst_height, Imagick::FILTER_TRIANGLE, 1);
		
		//Output the final Image using Imagick
		return $src_image->writeImage($dst_file);
		

    }


}

I saw image improvements and lower file sizes. DO NOT FORGET: clear the Cache and Regenerate the Images, and just in case, clear the browser cache.

 

This code it's far from perfect and all the suggestions and improvements and very welcome. I think this can be implemented in PS core with a simple detection if Imagick it's present, if not present, give the possibility to roll back to default GD.

Thank you.

Edited by pedroserapio (see edit history)
  • Like 1
Link to comment
Share on other sites

Hi pedroserapio. Thank you very much for your solution :). I have to tell you that it just works!

The only problem I have is the speed of your solution. It is 2 - 3 times slower then previos GD library. Do you see some parts of code which should be more optimized for speed?

Or you think the ImageMagick is generally slower then GD (I do not think so).

 

EDIT:

There is also another problem. Sometimes when I try to regenerate thumbnails with your modification I have error message on white blank page. The error is about max execution time 90s on line 88 (which is set by my provider to php). But this error happens only after about 30s (so not after 90s). So actually there should be some problem. It happens only sometimes not always.

Edited by jano.jakubik (see edit history)
Link to comment
Share on other sites

Hi pedroserapio. Thank you very much for your solution :). I have to tell you that it just works!

The only problem I have is the speed of your solution. It is 2 - 3 times slower then previos GD library. Do you see some parts of code which should be more optimized for speed?

Or you think the ImageMagick is generally slower then GD (I do not think so).

 

I felt the same, more slower when I regenerated all the images. Probably I'm missing some optimization. 

Edited by pedroserapio (see edit history)
Link to comment
Share on other sites

Hello,

 

Do you have any news? I still have errors when I try to regenerate thumbnails and also speed issues. Errors are about max execution time limit (which is in my hosting 90s) but the script is working only about 10s. This is really random. Sometimes it is gointg to max limit 90s but sometimes it stops about 10 seconds from the begin.

Link to comment
Share on other sites

  • 2 months later...

Hi pedroserapio,

 

thanks for the imageMagic code, it does improve images visibly.

Just one question: when resizing, ImageMagic doesn't keep the aspect ratio. With GD, the aspect ratio was kept the same as for the original. 

How can I change the code so that the aspect ration is kept the same as  the original?

 

UPDATE: I apologize - after using the override with the code mentioned above, it worked.

 

many thanks in advance,

Roland

Edited by Netzfischer (see edit history)
Link to comment
Share on other sites

  • 2 weeks later...
  • 2 weeks later...

Hi

My apologies for posting on here if it's deemed off topic, but I've noticed that if I import product images with a CSV file, the image quality is blurred, compared to adding the images manually in the back office. I've searched for a reason/solution and have been unable to find one!

Link to comment
Share on other sites

  • 2 months later...

I think that this method supossed to be included in new version. Good work.

 

I have changed

//Set Imagick Object values
		$src_image = new Imagick();
		$src_image->readImage($src_file);
		$src_image->setImageCompression(Imagick::COMPRESSION_JPEG);
		$src_image->setInterlaceScheme(Imagick::INTERLACE_PLANE);
		$src_image->setImageCompressionQuality(82);
		$src_image->gaussianBlurImage(0.05,0.05);
		$src_image->stripImage();
		$src_image->thumbnailImage($dst_width, $dst_height, Imagick::FILTER_TRIANGLE, 1);

to this

 //Set Imagick Object values
        $src_image = new Imagick();
        $src_image->readImage($src_file);
        $src_image->setImageCompression(Imagick::COMPRESSION_JPEG);
        $src_image->setInterlaceScheme(Imagick::INTERLACE_PLANE);
        $src_image->setImageCompressionQuality(89);
        $src_image->sharpenimage(2, 0.5, 134217727);
        $src_image->gaussianBlurImage(0.03,0.03);
        $src_image->stripImage();
        $src_image->thumbnailImage($dst_width, $dst_height, Imagick::FILTER_SINC, 1);

Images are much more sharper and with better quality but they're a little bit more heavier. You can check them and let me know.

Edited by hakeryk2 (see edit history)
Link to comment
Share on other sites

  • 2 months later...
  • 2 years later...
Just now, dandumit said:

that thread doesn't exist anymore.  More than that , generally ImageMagick creates bigger files than GD.

No, I made it with ImageMagick to make 360x360 pictures with awesome quality and they are like 5-13 kb per size and without those flaws like GD do.
 

Link to comment
Share on other sites

  • 1 year later...

Hello to everyone,

still with Prestashop 1.7 I faced the same issues then described before here:

1. Pictures uploaded with Prestashop were blurred
2. Also the filesize was too big

So I wondered if those recommended changes would work with 1.7 too. And well it didn't work straight out of the box. But after some try and error I could make it run so that both problems were solved.

My Prestashop Version is 1.7.6.3

To make it work I have first changed classes/ImageManager.php as described by pedroserapio for Prestashop 1.6.0.14 in his first post. It worked straight: After Regenerate thumbnails all the pictures looked straight sharp not blurred anymore. BUT the files where quite too big. So I tried to implement also the second change suggested by pedroserapio for Prestashop 1.6.2. Unfortunately it seems like 1.7 don't allow overrides of core classes anymore. At least I couldn't make it work. So I have just edit the first version from pedroserapio with the second change from him. Then I also implemented the suggested change from hakeryk2. The change from hakeryk2 didn't bring a very big improvement for me but when you look close indeed you can see an improvement.

Here is the whole change I did for classes/ImageManager.php:

<?php
class ImageManagerCore
{
	const ERROR_FILE_NOT_EXIST = 1;
	const ERROR_FILE_WIDTH     = 2;
	const ERROR_MEMORY_LIMIT   = 3;

	/**
	 * Generate a cached thumbnail for object lists (eg. carrier, order statuses...etc)
	 *
	 * @param string $image Real image filename
	 * @param string $cache_image Cached filename
	 * @param int $size Desired size
	 * @param string $image_type Image type
	 * @param bool $disable_cache When turned on a timestamp will be added to the image URI to disable the HTTP cache
	 * @param bool $regenerate When turned on and the file already exist, the file will be regenerated
	 * @return string
	 */
	public static function thumbnail($image, $cache_image, $size, $image_type = 'jpg', $disable_cache = true, $regenerate = false)
	{
		if (!file_exists($image))
			return '';

		if (file_exists(_PS_TMP_IMG_DIR_.$cache_image) && $regenerate)
			@unlink(_PS_TMP_IMG_DIR_.$cache_image);

		if ($regenerate || !file_exists(_PS_TMP_IMG_DIR_.$cache_image))
		{
			$infos = getimagesize($image);

			// Evaluate the memory required to resize the image: if it's too much, you can't resize it.
			if (!ImageManager::checkImageMemoryLimit($image))
				return false;

			$x = $infos[0];
			$y = $infos[1];
			$max_x = $size * 3;

			// Size is already ok
			if ($y < $size && $x <= $max_x)
				copy($image, _PS_TMP_IMG_DIR_.$cache_image);
			// We need to resize */
			else
			{
				$ratio_x = $x / ($y / $size);
				if ($ratio_x > $max_x)
				{
					$ratio_x = $max_x;
					$size = $y / ($x / $max_x);
				}

				ImageManager::resize($image, _PS_TMP_IMG_DIR_.$cache_image, $ratio_x, $size, $image_type);
			}
		}
		// Relative link will always work, whatever the base uri set in the admin
		if (Context::getContext()->controller->controller_type == 'admin')
			return '<img src="../img/tmp/'.$cache_image.($disable_cache ? '?time='.time() : '').'" alt="" class="imgm img-thumbnail" />';
		else
			return '<img src="'._PS_TMP_IMG_.$cache_image.($disable_cache ? '?time='.time() : '').'" alt="" class="imgm img-thumbnail" />';
	}

	/**
	 * Check if memory limit is too long or not
	 *
	 * @static
	 * @param $image
	 * @return bool
	 */
	public static function checkImageMemoryLimit($image)
	{
		$infos = @getimagesize($image);

		if (!is_array($infos) || !isset($infos['bits']))
			return true;

		$memory_limit = Tools::getMemoryLimit();
		// memory_limit == -1 => unlimited memory
		if (function_exists('memory_get_usage') && (int)$memory_limit != -1)
		{
			$current_memory = memory_get_usage();
			$channel = isset($infos['channels']) ? ($infos['channels'] / 8) : 1;

			// Evaluate the memory required to resize the image: if it's too much, you can't resize it.
			if (($infos[0] * $infos[1] * $infos['bits'] * $channel + pow(2, 16)) * 1.8 + $current_memory > $memory_limit - 1024 * 1024)
				return false;
		}

		return true;
	}

	/**
	 * Resize, cut and optimize image
	 *
	 * @param string $src_file Image object from $_FILE
	 * @param string $dst_file Destination filename
	 * @param integer $dst_width Desired width (optional)
	 * @param integer $dst_height Desired height (optional)
	 * @param string $file_type
	 * @return boolean Operation result
	 */
	public static function resize($src_file, $dst_file, $dst_width = null, $dst_height = null, $file_type = 'jpg', $force_type = false, &$error = 0)
	{
		if (PHP_VERSION_ID < 50300)
			clearstatcache();
		else
			clearstatcache(true, $src_file);
		
		if (!file_exists($src_file) || !filesize($src_file))
			return !($error = self::ERROR_FILE_NOT_EXIST);

		list($src_width, $src_height, $type) = getimagesize($src_file);

		// If PS_IMAGE_QUALITY is activated, the generated image will be a PNG with .jpg as a file extension.
		// This allow for higher quality and for transparency. JPG source files will also benefit from a higher quality
		// because JPG reencoding by GD, even with max quality setting, degrades the image.
		if (Configuration::get('PS_IMAGE_QUALITY') == 'png_all'
			|| (Configuration::get('PS_IMAGE_QUALITY') == 'png' && $type == IMAGETYPE_PNG) && !$force_type)
			$file_type = 'png';

		if (!$src_width)
			return !($error = self::ERROR_FILE_WIDTH);
		if (!$dst_width)
			$dst_width = $src_width;
		if (!$dst_height)
			$dst_height = $src_height;
		
		$width_diff = $dst_width / $src_width;
		$height_diff = $dst_height / $src_height;

		if ($width_diff > 1 && $height_diff > 1)
		{
			$next_width = $src_width;
			$next_height = $src_height;
		}
		else
		{
			if (Configuration::get('PS_IMAGE_GENERATION_METHOD') == 2 || (!Configuration::get('PS_IMAGE_GENERATION_METHOD') && $width_diff < $height_diff))
			{
				$next_height = $dst_height;
				$next_width = round(($src_width * $next_height) / $src_height);
				$dst_width = (int)(!Configuration::get('PS_IMAGE_GENERATION_METHOD') ? $dst_width : $next_width);
			}
			else
			{
				$next_width = $dst_width;
				$next_height = round($src_height * $dst_width / $src_width);
				$dst_height = (int)(!Configuration::get('PS_IMAGE_GENERATION_METHOD') ? $dst_height : $next_height);
			}
		}

		if (!ImageManager::checkImageMemoryLimit($src_file))
			return !($error = self::ERROR_MEMORY_LIMIT);
		
				//Set Imagick Object values
        $src_image = new Imagick();
        $src_image->readImage($src_file);
        $src_image->setImageCompression(Imagick::COMPRESSION_JPEG);
        $src_image->setInterlaceScheme(Imagick::INTERLACE_PLANE);
        $src_image->setImageCompressionQuality(70);
        $src_image->sharpenimage(2, 0.5, 134217727);
        $src_image->gaussianBlurImage(0.03,0.03);
        $src_image->stripImage();
        $src_image->thumbnailImage($dst_width, $dst_height, Imagick::FILTER_SINC, 1);
		
		//Output the final Image using Imagick
		return $src_image->writeImage($dst_file);
		
	}

	/**
	 * Check if file is a real image
	 *
	 * @param string $filename File path to check
	 * @param string $file_mime_type File known mime type (generally from $_FILES)
	 * @param array $mime_type_list Allowed MIME types
	 * @return bool
	 */
	public static function isRealImage($filename, $file_mime_type = null, $mime_type_list = null)
	{
		// Detect mime content type
		$mime_type = false;
		if (!$mime_type_list)
			$mime_type_list = array('image/gif', 'image/jpg', 'image/jpeg', 'image/pjpeg', 'image/png', 'image/x-png');

		// Try 4 different methods to determine the mime type
		if (function_exists('getimagesize'))
		{
			$image_info = @getimagesize($filename);

			if ($image_info)
				$mime_type = $image_info['mime'];
			else
				$file_mime_type = false;
		}
		elseif (function_exists('finfo_open'))
		{
			$const = defined('FILEINFO_MIME_TYPE') ? FILEINFO_MIME_TYPE : FILEINFO_MIME;
			$finfo = finfo_open($const);
			$mime_type = finfo_file($finfo, $filename);
			finfo_close($finfo);
		}
		elseif (function_exists('mime_content_type'))
			$mime_type = mime_content_type($filename);
		elseif (function_exists('exec'))
		{
			$mime_type = trim(exec('file -b --mime-type '.escapeshellarg($filename)));
			if (!$mime_type)
				$mime_type = trim(exec('file --mime '.escapeshellarg($filename)));
			if (!$mime_type)
				$mime_type = trim(exec('file -bi '.escapeshellarg($filename)));
		}

		if ($file_mime_type && (empty($mime_type) || $mime_type == 'regular file' || $mime_type == 'text/plain'))
			$mime_type = $file_mime_type;

		// For each allowed MIME type, we are looking for it inside the current MIME type
		foreach ($mime_type_list as $type)
			if (strstr($mime_type, $type))
				return true;

		return false;
	}

	/**
	 * Check if image file extension is correct
	 *
	 * @static
	 * @param $filename real filename
	 * @return bool true if it's correct
	 */
	public static function isCorrectImageFileExt($filename, $authorized_extensions = null)
	{
		// Filter on file extension
		if ($authorized_extensions === null)
			$authorized_extensions = array('gif', 'jpg', 'jpeg', 'jpe', 'png');
		$name_explode = explode('.', $filename);
		if (count($name_explode) >= 2)
		{
			$current_extension = strtolower($name_explode[count($name_explode) - 1]);
			if (!in_array($current_extension, $authorized_extensions))
				return false;
		}
		else
			return false;

		return true;
	}

	/**
	 * Validate image upload (check image type and weight)
	 *
	 * @param array $file Upload $_FILE value
	 * @param integer $max_file_size Maximum upload size
	 * @return bool|string Return false if no error encountered
	 */
	public static function validateUpload($file, $max_file_size = 0, $types = null)
	{
		if ((int)$max_file_size > 0 && $file['size'] > (int)$max_file_size)
			return sprintf(Tools::displayError('Image is too large (%1$d kB). Maximum allowed: %2$d kB'), $file['size'] / 1024, $max_file_size / 1024);
		if (!ImageManager::isRealImage($file['tmp_name'], $file['type']) || !ImageManager::isCorrectImageFileExt($file['name'], $types) || preg_match('/\%00/', $file['name']))
			return Tools::displayError('Image format not recognized, allowed formats are: .gif, .jpg, .png');
		if ($file['error'])
			return sprintf(Tools::displayError('Error while uploading image; please change your server\'s settings. (Error code: %s)'), $file['error']);
		return false;
	}

	/**
	 * Validate icon upload
	 *
	 * @param array $file Upload $_FILE value
	 * @param int $max_file_size Maximum upload size
	 * @return bool|string Return false if no error encountered
	 */
	public static function validateIconUpload($file, $max_file_size = 0)
	{
		if ((int)$max_file_size > 0 && $file['size'] > $max_file_size)
			return sprintf(
				Tools::displayError('Image is too large (%1$d kB). Maximum allowed: %2$d kB'),
				$file['size'] / 1000,
				$max_file_size / 1000
			);
		if (substr($file['name'], -4) != '.ico')
			return Tools::displayError('Image format not recognized, allowed formats are: .ico');
		if ($file['error'])
			return Tools::displayError('Error while uploading image; please change your server\'s settings.');
		return false;
	}

	/**
	 * Cut image
	 *
	 * @param array $src_file Origin filename
	 * @param string $dst_file Destination filename
	 * @param integer $dst_width Desired width
	 * @param integer $dst_height Desired height
	 * @param string $file_type
	 * @param int $dst_x
	 * @param int $dst_y
	 *
	 * @return bool Operation result
	 */
	public static function cut($src_file, $dst_file, $dst_width = null, $dst_height = null, $file_type = 'jpg', $dst_x = 0, $dst_y = 0)
	{
		if (!file_exists($src_file))
			return false;

		// Source information
		$src_info = getimagesize($src_file);
		$src = array(
			'width' => $src_info[0],
			'height' => $src_info[1],
			'ressource' => ImageManager::create($src_info[2], $src_file),
		);

		// Destination information
		$dest = array();
		$dest['x'] = $dst_x;
		$dest['y'] = $dst_y;
		$dest['width'] = !is_null($dst_width) ? $dst_width : $src['width'];
		$dest['height'] = !is_null($dst_height) ? $dst_height : $src['height'];
		$dest['ressource'] = ImageManager::createWhiteImage($dest['width'], $dest['height']);

		$white = imagecolorallocate($dest['ressource'], 255, 255, 255);
		imagecopyresampled($dest['ressource'], $src['ressource'], 0, 0, $dest['x'], $dest['y'], $dest['width'], $dest['height'], $dest['width'], $dest['height']);
		imagecolortransparent($dest['ressource'], $white);
		$return = ImageManager::write($file_type, $dest['ressource'], $dst_file);
		return	$return;
	}

	/**
	 * Create an image with GD extension from a given type
	 *
	 * @param string $type
	 * @param string $filename
	 * @return resource
	 */
	public static function create($type, $filename)
	{
		switch ($type)
		{
			case IMAGETYPE_GIF :
				return imagecreatefromgif($filename);
			break;

			case IMAGETYPE_PNG :
				return imagecreatefrompng($filename);
			break;

			case IMAGETYPE_JPEG :
			default:
				return imagecreatefromjpeg($filename);
			break;
		}
	}

	/**
	 * Create an empty image with white background
	 *
	 * @param int $width
	 * @param int $height
	 * @return resource
	 */
	public static function createWhiteImage($width, $height)
	{
		$image = imagecreatetruecolor($width, $height);
		$white = imagecolorallocate($image, 255, 255, 255);
		imagefill($image, 0, 0, $white);
		return $image;
	}

	/**
	 * Generate and write image
	 *
	 * @param string $type
	 * @param resource $resource
	 * @param string $filename
	 * @return bool
	 */
	public static function write($type, $resource, $filename)
	{
		switch ($type)
		{
			case 'gif':
				$success = imagegif($resource, $filename);
			break;

			case 'png':
				$quality = (Configuration::get('PS_PNG_QUALITY') === false ? 7 : Configuration::get('PS_PNG_QUALITY'));
				$success = imagepng($resource, $filename, (int)$quality);
			break;

			case 'jpg':
			case 'jpeg':
			default:
				$quality = (Configuration::get('PS_JPEG_QUALITY') === false ? 90 : Configuration::get('PS_JPEG_QUALITY'));
				imageinterlace($resource, 1); /// make it PROGRESSIVE
				$success = imagejpeg($resource, $filename, (int)$quality);
			break;
		}
		imagedestroy($resource);
		@chmod($filename, 0664);
		return $success;
	}

	/**
	 * Return the mime type by the file extension
	 *
	 * @param string $file_name
	 * @return string
	 */
	public static function getMimeTypeByExtension($file_name)
	{
		$types = array(
						'image/gif' => array('gif'),
						'image/jpeg' => array('jpg', 'jpeg'),
						'image/png' => array('png')
					);
		$extension = substr($file_name, strrpos($file_name, '.') + 1);

		$mime_type = null;
		foreach ($types as $mime => $exts)
			if (in_array($extension, $exts))
			{
				$mime_type = $mime;
				break;
			}

		if ($mime_type === null)
			$mime_type = 'image/jpeg';

		return $mime_type;
	}
}

I hope it might be helpful for someone else too.

Unfortunately I had to edit the original core files. So with the next Prestashop update it will be lost. So if someone knows how to still use the overrides in 1.7, maybe one can post the right version for it.

 

Edited by Afriluka (see edit history)
Link to comment
Share on other sites

This code seems to be based on old version and does not consider options from the backoffice (e.g. JPG/PNG Compression).

I took the latest 1.7.6.4 code and used your Imagick code in an override. You can out the attached file into /override/classes/ - to activate it delete /var/cache/prod/class_index.php. Didn't test the rotate function which is copied from PHP.net comments.

ImageManager.php

  • Thanks 1
Link to comment
Share on other sites

Hey JBW,

thanks so much for your help. Unfortunately I have only basic understanding of coding. After using the code that I have posted here I was facing problems with uploading new images for a product.

I am actually testing where the problem could be.

So the code above is working only on pictures that were already uploaded but not on pictures that will be newly uploaded.

I will test your version too and let you know how it worked.

 

 

Product_picture.png

Edited by Afriluka (see edit history)
Link to comment
Share on other sites

Hey JBW,

I have found my problem. It was another override that I have tried to use before. This was causing the problem. Now I set everything to the original Prestashop files and used your provided file as a new override and deleted the classes_index.php. And voi­là your solution works like a charme!

Thank you very much for your help!

As you can see in the example, the picture before was blurred. And before with the same compression the picture had almost 200kb. Now it looks better and it has only about 140kb.

After.png

before.png

Edited by Afriluka (see edit history)
Link to comment
Share on other sites

  • 3 months later...
  • 5 weeks later...
  • 1 month later...
On 4/1/2020 at 11:06 AM, JBW said:

This code seems to be based on old version and does not consider options from the backoffice (e.g. JPG/PNG Compression).

I took the latest 1.7.6.4 code and used your Imagick code in an override. You can out the attached file into /override/classes/ - to activate it delete /var/cache/prod/class_index.php. Didn't test the rotate function which is copied from PHP.net comments.

ImageManager.php 6.23 kB · 23 downloads

Only small improvement, because there is 2x  ImageManager::checkImageMemoryLimit 

That can speed up things  a little bit 😉

I also deleted the commented code at the beginning.

ImageManager.php

  • Like 1
Link to comment
Share on other sites

  • 1 month later...

Maybe this should still be considered:

At he beginning:

if (preg_match('/\/img\/(p|c|m|s|st)\/(\d+\/)*\d+\.(jpg|jpeg|png|gif)$/Us', $destinationFile)) {
    return @copy($sourceFile, $destinationFile);
}

From here: https://www.sunnytoo.com/42096/an-important-fix-to-optimize-prestashops-image-upload-function

 

To keep the original image as it was. For further possible optimization in the future

Edited by Peter Liska (see edit history)
Link to comment
Share on other sites

  • 1 year later...
  • 7 months later...

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...