Skip to main content

ASP.NET/C# image resizer for responsive layouts

Tags:
Reading time: 3 minutes Suggest an edit

I will probably elaborate on this a bit further when I find some more time, but for now, this post is going to be mostly code. What I have here is a relatively simple way to generate images that are resized server-side based on the screen dimensions (note: not window dimensions) of the web browser requesting them. This way, you're not sending huge images to phones. Pair this with some CSS to scale your images to fit their containers, and you're cooking with gas!

web.config section (IIS URL Rewrite 2.0)

<rewrite>
<rules>
<rule name="Responsive image handler">
<match url="^.+\.(jpe?g|gif|png)$" />
<conditions>
<add input="{QUERY_STRING}" pattern="r" />
</conditions>
<action type="Rewrite" url="ImageResizer.ashx?i={UrlEncode:{R:0}}" />
</rule>
</rules>
</rewrite>

screen-dimensions.js

(function(){ document.cookie = "screendim=" + screen.width + "x" + screen.height; }());

ImageResizer.ashx

<%@ WebHandler Language="C#" Class="ImageResizeHandler" %>

using System;
using System.Web;

public class ImageResizeHandler : IHttpHandler
{
// path to generated images
protected static const string imgPath = "~/App_Data/responsive/";
// how long to cache images (in seconds)
protected static const int cacheLength = 36000;
// image resizing breakpoints and their adjusted percentages
private static const ResponsiveBreakpoint[] breakpoints = {
new ResponsiveBreakpoint(320, 0.15f),
new ResponsiveBreakpoint(480, 0.25f),
new ResponsiveBreakpoint(560, 0.4f),
new ResponsiveBreakpoint(768, 0.6f)
};

public void ProcessRequest(HttpContext context)
{
// init
int screenw = -1, screenh = -1;
var img = context.Request.QueryString["i"];

// split screen dimensions from cookie
if (context.Request.Cookies["screendim"] != null)
{
var dim = context.Request.Cookies["screendim"].Value.Split('x');

// invalid cookie value; serve the original and drop out
if (dim.Length != 2)
{
context.Response.Redirect(img);
return;
}

int.TryParse(dim[0], out screenw);
int.TryParse(dim[1], out screenh);
}

// no screen dimensions or desktop size; serve original file
if (screenw < 0 || screenh < 0
|| screenw >= breakpoints[breakpoints.Length - 1].width)
{
context.Response.Redirect(img);
return;
}

// replace path slashes with underscores so the path becomes part of the filename in the cache folder
var imgName = img.Replace("/", "__");
// add protocol and host name to img request
img = "http" + (context.Request.IsSecureConnection ? "s" : "") + "://" + context.Request.Url.Host + context.Request.Url.AbsolutePath + img;
// determine new size; percent of original
var pct = 1.0f;

// find our breakpoint for this screen width
foreach (ResponsiveBreakpoint breakpoint in ImageResizeHandler.breakpoints)
{
if (screenw < breakpoint.width)
{
pct = breakpoint.percentage;
break;
}
}

// finish making file name; add size percentage and .jpg
var fileName = context.Server.MapPath(ImageResizeHandler.imgPath + imgName + "!" + ((int)Math.Round(pct * 100)).ToString() + ".jpg");
context.Response.ContentType = "image/jpeg";

// create new file if it doesn't exist or if it's expired
if (!System.IO.File.Exists(fileName)
|| DateTime.Now.AddSeconds(0 - ImageResizeHandler.cacheLength) > System.IO.File.GetCreationTime(fileName))
{
var rq = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(img + "?resize=");
rq.AllowWriteStreamBuffering = true;
var wr = rq.GetResponse();
var ws = wr.GetResponseStream();
var imgdata = System.Drawing.Image.FromStream(ws);
var neww = (int)Math.Round(imgdata.Width * pct);
var newh = (int)Math.Round(imgdata.Height * pct);
var bmp = new System.Drawing.Bitmap(neww, newh);
var gfx = System.Drawing.Graphics.FromImage((System.Drawing.Image)bmp);
gfx.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
gfx.DrawImage(imgdata, 0, 0, neww, newh);
gfx.Dispose();
imgdata = (System.Drawing.Image)bmp;
imgdata.Save(fileName, System.Drawing.Imaging.ImageFormat.Jpeg);
}

// serve image
context.Response.WriteFile(fileName);
}

// don't immediately destroy this handler after each use
public bool IsReusable { get { return true; } }

// class for responsive breakpoints
protected class ResponsiveBreakpoint
{
public int width = 0;
public float percentage = 0.0f;

public ResponsiveBreakpoint(int pWidth, float pPercentage)
{
this.width = pWidth;
this.percentage = pPercentage;
}
}
}

example.html

<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<script type="text/javascript" src="/js/screen-dimensions.js"></script>
</head>
<body>
<p>This here image is responsive, y'all!</p>
<img src="/img/some-image.png?r" />

<p>This one ain't.</p>
<img src="/img/some-image.png" />
</body>
</html>

Edit: If you want to speed up the response by having IIS handle the transfer of the image (rather than letting ASP.NET write the image to the response stream), check out this X-SendFile module for IIS.