|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
AbstractWith the release of the GDI+ API, Microsoft significantly increased the power and flexibility of its graphics API, while at the same time increasing the complexity and surface-area of an already obfuscated GDI API. Fortunately, the .NET Framework provides the In this article, I present my IntroductionThis article presents the The attached source archive contains Visual Studio.NET 2002 project files, however the source code should work with either VS.NET 2K2 or VS.NET 2K3. Background
All images are represented by the In addition to the In addition to BitmapManipulator Gestalt
In general, each function performs some sort of manipulation on a bitmap. To that end, each function takes at least a An example of a public static Bitmap ScaleBitmap(Bitmap inputBmp, double scaleFactor)
In this case, in addition to the input The Bitmap manipulationsGetBitmapFromUripublic static Bitmap GetBitmapFromUri(String uri);
public static Bitmap GetBitmapFromUri(Uri uri);
public static Bitmap GetBitmapFromUri(String uri, int timeoutMs);
public static Bitmap GetBitmapFromUri(Uri uri, int timeoutMs);
Though various overloaded forms of this method are provided, they all perform the same task: retrieve an image file from a URI, load that file into a The only interesting point to note about these methods is the wrapper around the ConvertBitmappublic static Bitmap ConvertBitmap(Bitmap inputBmp, String destMimeType);
public static Bitmap ConvertBitmap(Bitmap inputBmp,
System.Drawing.Imaging.ImageFormat destFormat);
As the name implies, each of the overloads of this method convert from one bitmap format to another (e.g., JPEG to GIF or TIFF to PNG). The code required to do this is an example of the simple yet non-obvious contortions one often goes through with GDI+: //Create an in-memory stream which will be used to save
//the converted image
System.IO.Stream imgStream = new System.IO.MemoryStream();
//Save the bitmap out to the memory stream,
//using the format indicated by the caller
inputBmp.Save(imgStream, destFormat);
//At this point, imgStream contains the binary form of the
//bitmap in the target format. All that remains is to load it
//into a new bitmap object
Bitmap destBitmap = new Bitmap(imgStream);
That's right; the image is saved to a memory stream in the destination file format, then loaded from the stream into a new ConvertBitmapToJpegpublic static Bitmap ConvertBitmapToJpeg(Bitmap inputBmp, int quality);
A special case of Unfortunately, specifying the quality parameter to the JPEG encoder is non-trivial, and non-obvious. As this seems to be a frequently asked question, it is likely useful to present the code required: //Create an in-memory stream which will be used to save
//the converted image
System.IO.Stream imgStream = new System.IO.MemoryStream();
//Get the ImageCodecInfo for the desired target format
ImageCodecInfo destCodec = FindCodecForType
(MimeTypeFromImageFormat(ImageFormat.Jpeg));
if (destCodec == null) {
//No codec available for that format
throw new ArgumentException("The requested format " +
MimeTypeFromImageFormat(ImageFormat.Jpeg) +
" does not have an available codec installed",
"destFormat");
}
//Create an EncoderParameters collection to contain the
//parameters that control the dest format's encoder
EncoderParameters destEncParams = new EncoderParameters(1);
//Use quality parameter
EncoderParameter qualityParam = new
EncoderParameter(Encoder.Quality, quality);
destEncParams.Param[0] = qualityParam;
//Save w/ the selected codec and encoder parameters
inputBmp.Save(imgStream, destCodec, destEncParams);
//At this point, imgStream contains the binary form of the
//bitmap in the target format. All that remains is to load it
//into a new bitmap object
Bitmap destBitmap = new Bitmap(imgStream);
To summarize, one must find the ConvertBitmapToTiffpublic static Bitmap ConvertBitmapToTiff(Bitmap inputBmp,
TiffCompressionEnum compression);
Conceptually, this method is identical to
Be warned that Passing The same code used in ScaleBitmappublic static Bitmap ScaleBitmap(Bitmap inputBmp, double scaleFactor);
public static Bitmap ScaleBitmap(Bitmap inputBmp,
double xScaleFactor, double yScaleFactor);
Obviously, this function scales the dimensions of a bitmap by a scale factor. Passing 1.0 returns an exact copy of the input bitmap, 2.0 yields a bitmap twice the size of the original, 0.5 a bitmap at half the size, etc. The implementation of //Create a new bitmap object based on the input
Bitmap newBmp = new Bitmap(
(int)(inputBmp.Size.Width*xScaleFactor),
(int)(inputBmp.Size.Height*yScaleFactor),
PixelFormat.Format24bppRgb);
//Graphics.FromImage doesn't like Indexed pixel format
//Create a graphics object attached to the new bitmap
Graphics newBmpGraphics = Graphics.FromImage(newBmp);
//Set the interpolation mode to high quality bicubic
//interpolation, to maximize the quality of the scaled image
newBmpGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
newBmpGraphics.ScaleTransform((float)xScaleFactor, (float)yScaleFactor);
//Draw the bitmap in the graphics object, which will apply
//the scale transform
//Note that pixel units must be specified to
//ensure the framework doesn't attempt
//to compensate for varying horizontal resolutions
//in images by resizing; in this case,
//that's the opposite of what we want.
Rectangle drawRect = new Rectangle(0, 0,
inputBmp.Size.Width, inputBmp.Size.Height);
newBmpGraphics.DrawImage(inputBmp, drawRect,
drawRect, GraphicsUnit.Pixel);
//Return the bitmap, as the operations on the graphics object
//are applied to the bitmap
newBmpGraphics.Dispose();
//newBmp will have a RawFormat of MemoryBmp because it was created
//from scratch instead of being based on inputBmp.
//Since it it inconvenient
//for the returned version of a bitmap to be of a
//different format, now convert
//the scaled bitmap to the format of the source bitmap
return ConvertBitmap(newBmp, inputBmp.RawFormat);
Here, a Finally, the input image is drawn onto the This concept of a transformation pipeline is central to many of the transformations supported by GDI+. ResizeBitmappublic static Bitmap ResizeBitmap(Bitmap inputBmp,
int imgWidth, int imgHeight);
The implementation of //Simply compute scale factors that result in the desired size,
//then call ScaleBitmap
return ScaleBitmap(inputBmp,
(float)imgWidth/(float)inputBmp.Size.Width,
(float)imgHeight/(float)inputBmp.Size.Height);
Since GDI+ does not provide the notion of a resize transform, resizing must be implemented in terms of scaling factors. Due to the precision of floating point math, it is possible the resulting image could be off by a pixel after round-off error, but for most applications this is not critical. ThumbnailBitmapUntil now, all of the methods we have examined have been somewhat pedestrian. This method, on the other hand, presents a very useful, and somewhat obscure ability: given a bitmap, and a bounding rectangle, returns a copy of the input bitmap, scaled to the largest size that will fit within the bounding rectangle, without changing the image aspect ratio. Once again, I created this feature for my photo album application, so I could present a table of thumbnails, which each table cell a uniform size. The implementation of this method is unremarkable; as one might imagine, it relies upon RotateBitmapRightpublic static Bitmap RotateBitmapRight90(Bitmap inputBmp);
public static Bitmap RotateBitmapRight180(Bitmap inputBmp);
public static Bitmap RotateBitmapRight270(Bitmap inputBmp);
Rotates a bitmap right by 90, 180, and 270 degrees, respectively. Surprisingly, the implementation is simple and straightforward, an exception when dealing with GDI+. Consider //Copy bitmap
Bitmap newBmp = (Bitmap)inputBmp.Clone();
newBmp.RotateFlip(RotateFlipType.Rotate90FlipNone);
//The RotateFlip transformation converts bitmaps to memoryBmp,
//which is uncool. Convert back now
return ConvertBitmap(newBmp, inputBmp.RawFormat);
Amazingly, the ReverseBitmap and FlipBitmappublic static Bitmap ReverseBitmap(Bitmap inputBmp);
public static Bitmap FlipBitmap(Bitmap inputBmp);
Reverse (mirror-image) and flip (upside-down) an image, respectively. These also use the CropBitmappublic static Bitmap CropBitmap(Bitmap inputBmp, Rectangle cropRectangle);
Crops the input image, returning a bitmap containing the portion of the input bitmap enclosed by the crop rectangle. The implementation of this method is another simple yet non-obvious solution: //Create a new bitmap object based on the input
Bitmap newBmp = new Bitmap(cropRectangle.Width,
cropRectangle.Height,
PixelFormat.Format24bppRgb);
//Graphics.FromImage doesn't like Indexed pixel format
//Create a graphics object and attach it to the bitmap
Graphics newBmpGraphics = Graphics.FromImage(newBmp);
//Draw the portion of the input image in the crop rectangle
//in the graphics object
newBmpGraphics.DrawImage(inputBmp,
new Rectangle(0, 0, cropRectangle.Width, cropRectangle.Height),
cropRectangle,
GraphicsUnit.Pixel);
//Return the bitmap
newBmpGraphics.Dispose();
//newBmp will have a RawFormat of MemoryBmp because it was created
//from scratch instead of being based on inputBmp.
//Since it it inconvenient
//for the returned version of a bitmap to be
//of a different format, now convert
//the scaled bitmap to the format of the source bitmap
return ConvertBitmap(newBmp, inputBmp.RawFormat);
Notice that GDI+ is not providing any explicit crop support. Instead, a new bitmap is created, equal in dimensions to the crop rectangle. Then, a OverlayBitmappublic static Bitmap OverlayBitmap(Bitmap destBmp,
Bitmap bmpToOverlay, Point overlayPoint);
public static Bitmap OverlayBitmap(Bitmap destBmp,
Bitmap bmpToOverlay, ImageCornerEnum corner);
public static Bitmap OverlayBitmap(Bitmap destBmp,
Bitmap bmpToOverlay, int overlayAlpha, Point overlayPoint);
public static Bitmap OverlayBitmap(Bitmap destBmp,
Bitmap bmpToOverlay, int overlayAlpha, ImageCornerEnum corner);
This method is the main element that sets this class apart from myriad other C# imaging classes. With this method, one (presumably smaller) bitmap can be overlaid atop another (presumably larger) bitmap, at a corner, the center, or an arbitrary point, and with an arbitrary alpha (transparency). This was developed so that my photo album application could place a small, translucent watermark on each photo. Use of this method is straightforward: pass the image upon which the overlay is placed, the image to overlay, some specification of where the overlay image goes, and an optional alpha value (0 to 100; 0 is transparent, 100 is opaque). Implementation of this method is anything but straightforward. While overlaying one image atop another is relatively straightforward (just bind a //Convert alpha to a 0..1 scale
float overlayAlphaFloat = (float)overlayAlpha / 100.0f;
//Copy the destination bitmap
//NOTE: Can't clone here, because if destBmp is indexed instead of just RGB,
//Graphics.FromImage will fail
Bitmap newBmp = new Bitmap(destBmp.Size.Width,
destBmp.Size.Height);
//Create a graphics object attached to the bitmap
Graphics newBmpGraphics = Graphics.FromImage(newBmp);
//Draw the input bitmap into this new graphics object
newBmpGraphics.DrawImage(destBmp,
new Rectangle(0, 0,
destBmp.Size.Width,
destBmp.Size.Height),
0, 0, destBmp.Size.Width, destBmp.Size.Height,
GraphicsUnit.Pixel);
//Create a new bitmap object the same size as the overlay bitmap
Bitmap overlayBmp = new Bitmap(bmpToOverlay.Size.Width,
bmpToOverlay.Size.Height);
//Make overlayBmp transparent
overlayBmp.MakeTransparent(overlayBmp.GetPixel(0,0));
//Create a graphics object attached to the bitmap
Graphics overlayBmpGraphics = Graphics.FromImage(overlayBmp);
First, a new //Create a color matrix which will be applied to the overlay bitmap
//to modify the alpha of the entire image
float[][] colorMatrixItems = {
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, overlayAlphaFloat, 0},
new float[] {0, 0, 0, 0, 1}
};
ColorMatrix colorMatrix = new ColorMatrix(colorMatrixItems);
//Create an ImageAttributes class to contain a color matrix attribute
ImageAttributes imageAttrs = new ImageAttributes();
imageAttrs.SetColorMatrix(colorMatrix,
ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
//Draw the overlay bitmap into the graphics object,
//applying the image attributes
//which includes the reduced alpha
Rectangle drawRect = new Rectangle(0, 0,
bmpToOverlay.Size.Width, bmpToOverlay.Size.Height);
overlayBmpGraphics.DrawImage(bmpToOverlay,
drawRect,
0, 0, bmpToOverlay.Size.Width, bmpToOverlay.Size.Height,
GraphicsUnit.Pixel,
imageAttrs);
overlayBmpGraphics.Dispose();
Next, a 5x5 matrix is created, which consists of the identity matrix with cell (4,4) set to the alpha value, scaled from 0 to 1. Those readers with any linear algebra experience will recognize that a linear transformation is being built here, though to what end is perhaps not yet clear. This matrix is used to create an Finally, the familiar //overlayBmp now contains bmpToOverlay w/ the alpha applied.
//Draw it onto the target graphics object
//Note that pixel units must be specified
//to ensure the framework doesn't attempt
//to compensate for varying horizontal resolutions
//in images by resizing; in this case,
//that's the opposite of what we want.
newBmpGraphics.DrawImage(overlayBmp,
new Rectangle(overlayPoint.X, overlayPoint.Y,
bmpToOverlay.Width, bmpToOverlay.Height),
drawRect,
GraphicsUnit.Pixel);
newBmpGraphics.Dispose();
//Recall that newBmp was created as a memory bitmap;
//convert it to the format
//of the input bitmap
return ConvertBitmap(newBmp, destBmp.RawFormat);
Now, given a version of the overlay Clearly, this is the most painful example of GDI+ obfuscation and contortions. However, the end result is a professional, compelling blending of the input and overlay bitmaps, well worth the effort. MiscA few other public methods exist for MIME type conversion, etc, however they do not perform any substantive bitmap manipulation roles. The sample applicationIncluded in the source archive for this article is a simple Windows Forms application in C#, which exercises all of the functionality in ConclusionThis article introduced the History
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||