如何裁剪具有平滑边框的图像的椭圆区域 [英] How to crop an elliptical region of an Image with smooth borders

查看:23
本文介绍了如何裁剪具有平滑边框的图像的椭圆区域的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的代码打开一个图像,调整它的大小,然后裁剪一个圆形区域.
我想要的是更平滑的边界,因为裁剪后的图像显示出粗糙的、非抗锯齿的边缘.

图片大小为60x60

我曾尝试使用 Graphics.SmoothingMode 属性,但没有成功.

到目前为止我的项目:

private void Recorte_Click(object sender, EventArgs e){OpenFileDialog open = new OpenFileDialog();//过滤图片文件open.Filter = "图片文件(*.jpg; *.jpeg; *.gif; *.bmp; *.png)|*.jpg; *.jpeg; *.gif; *.bmp; *.png";如果 (open.ShowDialog() == DialogResult.OK){//在图片框中显示图片PointF p = 新的 PointF(1, 1);Bitmap org = new Bitmap(open.FileName);图像 srcImage = Bitmap.FromFile(open.FileName);//在 60x60 中调整图像大小图像调整大小 = ResizeImage(srcImage, new Size(60, 60), false);MemoryStream memStream = new MemoryStream();//裁剪成圆形图像裁剪 = CropToCircle(resized,Color.Transparent);cropped.Save(@"....Cortada.png", System.Drawing.Imaging.ImageFormat.Png);pictureBox1.Image = 裁剪;}}public static Image CropToCircle(Image srcImage, Color backGround){Image dstImage = new Bitmap(srcImage.Width, srcImage.Height, srcImage.PixelFormat);图形 g = Graphics.FromImage(dstImage);使用 (Brush br = new SolidBrush(backGround)){g.FillRectangle(br, 0, 0, dstImage.Width, dstImage.Height);}浮动半径 = 25;PointF center = new Point(60, 60);GraphicsPath path = new GraphicsPath();path.AddEllipse(7, 7, radius * 2, radius * 2);g.SetClip(路径);g.SmoothingMode = SmoothingMode.AntiAlias;g.InterpolationMode = InterpolationMode.HighQualityBilinear;g.SmoothingMode = SmoothingMode.AntiAlias;g.DrawImage(srcImage, 0, 0);返回 dstImage;}public static Image ResizeImage(Image image, Size size, bool preserveAspectRatio = true){int newWidth;int newHeight;如果(保留纵横比){int originalWidth = image.Width;int originalHeight = image.Height;浮动百分比宽度 =(浮动)size.Width/(浮动)原始宽度;浮动百分比高度 =(浮动)大小.高度/(浮动)原始高度;浮动百分比=百分比高度<百分比宽度 ?百分比高度:百分比宽度;newWidth = (int)(originalWidth * percent);newHeight = (int)(originalHeight * percent);}别的{newWidth = size.Width;newHeight = size.Height;}图像 newImage = new Bitmap(newWidth, newHeight);使用 (Graphics graphicsHandle = Graphics.FromImage(newImage)){graphicsHandle.InterpolationMode = InterpolationMode.HighQualityBicubic;graphicsHandle.DrawImage(image, 0, 0, newWidth, newHeight);}返回新图像;}

解决方案

需要进行大量更改才能按预期工作:

  • 不要使用 Image.Fromfile():如果出于某种原因必须使用,请始终使用允许保留嵌入的颜色管理信息.既然您可以避免它,请采用此处显示的方法,使用 File.ReadAllBytes()MemoryStream(或 FileStreamFileStream>使用块).

  • private void Recorte_Click(object sender, EventArgs e){使用 (var ofd = new OpenFileDialog()) {ofd.Filter = "图像文件 (*.jpg; *.jpeg; *.gif; *.bmp; *.png)|*.jpg;*.jpeg;*.gif;*.bmp;*.png";ofd.RestoreDirectory = true;如果 (ofd.ShowDialog() != DialogResult.OK) 返回;图片裁剪Image = null;使用 (var sourceImage = Image.FromStream(new MemoryStream(File.ReadAllBytes(ofd.FileName))))使用 (var resizedImage = ResizeImage(sourceImage, new Size(100, 300), false)) {croppedImage = CropToCircle(resizedImage, Color.Transparent, Color.Turquoise);pictureBox1.Image?.Dispose();pictureBox1.Image =croppedImage;string destinationPath = Path.Combine(Application.StartupPath, @CroppedImagesCortada.png");croppedImage.Save(destinationPath, ImageFormat.Png);}}}

    CropToCircle 方法被重写,我添加了一个允许指定钢笔颜色的重载.
    然后将使用 Pen 在裁剪后的椭圆区域周围绘制边框.

    public static Image CropToCircle(Image srcImage, Color backColor){return CropToCircle(srcImage, backColor, Color.Transparent);}public static Image CropToCircle(Image srcImage, Color backColor, Color borderColor){var rect = new Rectangle(0, 0, srcImage.Width, srcImage.Height);varcroped = new Bitmap(srcImage.Width, srcImage.Height, PixelFormat.Format32bppArgb);使用 (var tBrush = new TextureBrush(srcImage))使用 (var pen = new Pen(borderColor, 2))使用 (var g = Graphics.FromImage(cropped)) {g.SmoothingMode = SmoothingMode.AntiAlias;if (backColor != Color.Transparent) g.Clear(backColor);g.FillEllipse(tBrush, rect);if (borderColor != Color.Transparent) {rect.Inflate(-1, -1);g.DrawEllipse(pen, rect);}返回裁剪;}}

    简化了 ResizeImage 方法.

    public static Image ResizeImage(Image image, Size newSize, bool preserveAspectRatio = true){浮动比例 = Math.Max(newSize.Width, newSize.Height)/(float)Math.Max(image.Width, image.Height);大小图像大小 = 保留纵横比?Size.Round(new SizeF(image.Width * scale, image.Height * scale)):新尺寸;var resizedImage = new Bitmap(imageSize.Width, imageSize.Height);使用 (var g = Graphics.FromImage(resizedImage)) {g.PixelOffsetMode = PixelOffsetMode.Half;g.InterpolationMode = InterpolationMode.HighQualityBicubic;g.DrawImage(image, 0, 0, imageSize.Width, imageSize.Height);}返回调整大小的图像;}

    My code opens an Image, resizes it, then crops a circular region.
    What I want is smoother borders, since the cropped Image shows rough, non antialiased, edges.

    The image size is 60x60

    I have tried to use the Graphics.SmoothingMode property but without success.

    What I have so far in my project:

    private void Recorte_Click(object sender, EventArgs e)
    {
        OpenFileDialog open = new OpenFileDialog();
        // Filter for image files
        open.Filter = "Image Files(*.jpg; *.jpeg; *.gif; *.bmp; *.png)|*.jpg; *.jpeg; *.gif; *.bmp; *.png";
        if (open.ShowDialog() == DialogResult.OK)
        {
            // display image in picture box 
            PointF p = new PointF(1, 1);
            Bitmap org = new Bitmap(open.FileName);
            Image srcImage = Bitmap.FromFile(open.FileName);
            // Resize image in 60x60
            Image resized = ResizeImage(srcImage, new Size(60, 60), false);
            MemoryStream memStream = new MemoryStream();
            // Crop in round shape
            Image cropped = CropToCircle(resized,Color.Transparent);
            cropped.Save(@"....Cortada.png", System.Drawing.Imaging.ImageFormat.Png);
            pictureBox1.Image = cropped;
        }
    }
    
    public static Image CropToCircle(Image srcImage, Color backGround)
    {
        Image dstImage = new Bitmap(srcImage.Width, srcImage.Height, srcImage.PixelFormat);
        Graphics g = Graphics.FromImage(dstImage);
        using (Brush br = new SolidBrush(backGround))
        {
            g.FillRectangle(br, 0, 0, dstImage.Width, dstImage.Height);
        }
        float radius = 25;
        PointF center = new Point(60, 60);
        GraphicsPath path = new GraphicsPath();
        path.AddEllipse(7, 7, radius * 2, radius * 2);
        g.SetClip(path);
        g.SmoothingMode = SmoothingMode.AntiAlias;
        g.InterpolationMode = InterpolationMode.HighQualityBilinear;
        g.SmoothingMode = SmoothingMode.AntiAlias;
        g.DrawImage(srcImage, 0, 0);
    
        return dstImage;
    }
    
    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 = (float)size.Width / (float)originalWidth;
            float percentHeight = (float)size.Height / (float)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;
    }
    

    解决方案

    Quite a few changes are required to get this working as expected:

    • Don't use Image.Fromfile(): if you have to for some reason, always use the overload (string, bool) that allows to preserve the embedded color management information. Since you can avoid it, adopt the method shown here, using File.ReadAllBytes() and a MemoryStream (or a FileStream in a using block).

    • Graphics.SetClip() doesn't allow anti-aliasing. This also applies to Regions (without further adjustments, at least). Here, I'm using a TextureBrush, a special brush build from a Bitmap (your resized Bitmap), which is then used to fill the ellipse that crops the resized image.

    • Disposing of the disposable objects you create is quite important, especially when you deal with graphics objects. Of course you need to dispose of all the disposable objects you create (objects that provide a Dispose() method). This includes the OpenFileDialog object.

    • Don't use paths in this form: @"....Image.png": this path won't exist (or it will not be inaccessible, or simply wrong) when you move your executable somewhere else.
      Always use Path.Combine() (as shown here) to build a full path.
      The example here saves the Image inside a CroppedImages subfolder in the executable path.
      This topic is quite broad, though (e.g., you may not be allowed to store data in the executable path, so you may need to use a dedicated path in the User AppData folder or in the ProgramData directory).

    • All calculations need to be revisited, take a look t what I posted here.

    private void Recorte_Click(object sender, EventArgs e)
    {
        using (var ofd = new OpenFileDialog()) {
            ofd.Filter = "Image Files (*.jpg; *.jpeg; *.gif; *.bmp; *.png)|*.jpg; *.jpeg; *.gif; *.bmp; *.png";
            ofd.RestoreDirectory = true;
            if (ofd.ShowDialog() != DialogResult.OK) return;
    
            Image croppedImage = null;
            using (var sourceImage = Image.FromStream(new MemoryStream(File.ReadAllBytes(ofd.FileName)))) 
            using (var resizedImage = ResizeImage(sourceImage, new Size(100, 300), false)) {
                croppedImage = CropToCircle(resizedImage, Color.Transparent, Color.Turquoise);
                pictureBox1.Image?.Dispose();
                pictureBox1.Image = croppedImage;
                string destinationPath = Path.Combine(Application.StartupPath, @"CroppedImagesCortada.png");
                croppedImage.Save(destinationPath, ImageFormat.Png);
            }
        }
    }
    

    The CropToCircle method is rewritten and I've added an overload that alllows to specify a Pen Color.
    The Pen will then be used to draw a border around the cropped elliptical region.

    public static Image CropToCircle(Image srcImage, Color backColor)
    {
        return CropToCircle(srcImage, backColor, Color.Transparent);
    }
    
    public static Image CropToCircle(Image srcImage, Color backColor, Color borderColor)
    {
        var rect = new Rectangle(0, 0, srcImage.Width, srcImage.Height);
        var cropped = new Bitmap(srcImage.Width, srcImage.Height, PixelFormat.Format32bppArgb);
        using (var tBrush = new TextureBrush(srcImage))
        using (var pen = new Pen(borderColor, 2))
        using (var g = Graphics.FromImage(cropped)) {
            g.SmoothingMode = SmoothingMode.AntiAlias;
            if (backColor != Color.Transparent) g.Clear(backColor);
            g.FillEllipse(tBrush, rect);
            if (borderColor != Color.Transparent) {
                rect.Inflate(-1, -1);
                g.DrawEllipse(pen, rect);
            }
            return cropped;
        }
    }
    

    The ResizeImage method is simplified.

    • The scale ratio, when used, takes the maximum value of new Size specified and resizes the Image to fit this Size boundaries.
    • The Graphics PixelOffsetMode is set to PixelOffsetMode.Half. The notes here explain why.

    public static Image ResizeImage(Image image, Size newSize, bool preserveAspectRatio = true)
    {
        float scale = Math.Max(newSize.Width, newSize.Height) / (float)Math.Max(image.Width, image.Height);
        Size imageSize = preserveAspectRatio 
                       ? Size.Round(new SizeF(image.Width * scale, image.Height * scale)) 
                       : newSize;
    
        var resizedImage = new Bitmap(imageSize.Width, imageSize.Height);
        using (var g = Graphics.FromImage(resizedImage)) {
            g.PixelOffsetMode = PixelOffsetMode.Half;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g.DrawImage(image, 0, 0, imageSize.Width, imageSize.Height);
        }
        return resizedImage;
    }
    

    这篇关于如何裁剪具有平滑边框的图像的椭圆区域的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆