如何在光标位置裁剪图像的一部分?
How to crop a section of an Image at cursor position?
我正在按照 对图像进行裁剪和圆角处理。但是它不能按我想要的方式工作。我花了一些时间,但不明白在哪里修复代码以获得我想要的。
public Image CropToCircle(Image srcImage, PointF center, float radius, Color backGround)
{
Image dstImage = new Bitmap((int)Math.Round(Math.Ceiling(radius*2)), (int)Math.Round(Math.Ceiling(radius*2)), srcImage.PixelFormat);
using (Graphics g = Graphics.FromImage(dstImage))
{
RectangleF r = new RectangleF(center.X - radius, center.Y - radius, 2*radius, 2 * radius);
using (Brush br = new SolidBrush(backGround))
{
g.FillRectangle(br, 0, 0, dstImage.Width, dstImage.Height);
}
GraphicsPath path = new GraphicsPath();
path.AddEllipse(r);
g.SetClip(path);
g.DrawImage(srcImage, 0, 0);
return dstImage;
}
}
dstImage - 应在给定光标位置显示主图像的裁剪图像。
以上代码工作正常,但输出图像位置随 X、Y 坐标移动。我想要的是始终在光标位置下方的主图像中显示 100x100 正方形图像。 (就像镜头在图像上移动一样)
这就是我调用函数的方式
private void drawWindows(Point mousePoint)
{
Image RoundedImage = CropToCircle(StartImage, new PointF(mousePoint.X, mousePoint.Y), 75, Color.FromArgb(0, 101, 167));
PB.Image = RoundedImage;
}
我想在图像中心的给定位置下显示图像,如下所示:
但是当我更改 X,Y 坐标时,当前裁剪的图像会向内移动。我希望圆形图像仍然位于中心。
我哪里出错了?我觉得 g.DrawImage(srcImage, 0, 0)
可能是罪魁祸首。
有什么想法吗?
当您使用控件作为图像的容器并且图像被缩放以适合容器的边界(例如,设置 PictureBox.SizeMode to PictureBoxSizeMode.Zoom)以便图像可以显示在 UI使用预定义的度量,当您需要 select 图像的一部分时,您需要计算比例因子。换句话说,确定容器大小与图像实际大小之间的比率。
使用较小的容器作为参考可能更好,因此您可以将相对度量值乘以而不是除以比例:
private float GetImageScaledRatio(RectangleF canvas, SizeF imageSize)
{
return Math.Max(canvas.Width, canvas.Height) /
Math.Max(imageSize.Width, imageSize.Height);
}
镜头在容器内的位置 - 如果您希望镜头跟随鼠标指针的位置 - 由指针坐标减去镜头尺寸的一半给出:
private PointF GetLensPosition(PointF centerPosition, RectangleF lens)
{
return new PointF(centerPosition.X - (lens.Width / 2),
centerPosition.Y - (lens.Height / 2));
}
为了确定 Lens(selection)尺寸相对于 Bitmap 实际尺寸的实际尺寸,当需要绘制 Bitmap 的一部分时,必须缩放 Lens 尺寸或否则剪裁:
private SizeF GetScaledLensSize(RectangleF canvas, SizeF imageSize, SizeF lensSize)
{
float scaleRatio = GetImageScaledRatio(canvas, imageSize);
return new SizeF(lensSize.Width * scaleRatio, lensSize.Width * scaleRatio);
}
此外,当显示当前镜头所代表的select离子的预览时,select离子需要缩放到用于预览镜头的容器的大小select离子:
private RectangleF CanvasToImageRect(RectangleF canvas, SizeF imageSize, RectangleF rect)
{
float scaleRatio = GetImageScaledRatio(canvas, imageSize);
return new RectangleF(new PointF(rect.X / scaleRatio, rect.Y / scaleRatio),
new SizeF(rect.Width / scaleRatio, rect.Height / scaleRatio));
}
这些简单的方法允许计算 selection 相对于所考虑图像的实际大小以及用于预览的控件的大小。
使用Lens selection绘制预览图时,最好使用常用方法绘制Image部分:也可用于绘制selection 在一个新的位图中,然后可以将其保存到光盘或以其他方式存储。
这里,pctLens
是用于预览的PictureBox,RectangleF section
是缩放后的Lens measure pctLens
大小(用于预览),当然 sourceImage
是原始图像:
private void pctLens_Paint(object sender, PaintEventArgs e)
{
RectangleF section = CanvasToImageRect(pctOriginal.ClientRectangle, sourceImage.Size, imageLens);
DrawImageSelection(e.Graphics, pctLens.ClientRectangle, section, sourceImage);
}
private void DrawImageSelection(Graphics g, RectangleF canvas, RectangleF imageSection, Image image)
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(image, canvas, imageSection, GraphicsUnit.Pixel);
switch (lensType)
{
case LensType.Circular:
using (var path = new GraphicsPath())
{
path.AddEllipse(canvas);
g.SetClip(path, CombineMode.Exclude);
using (var brush = new SolidBrush(Color.FromArgb(160, Color.Black)))
{
g.FillRectangle(brush, canvas);
g.ResetClip();
using (var pen = new Pen(brush, 1f))
g.DrawEllipse(pen, canvas);
}
}
break;
case LensType.Rectangular:
// NOP
break;
}
}
视觉效果(图像:1200x675
,图片框:300x175
,SizeMode: Zoom
)
重现动画中显示内容的完整源代码:
Bitmap sourceImage
为原始Bitmap,必须设置为已有对象。
RectangleF imageLens
是用来定义相对镜头尺寸的形状。
Size lensPixelSize
是 imageLens
的像素大小,相对于 UI 表示。
pctOriginal
是显示原始图像的 PictureBox。
pctLens
是画Lens部分preview的PictureBox。
Bitmap sourceImage = null;
RectangleF imageLens = RectangleF.Empty;
Size lensPixelSize = new Size(100, 100);
LensType lensType = LensType.Circular;
bool lensUseRelativeSize = false;
bool drawLens = false;
private enum LensType
{
Circular,
Rectangular
}
private void pctOriginal_MouseMove(object sender, MouseEventArgs e)
{
imageLens.Location = GetLensPosition(e.Location, imageLens);
imageLens.Size = lensUseRelativeSize
? GetScaledLensSize(pctOriginal.ClientRectangle, sourceImage.Size, lensPixelSize)
: lensPixelSize;
pctOriginal.Invalidate();
pctLens.Invalidate();
}
private PointF GetLensPosition(PointF centerPosition, RectangleF rect)
{
return new PointF(centerPosition.X - (rect.Width / 2),
centerPosition.Y - (rect.Height / 2));
}
private SizeF GetScaledLensSize(RectangleF canvas, SizeF imageSize, SizeF lensSize)
{
float scaleRatio = GetImageScaledRatio(canvas, imageSize);
return new SizeF(lensSize.Width * scaleRatio, lensSize.Width * scaleRatio);
}
private float GetImageScaledRatio(RectangleF canvas, SizeF imageSize)
{
return Math.Max(canvas.Width, canvas.Height) /
Math.Max(imageSize.Width, imageSize.Height);
}
private RectangleF CanvasToImageRect(RectangleF canvas, SizeF imageSize, RectangleF rect)
{
float scaleRatio = GetImageScaledRatio(canvas, imageSize);
return new RectangleF(new PointF(rect.X / scaleRatio, rect.Y / scaleRatio),
new SizeF(rect.Width / scaleRatio, rect.Height / scaleRatio));
}
private void pctOriginal_Paint(object sender, PaintEventArgs e)
{
using (Pen pen = new Pen(Color.Red, 2.0f))
{
pen.DashStyle = DashStyle.Dash;
switch (lensType)
{
case LensType.Circular:
e.Graphics.DrawEllipse(pen, Rectangle.Round(imageLens));
break;
case LensType.Rectangular:
e.Graphics.DrawRectangle(pen, Rectangle.Round(imageLens));
break;
}
}
}
private void pctLens_Paint(object sender, PaintEventArgs e)
{
if (!drawLens) return;
RectangleF section = CanvasToImageRect(pctOriginal.ClientRectangle, sourceImage.Size, imageLens);
DrawImageSelection(e.Graphics, pctLens.ClientRectangle, section, sourceImage);
}
private void DrawImageSelection(Graphics g, RectangleF canvas, RectangleF imageSection, Image image)
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(image, canvas, imageSection, GraphicsUnit.Pixel);
switch (lensType)
{
case LensType.Circular:
using (var path = new GraphicsPath())
{
path.AddEllipse(canvas);
g.SetClip(path, CombineMode.Exclude);
using (var brush = new SolidBrush(Color.FromArgb(160, Color.Black)))
{
g.FillRectangle(brush, canvas);
g.ResetClip();
using (var pen = new Pen(brush, 1f))
g.DrawEllipse(pen, canvas);
}
}
break;
case LensType.Rectangular:
// NOP
break;
}
}
private void chkSizeRelative_CheckedChanged(object sender, EventArgs e)
=> lensUseRelativeSize = chkSizeRelative.Checked;
private void radLensType_CheckedChanged(object sender, EventArgs e)
=> lensType = (LensType)(int.Parse((sender as Control).Tag.ToString()));
private void pctOriginal_MouseEnter(object sender, EventArgs e)
=> drawLens = true;
private void pctOriginal_MouseLeave(object sender, EventArgs e)
{
drawLens = false;
pctLens.Invalidate();
}
我正在按照
public Image CropToCircle(Image srcImage, PointF center, float radius, Color backGround)
{
Image dstImage = new Bitmap((int)Math.Round(Math.Ceiling(radius*2)), (int)Math.Round(Math.Ceiling(radius*2)), srcImage.PixelFormat);
using (Graphics g = Graphics.FromImage(dstImage))
{
RectangleF r = new RectangleF(center.X - radius, center.Y - radius, 2*radius, 2 * radius);
using (Brush br = new SolidBrush(backGround))
{
g.FillRectangle(br, 0, 0, dstImage.Width, dstImage.Height);
}
GraphicsPath path = new GraphicsPath();
path.AddEllipse(r);
g.SetClip(path);
g.DrawImage(srcImage, 0, 0);
return dstImage;
}
}
dstImage - 应在给定光标位置显示主图像的裁剪图像。
以上代码工作正常,但输出图像位置随 X、Y 坐标移动。我想要的是始终在光标位置下方的主图像中显示 100x100 正方形图像。 (就像镜头在图像上移动一样)
这就是我调用函数的方式
private void drawWindows(Point mousePoint)
{
Image RoundedImage = CropToCircle(StartImage, new PointF(mousePoint.X, mousePoint.Y), 75, Color.FromArgb(0, 101, 167));
PB.Image = RoundedImage;
}
我想在图像中心的给定位置下显示图像,如下所示:
但是当我更改 X,Y 坐标时,当前裁剪的图像会向内移动。我希望圆形图像仍然位于中心。
我哪里出错了?我觉得 g.DrawImage(srcImage, 0, 0)
可能是罪魁祸首。
有什么想法吗?
当您使用控件作为图像的容器并且图像被缩放以适合容器的边界(例如,设置 PictureBox.SizeMode to PictureBoxSizeMode.Zoom)以便图像可以显示在 UI使用预定义的度量,当您需要 select 图像的一部分时,您需要计算比例因子。换句话说,确定容器大小与图像实际大小之间的比率。
使用较小的容器作为参考可能更好,因此您可以将相对度量值乘以而不是除以比例:
private float GetImageScaledRatio(RectangleF canvas, SizeF imageSize)
{
return Math.Max(canvas.Width, canvas.Height) /
Math.Max(imageSize.Width, imageSize.Height);
}
镜头在容器内的位置 - 如果您希望镜头跟随鼠标指针的位置 - 由指针坐标减去镜头尺寸的一半给出:
private PointF GetLensPosition(PointF centerPosition, RectangleF lens)
{
return new PointF(centerPosition.X - (lens.Width / 2),
centerPosition.Y - (lens.Height / 2));
}
为了确定 Lens(selection)尺寸相对于 Bitmap 实际尺寸的实际尺寸,当需要绘制 Bitmap 的一部分时,必须缩放 Lens 尺寸或否则剪裁:
private SizeF GetScaledLensSize(RectangleF canvas, SizeF imageSize, SizeF lensSize)
{
float scaleRatio = GetImageScaledRatio(canvas, imageSize);
return new SizeF(lensSize.Width * scaleRatio, lensSize.Width * scaleRatio);
}
此外,当显示当前镜头所代表的select离子的预览时,select离子需要缩放到用于预览镜头的容器的大小select离子:
private RectangleF CanvasToImageRect(RectangleF canvas, SizeF imageSize, RectangleF rect)
{
float scaleRatio = GetImageScaledRatio(canvas, imageSize);
return new RectangleF(new PointF(rect.X / scaleRatio, rect.Y / scaleRatio),
new SizeF(rect.Width / scaleRatio, rect.Height / scaleRatio));
}
这些简单的方法允许计算 selection 相对于所考虑图像的实际大小以及用于预览的控件的大小。
使用Lens selection绘制预览图时,最好使用常用方法绘制Image部分:也可用于绘制selection 在一个新的位图中,然后可以将其保存到光盘或以其他方式存储。
这里,pctLens
是用于预览的PictureBox,RectangleF section
是缩放后的Lens measure pctLens
大小(用于预览),当然 sourceImage
是原始图像:
private void pctLens_Paint(object sender, PaintEventArgs e)
{
RectangleF section = CanvasToImageRect(pctOriginal.ClientRectangle, sourceImage.Size, imageLens);
DrawImageSelection(e.Graphics, pctLens.ClientRectangle, section, sourceImage);
}
private void DrawImageSelection(Graphics g, RectangleF canvas, RectangleF imageSection, Image image)
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(image, canvas, imageSection, GraphicsUnit.Pixel);
switch (lensType)
{
case LensType.Circular:
using (var path = new GraphicsPath())
{
path.AddEllipse(canvas);
g.SetClip(path, CombineMode.Exclude);
using (var brush = new SolidBrush(Color.FromArgb(160, Color.Black)))
{
g.FillRectangle(brush, canvas);
g.ResetClip();
using (var pen = new Pen(brush, 1f))
g.DrawEllipse(pen, canvas);
}
}
break;
case LensType.Rectangular:
// NOP
break;
}
}
视觉效果(图像:1200x675
,图片框:300x175
,SizeMode: Zoom
)
重现动画中显示内容的完整源代码:
Bitmap sourceImage
为原始Bitmap,必须设置为已有对象。
RectangleF imageLens
是用来定义相对镜头尺寸的形状。
Size lensPixelSize
是 imageLens
的像素大小,相对于 UI 表示。
pctOriginal
是显示原始图像的 PictureBox。
pctLens
是画Lens部分preview的PictureBox。
Bitmap sourceImage = null;
RectangleF imageLens = RectangleF.Empty;
Size lensPixelSize = new Size(100, 100);
LensType lensType = LensType.Circular;
bool lensUseRelativeSize = false;
bool drawLens = false;
private enum LensType
{
Circular,
Rectangular
}
private void pctOriginal_MouseMove(object sender, MouseEventArgs e)
{
imageLens.Location = GetLensPosition(e.Location, imageLens);
imageLens.Size = lensUseRelativeSize
? GetScaledLensSize(pctOriginal.ClientRectangle, sourceImage.Size, lensPixelSize)
: lensPixelSize;
pctOriginal.Invalidate();
pctLens.Invalidate();
}
private PointF GetLensPosition(PointF centerPosition, RectangleF rect)
{
return new PointF(centerPosition.X - (rect.Width / 2),
centerPosition.Y - (rect.Height / 2));
}
private SizeF GetScaledLensSize(RectangleF canvas, SizeF imageSize, SizeF lensSize)
{
float scaleRatio = GetImageScaledRatio(canvas, imageSize);
return new SizeF(lensSize.Width * scaleRatio, lensSize.Width * scaleRatio);
}
private float GetImageScaledRatio(RectangleF canvas, SizeF imageSize)
{
return Math.Max(canvas.Width, canvas.Height) /
Math.Max(imageSize.Width, imageSize.Height);
}
private RectangleF CanvasToImageRect(RectangleF canvas, SizeF imageSize, RectangleF rect)
{
float scaleRatio = GetImageScaledRatio(canvas, imageSize);
return new RectangleF(new PointF(rect.X / scaleRatio, rect.Y / scaleRatio),
new SizeF(rect.Width / scaleRatio, rect.Height / scaleRatio));
}
private void pctOriginal_Paint(object sender, PaintEventArgs e)
{
using (Pen pen = new Pen(Color.Red, 2.0f))
{
pen.DashStyle = DashStyle.Dash;
switch (lensType)
{
case LensType.Circular:
e.Graphics.DrawEllipse(pen, Rectangle.Round(imageLens));
break;
case LensType.Rectangular:
e.Graphics.DrawRectangle(pen, Rectangle.Round(imageLens));
break;
}
}
}
private void pctLens_Paint(object sender, PaintEventArgs e)
{
if (!drawLens) return;
RectangleF section = CanvasToImageRect(pctOriginal.ClientRectangle, sourceImage.Size, imageLens);
DrawImageSelection(e.Graphics, pctLens.ClientRectangle, section, sourceImage);
}
private void DrawImageSelection(Graphics g, RectangleF canvas, RectangleF imageSection, Image image)
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(image, canvas, imageSection, GraphicsUnit.Pixel);
switch (lensType)
{
case LensType.Circular:
using (var path = new GraphicsPath())
{
path.AddEllipse(canvas);
g.SetClip(path, CombineMode.Exclude);
using (var brush = new SolidBrush(Color.FromArgb(160, Color.Black)))
{
g.FillRectangle(brush, canvas);
g.ResetClip();
using (var pen = new Pen(brush, 1f))
g.DrawEllipse(pen, canvas);
}
}
break;
case LensType.Rectangular:
// NOP
break;
}
}
private void chkSizeRelative_CheckedChanged(object sender, EventArgs e)
=> lensUseRelativeSize = chkSizeRelative.Checked;
private void radLensType_CheckedChanged(object sender, EventArgs e)
=> lensType = (LensType)(int.Parse((sender as Control).Tag.ToString()));
private void pctOriginal_MouseEnter(object sender, EventArgs e)
=> drawLens = true;
private void pctOriginal_MouseLeave(object sender, EventArgs e)
{
drawLens = false;
pctLens.Invalidate();
}