问题描述
问题:尝试使用Paint事件中的转换将鼠标(或鼠标位置)上的图像缩放(缩放),将位图原点转换为鼠标位置,然后缩放图像并将其原点返回.
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.
相关代码块:
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.
- 划分并征服:将不同的图形效果和转换拆分为可做一件事情的不同专业方法.然后以一种使这种方法在需要时可以一起工作的方式进行设计.
-
保持简单:当Graphics对象需要累积多个转换时,矩阵的堆叠顺序可能会引起误解.预先计算一些通用转换(主要是平移和缩放),然后让GDI +渲染已经预煮的对象和形状更简单(更容易产生 weird 结果) .
在这里,只有 Matrix.RotateAt 和 Matrix.Multiply .
此处有关矩阵转换的一些说明:翻转GraphicsPath
- 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 this methods to work together when needed.
Keep it simple: when Graphics objects need to accumulate more than a couple 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 tranformations here: Flip the GraphicsPath
使用正确的工具:例如,用作 canvas 的面板并不是最佳选择.此控件不是双缓冲的;可以启用此功能,但Panel并非用于绘图,而PictureBox(或平面Label)则单独支持它.
这里还有一些注意事项:如何对图像应用淡入淡出过渡效果
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 not meant for drawing, while a PictureBox (or a flat Label) supports it on its own.
Some more notes here: How to apply a fade transition effect to Images
该示例代码显示了4种缩放方法,还生成了旋转变换(可并排使用,不会累加).
使用枚举器( private enum ZoomMode
)选择缩放模式:
The sample code shows 4 zoom methods, plus generates rotation transformations (which works side-by-side, don't accumulate).
The Zoom modes are selected using an enumerator (private enum ZoomMode
):
缩放模式:
-
ImageLocation
:图像缩放在原位执行 ,将画布上的当前位置保持在固定位置. -
CenterCanvas
:在缩放图像时,它仍在画布"上居中. -
CenterMouse
:图像经过缩放和平移,以使其自身在画布"上当前鼠标的位置上居中. -
MouseOffset
:缩放并平移图像,以保持相对位置,该相对位置由鼠标指针在图像本身上的初始位置确定.
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.
您会注意到,该代码简化了所有计算,仅相对于定义当前Image边界的Rectangle且仅与此Shape的位置有关地应用平移.
仅在计算需要抢先确定在鼠标滚轮生成下一个缩放系数后 后将要显示的图像大小时,才缩放矩形.
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.
已实现功能的直观示例:
示例代码:
-
canvas
是自定义控件,从PictureBox派生而来(您可以在底部找到其定义).此控件通过代码添加到表单中.根据需要进行修改. -
trkRotationAngle
是用于定义图像当前旋转的TrackBar.将此控件添加到设计器中的窗体. -
radZoom_CheckedChanged
是用于设置当前缩放模式的所有RadioButton的事件处理程序.这些控件集的值在其Tag
属性中分配.将这些控件添加到设计器中的窗体"中.
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 defined 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 theirTag
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 += this.canvas_MouseWheel;
canvas.MouseMove += this.canvas_MouseMove;
canvas.MouseDown += this.canvas_MouseDown;
canvas.MouseUp += this.canvas_MouseUp;
canvas.Paint += this.canvas_Paint;
this.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
}
简单的PictureBoxEx
自定义控件(根据需要修改和扩展):
The simple PictureBoxEx
custom control (modify and extend as needed):
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);
this.BorderStyle = BorderStyle.FixedSingle;
this.Size = size;
}
}
这篇关于从鼠标位置缩放和平移图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!