Improve Your Image(s)
Master Image Processing and Management
By Steve
C. Orr
A picture is worth a thousand words — and in some cases, they’re worth quite a few
dollars too. Content is king on the Internet. Scattered throughout company hard
drives everywhere are marketing materials, scanned documentation, artwork, charts
containing sensitive data, and other valuable images that can do wonders in the
right hands — or horrors in the wrong hands. Consolidating these materials into
one central system is a common optimization of corporate dollars these days, and
these systems usually must provide some way to get at files from across the Internet.
Security is rightly a top concern in most document management systems.
In some basic cases you can configure IIS to manage the files and their permissions
for you, but often a more customized system is necessary. As you’re probably aware,
a standard Image control is defined with the following ASPX code:
<asp:Image ID="Image1" Runat="server"
ImageUrl="SomeImage.jpg" />
When the page is output to the browser, the resulting HTML will consist of a standard
<img> tag similar to this:
<img ID="Image1" src="SomeImage.jpg"
/>
A key point here is that the image is not really part of the page from the server’s
point of view. Therefore, you can’t really do any custom image processing (such
as cropping, resizing, or adding annotations) within the page itself. Rather, the
image file name is all that’s written to the page (inside the image tag). As the
browser interprets the HTML, it downloads the image from the Web server as a completely
separate request.
Now consider the following code:
<asp:Image ID="Image1" Runat="server"
ImageUrl="GenImage1.aspx"
/>
This Image control declaration illustrates that, instead of pointing directly to
an image file, you can point an Image control toward a separate ASP.NET page where
you can do any fancy dynamic image processing that is needed.
In this example, GenImage1.aspx doesn’t contain any HTML because its sole purpose
is to output an image for inclusion in another page. The only code in the Page_Load
event calls the procedure listed in Figure 1.
DisplayImage(New Bitmap("C:\PrivateDir\TopSecret.jpg")))
Private Sub
DisplayImage(ByVal bmp As
Bitmap)
With HttpContext.Current
'Clear any existing page
content
.Response.Clear()
'Set the content type
.Response.ContentType = "image/jpeg"
'Output the image to the
OutputStream object
bmp.Save(.Response.OutputStream, _
Imaging.ImageFormat.Jpeg)
'Ensure the image is the
only thing that is output
.Response.End()
End
With
End Sub
Figure 1: ASPX pages don’t have to output HTML. This example
outputs an image, so that image controls on other pages can reference this page
instead of pointing directly to a static image file.
You might choose to add authentication code to a page such as GenImage1 to ensure
only proper individuals see the image. You’re also likely to sprinkle in some code
to make this simple example more versatile by accepting an image as a url parameter
or some other mechanism to serve out a variety of image files instead of a single
hard-coded one.
For an ASP.NET application to effectively manage files, it must have permission
to access these files. By default, ASP.NET runs under a user account (intuitively)
named ASPNET. This user account has very limited permissions. It will not be able
to interact with most of the server’s file system by default, and it won’t have
access to any network shares, either. Therefore, you’ll want to give the ASPNET
user account the folder permissions it needs, or have ASP.NET use a different user
account that does have the necessary permissions.
SECURITY ALERT: For an ASP.NET application to effectively
manage files, it must have permission to access these files.
By default ASP.NET runs under a user account (intuitively) named ASPNET.
This user account has very limited permissions.
It will not be able to interact
with most of the server’s file system by default, and it won’t have access to any
network shares either.
Therefore you’ll
want to give the ASPNET user account the folder permissions it needs, or have ASP.NET
use a different user account that does have the necessary permissions.
You can adjust the user account from within IIS, or you can configure Impersonation
in the web.config file or the machine.config file.
For initial experimentation and debugging I’d suggest having ASP.NET run
under your user account since you know what files you have permission to access.
<!-- Web.config file. -->
<identity impersonate="true"/>
<identity impersonate="true" userName="Redmond\BillG" password="Melinda"/>
You can adjust the user account from within IIS, or you can configure Impersonation
in the web.config file or the machine.config file. For initial experimentation and
debugging I’d suggest having ASP.NET run under your user account because you know
what files you have permission to access:
If the images aren’t stored in a file system, but instead are stored in a SQL Server
database, then the code behind for GenImage1.aspx might look more like that shown
in Figure 2.
Dim
dr As System.Data.SqlClient.SqlDataReader
cmdGetFile.Parameters("@File_ID").Value = _
Request("AttachmentID").ToString
dbConn.Open()
dr = cmdGetFile.ExecuteReader
If
dr.Read Then
Response.Clear()
Response.ContentType = dr("ContentType").ToString
Response.OutputStream.Write(CType(dr("FileData"), _
Byte()), 0, CInt(dr("FileSize")))
Response.AddHeader("Content-Disposition",
_
"inline;filename=" + dr("FileName").ToString())
End
If
Figure 2: You can grab the image data from a database and
write the raw file data directly into the Output Stream just before it’s sent to
the browser.
This technique shows how you can dump a file directly from a database into the Response.OutputStream.
ADO.NET is used to extract the binary data from a SQL Server image field, the data
is then converted into a byte array, and, finally, it’s written to the output stream
along with a descriptive header to help the browser better interpret the resulting
file. For more details on this technique, see
Easy Uploads.
Custom Image Generation
By using the functionality included in the System.Drawing namespace, your image
manipulation capabilities are limitless. As if that weren’t enough power for a single
developer to wield, there are also dozens of third-party components available under
such categories as charting, reporting, and image processing libraries. Additionally,
you can build your own image processing object models either from scratch or by
building on existing technologies. Hopefully by now you’re beginning to realize
the full power that can really lie behind the seemingly humble image control.
The previous techniques are great for distributing pre-existing images, but if you
need to dynamically create an image from scratch (or modify an existing image on
the fly,) then the System.Drawing namespace will become quite familiar to you. Using
the classes within this namespace you could create dynamic charts, graphs, or other
useful output. However, that’s soooo boring! The next example will focus on less
tangible corporate enhancements, such as improved morale.
Smiles can be infectious, and the next example will generate as many as you’d like.
Call the subroutine shown in Figure 3 to create a randomly generated
smiley face.
Private Sub
DrawSmiley(ByVal g As
Graphics, _
ByVal Width
As Integer, ByVal
Height As Integer,
_
ByVal rand
As Random)
Dim SmileyWidth
As Integer = rand.Next(Width / 2)
Dim SmileyHeight
As Integer = rand.Next(Height / 2)
'Draw the head (a big circle)
Dim x
As Integer = rand.Next(Width - SmileyWidth)
Dim y
As Integer = rand.Next(Height - SmileyHeight)
Dim PenWidth
As Integer = rand.Next(5)
Dim RandomColor
As Color = _
Color.FromArgb(rand.Next(255), _
rand.Next(255), rand.Next(255))
Dim Pen
As New Pen(RandomColor, PenWidth)
g.DrawEllipse(Pen, x, y, SmileyWidth, SmileyHeight)
'Draw the Nose (in the center of the
head)
Dim NoseRect
As System.Drawing.RectangleF
NoseRect.Width = CInt(SmileyWidth
/ 50)
NoseRect.Height = CInt(SmileyHeight
/ 50)
NoseRect.X = CInt(x + (SmileyWidth
/ 2) - _
(NoseRect.Width / 2))
NoseRect.Y = CInt(y + (SmileyHeight
/ 2) - _
(NoseRect.Height / 2))
g.DrawEllipse(Pen, NoseRect)
g.FillEllipse(Brushes.Green, NoseRect)
'Draw the Left Eye
Dim EyeRect
As System.Drawing.RectangleF
EyeRect.Width = CInt(SmileyWidth
/ 30)
EyeRect.Height = CInt(SmileyHeight
/ 30)
EyeRect.X = CInt(x + (SmileyWidth
/ 2) - _
(EyeRect.Width / 2) - (SmileyWidth / 4))
EyeRect.Y = CInt(y + (SmileyHeight
/ 3) - _
(EyeRect.Height / 2))
g.DrawEllipse(New Pen(Color.Blue,
PenWidth), EyeRect)
g.FillEllipse(Brushes.Blue, EyeRect)
'Draw the Right Eye
EyeRect.Width = CInt(SmileyWidth
/ 30)
EyeRect.Height = CInt(SmileyHeight
/ 30)
EyeRect.X = CInt(x + (SmileyWidth
/ 2) - _
(EyeRect.Width / 2) + (SmileyWidth / 4))
EyeRect.Y = CInt(y + (SmileyHeight
/ 3) - _
(EyeRect.Height / 2))
g.DrawEllipse(New Pen(Color.Blue,
PenWidth), EyeRect)
g.FillEllipse(Brushes.Blue, EyeRect)
'Draw the smile
Dim points(2)
As System.Drawing.PointF
points(0) = New System.Drawing.PointF(CInt(x + _
(SmileyWidth / 2) - (EyeRect.Width / 2) - _
(SmileyWidth / 4)), y + (SmileyHeight / 2))
points(1) = New System.Drawing.PointF(CInt(x + _
(SmileyWidth / 2)), y + (SmileyHeight / 2) + _
(SmileyHeight / 4))
points(2) = New System.Drawing.PointF(CInt(x + _
(SmileyWidth / 2) - (EyeRect.Width / 2) + _
(SmileyWidth / 4)), y + (SmileyHeight / 2))
g.DrawCurve(Pen, points, 1)
End Sub
Figure 3: By using the classes within the System.Drawing namespace,
nearly any illustration imaginable can be generated at run time, including a bunch
of smiley faces.
The first parameter is a Graphics object, which is the canvas on which this masterpiece
will be painted. The height and width of the canvas are also passed along, to help
ensure no smileys get abruptly cut off at the edges of the canvas. Finally, a Random
object is passed along, which will be used to mix things up a bit.
Using the Random object, a random height and width are generated for the current
smiley face and the head is drawn within this bounding rectangle. A pen is created
of random thickness and color. This pen will be used to draw most features of the
face. The DrawEllipse method creates a circle, which is used in concert with the
FillEllipse method to fill it with color. Three smaller circles are then drawn within
the head to represent the nose and two eyes. Finally, the smile is drawn by passing
an array of points to the DrawCurve method of the Graphics object. All of the mathematical
formulas throughout the example are there simply to calculate the position and size
of each facial feature.
The final piece of this image generation puzzle is the code that will fill the Page_Load
event of GenImage1.aspx and call the DrawSmiley routine. This Page_Load code is
listed in Figure 4.
Dim g As
Graphics
Dim rand
As New Random 'random
number generator
Dim bmp As
Bitmap 'to hold the picture
Dim Width
As Integer = 200
'image height
Dim Height
As Integer = 200
'image width
Dim NumberOfSmileys
As Integer = 3
'Grab parameters
from the querystring (if any)
If Not
IsNothing(Request.QueryString("NumSmileys")) Then
NumberOfSmileys = _
Int32.Parse(Request.QueryString("NumSmileys"))
End If
If Not
IsNothing(Request.QueryString("Width")) Then
Width = _
CType(Request.QueryString("Width"),
Integer)
End If
If Not
IsNothing(Request.QueryString("Height")) Then
Height = _
CType(Request.QueryString("Height"),
Integer)
End If
'create a new
bitmap of the specified size
bmp =
New Bitmap(Width, Height, _
Drawing.Imaging.PixelFormat.Format16bppRgb565)
'Get the underlying
Graphics object.
g = Graphics.FromImage(bmp)
'Specify a white
background
g.FillRectangle(Brushes.White,
g.ClipBounds)
'Smooth out
curves
g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
'generate random
smileys
For i As
Integer = 1 To
NumberOfSmileys
DrawSmiley(g, Width, Height, rand)
Next
DisplayImage(bmp)
Figure 4: This code goes in the Page_Load event of GenImage1.aspx,
which can be referenced by the ImageURL property of a standard image control placed
on any other page.
First, a few variables are declared with some default values specifying the size
of the image and the number of smiley faces that will be drawn. Then the querystring
is examined for optional parameters, which will replace the defaults. A blank bitmap
is then created with a white background. Antialiasing is turned on to create smoother
looking curves for rounded shapes, such as circles and smiles.
The main loop is then entered, iterating once for each smiley face to be drawn by
calling the DrawSmiley subroutine mentioned earlier. Finally, the completed image
is output by the DisplayImage subroutine in Figure 1.
To see the code in action, create a new WebForm and drop an Image control onto it.
Then simply set the ImageURL property of that Image control to point to the GenImage1.aspx
page. The result will look a lot like Figure 5.

Figure 5: The humble Image control can turn into a powerful
tool once you’ve mastered the art of creating dynamic, configurable images at run
time.
Conclusion
You should now have enough knowledge to manage and manipulate images in all kinds
of complex ways. The graphical possibilities are endless with these tools at your
disposal. You can expand on these ideas in all kinds of ways. For example, you could
create image buttons and other graphical page elements on demand to keep your Web
site feeling constantly fresh and new. Look for a future article about manipulating
existing images at run time, such as: resizing, optimizing, cropping, rotating,
adding borders, altering colors and brightness, etc.
The techniques outlined in this article are the foundation for virtually every modern
third-party graphing component available on the market today. You could also create
your own, if so inclined. Let your imagination wander and let me know what kinds
of image creation tools you produce as a result.
The sample code in this article is available for
download.
This article was originally published in
ASP.NET Pro Magazine.