Download Source Code: ImageEditor.zip - 515.05KB

Image Cropping
Cropping is a method to remove parts from the sides of an image. We already described how, with negative Width, Height, Left, Top or BorderSize property values, you can already do cropping and cut parts of the image.
ImageControl provides additional functionality for manual cropping. Any rectangular area from within the image can be selected, isolated and preserved, the other edge parts being automatically removed. Selecting and cropping the image require a StartSelection and EndSelection point, for the upper-left and bottom-right corners of the area to be selected.
You can perform manual image cropping only from the Image Editor. Push the mouse button, and this will define the StartSelection point. Holding the mouse button pressed, move to another point within image's area. You'll see the selection area isolated within a dashed-line rectangle. Once you release the mouse button, this will automatically set the EndSelection point and also perform the cropping.
Rotations and Symmetries
The Image class already provide a RotateFlip method, with which you can perform rotations and flips, in 90 degrees steps. ImageEdit will just provide three additional properties, to be able to perform these actions by simply setting a property value in the PropertyGrid.
What Rotations, FlipHorizontal and FlipVertical properties return is not important. You can set Rotations to a value of 1, 2 or 3, and rotate the image with 90, 180 or 270 degrees clockwise. FlipHorizontal and FlipVertical, when set to true, will both rotate with 180 degrees and mirror the image, toward the horizontal or vertical axis.
Write Text on Image
To write some text somewhere on image's area, just set the Text property to some string value. But before, set other related text properties accordingly. Essentially, you need a starting point for the text area. From the Image Editor, you can either click once on the image area, or manually set TextLocation point value.
The text is also displayed with attributes defined by TextForeColor, TextBackColor, TextFont and TextStringFormatFlags properties. All text-related properties and internal GDI+ objects (pens, brushes) are initialized by the private method Init.
Here is the sequence that writes - or rather paints - some text on the image, when Text property is set:
Image image = new Bitmap(_image);
using (Graphics g = Graphics.FromImage(image))
{
SolidBrush brushText = new SolidBrush(_textForeColor);
g.DrawString(value, _textFont, brushText,
_textLocation.X, _textLocation.Y, _textFormat);
brushText.Dispose();
}
Image = image;Image Files
Current version of this editor supports and has been tested for the foolowing image formats: JPEG (*.jpg), CompuServe GIF (*.gif), Portable Network Graphics (*.png), Windows Bitmaps (*.bmp) and TIFF (*.tif). While the Image class is generic and may also support other image formats, you'll not be able to open or save other types of images with this version of the Image Editor.
Static ExtensionFromFormat and FormatFromExtension methods map file extensions to supported ImageFormat values. Remark that System.Drawing.Imaging.ImageFormat is a class with static get properties, not enum constants.
ImageEdit provides a static Open method, to load an image from file, through a "Open File" dialog box. SaveAs method will show a "Save File As" dialog, with the possibility to change the file name and automatically save it. New empty images can be saved in files this way.
Save method provides particular support for JPEG format. You can essentially specify a compression ratio with Quality property before. Quality (by default 100) can be set to 80 for an 80% compression ratio. Compressed JPEG images will basically occupy less disk space, with the price of a poorer image definition:
/// <summary>
/// Save changes on current image in file
/// </summary>
public void Save()
{
// filename not specified. Use FileName = ...
if (_filename == null || _filename.Length == 0)
throw new Exception("Unspecified file name");
// cannot override RO file
if (File.Exists(_filename)
&& (File.GetAttributes(_filename)
& FileAttributes.ReadOnly) != 0)
throw new Exception("File exists and is read-only!");
// check supported image formats
ImageFormat format = FormatFromExtension(_filename);
if (format == null)
throw new Exception("Unsupported image format");
// JPG images get special treatement
if (format.Equals(ImageFormat.Jpeg))
{
EncoderParameters oParams = new EncoderParameters(1);
oParams.Param[0] = new EncoderParameter(
System.Drawing.Imaging.Encoder.Quality, _quality);
ImageCodecInfo oCodecInfo = GetEncoderInfo("image/jpeg");
_image.Save(_filename, oCodecInfo, oParams);
}
else
_image.Save(_filename, format);
// remove undo image
if (_imageOld != null)
_imageOld.Dispose();
_imageOld = null;
// notify image changed (to enable Save/Undo commands)
if (OnChange != null)
OnChange(this, new EventArgs());
}Other Implementation Details
During editing, it's easy to make mistakes. ImageEdit implements a simple Undo method and mechanism, which keeps the previous modified Image data in a private backup copy. IsChanged property returns true if there is such copy. Undo will discard current changes and restore data from the backup copy. All this is transparent, and it's possible to undo only the last incremental change action, such as resizing or rotating the image.
Both ImageEdit and ImageControl can raise OnChange events, to notify their parents that operations that potentially changed the image data occured. In our editor, ImageControl is first notified for changes by its ImageEdit object, and then it sends its own OnChange notification to the MainForm, which updates the values displayed by the ThumbnailControl and PropertyGrid. Remark that ThumbnailControl will read the Thumbnail property and draw a new thumbnail image each time a change notification is received.
ImageEdit and image controls are all disposable classes, with custom Dispose method. This is because the aggregate objects they hold are directly based on one or two Image instances, which have to be properly released, otherwise the memory is not freed.
Automate Operations on Images
The project provides a custom demo complex operation on a set of images, which demonstrates the utility and advantage of having your own image editor's source code.
We included two royalty-free pictures from bigphoto.com, saved with different sizes and file formats. If you want to use them for your own site or application, please read their copyright and conditions. Essentially, for publishing on non-commercial sites, you are required to provide just a link to their web site.
The demo implements automation and creates alternate images from all image files from your execution directory. On each image, we applied a set of operations, using the properties and methods implemented by ImageEdit class, exposed by ImageControl. Each original image is opened in the editor, resized, a text is added, as copyright notice - some use case you may need, when you have to publish lots of your images with some protection, on the web - and finally SaveAs saves changed imaged as a new file:
/// <summary>
/// Custom implementation of an automation method.
/// Automatically load images from execution directory
/// and apply a set of transformation on each.
/// </summary>
private void DemoAutomation()
{
string SUFFIX = "_copyrighted";
if (MessageBox.Show(
"This demo will load all image files from your"
+ " execution directory,\r\n"
+ "one by one, apply a set of"
+ " image transformation operations\r\n"
+ "implemented by ImageEdit class,"
+ " including adding a copyright notice\r\n"
+ "and save the images with '" + SUFFIX
+ "' suffix. Press OK to proceed.",
"Demo Automation", MessageBoxButtons.OKCancel,
MessageBoxIcon.Information)
!= DialogResult.OK)
return;
// for each image file in execution directory
DirectoryInfo dir = new DirectoryInfo(
Directory.GetCurrentDirectory());
foreach (FileInfo file in dir.GetFiles())
if (ImageEdit.FormatFromExtension(file.Name)!=null
&& !Path.GetFileNameWithoutExtension(
file.FullName).EndsWith(SUFFIX))
{
// load image
imgMain.Image = new ImageEdit(file.FullName);
Application.DoEvents();
// resize to max width/height 300 pixels
imgMain.Image.MaxSize = 300;
Application.DoEvents();
// add gray 10 pixels wide border
imgMain.Image.BorderColor = Color.Gainsboro;
imgMain.Image.BorderWidth = 10;
Application.DoEvents();
// add 20 pixels height band at the bottom
imgMain.Image.AutoSize = imgMain.Image.KeepRatio = false;
imgMain.Image.Height = imgMain.Image.Height + 20;
Application.DoEvents();
// write copyright text at the bottom
imgMain.Image.TextBackColor = Color.Gainsboro;
imgMain.Image.TextForeColor = Color.Blue;
imgMain.Image.TextLocation
= new Point(10, imgMain.Image.Height - 20);
imgMain.Image.Text = "Copyright (c) bigfoto.com";
Application.DoEvents();
// save image with "_copyrighted" suffix
imgMain.Image.FileName
= Path.GetFileNameWithoutExtension(
imgMain.Image.FileName) + SUFFIX
+ Path.GetExtension(imgMain.Image.FileName);
Application.DoEvents();
}
MessageBox.Show("Done.", "Demo Automation");
}Remark that drawing operations on images cannot be implemented transparently, with no graphical user interface. This is because drawing on a GDI+ surface requires Paint events, and the image content has to be visible on screen. This is why, after the operations that required drawing, we called Application.DoEvents method, and allowed the screen to be refreshed.
However, with this kind of Image Editor, with full source code available, classes and controls enhancing the functionality of the already rich Image and PictureBox classes, it's easy to add your own automation sequences and implement new operations on images.