Introduction
This article and the accompanying ASP.NET Web Application demonstrate the use of ImageMagick, a well-known open-source application from ImageMagick Studio LLC. The application creates a small thumbnail image and a larger preview image, both from a single JPEG or TIFF image, uploaded by the user through a web-browser. The application was written in C# using Microsoft Visual Studio 2008 and Developer Express' CodeRush. Developer Express' Refactor! Pro was used to optimize and refactor the source code.
Background
Working in the graphic arts industry, much of the development I am involved with deals with the creation, processing, storage, and distribution of images and visually rich documents. One such recent project was the creation of a web-to-print solution for the Fine Art Reproduction division of our company, Lazer Incorporated. Preview the site at Lazer Incorporated Fine Art Gallery. The fine arts web-to-print web solution consists of three primary user interfaces, as follows:
- Public art gallery to display the works of artists who use our services
- Private and secure ordering system for artists to order fine art prints and other services
- Customer service interface to administer the artists, artwork, thumbnail/previews, and orders.
The fine arts application is an ASP.NET 2.0 Web Application, written in C# 2.0 using Microsoft Visual Studio 2005. The solution uses SQL Server 2005 for storage of artist and artwork data. The solution integrates XML Flash Slideshow v3, an Adobe Dreamweaver Flash-based extension from dwusers.com, to display the artist's portfolio. In addition, the solution automatically creates thumbnails and larger preview images of the artwork using ImageMagick. ImageMagick is a free application from ImageMagick Studio LLC. ImageMagick is available for multiple Operating Systems and programming environments.
Using the Code
The Application
The application calls ImageMagick v6.4.5 from the command-line to create thumbnails and previews from a single uploaded image file. It duplicates the approximate functionality of the aforementioned fine art solution's thumbnail/preview creation routine. The application is written in Visual Studio 2008 using C# 3.0. This simple application contains a single Web Form and an associated class file. The application's workflow is as follows:
- User uploads a TIFF or JPEG image, less than 5 Mbs in size, through the web browser.
- Application verifies the image has been successfully uploaded, and is within the specs above.
- Application calls ImageMagick from the command-line to return information about the uploaded image (optional).
- Application calls ImageMagick to create a small thumbnail image from the uploaded image, no bigger than 100 pixels in height.
- Application calls ImageMagick to create a larger-sized preview image, no bigger than 450 pixels in width or height.
- Application deletes the original uploaded file from the server.
- Application returns a detailed status report of all the actions to the user.
Preparing to Install the Application
Before you compile and run the source files provided, you must complete two simple tasks. First, download and install ImageMagick. As of the date of this article, v6.5.2-1 is the most current. Since the instructions to install ImageMagick are so well documented at http://www.imagemagick.org/script/binary-releases.php#windows, I won't be redundant, herein.
Secondly, install the folder-set I have provided as part of the source code. Inside the ImageMagick parent folder of the set, there are four folders. They will hold uploaded images, thumbnails, previews, and an ICC color profile. The profile ensures optimal color accuracy during image conversion and display of images in the web-browser. The source code assumes you will install the folder-set at the root-level of your C: drive (c:\imagemagick). You can easily change this property in the source code, if necessary.
After downloading the folders, create a virtual directory within Internet Information Server (IIS), with the local path pointed to the 'imagemagick' folder (see screen capture below). This step is necessary so the application can display the preview and thumbnail in the web-browser after they are created.
Lastly, you must give the ASP.NET user account read and write access to the folder-set (see screen capture below). The Web Application, and subsequently ImageMagick, will need these privileges to create the new images in the appropriate folders, read the images back into the web-browser, and delete the original uploaded image.
Installing the Application
Now, you can download and install the source files and compile the web application. To do so:
- Create a new C# ASP.NET Web Application in VS 2008 called '
IMPreviewDemo
'. - Replace the Default.aspx Web Form with the Default.aspx form provided.
- Install the Global.aspx, ImageMaker.cs, Result.cs files in the root directory of the
IMPreviewDemo
application. - Optionally, open your existing Web.config file and add a
<httpRuntime/>
tag to the <system.web>
section (see screen capture below). This will allow you to also adjust the maximum allowable uploaded image size, and also importantly, set a time limit for uploads to complete.
The Code
The Default.aspx Web Form contains the VerifyUpload()
method. When the Process button is clicked, VerifyUpload()
checks that the file has successfully been uploaded, and checks that the file is either a JPEG or TIFF file. VerifyUpload()
returns a value indicating whether or not the tests were passed. Only then is an instance of the ImageMaker
class created, and a call placed to the ProcessImage(string uploadedFile, int detailLevel)
method, part of the ImageMaker
class.
using System;
namespace IMPreviewDemo
{
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if(Request.QueryString["error"] == "filesize")
{
Message.CssClass = "textred";
Message.Text = "File was to large to upload. Must be < 5 Mb.";
}
}
protected void Process_Click(object sender, EventArgs e)
{
ImagePanel.Visible = false;
if(VerifyUpload() == Result.Success)
{
CreateImages();
}
}
protected Result VerifyUpload()
{
if(ImageUpload.HasFile)
{
string fileExtension = System.IO.Path
.GetExtension(ImageUpload.FileName).ToLower();
string[] allowedExtensions =
new string[] { ".jpeg", ".jpg", ".tif", ".tiff" };
foreach(string allowedExtension in allowedExtensions)
if(fileExtension == allowedExtension)
return Result.Success;
}
Message.CssClass = "textred";
Message.Text =
"Cannot accept this image type. Must be JPEG or TIFF.";
ImagePanel.Visible = false;
return Result.Failure;
}
private void CreateImages()
{
ImageMaker PreviewMaker = new ImageMaker();
string uploadedFile = String.Format("{0}{1}",
ImageMaker.PathUploadedFiles,
ImageUpload.FileName.Replace(" ", "_"));
ImageUpload.PostedFile.SaveAs(uploadedFile);
int detailLevel = Convert.ToInt16(DetailLevel.SelectedValue);
Message.CssClass = "";
Message.Text = PreviewMaker.ProcessImage(uploadedFile, detailLevel)
.Replace("\n", "<br />");
Thumbnail.ImageUrl = PreviewMaker.UrlThumbnail.ToString();
Preview.ImageUrl = PreviewMaker.UrlPreview.ToString();
ImagePanel.Visible = true;
}
}
}
The latest version of this application, starting 1.1, uses the Global.aspx file to check uploaded file size. I found this technique on Vikram Lakhotia's Blog. The Application_BeginRequest
event checks the uploaded file size. If the file is bigger than the limit I have set, 5 Mb (measured in bytes), an error sent back to the Default.aspx page.
Reviewing the ImageMaker
class, you will notice that all file paths are stored in a series of properties and fields at the top of the class. You can modify these to suit your own local file environment. The ImageMaker
class also has two fields that identify the path to the ImageMagick application you installed. Again, adjust the fields to match your own environment.
The ImageMaker
class has two primary methods. The ProcessImage(string uploadedFile, int detailLevel)
method formats the file arguments to be send to the ImageMagick application through the command-line. This method also concatenates and formats the status information returned to the user.
The second method, CallImageMagick(string fileArgs)
, uses the argument passed to it by ProcessImage(string uploadedFile, int detailLevel)
to direct the ImageMagic application from the command-line. Depending on the argument, CallImageMagick(string fileArgs)
may or may not return a response string. ImageMagick is called by instantiating the System.Diagnostics.Process
class. This class is able to start and stop local system processes.
In the source code, the file arguments passed to ImageMagick are specific to our workflow. We convert the uploaded image to an 8-bit RGB file, scale it down, sharpen it, assign a color profile, and finally save it as a compressed JPEG for display in a web-browser. You can change any of the ImageMagick arguments to fit your needs, along with the file size limitations and file types used in this demo. Refer to the ImageMagick website for a complete list of all ImageMagick's tools and options (arguments).
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
namespace IMPreviewDemo
{
public class ImageMaker
{
private const string pathImageMagick =
@"C:\Program Files\ImageMagick-6.4.5-Q16\";
private const string appImageMagick = "MagickCMD.exe";
private string ticksID;
private string previewName;
private string thumbnailName;
private const string pathRoot = @"C:\imagemagick";
private const string pathPreviews = pathRoot + @"\previews\";
private const string pathThumbnails = pathRoot + @"\thumbnails\";
private const string pathColorProfile = pathRoot + @"\profiles\";
private const string colorProfile =
"sRGB_IEC61966-2-1_black_scaled.icc";
public static string PathUploadedFiles
{ get { return String.Format(@"{0}\uploads\", pathRoot); } }
public Uri UrlThumbnail { get; private set; }
public Uri UrlPreview { get; private set; }
StringBuilder sbResults = new StringBuilder();
public string ProcessImage(string uploadedFile, int detailLevel)
{
try
{
ticksID = DateTime.Now.Ticks.ToString();
sbResults.AppendLine(String.Format("Start time: {0}\n",
DateTime.Now.ToLongTimeString()));
if(detailLevel == 1) { GetFileInfo(uploadedFile); }
CreateThumbnailImage(uploadedFile);
CreatePreviewImage(uploadedFile);
File.Delete(uploadedFile);
sbResults.AppendLine(String.Format("Uploaded file deleted: {0}\n",
uploadedFile));
sbResults.AppendLine(String.Format("Stop time: {0}\n",
DateTime.Now.ToLongTimeString()));
return sbResults.ToString();
}
catch(Exception ex)
{
return ex.Message;
}
}
private void GetFileInfo(string uploadedFile)
{
string fileArgs = String.Format("identify -verbose {0}", uploadedFile);
sbResults.AppendLine(String.Format("Uploaded file arguments: {0}\n",
fileArgs));
sbResults.AppendLine(String.Format("Uploaded file info: {0}\n",
CallImageMagick(fileArgs)));
}
private void CreatePreviewImage(string uploadedFile)
{
previewName = String.Format("preview_{0}.jpg", ticksID);
UrlPreview =
new Uri(String.Format(@"http://localhost/imagemagick/previews/{0}",
previewName));
StringBuilder sbFileArgs = new StringBuilder()
.Append(String.Format("convert {0}", uploadedFile))
.Append(@" -intent relative")
.Append(String.Format(@" -profile {0}{1}",
pathColorProfile, colorProfile))
.Append(@" -filter Sinc -resize 450x450>")
.Append(@" -unsharp .5x.5+.5+0")
.Append(@" -depth 8")
.Append(@" -strip")
.Append(String.Format(@" -profile {0}{1}",
pathColorProfile, colorProfile))
.Append(@" -quality 80 ")
.Append(pathPreviews + previewName);
string fileArgs = sbFileArgs.ToString();
sbResults.AppendLine(String.Format("Thumbnail file arguments: {0}\n",
fileArgs));
sbResults.AppendLine(String.Format("Thumbnail created: {0}\n",
CallImageMagick(fileArgs)));
}
private void CreateThumbnailImage(string uploadedFile)
{
thumbnailName = String.Format("thumbnail_{0}.jpg", ticksID);
UrlThumbnail =
new Uri(String.Format(@"http://localhost/imagemagick/thumbnails/{0}",
thumbnailName));
StringBuilder sbFileArgs = new StringBuilder()
.Append(String.Format("convert {0}", uploadedFile))
.Append(@" -intent relative")
.Append(String.Format(@" -profile {0}{1}",
pathColorProfile, colorProfile))
.Append(@" -filter Sinc -resize x100>")
.Append(@" -unsharp .5x.5+.5+0")
.Append(@" -depth 8")
.Append(@" -strip")
.Append(String.Format(@" -profile {0}{1}",
pathColorProfile, colorProfile))
.Append(@" -quality 60 ")
.Append(pathThumbnails + thumbnailName);
string fileArgs = sbFileArgs.ToString();
sbResults.AppendLine(String.Format("Preview file arguments: {0}\n",
fileArgs));
sbResults.AppendLine(String.Format("Preview created: {0}\n",
CallImageMagick(fileArgs)));
}
private static string CallImageMagick(string fileArgs)
{
ProcessStartInfo startInfo = new ProcessStartInfo
{
Arguments = fileArgs,
WorkingDirectory = pathImageMagick,
FileName = appImageMagick,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true
};
using(Process exeProcess = Process.Start(startInfo))
{
string IMResponse = exeProcess.StandardOutput.ReadToEnd();
exeProcess.WaitForExit();
exeProcess.Close();
return !String.IsNullOrEmpty(IMResponse) ? IMResponse : "True";
}
}
}
}
Conclusion
ImageMagick is a powerful image creation and image-processing engine, capable of the most complex tasks. It is similar to Adobe Photoshop without the GUI in many respects. In addition to the command-line, ImageMagick can be accessed programmatically through its API. There are options available to Windows programmers using COM+ and .NET. You can read more at http://imagemagick.net/script/api.php.
History
- July 2, 2009 - Version 1.0
- August 30, 2009 - Version 1.1
- Refactored and optimized all code
- Fixed image refresh bug (see feedback from jessy_j10)
- Uploaded file size is now checked in the Global.asax file
- Added option to return either basic or details feedback on processes