Introduction
As we all know, raster images cannot scale very well. If you try to scale the image, you will run into jagged pixelization. Of course, you can use vector images to overcome this problem. However, we still need to deal with raster images as well. Now, enter the EnhancedImage project: it simply helps us to scale the image gracefully by introducing Insets concept.
I have been asked many times how to do scaling of raster images. I believe, programmers who have worked in web-based development have already run into similar cases like that. And probably many times they must have come up with quick and dirty solutions by writing custom code for a customized look and feel. I will use a well known technique to accomplish the desired effect. In this approach, the idea is quite simple: divide the picture into several regions and draw them separately. And here is the key: draw some of them as is, and let the others be stretched to satisfy the required image dimension.
Now, let's take a look at the following image. As you can see, the image is divided into 9 regions. I call them, NW, N, NE, W, Center, E, SW, S, and SE, where N: North, S: South, E: East and W: West.
In order to scale well, we will draw all corner images as-is. However the regions in the middle (such as N, W, E, S, and Center) will be all scaled (stretched). Therefore L1, L3, L4 and L6 are constant, on the other side L2 and L5 should be calculated properly. At this point, let me introduce the Insets
class to you. It is a simple class that just holds the left, top, bottom and right values. In the previous image, the Insets
object required at the constructor of the EnhancedImage
class will be the borders of the center region. The following code provides a good detail of information about the usage of the Insets
.
In short, the EnhancedImage
class simply accomplishes this graceful scaling effect. Also, developers will be able to adjust the opacity of the image as well. A few things that I need to explain here. As you may know, the Image
in .NET is an abstract base class. However, it is defined as an internal
class in the System.Drawing
namespace. That means, we cannot simply derive a new class based on it. It does provide functionality for System.Drawing.Bitmap
and System.Drawing.Imaging.Metafile
. Guess what? These two concrete classes are also sealed
and therefore cannot be extended! Wow! What are we going to do? All that means is that we cannot establish a 'IS-A' relationship, but there is nothing that can stop us establishing a 'HAS-A' relationship. And that's really what we are going to do here. In my implementation, I have a private data member called mPeer
to represent a base image for me.
The most interesting part of the code is the PrepareRegions()
method:
Insets sourceNW = new Insets(0, 0, L, T);
Insets sourceN = new Insets(L, 0, R, T);
Insets sourceNE = new Insets(R, 0, W, T);
Insets sourceW = new Insets(0, T, L, B);
Insets sourceC = new Insets(L, T, R, B);
Insets sourceE = new Insets(R, T, W, B);
Insets sourceSW = new Insets(0, B, L, H);
Insets sourceS = new Insets(L, B, R, H);
Insets sourceSE = new Insets(R, B, W, H);
W = Width;
H = Height;
R = W - sourceSE.Width;
B = H - sourceSE.Height;
Insets destinationNW = new Insets(0, 0, L, T);
Insets destinationN = new Insets(L, 0, R, T);
Insets destinationNE = new Insets(R, 0, W, T);
Insets destinationW = new Insets(0, T, L, B);
Insets destinationC = new Insets(L, T, R, B);
Insets destinationE = new Insets(R, T, W, B);
Insets destinationSW = new Insets(0, B, L, H);
Insets destinationS = new Insets(L, B, R, H);
Insets destinationSE = new Insets(R, B, W, H);
DrawInsetsOn(g, sourceNW, destinationNW);
DrawInsetsOn(g, sourceN, destinationN);
DrawInsetsOn(g, sourceNE, destinationNE);
DrawInsetsOn(g, sourceW, destinationW);
DrawInsetsOn(g, sourceE, destinationE);
DrawInsetsOn(g, sourceSW, destinationSW);
DrawInsetsOn(g, sourceS, destinationS);
DrawInsetsOn(g, sourceSE, destinationSE);
if (mDrawCenter)
DrawInsetsOn(g, sourceC, destinationC);
.
.
.
If you are interested with the details, I would recommend that you take a good look at that part of the code.
Using the code
OK, how do we use the new class? Actually it is quite simple. First, create your enhanced image based on a base image, provide the insets, the desired width and height of the your enhanced image, and of course the opacity value. And voila! It is there for you to use :)
Insets insets = new Insets(left, top, right, bottom);
EnhacedImage enhancedImage = new EnhancedImage(baseImage,
width, height, insets, opacity);
.
.
.
Graphics g;
Image reqularImage = enhancedImage.RegularImage;
g.DrawImage(regularImage, x, y);
Points of Interest
Now it is time to take a look at the provided program. It has quite a few knobs for you to play with. You can change the inset points visually by using the Insets Adjuster. Opacity, width and height can be also changed on the fly by sliding the trackbars. You will realize that interesting effects can be accomplished by changing the insets. Also take a look at the scaling effect by toggling the check box called Scale Properly. You will realize the regular vs. properly scale image differences right away. Also, don't forget to try different sample images provided in the application program.
History
I used similar techniques in my Java applications. Now, I added the same functionality to my C# library. You can always provide feedback and feature request so that we can make it even better.
Have fun :)