drupal hit counter
Jerry Huang | A WinForm Image Selector Control

Jerry Huang apps and developing apps

A WinForm Image Selector Control

24. April 2011 12:35 by Jerry in UI

First of all, I have to point out that the control is far from perfect. So far it's just workable for my Smart Dial Agent. If you want to make use of it, you will need to do your own customization or make it flexible by using control property. You are welcome to send me back an enhanced version if you have done some works on itLaughing

Before going into technical details, let's have a look how it works

The rectangle with black border in the photo is the selector, while the image on the left is the target cropped photo. The selector could move or resize, but it couldn't smaller than 96*96.

The original source code is from a Chinese website (I forgot which one now, sorry) and base on .Net Framework 1.1. It doesn't work probably at the beginning, I'm not sure if relates to the framework. I just fix some problems and make some changes to fit my needs.

Usage:

New a Form in Visual Studio, drap a PictureBox and then place a Selector control on top of the PictureBox. drap another PictureBox as target image. Trigger the Mouse-Up event to retrieve target image. Below is a sample:

[code language=C#]using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SmartDialAgent.Utils;
using SmartDialAgent.Controls;

namespace SmartDialAgent
{
    public partial class CropImage : Form
    {
        public CropImage()
        {
            InitializeComponent();
        }

        private void btnBrowse_Click(object sender, EventArgs e)
        {
            try
            {
                OpenFileDialog dlg = new OpenFileDialog();
                dlg.Filter = "Images|*.jpg;*.jpeg;*.png;*.bmp";
                if (dlg.ShowDialog() == DialogResult.OK)
                {
                    LoadImage(dlg.FileName);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void LoadImage(string path)
        {
            Image img = Image.FromFile(path);
            if (img.Width > 640 || img.Height > 480)
            {
                //source img too big, adjust size
                int x1, y1;//base on width
                if (img.Width > 640)
                {
                    x1 = 640;
                    y1 = 640 * img.Height / img.Width;
                }
                else
                {
                    x1 = img.Width;
                    y1 = img.Height;
                }
                int x2, y2;//base on height
                if (img.Height > 480)
                {
                    y2 = 480;
                    x2 = 480 * img.Width / img.Height;
                }
                else
                {
                    x2 = img.Width;
                    y2 = img.Height;
                }
                //choose the smaller pairs
                if (x1 > x2)
                {
                    img = img.GetThumbnailImage(x2, y2, null, new IntPtr());
                }
                else
                {
                    img = img.GetThumbnailImage(x1, y1, null, new IntPtr());
                }
            }
            pbImage.Image = img;
            pbImage.Width = img.Width;
            pbImage.Height = img.Height;
            int len;
            if (img.Width > img.Height)
                len = img.Height;
            else
                len = img.Width;
            if ((len / 2) > 96)
            {
                pictureBoxSelection1.Width = len / 2;
                pictureBoxSelection1.Height = pictureBoxSelection1.Width;
            }
            else
            {
                pictureBoxSelection1.Width = 96;
                pictureBoxSelection1.Height = 96;
            }
            pictureBoxSelection1.PictureBox = pbImage;
            groupBox1.Width = pbImage.Width;
            groupBox1.Height = pbImage.Height;
            pictureBoxSelection1.Left = pbImage.Left;
            pictureBoxSelection1.Top = pbImage.Top;
            this.GetTargetImage();

        }


        private void GetTargetImage()
        {
            Image img;
            if (pictureBoxSelection1.SelectedImage == null)
            {
                img = PictureBoxSelector.Crop(pbImage.Image, pictureBoxSelection1.Size, pictureBoxSelection1.Left - pbImage.Left, pictureBoxSelection1.Top - pbImage.Top);
            }
            else
            {
                img = pictureBoxSelection1.SelectedImage;
            }
             if (img != null && img.Width > 96)
                    img = img.GetThumbnailImage(96, 96, null, new IntPtr());
                pbTarget.Image = img;
        }

        private void pictureBoxSelection1_MouseUp(object sender, MouseEventArgs e)
        {
            GetTargetImage();
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.Cancel;
        }
        private Image Image
        {
            get
            {
                return pbTarget.Image;
            }
        }
        private void btnSave_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.OK;
        }

        public static Image GetCropImage(string path)
        {
            using (CropImage frm = new CropImage())
            {
                frm.LoadImage(path);
                if (frm.ShowDialog() == DialogResult.OK)
                {
                    return frm.Image;
                }
                else
                    return null;
            }
        }

 

    }
}

[/code]

 

Control source code:

[code language=C#]

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using SmartDialAgent.Utils;
using System.Drawing.Drawing2D;

namespace SmartDialAgent.Controls
{

    public class PictureBoxSelector : System.Windows.Forms.Control
    {

        private Color _BorderColor = new Color();
        //private Color _BackColor = new Color();
        private bool _ReSizeble;
        private Point _SelfLocation = new Point();
        private Point _MouseLocation = new Point();
        private int _SelfWidth;
        private int _SelfHeight;
        private int _SelectSelctedIndex;//0-8,0:SizeAll
        private Rectangle _rectLeftSelector = new Rectangle();
        private Rectangle _rectTopSelector = new Rectangle();
        private Rectangle _rectRightSelector = new Rectangle();
        private Rectangle _rectBottomSelector = new Rectangle();
        private Rectangle _rectLeftTopSelector = new Rectangle();
        private Rectangle _rectRightTopSelector = new Rectangle();
        private Rectangle _rectRightBottomSelector = new Rectangle();
        private Rectangle _rectLeftBottomSelector = new Rectangle();
        private System.ComponentModel.Container components = null;
        public PictureBoxSelector()
        {
            InitializeComponent();
        }
        [DefaultValue("Black"), Description("color of the border"), Category("Appearance")]
        public Color BorderColor
        {
            get
            {
                // Insert code here.
                return _BorderColor;
            }
            set
            {
                _BorderColor = value;
                this.Invalidate();
            }
        }
        //[DefaultValue("Control"), Description("Background color"), Category("Appearance")]
        //public override Color BackColor
        //{
        //    get
        //    {
        //        // Insert code here.
        //        return _BackColor;
        //    }
        //    set
        //    {
        //        _BackColor = value;
        //        this.Invalidate();
        //    }
        //}
        [DefaultValue(false), Description("Indicates the control coud be moved and resized"), Category("Behavior")]
        public bool ReSizeble
        {
            get
            {
                // Insert code here.
                return _ReSizeble;
            }
            set
            {
                _ReSizeble = value;
                this.Invalidate();
            }
        }

        [Description("Selected area"), Category("Behavior")]
        public Rectangle SelectedRectangle
        {
            get
            {
                Rectangle selectRectangler = new Rectangle();
                selectRectangler.X = this.Location.X ;
                selectRectangler.Y = this.Location.Y ;
                selectRectangler.Height = this.Height ;
                selectRectangler.Width = this.Width ;
                return selectRectangler;
            }

        }
        /// <summary>
        /// the selected image
        /// </summary>
        public Image SelectedImage { get; private set; }

        public static Bitmap Crop(Image image, Size targetSize, int x, int y)
        {
            try
            {
                Bitmap bmp = new Bitmap(targetSize.Width, targetSize.Height);//, PixelFormat.Format24bppRgb);
                bmp.SetResolution(image.HorizontalResolution, image.VerticalResolution);

                Graphics gfx = Graphics.FromImage(bmp);
                gfx.SmoothingMode = SmoothingMode.AntiAlias;
                gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
                gfx.PixelOffsetMode = PixelOffsetMode.HighQuality;
                gfx.DrawImage(image, new Rectangle(0, 0, targetSize.Width, targetSize.Height),
                    x, y, targetSize.Width, targetSize.Height, GraphicsUnit.Pixel);

                gfx.Dispose();

                return bmp;
            }
            catch (Exception)
            {
                return null;
            }
        }

        /// <summary>
        /// repaint the background by copying crop image from parent picturebox
        /// </summary>
        /// <param name="e"></param>
        private void RepaintBackground(PaintEventArgs e)
        {
            if (PictureBox != null && PictureBox.Image!=null)
            {
                Image img = Crop(PictureBox.Image, Size, Left - PictureBox.Left, Top - PictureBox.Top);
                if (img != null)
                    e.Graphics.DrawImage(img, 0, 0, Width, Height);
                SelectedImage = img;
            }
        }
        protected override void OnPaint(PaintEventArgs e)
        {
            RepaintBackground(e);
            ReDrawControl(e.Graphics);
        }
        private void DrawSelector(Graphics graphics)
        {
            SolidBrush SelectorPen = new SolidBrush(Color.White);
            Pen borderPen = new Pen(this._BorderColor, 1);
            try
            {
                //solid
                PointF[] LeftPoints = getPointF(0, this.Height / 2 - 3, 6, 6);
                graphics.FillClosedCurve(SelectorPen, LeftPoints);

                PointF[] TopPoints = getPointF(this.Width / 2 - 3, 0, 6, 6);
                graphics.FillClosedCurve(SelectorPen, TopPoints);

                PointF[] RightPoints = getPointF(this.Width - 7, this.Height / 2 - 3, 6, 6);
                graphics.FillClosedCurve(SelectorPen, RightPoints);

                PointF[] BottomPoints = getPointF(this.Width / 2 - 3, this.Height - 7, 6, 6);
                graphics.FillClosedCurve(SelectorPen, BottomPoints);
               
                //remove the 4 solid resize selectors, I don't need it, if you need to, add them back
                //PointF[] LeftTopPoints = getPointF(0, 0, 6, 6);
                //graphics.FillClosedCurve(SelectorPen, LeftTopPoints);

                //PointF[] RightTopPoints = getPointF(this.Width - 7, 0, 6, 6);
                //graphics.FillClosedCurve(SelectorPen, RightTopPoints);

                //PointF[] RightBottomPoints = getPointF(this.Width - 7, this.Height - 7, 6, 6);
                //graphics.FillClosedCurve(SelectorPen, RightBottomPoints);

                //PointF[] LeftBottomPoints = getPointF(0, this.Height - 7, 6, 6);
                //graphics.FillClosedCurve(SelectorPen, LeftBottomPoints);
                //borders
                int border = 6;
                _rectLeftSelector.X = 0;
                _rectLeftSelector.Y = this.Height / 2 - 3;
                _rectLeftSelector.Height = border;
                _rectLeftSelector.Width = border;
                graphics.DrawRectangle(borderPen, _rectLeftSelector);

                _rectTopSelector.X = this.Width / 2 - 3;
                _rectTopSelector.Y = 0;
                _rectTopSelector.Height = border;
                _rectTopSelector.Width = border;
                graphics.DrawRectangle(borderPen, _rectTopSelector);

                _rectRightSelector.X = this.Width - 7;
                _rectRightSelector.Y = this.Height / 2 - 3;
                _rectRightSelector.Height = border;
                _rectRightSelector.Width = border;
                graphics.DrawRectangle(borderPen, _rectRightSelector);

                _rectBottomSelector.X = this.Width / 2 - 3;
                _rectBottomSelector.Y = this.Height - 7;
                _rectBottomSelector.Height = border;
                _rectBottomSelector.Width = border;
                graphics.DrawRectangle(borderPen, _rectBottomSelector);
               
                //remove the 4 solid resize selectors, I don't need it, if you need to, add them back
                //_rectLeftTopSelector.X = 0;
                //_rectLeftTopSelector.Y = 0;
                //_rectLeftTopSelector.Width = border;
                //_rectLeftTopSelector.Height = border;
                //graphics.DrawRectangle(borderPen, _rectLeftTopSelector);

                //_rectRightTopSelector.X = this.Width - 7;
                //_rectRightTopSelector.Y = 0;
                //_rectRightTopSelector.Width = border;
                //_rectRightTopSelector.Height = border;
                //graphics.DrawRectangle(borderPen, _rectRightTopSelector);

                //_rectRightBottomSelector.X = this.Width - 7;
                //_rectRightBottomSelector.Y = this.Height - 7;
                //_rectRightBottomSelector.Width = border;
                //_rectRightBottomSelector.Height = border;
                //graphics.DrawRectangle(borderPen, _rectRightBottomSelector);

                //_rectLeftBottomSelector.X = 0;
                //_rectLeftBottomSelector.Y = this.Height - 7;
                //_rectLeftBottomSelector.Width = border;
                //_rectLeftBottomSelector.Height = border;
                //graphics.DrawRectangle(borderPen, _rectLeftBottomSelector);
            }
            catch (Exception E)
            {
                throw E;
            }
            finally
            {
                SelectorPen.Dispose();
                borderPen.Dispose();
            }

        }
        private void ReDrawControl(Graphics graphics)
        {

            try
            {

                //draw background
               
                //graphics.Clear(this._BackColor);
                //SolidBrush backPen=new SolidBrush(this._BackColor);
                //PointF point1 = new PointF(1,1);
                //PointF point2 = new PointF(this.Width-2,1);
                //PointF point3 = new PointF(this.Width-2,this.Height-2);
                //PointF point4 = new PointF(1,this.Height-2);
                //PointF[] points = {point1, point2, point3, point4};
                //graphics.DrawImage(ImageUtils.CropImage(this.PictureBox, new Rectangle(Left, Top, Width, Height)),);
                //graphics.FillClosedCurve(backPen, points);
               
                //border
                Rectangle rectBorder = new Rectangle();
                Pen borderPen = new Pen(this._BorderColor, 1);
                rectBorder.X = 0;
                rectBorder.Y = 0;
                rectBorder.Height = this.Height - 1;
                rectBorder.Width = this.Width - 1;
                graphics.DrawRectangle(borderPen, rectBorder);
                //selector
                if (_ReSizeble)
                {
                    DrawSelector(graphics);
                }
            }
            catch (Exception E)
            {
                throw E;
            }
            finally
            {
                graphics.Dispose();
            }
        }
        public PictureBox PictureBox { get; set; }
        protected override void OnPaintBackground(PaintEventArgs e)
        {
            this.RepaintBackground(e);
        }


        private PointF[] getPointF(int x, int y, int Width, int Height)
        {
            PointF point1 = new PointF(x, y);
            PointF point2 = new PointF(x + Width, y);
            PointF point3 = new PointF(x + Width, y + Height);
            PointF point4 = new PointF(x, y + Height);
            PointF[] points = { point1, point2, point3, point4 };
            return points;
        }
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (components != null)
                    components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region generated by Visual Studio

        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
            this.Resize += new EventHandler(ShapeEx_Resize);
            this.MouseDown += new MouseEventHandler(ShapeEx_MouseDown);
            this.MouseMove += new MouseEventHandler(ShapeEx_MouseMove);
            this.MouseLeave += new EventHandler(ShapeEx_MouseLeave);
            this.MouseUp += new MouseEventHandler(ShapeEx_MouseUp);
            this.Move += new EventHandler(Selection_Move);
            this._BorderColor = Color.Black;
            //this._BackColor = Color.Transparent;//.FromName("Control");
            this._ReSizeble = false;
            this._SelectSelctedIndex = -1;
            SetStyle(ControlStyles.SupportsTransparentBackColor, true);
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);  
            //SetStyle(ControlStyles.OptimizedDoubleBuffer, true);  

        }
        #endregion

        private void ShapeEx_Resize(object sender, EventArgs e)
        {
            //hard code 96, the smallest size could be,
            //will be better if use a Size property
            if (this.Width < 96 || this.Height < 96)
            {
                this.Width = 96;
                this.Height = 96;
            }
            this.Invalidate();
        }

 

        private void ShapeEx_MouseDown(object sender, MouseEventArgs e)
        {
            if (_ReSizeble)
            {
                if (_rectLeftSelector.Contains(e.X, e.Y) || _rectRightSelector.Contains(e.X, e.Y) || _rectTopSelector.Contains(e.X, e.Y) || _rectBottomSelector.Contains(e.X, e.Y) || _rectLeftTopSelector.Contains(e.X, e.Y) || _rectRightTopSelector.Contains(e.X, e.Y) || _rectRightBottomSelector.Contains(e.X, e.Y) || _rectLeftBottomSelector.Contains(e.X, e.Y))
                {
                    if (_rectLeftTopSelector.Contains(e.X, e.Y))
                    {
                        this.Cursor = Cursors.SizeNWSE;
                        this._SelectSelctedIndex = 1;
                    }

                    if (_rectTopSelector.Contains(e.X, e.Y))
                    {
                        this.Cursor = Cursors.SizeNS;
                        this._SelectSelctedIndex = 2;
                    }
                    if (_rectRightTopSelector.Contains(e.X, e.Y))
                    {
                        this.Cursor = Cursors.SizeNESW;
                        this._SelectSelctedIndex = 3;
                    }
                    if (_rectRightSelector.Contains(e.X, e.Y))
                    {
                        this.Cursor = Cursors.SizeWE;
                        this._SelectSelctedIndex = 4;
                    }
                    if (_rectRightBottomSelector.Contains(e.X, e.Y))
                    {
                        this.Cursor = Cursors.SizeNWSE;
                        this._SelectSelctedIndex = 5;
                    }
                    if (_rectBottomSelector.Contains(e.X, e.Y))
                    {
                        this.Cursor = Cursors.SizeNS;
                        this._SelectSelctedIndex = 6;
                    }
                    if (_rectLeftBottomSelector.Contains(e.X, e.Y))
                    {
                        this.Cursor = Cursors.SizeNESW;
                        this._SelectSelctedIndex = 7;
                    }
                    if (_rectLeftSelector.Contains(e.X, e.Y))
                    {
                        this.Cursor = Cursors.SizeWE;
                        this._SelectSelctedIndex = 8;
                    }

                }
                else
                {
                    this.Cursor = Cursors.SizeAll;
                    this._SelectSelctedIndex = 0;
                }
                this._SelfLocation.X = this.Location.X;
                this._SelfLocation.Y = this.Location.Y;
                this._MouseLocation.X = Cursor.Position.X;
                this._MouseLocation.Y = Cursor.Position.Y;
                this._SelfWidth = this.Width;
                this._SelfHeight = this.Height;
            }
        }

        private void ShapeEx_MouseMove(object sender, MouseEventArgs e)
        {
           
            //move and resize
            //since I removed 4 resize selector, it will only fall into cases like 2,4,6,8,etc
            switch (this._SelectSelctedIndex)
            {
                case 0:
                    this.Location = new Point(Cursor.Position.X - (_MouseLocation.X - _SelfLocation.X), Cursor.Position.Y - (_MouseLocation.Y - _SelfLocation.Y));
                    break;
                case 1:
                    this.Height = this._SelfHeight - (Cursor.Position.Y - _MouseLocation.Y);
                    this.Width = this._SelfWidth - (Cursor.Position.X - _MouseLocation.X);
                    this.Location = new Point(Cursor.Position.X - _MouseLocation.X + _SelfLocation.X, Cursor.Position.Y - _MouseLocation.Y + _SelfLocation.Y);
                    break;
                case 2:
                    this.Height = this._SelfHeight - (Cursor.Position.Y - _MouseLocation.Y);
                    this.Width = Height;//remove if Width<> Height
                    this.Location = new Point(_SelfLocation.X, Cursor.Position.Y - _MouseLocation.Y + _SelfLocation.Y);
                    break;
                case 3:
                    this.Height = this._SelfHeight - (Cursor.Position.Y - _MouseLocation.Y);
                    this.Width = this._SelfWidth + (Cursor.Position.X - _MouseLocation.X);
                    this.Location = new Point(_SelfLocation.X, Cursor.Position.Y - (_MouseLocation.Y - _SelfLocation.Y));
                    break;
                case 4:
                    this.Width = this._SelfWidth + (Cursor.Position.X - _MouseLocation.X);
                    Height = Width;//remove if Width<> Height
                    break;
                case 5:
                    this.Height = this._SelfHeight + (Cursor.Position.Y - _MouseLocation.Y);
                    this.Width = this._SelfWidth + (Cursor.Position.X - _MouseLocation.X);
                    break;
                case 6:
                    this.Height = this._SelfHeight + (Cursor.Position.Y - _MouseLocation.Y);
                    Width = Height;//remove if Width<> Height
                    break;
                case 7:
                    this.Height = this._SelfHeight + (Cursor.Position.Y - _MouseLocation.Y);
                    this.Width = this._SelfWidth - (Cursor.Position.X - _MouseLocation.X);
                    this.Location = new Point(Cursor.Position.X - _MouseLocation.X + _SelfLocation.X, _SelfLocation.Y);
                    break;
                case 8:
                    this.Width = this._SelfWidth - (Cursor.Position.X - _MouseLocation.X);
                    Height = Width;//remove if Width<> Height
                    this.Location = new Point(Cursor.Position.X - _MouseLocation.X + _SelfLocation.X, _SelfLocation.Y);
                    break;
            }
 
           
        }

        private void Selection_Move(object sender, EventArgs e)
        {
            Invalidate();
        }

        private void ShapeEx_MouseLeave(object sender, EventArgs e)
        {
            this.Cursor = Cursors.Default;
            this._SelectSelctedIndex = -1;
            Invalidate();
        }

        private void ShapeEx_MouseUp(object sender, MouseEventArgs e)
        {
            this.Cursor = Cursors.Default;
            this._SelectSelctedIndex = -1;
            Invalidate();
        }

    }
}

[/code]

As I stated at the beginning, this is a naive version, below is a list of known issues

  1. when the selector is out of the parent PictureBox, the area of out of PictureBox can't be blank, but some remaining pixes with lagacy color (I don't know how to say, you will see if you run it). Currently I use a groupBox as a container for the PictureBox so that it doesn't look so ugly. Ideally the selector is not allowed to move out size of parent PictureBox
  2. should proper control the PictureBox property, should throw error if the property is null, or force the control parent is PictureBox
  3. current version seems not very efficient, the image is flicking when moving the selector

 

source code also available here:

PictureBoxSelection.cs (19.17 kb)