image quality - what is best practice?

275 views
Skip to first unread message

Nicolaas Thiemen Francken - Sunny Side Up

unread,
Mar 16, 2010, 12:40:08 AM3/16/10
to silverst...@googlegroups.com
Hi Folks

Does anyone have any thoughts on image resampling quality:

* correct size is already loaded, image is still resampled?
* jpg vs png vs gif
* GD::set_quality

Overall, it seems resampling quality can be pretty low - which I
reckon is the built-in PHP image libraries. Even if the GD quality is
set fairly high (e.g. 85). PNG seems to be better than JPG.

What do you do when you have a logo, which can be uploaded by the CMS
editor, to be displayed on the site with a width of 200 (e.g. in the
template file you have $Logo.SetWidth(200)), do you recommend the
editor to upload an image with the exact width of 200 or anything over
200 wide? What would yield the best results?

Do any of you use external image libraries to improve quality?

Any thoughts appreciated.

What may come from here is some recommendations for best practice
(e.g. use PNG rather than JPG), perhaps some ideas for additional info
in CMS for CMS editor about image quality.

Cheers

Nicolaas

Roman

unread,
Mar 16, 2010, 3:34:45 AM3/16/10
to silverst...@googlegroups.com
Hi Nicolaas

I'm frequently developing Websites for Graphic Designers and they are quite picky when it comes to Image quality. It even occured, that they started to upload Photos as PNGs because of the better quality. Which of course resulted in huge load times (~300k per Photo). Not a good solution either ;)
What I usually do for all the Sites I'm developing:
Set GD quality to 85 or 90.
Create a special Image class, that I use for all Image-Relations:

<?php
class BetterImage extends Image
{	
	public function SetWidth($width) {
		if($width == $this->getWidth()){
			return $this;
		}
			
		return $this->getFormattedImage('SetWidth', $width);
	}
	
	public function SetHeight($height) {
		if($height == $this->getHeight()){
			return $this;
		}
			
		return $this->getFormattedImage('SetHeight', $height);
	}
	
}

This class simply checks if the Image already has the right dimensions and returns the current Image, if it fits. Above only SetWidth and SetHeight are implemented, but you can perform checks for other methods too.

Then in your Pages, use "BetterImage" instead of "Image". eg.

static $has_one = array(
	'Photo' => 'BetterImage'
);

IMHO these checks could directly be implemented in the core, since it's really redundant to re-calculate an Image if it already has the correct dimensions.

Cheers
Roman a.k.a banal

Mat Weir

unread,
Mar 16, 2010, 3:40:45 AM3/16/10
to silverst...@googlegroups.com
Yes, I do these same checks with the crop resize method and I would prefer they were in the core.

I add a sharpening method to all jpg resizes which dramatically improves the image quality (GD quality won't make much visible difference). I've had to change the GD class but it would be nice if sharpening was in GD by default and could be statically turned on/off. I'll share this code tomorrow.

Mat Weir
Xebidy Social Web Design



--
You received this message because you are subscribed to the Google Groups "SilverStripe Development" group.
To post to this group, send email to silverst...@googlegroups.com.
To unsubscribe from this group, send email to silverstripe-d...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/silverstripe-dev?hl=en.

Nicolaas Thiemen Francken - Sunny Side Up

unread,
Mar 16, 2010, 4:19:42 AM3/16/10
to silverst...@googlegroups.com
Hey Mat and Roman

Some interesting responses. Both definitely sound like good potentials
for adding to the core. I have to read up a bit more about Mat's
suggestion, but as far as Roman's idea goes, it seems an instant
winner (dont resize if it is already the right size).

Anyone else with ideas? What do you usually do if you want the CMS
editor to upload at least a certain size, do you enforce this or
anything like that?

Cheers

Nicolaas

Al Twohill

unread,
Mar 17, 2010, 4:54:21 PM3/17/10
to silverst...@googlegroups.com
Another nice feature would be to dynamically choose a quality to meet a maximum size, so a small image might be GD quality 100, whilst a large one might be 80 or whatever, in order to limit the size to a max of 100kb.

I guess for highly graphical sites you might even want the ability to turn off resizing completely, and use a professional program to do the resizing.

-- Al

Mat Weir

unread,
Apr 29, 2010, 4:10:57 AM4/29/10
to silverst...@googlegroups.com
Nicolaas,

Sorry for the wait.

Here is the code that I use to sharpen images. Unfortunately it means making changes to core as there isn't really any way of extending these objects or decorating them. It makes a massive difference and I can show you the site if you email me privately.

Replace generateFormattedImage function in sapphire/core/model/Image.php with:

function generateFormattedImage($format, $arg1 = null, $arg2 = null) {
$cacheFile = $this->cacheFilename($format, $arg1, $arg2);

$gd = new GD("../" . $this->Filename);
if($gd->hasGD()){
$generateFunc = "generate$format";
if($this->hasMethod($generateFunc)){
$gd = $this->$generateFunc($gd, $arg1, $arg2);
if($gd){
$gd = $gd->UnsharpMask();
$gd->writeTo("../" . $cacheFile);
}
} else {
USER_ERROR("Image::generateFormattedImage - Image $format function not found.",E_USER_WARNING);
}
}
}




Added to sapphire/filesystem/GD.php:

function UnsharpMask() { 
$newGD = imagecreatetruecolor($this->width, $this->height);
imagecopyresampled($newGD, $this->gd, 0, 0, 0, 0, $this->width, $this->height, $this->width, $this->height);
// typically 50 - 200
$amount = 80;
// typically 0.5 - 1
$radius = 0.5;
// typically 0 - 5
$threshold = 3;
//$amount = $this->amount;
//$radius = $this->radius;
//$threshold = $this->threshold;
////////////////////////////////////////////////////////////////////////////////////////////////  
////  
////                  Unsharp Mask for PHP - version 2.1.1  
////  
////    Unsharp mask algorithm by Torstein Hønsi 2003-07.  
////             thoensi_at_netcom_dot_no.  
////               Please leave this notice.  
////  
///////////////////////////////////////////////////////////////////////////////////////////////  
// $newGD is an image that is already created within php using 
// imgcreatetruecolor. No url! $newGD must be a truecolor image. 
// Attempt to calibrate the parameters to Photoshop: 
if ($amount > 500)    $amount = 500; 
$amount = $amount * 0.016; 
if ($radius > 50)    $radius = 50; 
$radius = $radius * 2; 
if ($threshold > 255)    $threshold = 255; 
$radius = abs(round($radius));     // Only integers make sense. 
if ($radius == 0) { 
return $newGD; imagedestroy($newGD); break;        
$w = imagesx($newGD); $h = imagesy($newGD); 
$imgCanvas = imagecreatetruecolor($w, $h); 
$imgBlur = imagecreatetruecolor($w, $h); 
// Gaussian blur matrix: 
//                         
//    1    2    1         
//    2    4    2         
//    1    2    1         
//                         
////////////////////////////////////////////////// 
if (function_exists('imageconvolution')) { // PHP >= 5.1  
$matrix = array(  
array( 1, 2, 1 ),  
array( 2, 4, 2 ),  
array( 1, 2, 1 )  
);  
imagecopy ($imgBlur, $newGD, 0, 0, 0, 0, $w, $h); 
imageconvolution($imgBlur, $matrix, 16, 0);  
}  
else {  
// Move copies of the image around one pixel at the time and merge them with weight 
// according to the matrix. The same matrix is simply repeated for higher radii. 
for ($i = 0; $i < $radius; $i++)    { 
imagecopy ($imgBlur, $newGD, 0, 0, 1, 0, $w - 1, $h); // left 
imagecopymerge ($imgBlur, $newGD, 1, 0, 0, 0, $w, $h, 50); // right 
imagecopymerge ($imgBlur, $newGD, 0, 0, 0, 0, $w, $h, 50); // center 
imagecopy ($imgCanvas, $imgBlur, 0, 0, 0, 0, $w, $h); 
imagecopymerge ($imgBlur, $imgCanvas, 0, 0, 0, 1, $w, $h - 1, 33.33333 ); // up 
imagecopymerge ($imgBlur, $imgCanvas, 0, 1, 0, 0, $w, $h, 25); // down 
if($threshold>0){ 
// Calculate the difference between the blurred pixels and the original 
// and set the pixels 
for ($x = 0; $x < $w-1; $x++)    { // each row
for ($y = 0; $y < $h; $y++)    { // each pixel 
$rgbOrig = ImageColorAt($newGD, $x, $y); 
$rOrig = (($rgbOrig >> 16) & 0xFF); 
$gOrig = (($rgbOrig >> 8) & 0xFF); 
$bOrig = ($rgbOrig & 0xFF); 
$rgbBlur = ImageColorAt($imgBlur, $x, $y); 
$rBlur = (($rgbBlur >> 16) & 0xFF); 
$gBlur = (($rgbBlur >> 8) & 0xFF); 
$bBlur = ($rgbBlur & 0xFF); 
// When the masked pixels differ less from the original 
// than the threshold specifies, they are set to their original value. 
$rNew = (abs($rOrig - $rBlur) >= $threshold)  
? max(0, min(255, ($amount * ($rOrig - $rBlur)) + $rOrig))  
: $rOrig; 
$gNew = (abs($gOrig - $gBlur) >= $threshold)  
? max(0, min(255, ($amount * ($gOrig - $gBlur)) + $gOrig))  
: $gOrig; 
$bNew = (abs($bOrig - $bBlur) >= $threshold)  
? max(0, min(255, ($amount * ($bOrig - $bBlur)) + $bOrig))  
: $bOrig; 
  
if (($rOrig != $rNew) || ($gOrig != $gNew) || ($bOrig != $bNew)) { 
$pixCol = ImageColorAllocate($newGD, $rNew, $gNew, $bNew); 
ImageSetPixel($newGD, $x, $y, $pixCol); 
else{ 
for ($x = 0; $x < $w; $x++)    { // each row 
for ($y = 0; $y < $h; $y++)    { // each pixel 
$rgbOrig = ImageColorAt($newGD, $x, $y); 
$rOrig = (($rgbOrig >> 16) & 0xFF); 
$gOrig = (($rgbOrig >> 8) & 0xFF); 
$bOrig = ($rgbOrig & 0xFF); 
$rgbBlur = ImageColorAt($imgBlur, $x, $y); 
$rBlur = (($rgbBlur >> 16) & 0xFF); 
$gBlur = (($rgbBlur >> 8) & 0xFF); 
$bBlur = ($rgbBlur & 0xFF); 
$rNew = ($amount * ($rOrig - $rBlur)) + $rOrig; 
if($rNew>255){$rNew=255;} 
elseif($rNew<0){$rNew=0;} 
$gNew = ($amount * ($gOrig - $gBlur)) + $gOrig; 
if($gNew>255){$gNew=255;} 
elseif($gNew<0){$gNew=0;} 
$bNew = ($amount * ($bOrig - $bBlur)) + $bOrig; 
if($bNew>255){$bNew=255;} 
elseif($bNew<0){$bNew=0;} 
$rgbNew = ($rNew << 16) + ($gNew <<8) + $bNew; 
ImageSetPixel($newGD, $x, $y, $rgbNew); 
imagedestroy($imgCanvas); 
imagedestroy($imgBlur); 
$output = new GD();
$output->setGD($newGD);
return $output;

--
You received this message because you are subscribed to the Google Groups "SilverStripe Core Development" group.

Nicolaas Thiemen Francken - Sunny Side Up

unread,
Apr 30, 2010, 11:25:12 AM4/30/10
to silverst...@googlegroups.com
Should this be added to the core?

Nicolaas

--
Nicolaas Thiemen Francken
Director - Sunny Side Up Ltd
skype: atyourservicehour
Phone: +64 4 889 2773
n...@sunnysideup.co.nz
http://www.sunnysideup.co.nz
- client login: https://rakau.updatelog.com/
- new quotes: http://www.sunnysideup.co.nz/services
- please support: http://www.localorganics.net/
- newsletter: http://www.sunnysideup.co.nz/contact
- R&D website: http://silverstripe-webdevelopment.com
Reply all
Reply to author
Forward
0 new messages