HalThumb – A Quick and Dirty Image Resizer Script for PHP or ASP.NET

As a web developer, you are going to have to display images at different sizes. When I was young and stupid, I used to make different versions of the same photo in Photoshop and locate them all on the server using different paths. As I got older, I got lazier, so I just started resizing them as needed via CSS (or inline style via javascript). This of course led to distortion, which is when I discovered TimThumb, which made my life infinitely easier, as I could resize images on the fly using PHP. A brilliant script, but it had a few security issues, and it also started getting a little too robust, which affected speed, so I decided to just create my own, with just the bare minimum for resizing, scaling and cropping. Use it for thumbnail generation, or to make your images just fit exactly.

You can see it in action below:





<img src="halthumb.php?src=/images/projects/home-business-card.jpg&w=600&h=400&crop=portfolio" />

Just set the script in your img src attribute, as above. The parameters to pass are:

  • src = the local path to the image on your server
  • w = the width, in pixels
  • h = the height, in pixels
  • crop (optional) = the type of crop. Use ‘portfolio’ to crop and scale dynamically and exactly fit your w/h. Use ‘full’ to simply output the original image (will override the passed sizes). If nothing is passed, the image will just scale proportionately.

You can copy the PHP or ASP.NET code below, or download both scripts as a zip.

Here’s the PHP code:


<?php 

/******************************************************************************
-- HalThumb - a quick and dirty thumbnail script
-- Parameters:
-- src = the path to the image
-- w = width of thumb, in pixels
-- h = height of thumb, in pixels
-- crop = type of crop. Options are portfolio or full
-- http://www.halnesbitt.com
******************************************************************************/

	//determine the path and parts
	$src = $_GET['src'];
	$path_parts = pathinfo($src);
	$ext = $path_parts['extension'];
	$file_name = $path_parts['filename'] . "." . $ext;
	$local_path = $_SERVER['DOCUMENT_ROOT'] . $src;

	//set defaults
	$MAX_WIDTH = 100;
	$MAX_HEIGHT = 100;
	$MAX_WIDTH_LIMIT = 1200;
	$MAX_HEIGHT_LIMIT = 1200;

	//get parameters from querystring
	$MAX_WIDTH = grabParam('w');
	$MAX_HEIGHT = grabParam('h');
	$CROP_TYPE = grabParam('crop');


	//setting limits so people can't just blow up thumbnails
	if ($MAX_WIDTH > $MAX_WIDTH_LIMIT) {
		$MAX_WIDTH = $MAX_WIDTH_LIMIT;
	}
	if ($MAX_HEIGHT > $MAX_HEIGHT_LIMIT) {
		$MAX_HEIGHT = $MAX_HEIGHT_LIMIT;
	}

	//get the photo into data form
	switch ($ext) {
		case "jpg":
			$mime_type = "image/jpeg";
         		$photo = imagecreatefromjpeg($local_path);
         		break;

		case "png":
			$mime_type = "image/png";
         		$photo = imagecreatefrompng($local_path);
         		break;

		case "gif":
			$mime_type = "image/gif";
         		$photo = imagecreatefromgif($local_path);
         		break;
	}

	//getting dimensions of real photo
	$old_width = imagesx($photo);
	$old_height = imagesy($photo);

	//set measurements for the crop and scale
	switch ($CROP_TYPE) {

		case "portfolio":
			//crops and resizes based on passed sizes - 600 x 400
			$thumb_width = $MAX_WIDTH;
			$thumb_height = $MAX_HEIGHT;

			$original_aspect = $old_width / $old_height;
			$thumb_aspect = $thumb_width / $thumb_height;

			if($original_aspect >= $thumb_aspect) {   
				// If image is wider than thumbnail (in aspect ratio sense)   
				$new_height = $thumb_height;   
				$new_width = $old_width / ($old_height / $thumb_height);
			} else {   
				// If the thumbnail is wider than the image   
				$new_width = $thumb_width;   
				$new_height = $old_height / ($old_width / $thumb_width);
			}

			$src_x = ($new_width - $thumb_width) / 2;
			$src_y = ($new_height - $thumb_height) / 2;
			break;
		
		case "full":
			//show the full image at its full, original size
			$new_width = $old_width;
			$new_height = $old_height;
			$thumb_width = $old_width;
			$thumb_height = $old_height;
			$src_x = 0;
			$src_y = 0;
			break;

		default:
			//true scaled
			$scale = min($MAX_WIDTH/$old_width, $MAX_HEIGHT/$old_height);
			$new_width = floor($scale*$old_width);
			$new_height = floor($scale*$old_height);
			$thumb_width = $new_width;
			$thumb_height = $new_height;
			$src_x = 0;
			$src_y = 0;
			break;

	}


	//create the new photo and assign the data
	$tmp_img = imagecreatetruecolor($thumb_width, $thumb_height);

	//handle transparency
	switch ($ext) {
		case "png":
		// integer representation of the color black (rgb: 0,0,0)
		$background = imagecolorallocate($tmp_img, 0, 0, 0);
		// removing the black from the placeholder
		imagecolortransparent($tmp_img, $background);

		// turning off alpha blending (to ensure alpha channel information is preserved, rather than removed (blending with the rest of the image in the form of black))
		imagealphablending($tmp_img, false);

		// turning on alpha channel information saving (to ensure the full range of transparency is preserved)
		imagesavealpha($tmp_img, true);
		break;

	case "gif":
		// integer representation of the color black (rgb: 0,0,0)
		$background = imagecolorallocate($tmp_img, 0, 0, 0);
		// removing the black from the placeholder
		imagecolortransparent($tmp_img, $background);
		break;
	}

	//do it, doug
	imagecopyresampled($tmp_img, $photo, 0, 0, $src_x, $src_y, $new_width, $new_height, $old_width, $old_height);
 	$photo = $tmp_img;        
 
	//set all of the headers
	header("Cache-Control: private, max-age=10800, pre-check=10800");
	header("Pragma: private");
	header("Expires: " . date(DATE_RFC822,strtotime(" 2 day")));

	//commenting out size - was fucking up and making the browser hang, for some reason
	//header("Content-length: $size");
	header("Content-type: $mime_type");
	header("Content-Disposition: inline; filename=$file_name");

	//output based on type
	switch ($ext) {
		case "png":
			imagepng($photo);
         		break;

		case "gif":
			imagegif($photo);
         		break;

		default:
			imagejpeg($photo);
			break;
		
	}

	//clean up
	if(is_resource($photo)) {
		imagedestroy($photo);
	}
	if(is_resource($tmp_img)) {
		imagedestroy($tmp_img);
	}

	exit();


	//misc helper functions
	function grabParam($x) {
		if(isset($_GET[$x]) && $_GET[$x] != "") {
			return $_GET[$x];
		}
	}

?>

And here’s the ASP.NET (VB) version (note: I wrote these two scripts at different times, and on different projects, so they don’t match exactly, but the basic functionality is the same):


<%@ Page Language="VB" Debug="False" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Drawing" %>
<%@ Import Namespace="System.Drawing.Imaging" %>
<%@ Import Namespace="System.Drawing.Graphics" %>
<%@ Import Namespace="System.Drawing.Drawing2D" %>
<script language="VB" runat="server">
Sub Page_Load(sender as Object, e as EventArgs)

'------------------------------------------------------------------------------------
'-- HalThumb - a quick and dirty thumbnail script
'-- Parameters:
'-- src = the path to the image
'-- w = width of thumb, in pixels
'-- h = height of thumb, in pixels
'-- crop = type of crop. Options are portfolio or full
'-- can also do custom crop by passing x_size, y_size - see below for full info
'-- http://www.halnesbitt.com
'------------------------------------------------------------------------------------

	'----get the image path
	Dim imageUrl As String = Request.QueryString("src")
	If imageUrl = "" Then
		Response.Write("no image")
		Exit Sub
	End If

	Try

		'----set default vars
		Dim default_thumb_height As Integer = 100
		Dim default_thumb_width As Integer = 180
		Dim default_thumb_height_max As Integer = 1200
		Dim default_thumb_width_max As Integer = 1200
		Dim default_crop_type As String = "scaled"
		
		'----get the sizes
		Dim thumbHeight, thumbWidth as Integer
		If Request.QueryString("h") = "" Then
			thumbHeight = default_thumb_height
		Else
			thumbHeight = Request.QueryString("h")
		End If

		If Request.QueryString("w") = ""  Then
			thumbWidth = default_thumb_width
		Else
			thumbWidth = Request.QueryString("w")
		End If

		'----set max values for the thumbnail
		If thumbHeight > default_thumb_height_max Then
			thumbHeight = default_thumb_height_max
		End If
	
		If thumbWidth > default_thumb_width_max Then
			thumbWidth = default_thumb_width_max
		End If


    		'----set all the image vars
		Dim imageName, fileName As String
		Dim fullSizeImg As System.Drawing.Image
		Dim fullPath As String
		Dim bmImg As Drawing.Bitmap
		Dim outputImage As System.Drawing.Image
		Dim MS As New MemoryStream '----Note: PNGS require outputting to an intermediate memory stream, rather than just directly to the OutputStream

		'----get the orginal image
		fullPath = Server.MapPath(imageUrl)
		fullSizeImg = System.Drawing.Image.FromFile(fullPath)

        	Dim originalWidth As Integer = fullSizeImg.Width 
        	Dim originalHeight As Integer = fullSizeImg.Height
		Dim cropType As String = Request.QueryString("crop")
		If cropType = "" Then
			cropType = default_crop_type
		End If

		Dim original_aspect As Integer
		Dim thumb_aspect As Integer
		Dim width_ratio As Decimal
		Dim height_ratio As Decimal
		Dim scale As Decimal
		Dim preview_scale As Decimal
		Dim crop_x As Integer = 0
		Dim crop_y As Integer = 0
		Dim crop_w As Integer = 0
		Dim crop_h As Integer = 0

		'----define ratios
		original_aspect = originalWidth / originalHeight
		thumb_aspect = thumbWidth / thumbHeight
		width_ratio = thumbWidth / originalWidth
		height_ratio = thumbHeight / originalHeight

		'----------------------------------------------------------
		'----Portfolio - this is the main one, so use it mostly for crop and scale
		'----------------------------------------------------------
		If cropType = "portfolio" Then

			If thumbWidth > thumbHeight Then
				'----landscape thimb
				crop_w = originalWidth
				crop_h = (thumbHeight / thumbWidth) * originalWidth
			ElseIf thumbWidth < thumbHeight Then
				'----portrait thumb
				crop_w = (thumbWidth / thumbHeight) * originalHeight
				crop_h = originalHeight
			Else
				'----square - treat it like landscape - it will get picked up by the cleanup below
				crop_w = originalWidth
				crop_h = (thumbHeight / thumbWidth) * originalWidth
			End If

			'----if the thumbnail size changes the orientation (from landscape to not landscape) then flip it
			If crop_h  > originalHeight Then
				crop_h = originalHeight
				crop_w = (thumbWidth / thumbHeight) * originalHeight
			End If
	
			'----or from portrait original to not portrait, then flip it
			If crop_w  > originalWidth Then
				crop_h = (thumbHeight / thumbWidth) * originalWidth
				crop_w = originalWidth
			End If

			crop_x = (originalWidth - crop_w) / 2
			crop_y = (originalHeight - crop_h) / 2

		'----------------------------------------------------------
		'----Scaled - keeps same proportions
		'----------------------------------------------------------

		Else
			scale = Math.Min((thumbWidth/originalWidth), (thumbHeight/originalHeight))
			crop_x = 0
			crop_y = 0
			crop_w = originalWidth
			crop_h = originalHeight
			thumbWidth = Math.Floor(scale * originalWidth)
			thumbHeight = Math.Floor(scale * originalHeight)

		End If

		'----------------------------------------------------------
		'----Custom crop - grab extra parameters
		'----------------------------------------------------------

		If Request.QueryString("x") <> "" AND Request.QueryString("y") <> "" Then
			If Request.QueryString("x_size") <> "" AND Request.QueryString("y_size") <> "" Then

				'----meant to be passed from a tool using a crop tool, such as Jcrop
				'----takes into account what the "preview image" size was and scales
				Dim default_preview_width As Integer = 650
				Dim default_preview_height As Integer = 650

				'----override the default view, if used in a different module
				If Request.QueryString("default_preview_width") <> "" Then
					default_preview_width = Request.QueryString("default_preview_width")
				End If

				If Request.QueryString("default_preview_height") <> "" Then
					default_preview_height = Request.QueryString("default_preview_height")
				End If


				preview_scale = Math.Min((default_preview_width/originalWidth), (default_preview_height/originalHeight))
				crop_x = Request.QueryString("x") / preview_scale
				crop_y = Request.QueryString("y") / preview_scale
				crop_w = Request.QueryString("x_size") / preview_scale
				crop_h = Request.QueryString("y_size") / preview_scale
				thumbWidth = Request.QueryString("w")
				thumbHeight = Request.QueryString("h")

			End If

		End If


		'----Generate the thumbnails
		If cropType <> "full" Then

			Dim dummyCallBack as System.Drawing.Image.GetThumbNailImageAbort
			dummyCallBack = New System.Drawing.Image.GetThumbnailImageAbort(AddressOf ThumbnailCallback)

       			'----create a bitmap window for cropping
        		bmImg = New Drawing.Bitmap(thumbWidth, thumbHeight) 
        		bmImg.SetResolution(72, 72)
         
        		'----create a new graphics object from our image and set properties
			Dim grImg As Drawing.Graphics = Drawing.Graphics.FromImage(bmImg)
			grImg.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
			grImg.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
			grImg.PixelOffsetMode = Drawing2D.PixelOffsetMode.HighQuality
			grImg.CompositingQuality = Drawing2D.CompositingQuality.HighQuality
			'----grImg.Clear(Color.Orange) '----add background color, for debug

 			'----set the rectangles and apply
			Dim src_rect As New Rectangle(crop_x, crop_y, crop_w, crop_h) '----part of the original image that should be copied
       			Dim dst_rect As New Rectangle(0, 0, bmImg.Width, bmImg.Height) '----where to draw the src in the new bitmap
			grImg.DrawImage(fullSizeImg, dst_rect, src_rect, GraphicsUnit.Pixel)

			'----assign the thumbnail to the output image
			outputImage = bmImg

			'----Clean up / Dispose...
			grImg.Dispose()

		Else

			'----assign the fullsize image to the output image
			outputImage = fullSizeImg
		End If

		'----set headers
     		imageName = imageUrl.Substring(imageUrl.LastIndexOf("/"))
		fileName = imageUrl.Substring(1,Len(imageName)-5) & "_thumb.png"
		If cropType = "full" Then
     			fileName = imageUrl.Substring(1,Len(imageName)-5) & ".png"
		End If
		Response.ContentType = "image/png"
     		Response.AddHeader("Content-Disposition", "inline;filename=" + fileName)
		Response.AddHeader("Etag","""" & imageUrl.Substring(1,Len(imageName)-5) & """")
		Response.AddHeader("Last-Modified", File.GetLastWriteTime(fullPath).ToString("ddd, dd MMM yyyy HH:mm:ss"))
		Response.Cache.SetCacheability(HttpCacheability.Public)
		Response.Cache.SetMaxAge(new TimeSpan(1, 0, 0))

    		'----output the image
     		outputImage.Save(MS, ImageFormat.png)
     		MS.WriteTo(Response.OutputStream) 

    		'----Clean up / Dispose...
    		MS.Dispose()
    		fullSizeImg.Dispose()
    		outputImage.Dispose()

		If Not bmImg Is Nothing Then
			bmImg.Dispose()
		End If

	Catch ex As Exception
		Response.Write(ex.ToString())
	End Try

End Sub

Function ThumbnailCallback() as Boolean
	Return False
End Function
</script>


Published by

Hal

Aside from being a champion yo-yoer, I am the full-time computer geek at the American Society of Nephrology. I recently completed my MBA from George Washington University which I am hoping will enable me to finally afford my own bad habits. I also do freelance design, specializing in Flash, PHP, and ASP/ASP.NET.

Leave a Reply

Your email address will not be published. Required fields are marked *