.NET and GDI+: Transforming Images on the Server - Introduction and Examples, Part III...

This article series focuses on the imaging elements of the GDI+ class available within the system.drawing namespace but also dips into the related graphics and typography facilities also available.


By: Chris Sully Date: March 16, 2004 Download the code.

Abstract

New to developers in the .NET Framework, and hence ASP.NET, is the ability to manipulate images through the GDI+ managed class interface (a set of wrappers) which is distributed as part of the Microsoft .NET Framework, allowing access to underlying operating system functionality that provides two-dimensional vector graphics, imaging, and typography. This article series focuses on the imaging elements of the GDI+ class available within the system.drawing namespace but also dips into the related graphics and typography facilities also available.

Introduction

In this article, part III in the series, we shall continue our exploration of GDI+ utilizing a series of examples to illustrate key concepts. We continue with examples 6 and 7.

6. Drawing and writing text on an image

Killing two birds with one stone, this example is going to show how to manipulate the Bitmap image by adding both text and a simple vector graphic element to the image. As per the cropping example we utilize the Graphics class to do this. For this example we also need the system.drawing.drawing2D namespace. Before we move onto the code this is the image it outputs:

gdi6.aspx code snippet:

dim myBitmap as New bitmap(server.mappath("images/alima.jpg"))
dim myGraphic As Graphics = Graphics.FromImage(myBitmap)

'set the smoothing mode
myGraphic.SmoothingMode = SmoothingMode.AntiAlias

'draw the oval
myGraphic.DrawArc(New Pen(Color.White, 2), 280, 0, 120, 50, 0, 360)

'draw the text
myGraphic.DrawString("Alima!", New Font("Helvetica", 30, FontStyle.Bold), SystemBrushes.WindowText, New PointF(290, 10))

'note: enable these 3 lines and disable the previous if you want to see vertical text in action
'Dim stringFormat As New StringFormat()
'stringFormat.FormatFlags = StringFormatFlags.DirectionVertical
'myGraphic.DrawString("Alima!", New Font("Helvetica", 30, FontStyle.Bold), SystemBrushes.WindowText, New PointF(290, 10), stringFormat)

'set the content type
response.contenttype="image/jpeg"

'save to the outputstream
myBitmap.Save(response.outputstream, ImageFormat.jpeg)

myGraphic.Dispose()
myBitmap.Dispose()

Examining the code:

dim myBitmap as New system.drawing.bitmap(server.mappath("images/alima.jpg"))
dim myGraphic As Graphics = Graphics.FromImage(myBitmap)

is as per the previous example.

myGraphic.SmoothingMode = SmoothingMode.AntiAlias

sets how any vector graphics will be rendered onto the image. Here we set the mode to anti-aliased via the appropriate member of the Graphics.SmoothingMode property enumeration. This does not affect how text is rendered. Text rendering is specified via the TextRenderingHint enumeration of the System.Drawing.Text namespace (not done in this example).

myGraphic.DrawArc(New Pen(Color.White, 2), 280, 0, 120, 50, 0, 360)

draws the oval using a pen object of colour white with a line width of 2 pixels.

DrawArc has four overloaded constructors. Here we are using:

Overloads Public Sub DrawArc( _
  ByVal pen As Pen, _
  ByVal x As Integer, _
  ByVal y As Integer, _
  ByVal width As Integer, _
  ByVal height As Integer, _
  ByVal startAngle As Integer, _
  ByVal sweepAngle As Integer _
)

which draws an arc representing a portion of an ellipse specified by a pair of coordinates, a width, and a height the start angle and the 'sweep' of the curve. So, in the example, we specifying a starting position of the curve of (280, 0) which is the top center point of the ellipse, with the width and height of 120 and 50 effectively defining a bounding rectangle for the curve, or ellipse in this case. The start angle is 0 degrees, which is by convention 3 o'clock, and the sweep 360 degrees - so all the way round to the same position again. I suggest you experiment with the parameters to gain a feel of how to control the output.

Next we add the text:

myGraphic.DrawString("Alima!", New Font("Helvetica", 30, FontStyle.Bold), Color.Black, New PointF(290, 10))

Here we add the text "Alima!" to the myGraphic object, specifying the following to do so

As an aside, to draw vertical text you use the StringFormat class. Amongst other functionality this class encapsulates text layout information (such as alignment and line spacing). To accomplish this you set the FormatFlags property to the appropriate member of the StringFormatFlags enumeration after instantiating the object:

Dim stringFormat As New StringFormat()
stringFormat.FormatFlags = StringFormatFlags.DirectionVertical

and use an alternate overloaded method of the drawstring method:

myGraphic.DrawString("Alima!", New Font("Helvetica", 30, FontStyle.Bold), Color.Black, New PointF(290, 10), stringFormat)

This is included in the example, albeit commented out, for your experimentation.

7. Saving a JPEG with different degrees of compression/ quality

As you are probably well aware the most popular format for photos on the web is JPEG. Actually, strictly, JPEG is a compression algorithm, not a file format. JPEG File Interchange Format (JFIF) is the file format commonly used for storing and transferring images that have been compressed according to the JPEG scheme. JPEG employs a lossy algorithm for image compression, i.e. some information is lost in the compression. One parameter to the compression is a quality metric varying from 100% down to 0. Deciding on the value of the metric to use is dependent on several factors, not least of which are the quality of the original image and the destination display mechanism. For web sites, where the original is of good quality, a suitable value is between 60-80%. Much below 60% the differences become more evident. In the following example you can see the 75% image is far from perfect but this is partly due to the quality of the original image. More important is the quality differences between the different jpgs.

ImageA: 75% (13K)

ImageB: 50% (8K)

ImageC: 25% (6K)

To produce the above images the following code was used:

gdi7.aspx code snippet:

Const intQuality=25

dim bmpBitmap1 as New Bitmap(server.mappath("images/alima.jpg"))

'Get the list of available encoders
Dim codecs() As ImageCodecInfo = ImageCodecInfo.GetImageEncoders()

'find the encoder with the image/jpeg mime-type
dim iciInfo as ImageCodecInfo
dim item as ImageCodecInfo

for each item in codecs
  if(item.MimeType="image/jpeg")then iciInfo=item
next

'Create a collection of encoder parameters (we only need one in the collection)
Dim ep as EncoderParameters = new EncoderParameters()

ep.Param(0)= new system.drawing.imaging.EncoderParameter(system.drawing.imaging.Encoder.Quality,intQuality)

'Set the content type
response.contenttype="image/jpeg"

'bmpBitmap1.save(server.mappath("." & "/article_images/alima25.jpg"), iciInfo, ep)
'send the bitmap to the outputstream
bmpBitmap1.save(response.outputstream, iciInfo, ep)

'tidy up
bmpBitmap1.dispose()

Note that the quality metric is set via the constant intQuality. This was run three times with values of 25, 50 and 75 to produce the output presented. After loading the original image from the file system we locate the information we need within the ImageCodecInfo class and store it in iciInfo for later use:

Dim codecs() As ImageCodecInfo = ImageCodecInfo.GetImageEncoders()

'find the encoder with the image/jpeg mime-type
dim iciInfo as ImageCodecInfo
dim item as ImageCodecInfo

for each item in codecs
  if(item.MimeType="image/jpeg")then iciInfo=item
next

We then create a new EncoderParameters collection so that we may instantiate and later reference the Encoder.Quality parameter. This is done via an overloaded save method of the Bitmap class:

bmpBitmap1.save(response.outputstream, iciInfo, ep)

If you consider the above you will realize that there is the possibility of extending the ImageCodec related classes via your own classes so as to add further image format support to the Framework.

That concludes article III. In the fourth article of this series we'll continue to look at features of GDI+ via further examples.

References

ASP.NET: Tips, tutorials and code
Mitchell et al.
Sams Publishing
ISBN 0-672-32143-2

.NET SDK Documentation

Various online articles, notably:

The Code Project - An ASP.NET thumbnail solution - ASP.NET
http://www.codeproject.com/aspnet/ThumbTools.asp

Chris Garretts ASPAlliance Column:
http://www.aspalliance.com/chrisg/

Bob Powell’s GDI+ FAQ
http://www.bobpowell.net/

http://www.c-sharpcorner.com/Code/2002/April/DrawTransparentImageUsingAB.asp
C# Corner: C# and .NET Developer's Network: transparent images
Mahesh Chand

http://www.c-sharpcorner.com/Code/2002/Dec/WorkWithPNG.asp
Working with the Portable Network Graphics (PNG) Format: Part 1
Tom Curry

http://www.c-sharpcorner.com/graphics/transforms_101.asp
Using Transforms with GDI+ in C#
Mike Gold

You may download the code here.