Extract Office Document Properties Collect Advertised Jobs
Apr 27

Printer Friendly Version

Download Source Code: ImageEditor.zip - 515.05KB

Image Drawing

.NET Framework Classes already implement a large part of the GDI+ functionality, so you can use directly graphical objects such as pens, brushes and fonts, with no need for PInvoke Win32 functions. You can get and paint on Graphics GDI+ drawing surfaces, draw rectangles, fill areas, write text.

ImageEdit class presents, at this first version, two constructors. When used with a filename, it will create an internal Image object from the image data already stored in a file.

The default constructor will create a new internal empty image, the size of a thumbnail. There will always be an image in our editor, either loaded from a local file, or created by default, as an empty image.

Here is the Empty static method that creates and draws an empty initial image, for either the ImageEdit default constructor, or an external client:

/// <summary>
/// Create an "empty" X image
/// </summary>
/// <param name="size">image width and height</param>
/// <returns></returns>
public static Image Empty(int size)
{
    int size4 = (int)(size / 4);
    int size43 = size4 * 3;

    Image image = new Bitmap(size, size, PIXEL_FORMAT);
    using (Graphics g = Graphics.FromImage(image))
    {
        SolidBrush brush = new SolidBrush(Color.White);
        g.FillRectangle(brush, new Rectangle(0, 0, size, size));

        Pen pen = new Pen(Color.Red, 1);
        g.DrawLine(pen, size4, size4, size43, size43);
        g.DrawLine(pen, size4, size43, size43, size4);

        brush.Dispose();
        pen.Dispose();
    }
    return image;
}

Image Thumbnails

A thumbnail is a small representation of an image, resized to some maximum width and height of under 200 pixels. By default, thumbnail sizes in Windows Explorer are 128 pixels.

With ImageEdit, the get property Thumbnail will create on-the-fly a thumbnail of your image. You can also customize the maximum and minimum size you want for a thumbnail, with MinThumbSize and MaxThumbSize properties. If either width or height is smaller than the minimum size, it will be enlarged, keeping the aspect ration and enlarging the adjacent dimension in the same proportion. If width or height is larger than the maximum size, the image will be reduced.

This is implemented by the FitInto method, which also makes an internal call to the already available Image method GetThumbnailImage:

// Adjust external image to have width/height fit within min/maxSize
public static Image FitInto(Image image,
    int minSize, int maxSize, Color borderColor, bool thumbnail)
{
    if (minSize <= 0 && maxSize <= 0)
        throw new Exception("Min or max size not strict pos values");

    int w = image.Width;
    int h = image.Height;
    if (maxSize > 0 && (maxSize < w || maxSize < h))
    {
        if (w >= h) { h = (int)(h * maxSize / w); w = maxSize; }
        else { w = (int)(w * maxSize / h); h = maxSize; }
    }
    else if (minSize > 0 && (minSize > w && minSize > h))
    {
        if (w >= h) { h = (int)(h * minSize / w); w = minSize; }
        else { w = (int)(w * minSize / h); h = minSize; }
    }
    if (w <= 0) w = 1;
    if (h <= 0) h = 1;

    Image newimage = image.GetThumbnailImage(w, h, null, IntPtr.Zero);
    if (w != h || w < maxSize)
    {
        Image thumb = new Bitmap(maxSize, maxSize, PIXEL_FORMAT);
        using (Graphics g = Graphics.FromImage(thumb))
        {
            SolidBrush brush = new SolidBrush(
                thumbnail ? Color.White : borderColor);
            g.FillRegion(brush,
                new Region(new Rectangle(0, 0, maxSize, maxSize)));
            brush.Dispose();

            g.DrawImage(newimage,
                new Rectangle((int)((maxSize - w) / 2),
                (int)((maxSize - h) / 2), w, h),
                0, 0, w, h, GraphicsUnit.Pixel);

            if (thumbnail)
            {
                Pen pen = new Pen(Color.Gainsboro, 1);
                g.DrawRectangle(pen, 0, 0, maxSize - 1, maxSize - 1);
                pen.Dispose();
            }
        }
        newimage.Dispose();
        newimage = thumb;
    }
    return newimage;
}

Image Resizing

Most transformation operations are implemented in ImageEdit as either static functions, to be applied on any external Image object, or instance methods, on the internal _image field. Resize properties will also transparently call these methods, when their value changes.

Beside FitInto method presented before, which resizes an image, keeping the same aspect ration, to have both width and height smaller than a maximum size and larger than a minimum size, another main resizing method is Resize, implemented as well as either a static function, or local, for ImageEdit's image:

// Resize external image, adding bands or cropping (for negative x, y)
public static Image Resize(Image image, int x, int y,
    int width, int height, Color borderColor)
{
    if (image == null || width <= 0 || height <= 0)
        throw new Exception("Invalid image, width or height");

    Image newimage = new Bitmap(width, height, PIXEL_FORMAT);
    using (Graphics g = Graphics.FromImage(newimage))
    {
        SolidBrush brush = new SolidBrush(borderColor);
        g.FillRegion(brush, new Region(
            new Rectangle(0, 0, width, height)));
        brush.Dispose();

        g.DrawImage(image, new Rectangle(
            x > 0 ? 0 : -x, y > 0 ? 0 : -y, width, height),
            x > 0 ? x : 0, y > 0 ? y : 0, width, height,
            GraphicsUnit.Pixel);
    }
    return newimage;
}

Resize will either add a right or bottom band, for larger width or height values, or cut a portion of the image, from right or bottom. The image will not be distorted, but its area gets enlarged with empty areas or cut.

However, when KeepRatio property is true (by default), when you change either Width or Height, the other dimension gets changed in the same proportion. The whole image appears non-distorted and automatically enlarged or reduced on both axis, in the same rapport value.

If KeepRatio is false, but AutoSize is true (by default), the whole content of the image, on the dimension you change, will be enhanced or reduced, to cover all area on that direction. The image will appear distorted.

To simply get the behavior described for using Resize, set both KeepRatio and AutoSize properties to false.

Here is the implementation of Width and Height properties, whose set methods implement a different behavior, depending on the properties described before:

[Description("Change width. Image will change"
   + " depending on KeepRation and AutoSize")]
[Category("Size")]
[DefaultValue(0)]
public int Width
{
    get { return _image.Width; }
    set
    {
        if (value > 0)
        {
            if (_keepRatio)
                Image = new Bitmap(_image, value,
                    (int)(_image.Height
                    * (((double)value) / _image.Width)));
            else if (_autoSize)
                Image = new Bitmap(_image, value, _image.Height);
            else
                Image = Resize(_image, 0, 0,
                    value, _image.Height, _borderColor);
        }
    }
}

[Description("Change height. Image will change"
    + " depending on KeepRation and AutoSize")]
[Category("Size")]
[DefaultValue(0)]
public int Height
{
    get { return _image.Height; }
    set
    {
        if (value > 0)
        {
            if (_keepRatio)
                Image = new Bitmap(_image,
                    (int)(_image.Width
                    * (((double)value) / _image.Height)), value);
            else if (_autoSize)
                Image = new Bitmap(_image, _image.Width, value);
            else
                Image = Resize(_image, 0, 0,
                    _image.Width, value, _borderColor);
        }
    }
}

As for a thumbnail image, you can specify a minimum or maximum size for your image. No matter which dimension needs to be adjusted, changing the MinSize or MaxSize properties will automatically resize your image, in the same ratio factor.

Manual or Automatic Image Transformations
Manual or Automatic Image Transformations

Image Zoom

Scale (or Zoom) is a resize operation which does not actually change the image data, but only filters it dynamically, to display it on screen enlarged or reduced, keeping the same aspect ratio for both width and height.

Scale property has a default 100 (%) value. When set to a different value, it just stores this value internally and does not perform any modification of the image. However, the next call to the get property Image will dynamically perform an in or out zoom on the stored image object. It's not the image data that you can store that you will get back, but the image data transformed by the scale factor. A scale factor below 100 will zoom it in and reduce the displayed image, while the zoom out, for factor bigger than 100, will enlarge the appearance of the image.

While the Scale property of ImageEdit accepts values larger than 0 and up to a maximum of 1000 (for images ten times larger), the ImageControl will limit the zoom factor to values between 10 and 500. You can get, on screen, images reduces up to ten times, or enlarged up to five times. This is because images already large enough can consume a lot of memory, if the enlarge factor is too big.

The static Zoom method performs an operation similar to the Scale property's set method, but for an external image and custom scale factor.

[Description("Save zoom in or out factor,"
    + " to be transparently applied when reading the Image")]
[Category("Resize")]
[DefaultValue(100)]
public int Scale
{
    get { return _scale; }
    set
    {
        if (value > 0 && value <= 1000)
        {
            bool changed = (_scale != value);
            _scale = value;
            if (changed && OnChange != null)
                OnChange(this, new EventArgs());
        }
    }
}
private int _scale = 100;

// Scale (zoom in or out) external image by a scale factor
public static Image Zoom(Image image, int scale)
{
    Debug.Assert(image != null);
    return (scale == 100 ? image
        : new Bitmap(image,
        (int)(image.Width * scale / 100),
        (int)(image.Height * scale / 100)));
}

The zoom operation is intensively used by the ImageControl, which can either automatically resize the display image for a Size-to-Fit, or show it with a fixed custom scale factor, chosen by user.

ImageControl renders the image in a PictureBox control, which is embedded in the middle of an auto-scrolling Panel. This pnlInner panel is embedded in the main pnlMain panel, from the upper-left corner. In Size-to-Fit mode, pnlInner is docked to fill its parent area, and the image is automatically scaled and reduced to fit the content of visible area, if its size is larger that this zone. No parts of the image are hidden behind some scroll bars.

For fixed scale factor, in either 100% or different value, other than Size-to-Fit, the image is always shown at either its actual size (if scale is 100%) or enlarged/reduced with the same fixed factor. There is no docking for pnlInner, and this panel will automatically resize its area to some hidden portions behind scroll bars, if necessary, if image's display size is bigger than visible area.

Image Border

BorderSize property is actually a command, which always returns 0 (value that should be ignored). However, when you set BorderSize to a positive or negative value, in the PropertyGrid, the image will either get bands of a same size on each of its edges, or have some portions cut, for a negative value.

When bands are added, the background color is dictated by the customizable BorderColor property. This is the same color that applies when you Resize and image, changing the Width or Height properties to grater values, if both KeepRatio and AutoSize are false. A Width increase will add a vertical band on the right edge, while a Height increase will add a horizontal band on the bottom edge.

With additional Left and Top properties, you can also add or remove a band from the left edge or the top edge of the image.

Continue reading »

Subscribe and Share: Subscribe using any feed reader Bookmark and Share

Leave a Reply