简体   繁体   中英

How to crop an elliptical region of an Image with smooth borders

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 (eg, 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.

裁剪的圆形图像 1 裁剪的圆形图像 2 裁剪的圆形图像 4 裁剪的圆形图像 3

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, @"CroppedImages\Cortada.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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM