I have to display a large image inside a windows forms application. The user should have the possibility to mark an area of the image, which should be then be zoomed like in the example illustrated below.
As mentioned before the image will be quite large, so my question is: Is it possible to achieve this with the default PictureBox
control or am I better of using a 3rd party control? If so, please recommend a library which contains a control that offers these features.
As promised, here is the source of the control I made:
/// <summary>
/// A panel used to display an image and zoom into areas of the displayed
/// image.
/// </summary>
public sealed class PictureZoomPanel : Panel
{
// The image to dispay, set in the Image property
private Image _image;
// The current zoom factor
private float _zoom = 1;
// The zoom rectangle on the panel.
private Rectangle _panelZoomRect;
// _panelZoomRect on the actual image
private Rectangle? _imageZoomRect;
// Used in the mouse event handlers
private bool _mouseDown;
// The pen used to draw the zoom rectangle
private Pen _zoomPen;
/// <summary>
/// Create a new <see cref="PictureZoomPanel"/>
/// </summary>
public PictureZoomPanel()
{
// To prevent flickering
DoubleBuffered = true;
// To make resizing smoother
ResizeRedraw = true;
// Set default zoom pen
ZoomPen = null;
}
/// <summary>
/// The image to be displayed
/// </summary>
[Category("Appearance"),
Description("The image to be displayed.")]
public Image Image
{
get { return _image; }
set
{
_image = value;
ZoomToFit();
}
}
/// <summary>
/// The pen used to draw the zoom rectangle.
/// </summary>
[Category("Appearance"),
Description("The pen used to draw the zoom rectangle.")]
public Pen ZoomPen
{
get { return _zoomPen; }
set {
_zoomPen = value ?? new Pen(Color.Green, 2);
}
}
/// <summary>
/// Sets the zoom to a value where the whole image is visible.
/// </summary>
public void ZoomToFit()
{
_imageZoomRect = null;
_mouseDown = false;
_zoom = 1;
// If no image is present, there is nothing further to do
if (_image == null)
return;
var widthZoom = (float) Width / _image.Width;
var heightZoom = (float) Height / _image.Height;
// Make sure the whole image is visible
_zoom = widthZoom < heightZoom ? widthZoom : heightZoom;
// Force redraw
Invalidate();
}
protected override void OnMouseDown(MouseEventArgs e)
{
if (_image == null)
return;
_mouseDown = true;
_panelZoomRect = new Rectangle(e.X, e.Y, 0, 0);
}
protected override void OnMouseUp(MouseEventArgs e)
{
if (_image == null || !_mouseDown)
return;
_mouseDown = false;
// Without this, doubling clicking the control would cause zoom
if (_panelZoomRect.Height == 0 || _panelZoomRect.Width == 0)
return;
// Tell the paint method to zoom
_imageZoomRect = CalculateImageZoomRectangle();
_zoom = RecalculateZoom();
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (_image == null)
return;
// This makes sure that the left mouse button is pressed.
if (e.Button == MouseButtons.Left)
{
// Draws the rectangle as the mouse moves
_panelZoomRect = new Rectangle(
_panelZoomRect.Left,
_panelZoomRect.Top,
e.X - _panelZoomRect.Left,
e.Y - _panelZoomRect.Top);
}
// Force redraw to make sure the zoomRegion is painted
Invalidate();
}
private Rectangle CalculateImageZoomRectangle()
{
// Calculate all the coordinates to required to transform
var topLeft = new Point(_panelZoomRect.X,
_panelZoomRect.Y);
var topRight = new Point(_panelZoomRect.X + _panelZoomRect.Width,
_panelZoomRect.Y);
var bottomLeft = new Point(_panelZoomRect.X,
_panelZoomRect.Y - _panelZoomRect.Height);
var bottomRight = new Point(_panelZoomRect.X + _panelZoomRect.Height,
_panelZoomRect.Y - _panelZoomRect.Height);
var points = new [] { topLeft, topRight, bottomLeft, bottomRight };
// Converts the points from panel to image position
var mx = new Matrix(_zoom, 0, 0, _zoom, 0, 0);
mx.Invert();
mx.TransformPoints(points);
var rectangleWidth = points[1].X - points[0].X;
var rectangleHeight = points[0].Y - points[2].Y;
// _imageZoom != null, means that we are zooming in on an
// already zoomed in image. We must add the original values
// to zoom in deeper
return _imageZoomRect == null
? new Rectangle(points[0].X,
points[0].Y,
rectangleWidth,
rectangleHeight)
: new Rectangle(points[0].X + _imageZoomRect.Value.X,
points[0].Y + _imageZoomRect.Value.Y,
rectangleWidth,
rectangleHeight);
}
private float RecalculateZoom()
{
if (!_imageZoomRect.HasValue)
return _zoom;
var widthZoom = (float)Width / _imageZoomRect.Value.Width;
var heightZoom = (float)Height / _imageZoomRect.Value.Height;
return widthZoom < heightZoom ? widthZoom : heightZoom;
}
protected override void OnPaint(PaintEventArgs e)
{
if (_image == null)
{
OnPaintBackground(e);
return;
}
e.Graphics.Transform = new Matrix(_zoom, 0, 0, _zoom, 0, 0);
// Turn of interpolation when zoomed
e.Graphics.InterpolationMode = _imageZoomRect != null
? InterpolationMode.NearestNeighbor
: InterpolationMode.Default;
DrawImage(e);
if (_mouseDown)
DrawZoomRectangle(e);
base.OnPaint(e);
}
private void DrawImage(PaintEventArgs e)
{
var destRec = !_imageZoomRect.HasValue
? new Rectangle(0, 0, _image.Width, _image.Height)
: new Rectangle(0, 0, _imageZoomRect.Value.Width,
_imageZoomRect.Value.Height);
var sourceRec = !_imageZoomRect.HasValue
? new Rectangle(0, 0, _image.Width, _image.Height)
: _imageZoomRect.Value;
e.Graphics.DrawImage(_image, destRec,
sourceRec.Location.X, sourceRec.Location.Y,
sourceRec.Width, sourceRec.Height,
GraphicsUnit.Pixel);
}
private void DrawZoomRectangle(PaintEventArgs e)
{
e.Graphics.Transform = new Matrix();
e.Graphics.DrawRectangle(_zoomPen, _panelZoomRect);
}
}
The zoom factor can be controlled using Ctrl+MouseWheel for zoom in and zoom out functionality. Left Button MouseDown+Move for pan/scroll functionality.
Upload the chosen image in PNG or JPG directly into the editor or drag-n-drop it to the editor. Use the Zoom In/Out slider at the bottom of your screen to zoom into your photo. Focus on the specific part of the image while zooming in and make edits where required.
You can achieve zooming through translating the graphic object. Instead of using a PictureBox, I think a double buffered Panel is the better tool of choice.
You handle the paint event, and adjust the matrix presentation of it from there. Also, you need to adjust the AutoScrollMinSize property to have the scrollbars represent the correct range of the scaled image.
Quick example:
Bitmap bmp = new Bitmap(@"c:\myimage.png");
int zoom = 2;
private void Form1_Load(object sender, EventArgs e) {
panel1.AutoScrollMinSize = new Size(bmp.Width * zoom, bmp.Height * zoom);
}
private void panel1_Paint(object sender, PaintEventArgs e) {
using (Matrix mx = new Matrix(zoom, 0, 0, zoom, 0, 0)) {
mx.Translate(panel1.AutoScrollPosition.X / zoom, panel1.AutoScrollPosition.Y / zoom);
e.Graphics.Transform = mx;
e.Graphics.DrawImage(bmp, new Point(0, 0));
}
}
This method is for tracking the mouse movement of a scaled image:
protected Point BacktrackMouse(MouseEventArgs e)
{
Matrix mx = new Matrix(_zoom, 0, 0, _zoom, 0, 0);
mx.Translate(this.AutoScrollPosition.X * (1.0f / zoom),
this.AutoScrollPosition.Y * (1.0f / zoom));
mx.Invert();
Point[] p = new Point[] { new Point(e.X, e.Y) };
mx.TransformPoints(p);
return p[0];
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With