Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
286 views
in Technique[技术] by (71.8m points)

c# - 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 i want the image i has getting

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;
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.

Cropped Rounded Image 1 Cropped Rounded Image 2 Cropped Rounded Image 4 Cropped Rounded Image 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, @"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;
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...