如何在光标位置剪裁图像的一部分?

3
我正在按照链接进行图片剪裁和圆角处理。但是它并不按照我想要的方式工作。我花了一些时间,但是还没有明白在哪里修改代码才能得到我想要的结果。
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;    
}

我想在给定位置下方展示图片,并将其置于图片中央,具体操作如下:

enter image description here

但是目前当我改变X,Y坐标时,裁剪后的图像会移动,我希望圆形图像仍然位于中心。

enter image description here

我在哪里犯了错?我感觉 g.DrawImage(srcImage, 0, 0) 很可能是罪魁祸首。
有什么想法吗?


1
你从哪里获取鼠标坐标?你有两个PictureBox控件,一个用于显示原始图像,另一个用于显示结果吗?SizeMode是什么? - Jimi
1
PB是什么?一个PictureBox吗?如果是的话:它有什么Sizemode?如果不是Normal,你需要转换/缩放鼠标坐标。 - TaW
1个回答

2
当您使用控件作为图像的容器,并且图像被缩放以适应容器的边界(例如,将PictureBox.SizeMode设置为PictureBoxSizeMode.Zoom),以便在UI中显示具有预定义尺寸的图像时,当您需要选择图像的一部分时,您需要计算比例因子。换句话说,确定容器大小与图像实际大小之间的比率。
最好使用较小的容器作为参考,这样您就可以通过比例因子乘以而不是除以相对度量:
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));
}

为了确定镜头(选择)尺寸与位图实际尺寸的实际比例,当需要绘制或剪切位图的一部分时,必须对镜头尺寸进行缩放。"最初的回答"
private SizeF GetScaledLensSize(RectangleF canvas, SizeF imageSize, SizeF lensSize)
{
    float scaleRatio = GetImageScaledRatio(canvas, imageSize);
    return new SizeF(lensSize.Width * scaleRatio, lensSize.Width * scaleRatio);
}

此外,在展示由镜头代表的当前选择的预览时,选择需要缩放到用于预览镜头选择的容器的大小:

原始答案: Original Answer

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));
}

这些简单的方法可以计算选择区域相对于图像的实际大小,还可以计算用于预览的控件的大小。在使用镜头选择绘制预览时,最好使用一种通用的方法来绘制图像部分:这种方法也可以用于在新位图中绘制选择区域,然后将其保存到磁盘或其他存储设备中。
在此处,pctLens是用于预览的PictureBox,RectangleF section是重新缩放为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,PictureBox:300x175SizeMode: Zoom

Image Lens preview

完整的源代码可以复制动画所示:

Bitmap sourceImage 是原始的位图,必须设置为现有对象。
RectangleF imageLens 是用于定义相对镜头大小的形状。
Size lensPixelSizeimageLens在UI表示中以像素为单位的大小。
pctOriginal 是显示原始图像的PictureBox。
pctLens 是绘制镜头部分预览的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();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接