Why does resizing a png image lose transparency? - c#

I am trying to resize an image as follows. I return the resized image into byte[] so that I can store it in database. The transparency of png image is lost. Please help to make this better.
private byte[] GetThumbNail(string imageFile, Stream imageStream,
int imageLen)
{
try
{
Image.GetThumbnailImageAbort imageCallBack =
new Image.GetThumbnailImageAbort(ThumbnailCallback);
Bitmap getBitmap = new Bitmap(imageFile);
byte[] returnByte = new byte[imageLen];
Image getThumbnail = getBitmap.GetThumbnailImage(160, 59,
imageCallBack, IntPtr.Zero);
using (Graphics g = Graphics.FromImage(getThumbnail))
{
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.DrawImage(getThumbnail, 0, 0, 160, 59);
}
using (MemoryStream ms = new MemoryStream())
{
getThumbnail.Save(ms, ImageFormat.Png);
getThumbnail.Save("test.png", ImageFormat.Png);
returnByte = ms.ToArray();
}
return returnByte;
}
catch (Exception)
{
throw;
}
}

Your code doesn't do quite what you think that it does...
You use the GetThumbnailImage to resize the image, then you draw the thumbnail image into itself which is rather pointless. You probably lose the transparency in the first step.
Create a blank bitmap instead, and resize the source image by drawing it on the blank bitmap.
private byte[] GetThumbNail(string imageFile) {
try {
byte[] result;
using (Image thumbnail = new Bitmap(160, 59)) {
using (Bitmap source = new Bitmap(imageFile)) {
using (Graphics g = Graphics.FromImage(thumbnail)) {
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.DrawImage(source, 0, 0, 160, 59);
}
}
using (MemoryStream ms = new MemoryStream()) {
thumbnail.Save(ms, ImageFormat.Png);
thumbnail.Save("test.png", ImageFormat.Png);
result = ms.ToArray();
}
}
return result;
} catch (Exception) {
throw;
}
}
(I removed some parameters that were never used for anything that had anything to do with the result, like the imageLen parameter that was only used to create a byte array that was never used.)

Try using the .MakeTransparent() call on your bitmap object.

May be you should do something like this because this thing worked for me:
String path = context.Server.MapPath("/images");
if (!path.EndsWith("\\"))
path += "\\";
path += "none.png";
Image img = CreateThumbnail(Image.FromFile(path));
MemoryStream ms = new MemoryStream();
img.Save(ms, ImageFormat.Png);
ms.WriteTo(context.Response.OutputStream);
private System.Drawing.Image CreateThumbnail(System.Drawing.Image i)
{
int dWidth = i.Width;
int dHeight = i.Height;
int dMaxSize = 150;
if (dWidth > dMaxSize)
{
dHeight = (dHeight * dMaxSize) / dWidth;
dWidth = dMaxSize;
}
if (dHeight > dMaxSize)
{
dWidth = (dWidth * dMaxSize) / dHeight;
dHeight = dMaxSize;
}
return i.GetThumbnailImage(dWidth, dHeight, delegate() { return false; }, IntPtr.Zero);
}

Related

A generic error occurred in GDI+ when I resize image file [duplicate]

This question already has answers here:
A generic error occurred in GDI+, JPEG Image to MemoryStream
(36 answers)
Closed 3 years ago.
I'm resizing an image, what could be wrong with my code?
var newSize = ResizeImageFile(ConvertToBytes(myFile), 2048);
using (MemoryStream ms = new MemoryStream(newSize, 0, newSize.Length))
{
ms.Write(newSize, 0, newSize.Length);
using (Image image = Image.FromStream(ms, true))
{
image.Save(targetLocation, ImageFormat.Jpeg);
}
}
I have used this function to resize my image
public static byte[] ResizeImageFile(byte[] imageFile, int targetSize) // Set targetSize to 1024
{
using (Image oldImage = Image.FromStream(new MemoryStream(imageFile)))
{
Size newSize = CalculateDimensions(oldImage.Size, targetSize);
using (Bitmap newImage = new Bitmap(newSize.Width, newSize.Height, PixelFormat.Format24bppRgb))
{
using (Graphics canvas = Graphics.FromImage(newImage))
{
canvas.SmoothingMode = SmoothingMode.AntiAlias;
canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
canvas.DrawImage(oldImage, new Rectangle(new Point(0, 0), newSize));
MemoryStream m = new MemoryStream();
newImage.Save(m, ImageFormat.Jpeg);
return m.GetBuffer();
}
}
}
}
I have been looking for answers for a long time, please help me.
Thank you
Since the both CalculateDimensions and ConvertToBytes methods are not shown, I tried to assume that the above methods are something like as follows:
// Calculate the Size at which the image width and height is lower than the specified value
// (Keep the aspect ratio)
private static Size CalculateDimensions(Size size, int targetSize)
{
double rate = Math.Max(size.Width * 1.0 / targetSize, size.Height * 1.0 / targetSize);
int w = (int)Math.Floor(size.Width / rate);
int h = (int)Math.Floor(size.Height / rate);
return new Size(w, h);
}
//Convert image file to byte array
private static byte[] ConvertToBytes(string fileName)
{
var result = File.ReadAllBytes(fileName);
return result;
}
If your code does not work well, then some problems must be in the above methods.

Cropping a Base64/Bitmap, doesn't crop it

Currently I'm making a function which can take a base64 image and crop it to the desired rectangle (X, Y, Width, Height). However, the below code doesn't seem to do the trick and I don't know why. It returns the image unchanged and uncropped.
Can anyone see the issue? :)
public static string CropImage(string base64, int x, int y, int width, int height)
{
byte[] bytes = Convert.FromBase64String(base64);
using (var ms = new MemoryStream(bytes))
{
Bitmap bmp = new Bitmap(ms);
Rectangle rect = new Rectangle(x, y, width, height);
Bitmap croppedBitmap = new Bitmap(rect.Width, rect.Height, bmp.PixelFormat);
using (Graphics gfx = Graphics.FromImage(croppedBitmap))
{
gfx.DrawImage(bmp, 0, 0, rect, GraphicsUnit.Pixel);
}
using (MemoryStream ms2 = new MemoryStream())
{
bmp.Save(ms2, ImageFormat.Jpeg);
byte[] byteImage = ms2.ToArray();
var croppedBase64 = Convert.ToBase64String(byteImage);
return croppedBase64;
}
}
}
The cropped image is in croppedBitmap, bmp is the original image. I think you want to use croppedBitmap in the second memory stream:
using (MemoryStream ms2 = new MemoryStream())
{
croppedBitmap.Save(ms2, ImageFormat.Jpeg);
byte[] byteImage = ms2.ToArray();
var croppedBase64 = Convert.ToBase64String(byteImage);
return croppedBase64;
}

Out of memory when cropping a bitmap with .Clone()

I am trying to automatically generate a thumbnail from an image uploaded by a user but I keep getting the exception "Out of memory".
From what I understand the out of memory exception is thrown when you specify a starting position or a width/height that's outside of the image but even if I do this
var rct = new Rectangle(5, 5, 10, 10);
var whatever = bitmap.Clone(rct, bitmap.PixelFormat);
on an image that is 800x900 pixels I still get the "Out of memory" exception, I can't figure out what's wrong with it and I can't really get any good answers from other threads since everything regarding the OOM exception is just the mistake of going outside the image boundaries.
Does anyone have an explanation or solution to this?
EDIT: A bit more context
The loop for the images.
foreach (var blob in fileInfoList)
{
var blockBlobName = CheckExistence(BaseBlobUrl, blob.FileName, blob.FileNameWithoutExtension);
var image = new Image()
{
BlobUrl = Path.Combine(BaseBlobUrl, blockBlobName),
FullName = blob.FileName,
FileName = blob.FileNameWithoutExtension,
BlockBlobName = blockBlobName,
OwningOrganizationId = CurrentUser.UserOrganization.OrganizationId,
ThumbnailUrl = CreateThumbnail(blob.File, blockBlobName),
Name = "Whatever"
};
blobList.Add(image);
RepositoryFactory.AzureStorageRepository.SaveImage(blob.File, blockBlobName, blob.ContentType, CurrentUser.UserOrganization.Organization.Id);
}
The method that is being called by each image in the list to generate the thumbnail.
public string CreateThumbnail(byte[] b, string parentImageName)
{
Bitmap bmp;
using (var ms = new MemoryStream(b))
{
bmp = new Bitmap(ms);
}
Bitmap thumbnail = bmp;
Rectangle rect = new Rectangle(5, 5, 10, 10);
if (bmp.Width > bmp.Height)
thumbnail = bmp.Clone(rect, bmp.PixelFormat);
else if (bmp.Height > bmp.Width)
thumbnail = bmp.Clone(new Rectangle((bmp.Height/2) - (bmp.Width/2), 0, bmp.Width, bmp.Width), bmp.PixelFormat);
byte[] bmpArray = new byte[0];
using (var ms = new MemoryStream())
{
finalCrop.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
ms.Close();
bmpArray = ms.ToArray();
}
var name = "Thumbnail_" + parentImageName;
RepositoryFactory.AzureStorageRepository.SaveThumbnail(bmpArray, name, "jpg/image", CurrentUser.UserOrganization.Organization.Id);
return BaseBlobUrl + "thumbnails/" + name;
}
I think the problem you're getting here is that Bitmaps need to be disposed. If one gets garbage collected without releasing its underlying unmanaged content (i.e. being disposed), then that memory can not be recovered...
Also note that you will need to dispose both bitmaps. Best thing to do is wrap them in a using, something like this:
using (var ms = new MemoryStream(b))
{
using (Bitmap bmp = new Bitmap(ms))
using (Bitmap thumbnail = bmp)
{
Rectangle rect = new Rectangle(5, 5, 10, 10);
if (bmp.Width > bmp.Height)
thumbnail = bmp.Clone(rect, bmp.PixelFormat);
else if (bmp.Height > bmp.Width)
thumbnail = bmp.Clone(new Rectangle((bmp.Height / 2) - (bmp.Width / 2), 0, bmp.Width, bmp.Width), bmp.PixelFormat);
byte[] bmpArray = new byte[0];
using (var ms = new MemoryStream())
{
finalCrop.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
ms.Close();
bmpArray = ms.ToArray();
}
var name = "Thumbnail_" + parentImageName;
RepositoryFactory.AzureStorageRepository.SaveThumbnail(bmpArray, name, "jpg/image", CurrentUser.UserOrganization.Organization.Id);
return BaseBlobUrl + "thumbnails/" + name;
}
}
It is worth noting that using will call Dispose() on it's target, even if an exception is thrown (thus having the same finally type functionality as #Scott Chamberlain answer
Okay so I found an answer to my problem by disposing the Bitmaps.
After this bit of code
Bitmap thumbnail = bmp;
I added
bmp.Dispose();
And during debugging I noticed that none of the properties from bmp were left in the Bitmap called thumbnail so I changed it into the following
Bitmap thumbnail = new Bitmap(bmp);
Thank you all for telling me to dispose the Bitmaps!
Here is the correct way to properly dispose of your objects.
Bitmap bmp = null;
Bitmap thumbnail = null;
try
{
using (var ms = new MemoryStream(b))
{
bmp = new Bitmap(ms);
}
Rectangle rect = new Rectangle(5, 5, 10, 10);
if (bmp.Width > bmp.Height)
thumbnail = bmp.Clone(rect, bmp.PixelFormat);
else if (bmp.Height > bmp.Width)
thumbnail = bmp.Clone(new Rectangle((bmp.Height/2) - (bmp.Width/2), 0, bmp.Width, bmp.Width), bmp.PixelFormat);
else
thumbnail = bmp;
byte[] bmpArray = new byte[0];
using (var ms = new MemoryStream())
{
finalCrop.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
ms.Close();
bmpArray = ms.ToArray();
}
var name = "Thumbnail_" + parentImageName;
RepositoryFactory.AzureStorageRepository.SaveThumbnail(bmpArray, name, "jpg/image", CurrentUser.UserOrganization.Organization.Id);
return BaseBlobUrl + "thumbnails/" + name;
}
finally
{
if(bmp != null)
bmp.Dispose();
if(thumbnail != null)
thumbnail.Dispose(); //If bmp and thumbnail are the same object this is still safe to do.
}
Use a try/finally block to ensure that even in the event of a error your objects get disposed. Doing the extra Bitmap thumbnail = new Bitmap(bmp); in your answer just makes a extra bitmap you are forgetting to dispose.
I received this Exception when the Rectangle used to crop the image was partly outside the bounds of the image.

Compressing large image to small format

I have images stored in DB as binary, to show them I want to compress them to smaller images (4000 x 3000) to (400 x 300), which basicly works however the images look awefull, can somebody point me to the right direction?
I am using now:
System.IO.MemoryStream myMemStream = new System.IO.MemoryStream(bytes);
System.Drawing.Image fullsizeImage = System.Drawing.Image.FromStream(myMemStream);
Type typeoff = fullsizeImage.GetType();
double height = fullsizeImage.Height;
double width = Convert.ToDouble(fullsizeImage.Width);
double aspect = setWidth / width;
setHeight = Convert.ToInt32(aspect * height);
System.Drawing.Image newImage = fullsizeImage.GetThumbnailImage(Convert.ToInt32(setWidth), setHeight, null, IntPtr.Zero);
System.IO.MemoryStream myResult = new System.IO.MemoryStream();
using (System.IO.MemoryStream imageMemStream = new System.IO.MemoryStream(bytes))
{
using (Bitmap bitmap = new Bitmap(imageMemStream))
{
ImageFormat imageFormat = bitmap.RawFormat;
if (bitmap.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Jpeg))
{
newImage.Save(myResult, System.Drawing.Imaging.ImageFormat.Jpeg);
}
if (bitmap.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Gif))
{
newImage.Save(myResult, System.Drawing.Imaging.ImageFormat.Gif);
}
if (bitmap.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Bmp))
{
newImage.Save(myResult, System.Drawing.Imaging.ImageFormat.Bmp);
}
if (bitmap.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Png))
{
newImage.Save(myResult, System.Drawing.Imaging.ImageFormat.Png);
}
if (bitmap.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Icon))
{
newImage.Save(myResult, System.Drawing.Imaging.ImageFormat.Icon);
}
}
}
_bytes = myResult.ToArray(); //Returns a new byte array.
Have been looking to this but have no idea yet how to implenet ths with my binary in and output:
Bitmap image = new Bitmap(fullsizeImage, Convert.ToInt32(newWidth), setHeight);
using (Graphics gr = Graphics.FromImage(image))
{
gr.SmoothingMode = SmoothingMode.HighQuality;
gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
gr.PixelOffsetMode = PixelOffsetMode.HighQuality;
gr.DrawImage(fullsizeImage, new Rectangle(0, 0, Convert.ToInt32(newWidth), setHeight));
_bytes = gr.T.ToArray();
}
Probebly I do something wrong but have no idea where to look to do this right, have not much experience in image compression.
Any help would be appriciated
UPDATE
trying to get out of the Image the mime type but not very lucky to get it, using this and cant find any other, code has a null return.
public byte[] imageToByteArray(System.Drawing.Image newImage)
{
System.IO.MemoryStream ms = new System.IO.MemoryStream();
ImageFormat format = newImage.RawFormat;
if (ImageCodecInfo.GetImageDecoders().FirstOrDefault(c => c.FormatID == format.Guid) != null)
{
ImageCodecInfo codec = ImageCodecInfo.GetImageDecoders().FirstOrDefault(c => c.FormatID == format.Guid);
string mimeType = codec.MimeType;
}
newImage.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
return ms.ToArray();
You can use this function to create the reduced Images:
public static Image ShrinkImage(Image original, int scale)
{
Bitmap bmp = new Bitmap(original.Width / scale, original.Height / scale,
original.PixelFormat);
using (Graphics G = Graphics.FromImage(bmp))
{
G.InterpolationMode = InterpolationMode.HighQualityBicubic;
G.SmoothingMode = SmoothingMode.HighQuality;
Rectangle srcRect = new Rectangle(0,0,original.Width, original.Height);
Rectangle destRect = new Rectangle(0,0,bmp.Width, bmp.Height);
G.DrawImage(original, destRect, srcRect, GraphicsUnit.Pixel);
bmp.SetResolution( original.HorizontalResolution, original.VerticalResolution);
}
return (Image)bmp;
}
Note that it can only work with real Bitmap Images, not with Icons; but it makes little sense trying to reduce icons anyway!
Also note that you may or may not want to change the Dpi of the new Images. In the code I don't but maybe you want to scale it up or set it to a fixed value..
Don't forget to Dispose of your Images, when you're done with them!

Optimal way to resize and bind byte[] to image in wpf

I have byte[] that store some image of unknown size. I need to resize this bitmap into 160 x 160 and bind to Wpf window.
Should i convert byte[] to Bitmap, resize, make BitmapSource from it and then use in wpf?
//result of this method binds to wpf
private static BitmapSource SetImage(byte[] dataBytes)
{
var picture = BitmapHelper.ByteToBitmap(dataBytes);
var resizedPicture = BitmapHelper.Resize(picture, 160, 160);
var bitmapSource = BitmapHelper.GetSourceFromBitmap(resizedPicture);
bitmapSource.Freeze();
return bitmapSource;
}
//BitmapHelper class:
public static Bitmap ByteToBitmap(byte[] byteArray)
{
using (var ms = new MemoryStream(byteArray))
{
var img = (Bitmap)Image.FromStream(ms);
return img;
}
}
internal static Bitmap Resize(Bitmap bitmap, int width, int height)
{
var newImage = new Bitmap(width, height);
using (var gr = Graphics.FromImage(newImage))
{
gr.SmoothingMode = SmoothingMode.HighQuality;
gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
gr.PixelOffsetMode = PixelOffsetMode.HighQuality;
gr.DrawImage(bitmap, new Rectangle(0, 0, width, height));
}
return newImage;
}
internal static BitmapSource GetSourceFromBitmap(Bitmap source)
{
Contract.Requires(source != null);
var ip = source.GetHbitmap();
BitmapSource bs;
int result;
try
{
bs = Imaging.CreateBitmapSourceFromHBitmap(ip,
IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
finally
{
result = NativeMethods.DeleteObject(ip);
}
if (result == 0)
throw new InvalidOperationException("NativeMethods.DeleteObject returns 0 (operation failed)");
return bs;
}
internal static class NativeMethods
{
[DllImport("gdi32")]
static internal extern int DeleteObject(IntPtr o);
}
What is the fastest and common method for that case?
I'd suggest you to use the DecodePixelWidth and / or DecodePixelHeight properties of the BitmapImageclass (which inherits from ImageSource).
Take a look here:
BitmapSource GetImage(byte[] dataBytes)
{
using (MemoryStream stream = new MemoryStream(dataBytes))
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
// This is important: the image should be loaded on setting the StreamSource property, afterwards the stream will be closed
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.DecodePixelWidth = 160;
// Set this to 160 to get exactly 160x160 image
// Comment it out to retain original aspect ratio having the image width 160 and auto calculated image height
bi.DecodePixelHeight = 160;
bi.StreamSource = stream;
bi.EndInit();
return bi;
}
}

Categories