![]() |
Site Archive (Complete) | |||
|
ABOUT US |
CONTACT |
ADVERTISE |
SUBSCRIBE |
SOURCE CODE |
CURRENT PRINT ISSUE |
NEWSLETTERS
|
RESOURCES
|
BLOGS
|
PODCASTS
|
CAREERS
|
||||
February 02, 2007
Image Manipulation with ASP.NET 2.0Displaying, manipulating, and protecting web site imagesEric Bergman-Terrell
You can display, manipulate, and protect web site images by taking advantage of the .NET 2.0 Bitmap class and Http Handlers.
Eric has developed everything from data reduction software for particle bombardment experiments to software for travel agencies. He can be contacted at ericterrell@comcast.net.
Ever since learning how to use a 35mm rangefinder camera, I've dreamed of selling my photographs. Now that I've taken some decent pictures with my digital camera, it's time to showcase them on a web site and sell them as stock photographs. I plan to display each image in a variety of resolutions and quality levels, draw copyright notices on the pictures, add EXIF tags, prevent unauthorized users from downloading the full-resolution images, and prevent other web sites from linking to them. Fortunately, ASP.NET 2.0 makes all of this easy.
In this article I show you how to display, manipulate, and protect web site images by taking advantage of the .NET Bitmap class and Http Handlers. Even though image manipulation can be computationally expensive, I display images without compromising on performance. I've included the full source code for a sample web site (available electronically; see "Resource Center," page 5). To build the web site, first extract the source code to a folder. Double-click StockPhotos.sln to launch Visual Studio 2005. Then press F5 or select Debug/Start Debugging to launch the web site. Click the links at the top of each page to see examples of the techniques covered in this article.
.NET Bitmap Manipulation
The web site uses a DLL named "GraphicsDLL" to scale images, draw copyright notices, add metadata to JPEG images, and reduce the quality of JPEG files to conserve bandwidth. GraphicsDLL operates on Bitmap objects because they're easy to manipulate and serve up in ASP.NET applications. The AddWatermark method (Listing One) draws a copyright notice in the middle of a Bitmap as a semitransparent "watermark" (Figure 1). The opacity of the watermark is calculated from the OpacityPercent parameter. The opacity can be any value between 0 (completely transparent) to 255 (completely opaque). After opacity is calculated, Graphics, Font, and Brush objects are instantiated. Then DrawString is called to draw the text.
public static class BitmapUtils
{
// Draw semi-transparent text in the middle of the Bitmap.
public static void AddWatermark(Bitmap Bitmap,
string WatermarkText, Color TextColor, int OpacityPercent,
string FontFamily, FontStyle FontStyle, int FontSize)
{
int opacity = (int)((255.0f * OpacityPercent) / 100.0f);
using (Graphics gr = Graphics.FromImage(Bitmap))
using (Font font = new Font(FontFamily, FontSize, FontStyle,
GraphicsUnit.Pixel))
using (Brush semiTransparentBrush = new SolidBrush(
Color.FromArgb(opacity, TextColor)))
{
// Determine the size of the bitmap that will contain the text.
SizeF size = gr.MeasureString(WatermarkText, font);
Listing One
[Click image to view at full size]
Figure 1: Copyright watermark.
The Graphics, Font, and Brush classes implement the IDisposable interface because their objects include resources not managed by the .NET garbage collector. It's important to call an IDisposable object's Dispose method the moment the object is no longer used, so that the unmanaged resources are freed immediately. The objects are instantiated in using statements so their Dispose methods are automatically called the moment they go out of scope. Neglecting to call an IDisposable object's Dispose method reduces your application's performance and scalability.
The JPEG file format lets metadata be embedded as EXIF (EXchangeable Image Format) tags. For example, the EXIF specification (www.exif.org/Exif2-1.PDF) includes defined tags such as Table 1. The WriteEXIFTag method inserts an EXIF tag into a JPEG bitmap. Because EXIF tag text must be in ASCII format, the TagText parameter is converted from Unicode to ASCII by calling encoder.GetBytes. EXIF tags are represented as PropertyItem objects. Because the PropertyItem class lacks a public constructor, you can't directly instantiate a PropertyItem object. Instead, the code takes the first PropertyItem object in the JPEG, changes it to the specified EXIF tag, and inserts it into the Bitmap by calling Bitmap.SetPropertyItem. (To see the EXIF tags in a JPEG file, run the web site. Click the EXIF Tags link. Right-click the image and save it as a file. Then run the DisplayEXIFTags program to display the tags. DisplayEXIFTags is part of the StockPhotos solution.)
Table 1: EXIF tags.
JPEG images are stored with lossy compression that degrades images slightly to reduce storage space. Your web site can save significant bandwidth by reducing JPEG image quality slightly. The Save method stores a JPEG Bitmap in a Stream at a specified quality level. The Quality parameter can range from 100 (best quality, largest size) to 0 (worst quality, smallest size). Saving a JPEG Bitmap to a Stream requires an EncoderParameters object that specifies the quality level. The EncoderParameters object is passed to Bitmap.Save, along with the JPEG ImageCodecInfo object returned by GetEncoderInfo. Click on the web site's Quality link to see the effect of different quality levels. Right-click the images and select Properties to compare their image sizes. The 100-percent quality image has a file size of 71,657 bytes. The 50-percent quality image looks almost identical, but is 60,902 bytesa savings of about 15 percent. The 30-percent quality image is still acceptable, at least to my eyes, and only takes up 55,006 bytesa savings of about 23 percent. Below 30-percent quality, the image degradation is excessive.
After a Bitmap has been saved to a Stream, it can be recreated by calling Bitmap.FromStream. If you do this, be sure that the Stream object you used to create the Bitmap object is kept open for the Bitmap's entire lifespan. If the Stream is closed or garbage collected while the Bitmap is still in use, the Bitmap cannot be rendered or saved to a Stream. To see this problem occur, add:
memoryStream.Close();
after the call to Bitmap.FromStream in the second EnhBitmap constructor. Then rerun the web site and watch the exceptions. You'll see ExternalException objects being thrown with messages of "A generic error occurred in GDI+."
The web site uses query string parameters to specify how an image appears. For example, if you type the following URL in your browser:
http://localhost/stockphotos/
Images/PICT0746.JPG?q=
95&sx=0.15&sy=0.15&w=&m=False&c
=True&h=ztwIhRLCwz7m
ImpJtSkvs8iVBqk%3d
the PICT0746.JPG image is displayed and formatted based on the query string values. For example, the q parameter reduces the image's quality to 95 percent. The sx and sy parameters shrink the image width and height to 15 percent of their original values. The query string parameters in Table 2 can be used.
Table 2: Query string parameters.
The ImgTagInfo class simplifies creating <IMG> tags with the aforementioned query string parameters. For example, see the copyright page's code-behind (Copyright.aspx.cs):
The code-behind instantiates an ImageTagInfo object and assigns values to its properties to specify various image-formatting options. For example, the ScaleX property, which corresponds to the sx query string parameter, specifies that the image width is 50 percent of its original value. The Copyright.aspx page contains a single <img> tag. The src attribute value is filled in by the ImgTagInfo object's SrcAttributeValue property:
When the HTML page is rendered, the <img> tag looks like this:
The purpose of the h query string parameter is to prevent users from changing image URLs to remove copyright notices, increase the image resolution, and so on. For example, if your web site displays thumbnail images for free and charges users for full-resolution images, you don't want users to be able to access full-resolution images by changing the sx and sy query string parameters to 1. The h parameter is an SHA-1 hash of the query string parameters and values, plus a private key. When the image is requested from the web server, the hash is recomputed from the query string values. If it matches the original hash, the image is returned (because it's clear that the query string parameters weren't changed). If you click on the web site's Incorrect Hash link, you'll see what happens when the hash has been manipulated; no image is displayed. If this hashing scheme didn't exist, the web site would be vulnerable to Denial-of-Service (DoS) attacks. Flooding the web server with requests for images scaled to ludicrously large sizes would swamp the web server and drastically reduce the site's responsiveness. |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|