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>