Image manipulation and watermarking in PHP with GD2

Update: The following code is now using the HTML5 XHR upload capabilities instead of the Flash uploader, you might want to check that out.

The following functions are what I usually use to handle images in my projects. We’ve got reScaleImage which will resize and save an image, keeping aspect ratio.


We’ve got constrainImage which will only get new width and height for an image if it is to be scaled using constraints in both x and y. Let’s pretend we’ve got an image that’s 1000×500 and pass its filename and 500 and 500 as constraints on x and y. It should then return 500×250. This function will not actually do anything with the image itself, only get the new dimensions.

And then we’ve got the workhorse, resizeDiscardAspect which will do all the real manipulations. It is for instance combined with constrainImage in reScaleImage to manage scaling whilst keeping aspect ratio. ResizeDiscardAspect will scale in place, ie no new file name is needed for the result, it is not very useful by itself, the only situation I can think of is rescaling avatar images down to small squares.

Scroll to the bottom of the article to see a use case of all of the below functions.

static function reScaleImage($filename, $conx, $cony){
	if(empty($conx) || empty($cony))
		return;
	list($new_width, $new_height) = self::constrainImage($filename, $conx, $cony);
	return self::resizeDiscardAspect($filename, $new_width, $new_height);
}

Update: I just got an email from Mark Davies:

I ran into a bug where my constraints ratio equaled my image dimensions ratio. In the constrainImage function one of the conditions should be <= (or >=) … I chose one arbitrarily as I don’t think it matters which!

I must’ve been lucky, I never experienced any problems but the logic is irrefutable, the listing below has been corrected.

Resize and discard aspect, that sounds a little bit crude? Sure but we keep it through constrainImage():

static function constrainImage($filename, $conx, $cony){
	list($orig_width, $orig_height, $type) = getimagesize($filename);
	
	$new_width	=	0;
	$new_height	=	0;
	
	// for instance 0.66 = 125 / 188
	$con_ratio 		= $conx / $cony;
	
	// for instance  1.33 =  300 / 225
	$orig_ratio		= $orig_width / $orig_height;
	
	//if the new picture is laying and the original is standing or laying
	//"less", the original height has to lead
	if($con_ratio >= $orig_ratio){
		$new_height = $cony;
		$new_width 	= round(( $cony * $orig_width) / $orig_height);
	}else if($con_ratio <= $orig_ratio){
		$new_height = round(( $conx * $orig_height) / $orig_width);
		$new_width 	= $conx;
	}
	
	return array($new_width, $new_height);
}

And finally the resizeDiscardAspect function which is just a copy of the GD2 section in the PHP manual:

static function resizeDiscardAspect($fileName, $new_width, $new_height){
	//we retrieve the info from the current image
	list($orig_width, $orig_height, $type) = getimagesize($fileName);
	//we create a new image template
	$image_p = imagecreatetruecolor($new_width, $new_height);
	//we create a variable that will hold the new image
	$image = null;
	//only the three first of all the possible formats are supported, the original image is loaded if it is one of them
	switch($type){
		case 1: //GIF
			$image = imagecreatefromgif($fileName);
			break;
		case 2: //JPEG
			$image = imagecreatefromjpeg($fileName);
			break;
		case 3: //PNG
			$image = imagecreatefrompng($fileName);
			break;
		default:
			return false;
			break;
	}
	//we copy the resized image from the original into the new one and save the result as a jpeg   
	imagecopyresampled($image_p, $image, 0, 0, 0, 0, $new_width, $new_height, $orig_width, $orig_height);
	imagejpeg($image_p, $fileName, 100);
	return true;
}

That was the resizing, let’s go through the watermarking stuff:

static function getImagePrefix($type){
	$arr = array(1 => "gif", 2 => "jpg", 3 => "png");
	return $arr[$type];
}

static function getImageType($filename){
	list($orig_width, $orig_height, $type) = getimagesize($filename);
	return self::getImagePrefix($type);
}

static function watermark($wm_path, $bkg_path){
	$watermark 						= imagecreatefrompng($wm_path);
	list($wm_width, $wm_height, $type) 	= getimagesize($wm_path);
	$bkg 							= imagecreatefromjpeg($bkg_path);
	list($bkg_width, $bkg_height, $type) 	= getimagesize($bkg_path);
	$dest_x 								= $bkg_width - $wm_width - 5;  
	$dest_y 								= $bkg_height - $wm_height - 5;
	imagecopy($bkg, $watermark, $dest_x, $dest_y, 0, 0, $wm_width, $wm_height);
	imagejpeg($bkg, $bkg_path, 100);
	imagedestroy($bkg);
	imagedestroy($watermark);
}

Watermark will take two paths to two different files, one to the image to be put as a watermark. It needs to be a PNG with alpha of course, we’re not lowering its opacity either so that has to be set in the image already.

The background needs to be a jpeg and we use imagecopy to put the watermark/png on top of the background/jpg. Finally the jpg/background is saved in place.

Let’s finish off with a use case:

function uploadImages(){
	$gallery = $this->loadObject("SELECT * FROM jos_igallery WHERE id = {$this->getArr['gallery_id']}");
	
	$file_type = Common::getImageType($_FILES["Filedata"]["tmp_name"]);
	$filename = uniqid().'.'.$file_type;
	$big_path = "images/stories/igallery/{$gallery->folder}/large/$filename";
	$thumb_path = "images/stories/igallery/{$gallery->folder}/thumbs/$filename";
	
	if(move_uploaded_file($_FILES["Filedata"]["tmp_name"], $big_path) && copy($big_path, $thumb_path)){
		Common::reScaleImage($big_path, $gallery->max_width, $gallery->max_height);
		Common::watermark('images/logo.png', $big_path);
		Common::reScaleImage($thumb_path, $gallery->thumb_width, $gallery->thumb_height);
		$obj = new stdClass();
		$obj->gallery_id = $gallery->id;
		$obj->filename = $filename;
		$obj->published = 1;
		$this->insertObject('jos_igallery_img', $obj);
		echo true;
	}else
		echo false;
}

The above is from a project using the Joomla 1.5 iGallery component, it’s also using the jQuery and Flash multi file uploader.

We’re:
1) Getting the gallery id in question, the id is passed in the URL so we get it through the $_GET global.

2) The file will now be accessible through $_FILES[“Filedata”][“tmp_name”], its name will be something completely random WITHOUT a file type, that’s why we have to use getImageType to have GD2 actually go look in its binary representation and find out its type.

3) Next we store the path to the big version and a thumbnail that will be used in the galleries.

4) The big image is moved to its folder and the thumbnail gets copied from there to its own folder.

5) We resize the big image using settings that were set when the gallery was created.

6) We watermark the big image.

7) We resize the thumb, note that it too will have its aspect ratio preserved.

8) Finally we save the gallery image entry to the database.


Related Posts

Tags: , , , ,