I have a picturebox in my c# windows forms application, and i've made some code so that I can alter the height and width of the picturebox, essentially zooming in. However, this only focuses on the top right area and I would like the user to be able to scroll the map to the area they need to using the scrollbar.
Whenever I zoom in, then use the scrollbar, my image goes to its default size, and then only scrolls in that size.
How can I move the entire image so that the user can pan the picturebox map?
Here's my code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Mapping_Application
{
public partial class Form1 : Form
{
//Declare zoom variable counter
int zoom;
//Main Void
public Form1()
{
InitializeComponent();
}
//Zoom In
private void button1_Click(object sender, EventArgs e)
{
if(zoom != 3)
{
pictureBox1.Height = pictureBox1.Height * 2;
pictureBox1.Width = pictureBox1.Width * 2;
zoom = zoom + 1;
}
}
//Zoom out
private void button2_Click(object sender, EventArgs e)
{
if (zoom != 0)
{
pictureBox1.Height = pictureBox1.Height / 2;
pictureBox1.Width = pictureBox1.Width / 2;
zoom = zoom -= 1;
}
}
//The horizontal scrollbar
private void horizontal_Scroll(object sender, ScrollEventArgs e)
{
//Create a graphics object and draw a portion of the image in the PictureBox.
Graphics g = pictureBox1.CreateGraphics();
int x;
int y;
if (e.ScrollOrientation == ScrollOrientation.HorizontalScroll)
{
x = e.NewValue * 19;
y = vertical.Value * 19;
}
else //e.ScrollOrientation == ScrollOrientation.VerticalScroll
{
y = e.NewValue * 19;
x = horizontal.Value * 19;
}
g.Clear(Color.White);
g.DrawImage(pictureBox1.Image,
new Rectangle(0, 0, pictureBox1.Width, pictureBox1.Width), //where to draw the image
new Rectangle(x, y, pictureBox1.Width, pictureBox1.Width), //the portion of the image to draw
GraphicsUnit.Pixel);
pictureBox1.Update();
}
//The vertical scrollbar
private void vertical_Scroll(object sender, ScrollEventArgs e)
{
//Create a graphics object and draw a portion of the image in the PictureBox.
Graphics g = pictureBox1.CreateGraphics();
int x;
int y;
if (e.ScrollOrientation == ScrollOrientation.HorizontalScroll)
{
x = e.NewValue * 19;
y = vertical.Value * 19;
}
else //e.ScrollOrientation == ScrollOrientation.VerticalScroll
{
y = e.NewValue * 19;
x = horizontal.Value * 19;
}
g.Clear(Color.White);
g.DrawImage(pictureBox1.Image,
new Rectangle(0, 0, pictureBox1.Width, pictureBox1.Width), //where to draw the image
new Rectangle(x, y, pictureBox1.Width, pictureBox1.Width), //the
portion of the image to draw
GraphicsUnit.Pixel);
pictureBox1.Update();
}
}
}
The scroll currently seems to work, but for no zoom in only. There is no zoom negative feature and i'm using a var named zoom to store which level zoom the program is at. Can't zoom to -2x , hence the if (zoom!=0)
Related
I am developping a custom C# UserControl WinForm to display an image on background and display scrollbars when I zoom with the mouse. For this, I overrided the OnPaint method. In it, if I have an image loaded, according some parameters I know the source and destination rectangle sizes. In the same way, I know what scale and translation apply to always keeping the top left corner on screen when zooming. And for the zoom, I use the scrollmouse event to update the zoom factory.
Here is my code related to this override method.
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// Draw image
if(image != null)
{
//
Rectangle srcRect, destRect;
Point pt = new Point((int)(hScrollBar1.Value/zoom), (int)(vScrollBar1.Value/zoom));
if (canvasSize.Width * zoom < viewRectWidth && canvasSize.Height * zoom < viewRectHeight)
srcRect = new Rectangle(0, 0, canvasSize.Width, canvasSize.Height); // view all image
else if (canvasSize.Width * zoom < viewRectWidth)
srcRect = new Rectangle(0, pt.Y, canvasSize.Width, (int)(viewRectHeight / zoom)); // view a portion of image but center on width
else if (canvasSize.Height * zoom < viewRectHeight)
srcRect = new Rectangle(pt.X, 0, (int)(viewRectWidth / zoom), canvasSize.Height); // view a portion of image but center on height
else
srcRect = new Rectangle(pt, new Size((int)(viewRectWidth / zoom), (int)(viewRectHeight / zoom))); // view a portion of image
destRect = new Rectangle((int)(-srcRect.Width/2),
(int)-srcRect.Height/2,
srcRect.Width,
srcRect.Height); // the center of apparent image is on origin
Matrix mx = new Matrix(); // create an identity matrix
mx.Scale(zoom, zoom); // zoom image
// Move image to view window center
mx.Translate(viewRectWidth / 2.0f, viewRectHeight / 2.0f, MatrixOrder.Append);
// Display image on widget
Graphics g = e.Graphics;
g.InterpolationMode = interMode;
g.Transform = mx;
g.DrawImage(image, destRect, srcRect, GraphicsUnit.Pixel);
}
}
My question is how to get the pixel value when I am on the MouseMove override method of this WinForm ?
I think understand that it is possible only in method with PaintEventArgs but I am not sure how to deal with it. I tried a lot of things but for now the better I got is use the mouse position on the screen and find the pixel value in the original bitmap with these "wrong" coordinates. I can't link this relative position on the screen with the real coordinates of the pixel of the image display at this place. Maybe there is method to "just" get the pixel value not passing through the image bitmap I use for the paint method ? Or maybe not.
Thank you in advance for your help. Best regards.
I couldn't completely understand your drawing code but you can do an inverse transformation on mouse coordinate. So, you can translate the mouse coordinate back to the origin and scale it by 1/zoom. This simple process gives you the image space coordinate.
I provide an example code with its own drawing code (not your code/algorithm) but that can still give you the idea of inverse transformation. It is pretty simple so look at the example code.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace GetPixelFromZoomedImage
{
public partial class MainForm : Form
{
public Form1()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true);
InitializeComponent();
}
private float m_zoom = 1.0f;
private Bitmap m_image;
private Point m_origin = Point.Empty;
private Point m_delta = Point.Empty;
private SolidBrush m_brush = new SolidBrush(Color.Transparent);
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.TranslateTransform(m_origin.X, m_origin.Y);
g.ScaleTransform(m_zoom, m_zoom);
g.DrawImageUnscaled(m_image, Point.Empty);
g.ResetTransform();
g.FillRectangle(m_brush, ClientSize.Width - 50, 0, 50, 50);
base.OnPaint(e);
}
protected override void OnHandleCreated(EventArgs e)
{
m_image = (Bitmap)Image.FromFile("test.png");
base.OnHandleCreated(e);
}
protected override void OnMouseDown(MouseEventArgs e)
{
if(e.Button == MouseButtons.Left)
{
m_delta = new Point(m_origin.X - e.X, m_origin.Y - e.Y);
}
base.OnMouseDown(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if(e.Button == MouseButtons.Left)
{
m_origin = new Point(e.X + m_delta.X, e.Y + m_delta.Y);
Invalidate();
}
int x = (int)((e.X - m_origin.X) / m_zoom);
int y = (int)((e.Y - m_origin.Y) / m_zoom);
if (x < 0 || x >= m_image.Width || y < 0 || y >= m_image.Height)
return;
m_brush.Color = m_image.GetPixel(x, y);
Invalidate();
base.OnMouseMove(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
}
protected override void OnMouseWheel(MouseEventArgs e)
{
float scaleFactor = 1.6f * (float)Math.Abs(e.Delta) / 120;
if(e.Delta > 0)
m_zoom *= scaleFactor;
else
m_zoom /= scaleFactor;
m_zoom = m_zoom > 64.0f ? 64.0f : m_zoom;
m_zoom = m_zoom < 0.1f ? 0.1f : m_zoom;
Invalidate();
base.OnMouseWheel(e);
}
}
}
Issue: Attempting to zoom (scale) an Image from (or at the) mouse location using transforms in the Paint event to translate bitmap origin to mouse location, then scale the Image and translate its origin back.
The Image jumps and fails to scale from the relocated origin when translating the mouse location.
Rotate, scale, and pan function correctly without translating to the the mouse location.
Running on .Net 4.7.2, using Visual Studio in Windows 10 1909
v18363.778
The relevant code blocks:
private void trackBar1_Scroll(object sender, EventArgs e)
{
// Get rotation angle
ang = trackBar1.Value;
pnl1.Invalidate();
}
private void pnl1_MouseWheel(object sender, MouseEventArgs e)
{
// Get mouse location
mouse = e.location;
// Get new scale (zoom) factor
zoom = (float)(e.Delta > 0 ? zoom * 1.05 : zoom / 1.05);
pnl1.Invalidate();
}
private void pnl1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
pan = true;
mouX = e.X;
mouY = e.Y;
oldX = imgX;
oldY = imgY;
}
private void pnl1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left || !pan) return;
// Coordinates of panned image
imgX = oldX + e.X - mouX;
imgY = oldY + e.Y - mouY;
pnl1.Invalidate();
}
private void pnl1_MouseUp(object sender, MouseEventArgs e)
{
pan = false;
}
private void pnl1_Paint(object sender, PaintEventArgs e)
{
// Apply rotation angle # center of bitmap
e.Graphics.TranslateTransform(img.Width / 2, img.Height / 2);
e.Graphics.RotateTransform(ang);
e.Graphics.TranslateTransform(-img.Width / 2, -img.Height / 2);
// Apply scaling factor - focused # mouse location
e.Graphics.TranslateTransform(mouse.X, mouse.Y, MatrixOrder.Append);
e.Graphics.ScaleTransform(zoom, zoom, MatrixOrder.Append);
e.Graphics.TranslateTransform(-mouse.X, -mouse.Y, MatrixOrder.Append);
// Apply drag (pan) location
e.Graphics.TranslateTransform(imgX, imgY, MatrixOrder.Append);
// Draw "bmp" # location
e.Graphics.DrawImage(img, 0, 0);
}
A few suggestions and a couple of tricks.
Not exactly tricks, just some methods to speed up the calculations when more than one graphic transformation is in place.
Divide and conquer: split the different graphics effects and transformations in different, specialized, methods that do one thing. Then design in a way that makes it possible for these methods to work together when needed.
Keep it simple: when Graphics objects need to accumulate more than a couple of transformations, the order in which Matrices are stacked can cause misunderstandings. It's simpler (and less prone to generate weird outcomes) to calculate some generic transformations (translate and scale, mostly) beforehand, then let GDI+ render already pre-cooked objects and shapes.
Here, only Matrix.RotateAt and Matrix.Multiply are used.
Some notes about Matrix transformations here: Flip the GraphicsPath
Use the right tools: for example, a Panel used as canvas is not exactly the best choice. This Control is not double-buffered; this feature can be enabled, but the Panel class is not meant for drawing, while a PictureBox (or a non-System flat Label) supports it on its own.
Some more notes here: How to apply a fade transition effect to Images
The sample code shows 4 zoom methods, plus generates rotation transformations (which work side-by-side, don't accumulate).
The Zoom modes are selected using an enumerator (private enum ZoomMode):
Zoom modes:
ImageLocation: Image scaling is performed in-place, keeping the current Location on the canvas in a fixed position.
CenterCanvas: while the Image is scaled, it remains centered on the Canvas.
CenterMouse: the Image is scaled and translated to center itself on the current Mouse location on the Canvas.
MouseOffset: the Image is scaled and translated to maintain a relative position determined by the initial location of the Mouse pointer on the Image itself.
You can notice that the code simplifies all the calculations, applying translations exclusively relative to the Rectangle that defines the current Image bounds and only in relation to the Location of this shape.
The Rectangle is only scaled when the calculation needs to preemptively determine what the Image size will be after the Mouse Wheel has generated the next Zoom factor.
Visual sample of the implemented functionalities:
Sample code:
canvas is the Custom Control, derived from PictureBox (you can find its definition at the bottom). This control is added to the Form in code, here. Modify as needed.
trkRotationAngle is the TrackBar used to define the current rotation of the Image. Add this control to the Form in the designer.
radZoom_CheckedChanged is the event handler of all the RadioButtons used to set the current Zoom Mode. The value these Controls set is assigned in their Tag property. Add these controls to the Form in the designer.
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Windows.Forms;
public partial class frmZoomPaint : Form
{
private float rotationAngle = 0.0f;
private float zoomFactor = 1.0f;
private float zoomStep = .05f;
private RectangleF imageRect = RectangleF.Empty;
private PointF imageLocation = PointF.Empty;
private PointF mouseLocation = PointF.Empty;
private Bitmap drawingImage = null;
private PictureBoxEx canvas = null;
private ZoomMode zoomMode = ZoomMode.ImageLocation;
private enum ZoomMode
{
ImageLocation,
CenterCanvas,
CenterMouse,
MouseOffset
}
public frmZoomPaint()
{
InitializeComponent();
string imagePath = [Path of the Image];
drawingImage = (Bitmap)Image.FromStream(new MemoryStream(File.ReadAllBytes(imagePath)));
imageRect = new RectangleF(Point.Empty, drawingImage.Size);
canvas = new PictureBoxEx(new Size(555, 300));
canvas.Location = new Point(10, 10);
canvas.MouseWheel += canvas_MouseWheel;
canvas.MouseMove += canvas_MouseMove;
canvas.MouseDown += canvas_MouseDown;
canvas.MouseUp += canvas_MouseUp;
canvas.Paint += canvas_Paint;
Controls.Add(canvas);
}
private void canvas_MouseWheel(object sender, MouseEventArgs e)
{
mouseLocation = e.Location;
float zoomCurrent = zoomFactor;
zoomFactor += e.Delta > 0 ? zoomStep : -zoomStep;
if (zoomFactor < .10f) zoomStep = .01f;
if (zoomFactor >= .10f) zoomStep = .05f;
if (zoomFactor < .0f) zoomFactor = zoomStep;
switch (zoomMode) {
case ZoomMode.CenterCanvas:
imageRect = CenterScaledRectangleOnCanvas(imageRect, canvas.ClientRectangle);
break;
case ZoomMode.CenterMouse:
imageRect = CenterScaledRectangleOnMousePosition(imageRect, e.Location);
break;
case ZoomMode.MouseOffset:
imageRect = OffsetScaledRectangleOnMousePosition(imageRect, zoomCurrent, e.Location);
break;
default:
break;
}
canvas.Invalidate();
}
private void canvas_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
mouseLocation = e.Location;
imageLocation = imageRect.Location;
canvas.Cursor = Cursors.NoMove2D;
}
private void canvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
imageRect.Location =
new PointF(imageLocation.X + (e.Location.X - mouseLocation.X),
imageLocation.Y + (e.Location.Y - mouseLocation.Y));
canvas.Invalidate();
}
private void canvas_MouseUp(object sender, MouseEventArgs e) =>
canvas.Cursor = Cursors.Default;
private void canvas_Paint(object sender, PaintEventArgs e)
{
var drawingRect = GetDrawingImageRect(imageRect);
using (var mxRotation = new Matrix())
using (var mxTransform = new Matrix()) {
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
e.Graphics.PixelOffsetMode = PixelOffsetMode.Half;
mxRotation.RotateAt(rotationAngle, GetDrawingImageCenterPoint(drawingRect));
mxTransform.Multiply(mxRotation);
e.Graphics.Transform = mxTransform;
e.Graphics.DrawImage(drawingImage, drawingRect);
}
}
private void trkRotationAngle_ValueChanged(object sender, EventArgs e)
{
rotationAngle = trkAngle.Value;
canvas.Invalidate();
canvas.Focus();
}
private void radZoom_CheckedChanged(object sender, EventArgs e)
{
var rad = sender as RadioButton;
if (rad.Checked) {
zoomMode = (ZoomMode)int.Parse(rad.Tag.ToString());
}
canvas.Focus();
}
#region Drawing Methods
public RectangleF GetScaledRect(RectangleF rect, float scaleFactor) =>
new RectangleF(rect.Location,
new SizeF(rect.Width * scaleFactor, rect.Height * scaleFactor));
public RectangleF GetDrawingImageRect(RectangleF rect) =>
GetScaledRect(rect, zoomFactor);
public PointF GetDrawingImageCenterPoint(RectangleF rect) =>
new PointF(rect.X + rect.Width / 2, rect.Y + rect.Height / 2);
public RectangleF CenterScaledRectangleOnCanvas(RectangleF rect, RectangleF canvas)
{
var scaled = GetScaledRect(rect, zoomFactor);
rect.Location = new PointF((canvas.Width - scaled.Width) / 2,
(canvas.Height - scaled.Height) / 2);
return rect;
}
public RectangleF CenterScaledRectangleOnMousePosition(RectangleF rect, PointF mousePosition)
{
var scaled = GetScaledRect(rect, zoomFactor);
rect.Location = new PointF(mousePosition.X - (scaled.Width / 2),
mousePosition.Y - (scaled.Height / 2));
return rect;
}
public RectangleF OffsetScaledRectangleOnMousePosition(RectangleF rect, float currentZoom, PointF mousePosition)
{
var currentRect = GetScaledRect(imageRect, currentZoom);
if (!currentRect.Contains(mousePosition)) return rect;
float scaleRatio = currentRect.Width / GetScaledRect(rect, zoomFactor).Width;
PointF mouseOffset = new PointF(mousePosition.X - rect.X, mousePosition.Y - rect.Y);
PointF scaledOffset = new PointF(mouseOffset.X / scaleRatio, mouseOffset.Y / scaleRatio);
PointF position = new PointF(rect.X - (scaledOffset.X - mouseOffset.X),
rect.Y - (scaledOffset.Y - mouseOffset.Y));
rect.Location = position;
return rect;
}
#endregion
}
The simple PictureBoxEx custom control (modify and extend as needed):
This PictureBox is selectable, so it can be focused, with a Mouse click
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
[DesignerCategory("Code")]
public class PictureBoxEx : PictureBox
{
public PictureBoxEx() : this (new Size(200, 200)){ }
public PictureBoxEx(Size size) {
SetStyle(ControlStyles.Selectable | ControlStyles.UserMouse, true);
BorderStyle = BorderStyle.FixedSingle;
Size = size;
}
}
#Jimi: Thank you for the detailed information - very useful for visualizing the concepts involved in the graphics manipulations. I had arrived at a functioning solution (see code below) however, your code utilizes steps with greater efficiency. Admittedly, my code is developed with more of an intent to learn the mechanics of image manipulation - as I am still at the early part of the learning curve. Nonetheless, your illustration of the mechanics and techniques is extremely helpful.
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace ZoomImage
{
public partial class Form1 : Form
{
Image img;
Bitmap bmp;
float ang = 0;
float zoom = 1;
bool pan;
bool? ctr = false;
Point mcurrent;
PointF mouse;
PointF image;
PointF _image;
PointF rotate;
public Form1()
{
InitializeComponent();
MouseWheel += mouseWheel;
img = Image.FromFile(#"C:\testimage.jpg");
bmp = new Bitmap(img);
// Set initial scale to fit canvas window
float wRatio = (float)pbx.Width / (float)img.Width;
float hRatio = (float)pbx.Height / (float)img.Height;
zoom = Math.Min(wRatio, hRatio);
image.X = (pbx.Width - zoom * img.Width) / 2;
image.Y = (pbx.Height - zoom * img.Height) / 2;
}
private void label()
{
string _imgX = string.Format("{0:000}", image.X);
string _imgY = string.Format("{0:000}", image.Y);
lbl1.Text = "Location: " + _imgX + ", " + _imgY + "\r\nRotation: " + ang + "\r\nZoom: " + zoom + "\r\nMouse: " + mcurrent.X + ", " + mcurrent.Y;
}
private void btnRotate_Click(object sender, EventArgs e)
{
if (ModifierKeys == Keys.Control)
{
string msg = "Set center of rotation point:\r\n\nMove mouse to desired center ";
msg += "of rotation then hold \"Alt\" and left-click.\r\n\n";
msg += "To restore center of rotation to center of image:\r\n\nHold \"Shift\" and";
msg += " click \"Rotate\".";
MessageBox.Show(msg,"Change center of rotation");
ctr = null;
pbx.Focus();
return;
}
else if (ModifierKeys == Keys.Shift)
{
ctr = false;
return;
}
ang = ang == 270 ? 0 : ang += 90;
if (ang > 360) ang -= 360;
trackBar1.Value = (int)ang;
ctr = ctr == null ? false : ctr;
if (ctr == false) rotate = new PointF(img.Width / 2, img.Height / 2);
pbx.Invalidate();
}
private void trackBar1_Scroll(object sender, EventArgs e)
{
ang = trackBar1.Value;
if (ctr == false) rotate = new PointF(img.Width / 2, img.Height / 2);
pbx.Invalidate();
}
private void mouseWheel(object sender, MouseEventArgs e)
{
mouse = new PointF(e.X - image.X, e.Y - image.Y);
float zinc = 0.05f;
float zfac = 1 + zinc;
zoom = (float)(e.Delta > 0 ? zoom * (zfac) : zoom / (zfac));
// Adjust "img" (bitmap) orgin to maintain fixed focus # mouse location
if (e.Delta > 0)
{
image.X -= zinc * mouse.X;
image.Y -= zinc * mouse.Y;
}
else
{
image.X += (1 - 1 / (zfac)) * mouse.X;
image.Y += (1 - 1 / (zfac)) * mouse.Y;
}
image = new PointF(image.X, image.Y);
pbx.Invalidate();
}
private void mouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
if (ModifierKeys == Keys.Alt && ctr == null)
{
ctr = true;
rotate = new PointF((e.X - image.X) / zoom, (e.Y - image.Y) / zoom);
return;
}
pan = true;
mouse = e.Location;
_image = image;
}
private void mouseMove(object sender, MouseEventArgs e)
{
mcurrent = e.Location;
label();
if (e.Button != MouseButtons.Left || !pan) return;
image.X = _image.X + e.X - mouse.X;
image.Y = _image.Y + e.Y - mouse.Y;
image = new PointF(image.X, image.Y);
pbx.Invalidate();
}
private void mouseUp(object sender, MouseEventArgs e)
{
pan = false;
}
private void pbx_Paint(object sender, PaintEventArgs e)
{
label();
// Generate bitmap "bmp" - this can be saved as drawn...if deisred
bmp = new Bitmap(img.Width, img.Height);
using (Graphics g = Graphics.FromImage(bmp))
{
Matrix transform = new Matrix();
transform.Scale(zoom, zoom, MatrixOrder.Append);
transform.RotateAt(ang, rotate);
transform.Translate(image.X, image.Y, MatrixOrder.Append);
g.Transform = transform;
g.DrawImage(img, 0, 0);
}
e.Graphics.DrawImage(bmp, 0, 0);
}
}
}
Similar to Photoshop, where you can type in a ratio or in my case a certain dimention such as 800x600, I want to be able to force a Rectangle to be a fixed ratio/size when dragging the mouse..
At the moment I have this:
Which will crop an image using the Rectangle created from clicking and dragging on the PictureBox. The bounding box selects the area without any constraints. I want to be able to force the rectangle to be a certain ratio (prefereably from a set resolution) similar to the way Photoshop's cropping tool works.
My source if anyone needs more detail:
Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CropResize
{
public partial class Form1 : Form
{
private static string path;
public Form1(string filePath)
{
InitializeComponent();
path = filePath;
}
private Image _originalImage;
private Image _newImage;
private bool _selecting;
private Rectangle _selection;
private void Form1_Load(object sender, System.EventArgs e)
{
pictureBox1.Image = Image.FromFile(path);
if (pictureBox1.Image.Height > Screen.PrimaryScreen.Bounds.Height - 50 || pictureBox1.Image.Width > Screen.PrimaryScreen.Bounds.Width - 50)
{
if (pictureBox1.Image.Height > Screen.PrimaryScreen.Bounds.Height - 50)
{
Height = Screen.PrimaryScreen.Bounds.Height - 50;
panel1.Height = Size.Height - statusStrip1.Height - buttonSave.Height - 60;
}
if (pictureBox1.Image.Width > Screen.PrimaryScreen.Bounds.Width - 50)
{
Size = new Size(Screen.PrimaryScreen.Bounds.Width - 50, Screen.PrimaryScreen.Bounds.Height - 50);
panel1.Width = Size.Width - statusStrip1.Height - buttonSave.Height - 60;
}
pictureBox1.Image = pictureBox1.Image.Fit2PictureBox(pictureBox1);
panel1.Size = new Size(pictureBox1.Image.Width, pictureBox1.Image.Height);
}
Size = new Size(panel1.Size.Width + 50, panel1.Size.Height + buttonSave.Height + statusStrip1.Height + 80);
// Create a copy of the original image for later use
_originalImage = pictureBox1.Image.Clone() as Image;
_newImage = pictureBox1.Image.Clone() as Image;
}
private void buttonOrig_Click(object sender, System.EventArgs e)
{
pictureBox1.Image = _originalImage.Clone() as Image;
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
// Starting point of the selection:
if (e.Button == MouseButtons.Left)
{
pictureBox1.Image.Dispose();
pictureBox1.Image = _originalImage.Clone() as Image;
_selecting = true;
_selection = new Rectangle(new Point(e.X, e.Y), new Size());
}
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
// Update the actual size of the selection:
if (_selecting)
{
_selection.Width = (e.X - _selection.X);
_selection.Height = (e.Y - _selection.Y);
//int nGCD = GetGreatestCommonDivisor(1920, 1080);
//_selection.Width = _selection.Width / nGCD;
//_selection.Height = _selection.Height / nGCD;
int widthRatio = 16;
int heightRatio = 9;
if (_selection.Height * widthRatio <= _selection.Width)
{
_selection.Width = _selection.Height * widthRatio;
}
else if (_selection.Width * heightRatio <= _selection.Width)
{
_selection.Height = _selection.Width * heightRatio;
}
// Redraw the picturebox:
pictureBox1.Refresh();
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (_selecting && _selection.Height != 0)
{
// Draw a rectangle displaying the current selection
e.Graphics.DrawRectangle(Pens.WhiteSmoke, _selection);
//e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(50, Color.Gray)), 0, pictureBox1.Height - pictureBox1.Image.Height, pictureBox1.Image.Width, pictureBox1.Image.Height);
e.Graphics.SetClip(_selection, CombineMode.Exclude);
e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(100, Color.Black)), 0, 0, pictureBox1.Width, pictureBox1.Height);
int nGCD = GetGreatestCommonDivisor(_selection.Width, _selection.Height);
string str = string.Format("{0}:{1}", _selection.Width / nGCD, _selection.Height / nGCD);
toolStripStatusLabel1.Text = "Image Size: " + _selection.Width + "x" + _selection.Height + "px. Aspect Ratio: " + str;
}
}
public static int GetGreatestCommonDivisor(int height, int width)
{
return width == 0 ? height : GetGreatestCommonDivisor(width, height % width);
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left &&
_selecting &&
_selection.Size != new Size())
{
// Create cropped image:
_newImage = pictureBox1.Image.Crop(_selection);
_selecting = false;
try
{
// Set new image to the picturebox:
//pictureBox1.Image = _newImage.Fit2PictureBox(pictureBox1);
pictureBox1.Image = _newImage;
//toolStripStatusLabel1.Text = "Image Cropped.";
}
catch (Exception)
{ }
}
else
{
_selecting = false;
}
}
private void buttonResize_Click(object sender, EventArgs e)
{
pictureBox1.Image = ResizeImage(pictureBox1.Image, new Size(800, 600));
int nGCD = GetGreatestCommonDivisor(pictureBox1.Image.Width, pictureBox1.Image.Height);
string str = string.Format("{0}:{1}", pictureBox1.Image.Width / nGCD, pictureBox1.Image.Height / nGCD);
toolStripStatusLabel1.Text = "Image Resized to " + pictureBox1.Image.Width + "x" + pictureBox1.Image.Height + "px. Aspect Ratio: " + str;
}
public static Image ResizeImage(Image image, Size size, bool preserveAspectRatio = true)
{
int newWidth;
int newHeight;
if (preserveAspectRatio)
{
int originalWidth = image.Width;
int originalHeight = image.Height;
float percentWidth = size.Width / originalWidth;
float percentHeight = size.Height / originalHeight;
float percent = percentHeight < percentWidth ? percentHeight : percentWidth;
newWidth = (int)(originalWidth * percent);
newHeight = (int)(originalHeight * percent);
}
else
{
newWidth = size.Width;
newHeight = size.Height;
}
Image newImage = new Bitmap(newWidth, newHeight);
using (Graphics graphicsHandle = Graphics.FromImage(newImage))
{
graphicsHandle.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphicsHandle.DrawImage(image, 0, 0, newWidth, newHeight);
}
return newImage;
}
private void buttonSave_Click(object sender, EventArgs e)
{
string filename = path.Substring(path.LastIndexOf("\\") + 1);
string newPath = path.Substring(0, path.LastIndexOf(".") - 1) + "NEW.png";
toolStripStatusLabel1.Text = "Saving " + filename + " to " + newPath;
pictureBox1.Image.Save(newPath, ImageFormat.Png);
toolStripStatusLabel1.Text = filename + " saved to " + newPath;
}
}
}
Program.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CropResize
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1(args[0]));
}
public static Image SetImageWithinResolution(this Image image, PictureBox pictureBox)
{
//Bitmap bitmap = null;
if (image.Height > Screen.PrimaryScreen.Bounds.Height)
{
//ActiveForm.Size = new Size(100, 100);
Form.ActiveForm.Size = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
image = image.Fit2PictureBox(pictureBox);
//Bitmap bitmap = new Bitmap(image, );
}
if (image.Width > Screen.PrimaryScreen.Bounds.Width)
{
//ActiveForm.Size = new Size(100, 100);
Form.ActiveForm.Size = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
image = image.Fit2PictureBox(pictureBox);
//Bitmap bitmap = new Bitmap();
}
return image;
}
public static Image Crop(this Image image, Rectangle selection)
{
Bitmap bmp = image as Bitmap;
try
{
// Check if it is a bitmap:
if (bmp == null)
throw new ArgumentException("No valid bitmap");
// Crop the image:
Bitmap cropBmp = bmp.Clone(selection, bmp.PixelFormat);
// Release the resources:
image.Dispose();
return cropBmp;
}
catch (Exception)
{
return bmp;
}
}
public static Image Fit2PictureBox(this Image image, PictureBox picBox)
{
Bitmap bmp = null;
Graphics g;
// Scale:
double scaleY = (double)image.Width / picBox.Width;
double scaleX = (double)image.Height / picBox.Height;
double scale = scaleY < scaleX ? scaleX : scaleY;
// Create new bitmap:
bmp = new Bitmap(
(int)((double)image.Width / scale),
(int)((double)image.Height / scale));
// Set resolution of the new image:
bmp.SetResolution(
image.HorizontalResolution,
image.VerticalResolution);
// Create graphics:
g = Graphics.FromImage(bmp);
// Set interpolation mode:
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Draw the new image:
g.DrawImage(
image,
new Rectangle( // Destination
0, 0,
bmp.Width, bmp.Height),
new Rectangle( // Source
0, 0,
image.Width, image.Height),
GraphicsUnit.Pixel);
// Release the resources of the graphics:
g.Dispose();
// Release the resources of the origin image:
image.Dispose();
return bmp;
}
}
Maybe this example will help; is shows how to restrict a drawn rectangle to a given ratio:
float ratio = 0.33f;
Rectangle setRect()
{
int x = Math.Min(mDown.X, currPt.X);
int y = Math.Min(mDown.Y, currPt.Y);
int w = Math.Max(mDown.X, currPt.X) - x;
int h = Math.Max(mDown.Y, currPt.Y) - y;
if (ratio > 1) h = (int)(1f * w / ratio);
else w = (int)(1f * h * ratio);
return new Rectangle(x, y, w, h);
}
It uses two Points, one set in the MouseDown and one updated in the MouseMove.
It is up to you to integrate it with you programm; instead of painting all those pixels during MouseMove I would simply draw a rubberband rectangle on the surface of the control using the Paint event..
If you are scaling things you may want to switch to using all floats.
I am trying to create an application in C# (WinForms), something similar to this iOS Question
I managed to get part of it working, I can blur an image using this algorithm
Also I am able to draw a selection rectangle, I do not know if I am going wrong with the blurring or passing the rectangle. I have attached the file as shown below.
As seen, the blurring is outside the selection box.
I have pasted the code below:
// Start Rectangle
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
// Determine the initial rectangle coordinates...
RectStartPoint = e.Location;
Invalidate();
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left)
return;
Point tempEndPoint = e.Location;
Rect.Location = new Point(
Math.Min(RectStartPoint.X, tempEndPoint.X),
Math.Min(RectStartPoint.Y, tempEndPoint.Y));
Rect.Size = new Size(
Math.Abs(RectStartPoint.X - tempEndPoint.X),
Math.Abs(RectStartPoint.Y - tempEndPoint.Y));
pictureBox1.Invalidate();
}
// Draw Area
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
// Draw the rectangle...
if (pictureBox1.Image != null)
{
if (Rect != null && Rect.Width > 0 && Rect.Height > 0)
{
e.Graphics.DrawRectangle(selectionPen, Rect);
}
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
//Right now I am using right click as a call to blur
if (e.Button == MouseButtons.Right)
{
if (Rect.Contains(e.Location))
{
pictureBox1.Image = Blur(pictureBox1.Image, Rect, 5);
pictureBox1.Refresh();
}
}
}
private void blurPageToolStripMenuItem_Click(object sender, EventArgs e)
{
FullRect = new Rectangle(0, 0, pictureBox1.Image.Width, pictureBox1.Image.Height);
pictureBox1.Image = Blur(pictureBox1.Image, FullRect, 5);
}
private System.Drawing.Image Blur(System.Drawing.Image image, Rectangle rectangle, Int32 blurSize)
{
Bitmap blurred = new Bitmap(image); //image.Width, image.Height);
using (Graphics graphics = Graphics.FromImage(blurred))
{
// look at every pixel in the blur rectangle
for (Int32 xx = rectangle.Left; xx < rectangle.Right; xx += blurSize)
{
for (Int32 yy = rectangle.Top; yy < rectangle.Bottom; yy += blurSize)
{
Int32 avgR = 0, avgG = 0, avgB = 0;
Int32 blurPixelCount = 0;
Rectangle currentRect = new Rectangle(xx, yy, blurSize, blurSize);
// average the color of the red, green and blue for each pixel in the
// blur size while making sure you don't go outside the image bounds
for (Int32 x = currentRect.Left; (x < currentRect.Right && x < image.Width); x++)
{
for (Int32 y = currentRect.Top; (y < currentRect.Bottom && y < image.Height); y++)
{
Color pixel = blurred.GetPixel(x, y);
avgR += pixel.R;
avgG += pixel.G;
avgB += pixel.B;
blurPixelCount++;
}
}
avgR = avgR / blurPixelCount;
avgG = avgG / blurPixelCount;
avgB = avgB / blurPixelCount;
// now that we know the average for the blur size, set each pixel to that color
graphics.FillRectangle(new SolidBrush(Color.FromArgb(avgR, avgG, avgB)), currentRect);
}
}
graphics.Flush();
}
return blurred;
}
Another problem I am facing is, when the form is loaded initially, it starts in minimised mode, now if I use the selection (red rectangle), and then if I maximise the application the selected part of the picture is different.
Thank you for the help/suggestion in advance. If any links to a tool similar is around, please do share as I might have missed it. Thanks
You may be experiencing this issue because your image is stretched in the PictureBox. You can verify this is the issue by setting the SizeMode property of the PictureBox to Normal.
This is the sequence of events:
The selection rectangle is drawn.
The point/rectangle for the selection is determined.
The unstretched image is retrieved from the PictureBox, and is passed to the Blur method, with the calculated rectangle.
The unstretched image is blurred over the area described by the rectangle.
The unstretched image, now blurred, is assigned to the PictureBox.
The PictureBox stretches the image out to according to its SizeMode setting.
This makes the image appear to have blurred in a different location than what you selected.
The code you have looks at the selection rectangle, and uses those points to operate on the original image, not the stretched version of the image. If you want to blur the stretched image, you will need to get the stretched image first, then apply the blur to the rectangle selected on that image. Here is an example:
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
//Right now I am using right click as a call to blur
if (e.Button == MouseButtons.Right)
{
if (Rect.Contains(e.Location))
{
pictureBox1.Image = Blur(getPictureBoxImage(), Rect, 5);
pictureBox1.Refresh();
}
}
}
private Bitmap getPictureBoxImage()
{
Bitmap bmp = new Bitmap(pictureBox1.Width, pictureBox1.Height);
using (Graphics g = Graphics.FromImage(bmp))
{
g.DrawImage(pictureBox1.Image,
new Rectangle(0, 0, bmp.Width, bmp.Height));
}
return bmp;
}
Code for retrieving the stretched image is derived from this answer: https://stackoverflow.com/a/8702405/935052
I have drawn a rectangle. It is undreneath a horizontal scroll bar on screen. Now i want to zoom the rectangle. On zooming the height of rectangle increases, the location shifts up and horizontal scroll bar moves up. How to do this? I am writing this piece of code:
rect = new Rectangle(rect.Location.X, this.Height - rect.Height,rect.Width, Convert.ToInt32(rect.Size.Height * zoom));
g.FillRectangle(brush, rect);
This works for the location of the rectangle that is the rectangle shifts up but the height doesn't increase. Help!
If you simply want to scale the rectangle around the center of the rectangle then you need to increase the width and height of the rectangle and subtract half the increase from the location.
This is not tested, but should give you the general idea
double newHeight = oldHeight * scale;
double deltaY = (newHeight - oldHeight) * 0.5;
rect = new Rectangle(
rect.Location.X, (int)(rect.Location.Y - deltaY),
rect.Width, (int)newHeight);
Possibly a better alternative would be to look at using Graphics.ScaleTransform.
Just add a txtZoom to your form:
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.txtZoom.Text = "1";
this.txtZoom.KeyDown += new KeyEventHandler(txtZoom_KeyDown);
this.txtZoom_KeyDown(txtZoom, new KeyEventArgs(Keys.Enter));
}
void txtZoom_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyData == Keys.Enter)
{
this.Zoom = int.Parse(txtZoom.Text);
this.Invalidate();
}
}
public int Zoom { get; set; }
protected override void OnPaint(PaintEventArgs e)
{
GraphicsPath path = new GraphicsPath();
path.AddRectangle(new Rectangle(10, 10, 100, 100));
Matrix m = new Matrix();
m.Scale(Zoom, Zoom);
path.Transform(m);
this.AutoScrollMinSize = Size.Round(path.GetBounds().Size);
e.Graphics.FillPath(Brushes.Black, path);
}
}
}