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 it
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
- 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
- should proper control the PictureBox property, should throw error if the property is null, or force the control parent is PictureBox
- current version seems not very efficient, the image is flicking when moving the selector
source code also available here:
PictureBoxSelection.cs (19.17 kb)