Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resizing System.Drawing.Bitmap to less than 1/128th (Bilinear) or 1/256th (Bicubic) of its original width/height corrupts the result #11248

Open
ChrisF-127 opened this issue Apr 20, 2024 · 5 comments
Assignees
Labels
area-System.Drawing System.Drawing issues

Comments

@ChrisF-127
Copy link

ChrisF-127 commented Apr 20, 2024

.NET version

8.0

Did it work in .NET Framework?

No

Did it work in any of the earlier releases of .NET Core or .NET 5+?

Unlikely

Issue description

The result of a resizing operation, for example via the Bitmap-constructor or using Graphics, becomes corrupted when an image is resized to less than 1/128th (using Bilinear interpolation) or 1/256th (using Bicubic) of the original size.
The resulting image turns transparent and colors become corrupted, and as the scale goes beyond the point where corruption starts, the output changes.

I have conducted tests for this with different sized images, 16384x16384, 16384x100, 2048x2048, 1024x1024 for example, resizing width and/or height, all with the results being faulty.

A 16384x16384 test image resized to 128x128:
16384x16384_Bilinear_128x128

The same resized to 127x127, it is barely visible:
16384x16384_Bilinear_127x127

Resized to 64x64, transparency is slightly reduced at 247 instead of 255:
16384x16384_Bilinear_64x64

My assumption is that resizing by that much causes overflowing in the interpolation algorithms, leading to higher bytes being modified when they should not be.
This is likely an issue in the underlying GDI+ library, but I do not know where else to report this bug.

I was unable to find any information on this issue.

Steps to reproduce

Resize an image to less than 1/128th of its original width and/or height, either using the Bitmap(Bitmap height, int width, int height)-constructor or via Graphics, using InterpolationMode.Bilinear (or Default).
Or resize it to less than 1/256th when using Bicubic interpolation.

Code example:

var file = [PATH_TO_IMAGE]; // image of at least 16384 pixels in either width or height
var fullSize = new Bitmap(file);

var size0 = 127;
var size1 = 128;

resizeAndSaveDefault(size0, size0); // FAULTY at 1/128
resizeAndSaveDefault(size1, size1); // OK

resizeAndSave(size0, size0, InterpolationMode.Bicubic); // FAULTY at 1/256
resizeAndSave(size1, size1, InterpolationMode.Bicubic); // OK

resizeAndSave(size0, size0, InterpolationMode.Bilinear); // FAULTY at 1/128 (same as InterpolationMode.Default)
resizeAndSave(size1, size1, InterpolationMode.Bilinear); // OK

resizeAndSave(size0, size0, InterpolationMode.HighQualityBicubic); // OK
resizeAndSave(size1, size1, InterpolationMode.HighQualityBicubic); // OK

resizeAndSave(size0, size0, InterpolationMode.HighQualityBilinear); // OK
resizeAndSave(size1, size1, InterpolationMode.HighQualityBilinear); // OK

resizeAndSave(size0, size0, InterpolationMode.NearestNeighbor); // OK
resizeAndSave(size1, size1, InterpolationMode.NearestNeighbor); // OK

void resizeAndSaveDefault(int width, int height) => 
	new Bitmap(fullSize, width, height).Save($"{Path.GetDirectoryName(file)}\\{Path.GetFileNameWithoutExtension(file)}_{width}x{height}.png");

void resizeAndSave(int width, int height, InterpolationMode interpolationMode)
{
	var bitmap = new Bitmap(width, height);
	using var graphics = Graphics.FromImage(bitmap);
	graphics.InterpolationMode = interpolationMode;
	graphics.Clear(Color.Transparent);
	graphics.DrawImage(fullSize, 0, 0, width, height);
	bitmap.Save($"{Path.GetDirectoryName(file)}\\{Path.GetFileNameWithoutExtension(file)}_{interpolationMode}_{width}x{height}.png");
}
@ChrisF-127 ChrisF-127 added the untriaged The team needs to look at this issue in the next triage label Apr 20, 2024
@elachlan elachlan added the area-System.Drawing System.Drawing issues label Apr 21, 2024
@elachlan
Copy link
Contributor

@JeremyKuhne is probably best to look at System.Drawing issues like this.

@JeremyKuhne
Copy link
Member

Unfortunately, we're unlikely to get a fix for GDI+. If we find a crash or AV here that would be different.

For now, it's good to have this written up here.

This resizing pattern is pretty common I think. We might want to consider adding a Bitmap Resize(int, int, InterpolationMode) method that could check for this case and throw appropriately.

If we were to add such an API we would want to do a thorough check on the conditions of the failure here. Does this happen for all source image formats? Is it related to a particular source image format (png, jpg, etc.)?

@paul1956
Copy link
Contributor

I suspect with images becoming significantly larger this issue will become more common. I am not sure throwing is the correct response because that would break existing applications.

I am not sure what the correct fix would be but it should at least be documented under GDI+ this could be causing image corruption without anyone knowing.

@merriemcgaw merriemcgaw added the 📭 waiting-author-feedback The team requires more information from the author label Apr 24, 2024
@merriemcgaw merriemcgaw removed the untriaged The team needs to look at this issue in the next triage label Apr 24, 2024
@ChrisF-127
Copy link
Author

The source image format does not seem to have any affect on the issue, I tested png, bmp and jpg.
As the full sized image loads fine, and since I assume that a Bitmap keeps the image as raw data in memory, it would have surprised me if different input formats affected the downscaling interpolation.

I tried some different PixelFormats for the downscaled bitmap as well, but with unchanged outcome.

Resizing the bitmap to an intermediate size, so it does not go beyond a 1/128th or 1/256th downscaling operation, for example downscaling to 1/64th first and then to the smaller size, works as a workaround. (But if I remember correctly, multiple resizing operations can cause artifacts. I consider it questionable how much this matters, but generally speaking it may be something to keep in mind.)
There also does not appear to be any issues when using HighQualityBilinear or HighQualityBicubic interpolation, I tested these to even smaller sizes with no output corruption.
Obviously, these options are slower (and more memory intensive).

@dotnet-policy-service dotnet-policy-service bot removed the 📭 waiting-author-feedback The team requires more information from the author label Apr 25, 2024
@paul1956
Copy link
Contributor

Resizing the bitmap to an intermediate size, so it does not go beyond a 1/128th or 1/256th downscaling operation, for example downscaling to 1/64th first and then to the smaller size, works as a workaround. (But if I remember correctly, multiple resizing operations can cause artifacts. I consider it questionable how much this matters, but generally speaking it may be something to keep in mind.) There also does not appear to be any issues when using HighQualityBilinear or HighQualityBicubic interpolation, I tested these to even smaller sizes with no output corruption. Obviously, these options are slower (and more memory intensive).

It seems to me that rather than throwing an exception if scale factor is too large, it is better the do 2 scaling or if possible, use the HighQuality versions which {without testing) is probably faster. Since the current results is corrupt both solutions are better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Drawing System.Drawing issues
Projects
None yet
Development

No branches or pull requests

5 participants