Scaling a System.Drawing.Bitmap to a given size while maintaining aspect ratio
Asked Answered
L

3

81

I want to scale a System.Drawing.Bitmap to at least less than some fixed width and height. This is to generate thumbnails for an image gallery on a website, so I want to keep the aspect ratio the same.

I have some across quite a few solutions but none seem to really do what I need; they revolve around scaling based on keeping the width or the height the same but not changing both.

An example:

If I have a 4272 by 2848 image and I want to scale it to a size of 1024 by 768, then the resulting image should be 1024 by 683 and padded (with a black border) to 1024 by 768.

How can I do this with images larger than the required size and smaller than the require sized and also pad images which don't come out to the exact size I need once scaled?

Lordan answered 4/5, 2012 at 3:22 Comment(2)
Why can't you just do the math to calculate the other dimension?Kibitz
@CodyGray What "other" dimension exactly? It's a 2 dimensional image that needs to be scaled down while maintaining its aspect ratio. I've tried just taking the aspect ratio and figuring out a common denominator for the width and height that's close to the target size, and it never worked right.Lordan
K
71

Target parameters:

float width = 1024;
float height = 768;
var brush = new SolidBrush(Color.Black);

Your original file:

var image = new Bitmap(file);

Target sizing (scale factor):

float scale = Math.Min(width / image.Width, height / image.Height);

The resize including brushing canvas first:

var bmp = new Bitmap((int)width, (int)height);
var graph = Graphics.FromImage(bmp);

// uncomment for higher quality output
//graph.InterpolationMode = InterpolationMode.High;
//graph.CompositingQuality = CompositingQuality.HighQuality;
//graph.SmoothingMode = SmoothingMode.AntiAlias;

var scaleWidth = (int)(image.Width * scale);
var scaleHeight = (int)(image.Height * scale);

graph.FillRectangle(brush, new RectangleF(0, 0, width, height));
graph.DrawImage(image, ((int)width - scaleWidth)/2, ((int)height - scaleHeight)/2, scaleWidth, scaleHeight);

And don't forget to do a bmp.Save(filename) to save the resulting file.

Kolyma answered 4/5, 2012 at 8:12 Comment(10)
This creates a solid black image.Lordan
Works perfectly for me, no need to downvote unless you're sure you've found the issue. Remove the FillRectangle line and see what you get. Just tested again and it works fine. I'm on .NET 4.0 if that makes a difference.Kolyma
It doesn't work if the target dimensions are both less than the image's dimensions. scale becomes 0.0. It also won't preserve the aspect ratio at all, from what I can tell.Lordan
Change var scale to float scale. I'm guessing somewhere in your code your width and height are perhaps declared as int which causes problems. It works just fine if you follow what I've given closely. The whole point of scale is to preserve aspect ratio. Remove the -1 when you're done.Kolyma
It looks "better" now with changing things to floats, yes. That was an issue. I suppose that's one reason I rarely use var personally! I noticed it and was about to respond and then saw your comment. Anyway, it produces essentially a black bar across the bottom of some images instead of the border I mentioned. Is there a way to make it a center scaling? Center scaling was the main problem I was having.Lordan
I know this is old, but this helped me, so thankyou. However, I wrapped graph in a using block as it implements IDisposable.Puisne
I see this is old too, but just though i'd mention Graphics is Disposable(), so don't forget to put that graphics object into a using statementHarv
Yeah, what @Kolyma said. When I replaced width with Bounds.Width of my control I double checked and that value is an int. That'll throw things off by a lot. So typecast it before, like so, float scale = Math.Min((float)Bounds.Width / image.Width, (float)Bounds.Height / image.Height); No need to typecast the image.Width and image.Height which is also an int, because float / int is a float.Tarbes
I wish I can add +2 for the maths.Tract
Is there a specific reason for using the FillRectangle method over the Clear method? This would save having to create a brush and disposing it.Aurify
P
204

The bitmap constructor has resizing built in.

Bitmap original = (Bitmap)Image.FromFile("DSC_0002.jpg");
Bitmap resized = new Bitmap(original,new Size(original.Width/4,original.Height/4));
resized.Save("DSC_0002_thumb.jpg");

http://msdn.microsoft.com/en-us/library/0wh0045z.aspx

If you want control over interpolation modes see this post.

Pendergrass answered 24/10, 2013 at 11:57 Comment(3)
Thank you. This helped a lot. I know this is a bit of an old answer, but is there a reason why my new image is fuzzy looking? Do I need to set any interpolation modes?Eventuality
Doesn't this just draw the original bitmap inside a new smaller bitmap? A part of the image should just be cut of.Northnorthwest
Doesn't this just draw the original bitmap inside a new smaller bitmap? The link to TFM is right there: Initializes a new instance of the Bitmap class from the specified existing image, scaled to the specified size.Whoop
K
71

Target parameters:

float width = 1024;
float height = 768;
var brush = new SolidBrush(Color.Black);

Your original file:

var image = new Bitmap(file);

Target sizing (scale factor):

float scale = Math.Min(width / image.Width, height / image.Height);

The resize including brushing canvas first:

var bmp = new Bitmap((int)width, (int)height);
var graph = Graphics.FromImage(bmp);

// uncomment for higher quality output
//graph.InterpolationMode = InterpolationMode.High;
//graph.CompositingQuality = CompositingQuality.HighQuality;
//graph.SmoothingMode = SmoothingMode.AntiAlias;

var scaleWidth = (int)(image.Width * scale);
var scaleHeight = (int)(image.Height * scale);

graph.FillRectangle(brush, new RectangleF(0, 0, width, height));
graph.DrawImage(image, ((int)width - scaleWidth)/2, ((int)height - scaleHeight)/2, scaleWidth, scaleHeight);

And don't forget to do a bmp.Save(filename) to save the resulting file.

Kolyma answered 4/5, 2012 at 8:12 Comment(10)
This creates a solid black image.Lordan
Works perfectly for me, no need to downvote unless you're sure you've found the issue. Remove the FillRectangle line and see what you get. Just tested again and it works fine. I'm on .NET 4.0 if that makes a difference.Kolyma
It doesn't work if the target dimensions are both less than the image's dimensions. scale becomes 0.0. It also won't preserve the aspect ratio at all, from what I can tell.Lordan
Change var scale to float scale. I'm guessing somewhere in your code your width and height are perhaps declared as int which causes problems. It works just fine if you follow what I've given closely. The whole point of scale is to preserve aspect ratio. Remove the -1 when you're done.Kolyma
It looks "better" now with changing things to floats, yes. That was an issue. I suppose that's one reason I rarely use var personally! I noticed it and was about to respond and then saw your comment. Anyway, it produces essentially a black bar across the bottom of some images instead of the border I mentioned. Is there a way to make it a center scaling? Center scaling was the main problem I was having.Lordan
I know this is old, but this helped me, so thankyou. However, I wrapped graph in a using block as it implements IDisposable.Puisne
I see this is old too, but just though i'd mention Graphics is Disposable(), so don't forget to put that graphics object into a using statementHarv
Yeah, what @Kolyma said. When I replaced width with Bounds.Width of my control I double checked and that value is an int. That'll throw things off by a lot. So typecast it before, like so, float scale = Math.Min((float)Bounds.Width / image.Width, (float)Bounds.Height / image.Height); No need to typecast the image.Width and image.Height which is also an int, because float / int is a float.Tarbes
I wish I can add +2 for the maths.Tract
Is there a specific reason for using the FillRectangle method over the Clear method? This would save having to create a brush and disposing it.Aurify
R
4

Just to add to yamen's answer, which is perfect for images but not so much for text.

If you are trying to use this to scale text, like say a Word document (which is in this case in bytes from Word Interop), you will need to make a few modifications or you will get giant bars on the side.

May not be perfect but works for me!

using (MemoryStream ms = new MemoryStream(wordBytes))
{
    float width = 3840;
    float height = 2160;
    var brush = new SolidBrush(Color.White);

    var rawImage = Image.FromStream(ms);
    float scale = Math.Min(width / rawImage.Width, height / rawImage.Height);
    var scaleWidth  = (int)(rawImage.Width  * scale);
    var scaleHeight = (int)(rawImage.Height * scale);
    var scaledBitmap = new Bitmap(scaleWidth, scaleHeight);

    Graphics graph = Graphics.FromImage(scaledBitmap);
    graph.InterpolationMode = InterpolationMode.High;
    graph.CompositingQuality = CompositingQuality.HighQuality;
    graph.SmoothingMode = SmoothingMode.AntiAlias;
    graph.FillRectangle(brush, new RectangleF(0, 0, width, height));
    graph.DrawImage(rawImage, new Rectangle(0, 0 , scaleWidth, scaleHeight));

    scaledBitmap.Save(fileName, ImageFormat.Png);
    return scaledBitmap;
}
Rinker answered 20/3, 2018 at 23:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.