Memory efficient bitmap handling in mono for android - c#

I got an application, which allows the user to take a picture. After the picture has been taken, the user can send it to my webserver. But before i do this, it needs to resize the bitmap because i like to have consistent sizes send to my webserver.
Anyway, the code i use to load the bitmap into memory and then manipulate it, does seem to occupy a lot of memory. This code is currently being used :
/*
* This method is used to calculate image size.
* And also resize/scale image down to 1600 x 1200
*/
private void ResizeBitmapAndSendToWebServer(string album_id) {
Bitmap bm = null;
// This line is taking up to much memory each time..
Bitmap bitmap = MediaStore.Images.Media.GetBitmap(Android.App.Application.Context.ApplicationContext.ContentResolver,fileUri);
/*
* My question is : Could i do the next image manipulation
* before i even load the bitmap into memory?
*/
int width = bitmap.Width;
int height = bitmap.Height;
if (width >= height) { // <-- Landscape picture
float scaledWidth = (float)height / width;
if (width > 1600) {
bm = Bitmap.CreateScaledBitmap (bitmap, 1600, (int)(1600 * scaledWidth), true);
} else {
bm = bitmap;
}
} else {
float scaledHeight = (float)width / height;
if (height > 1600) {
bm = Bitmap.CreateScaledBitmap (bitmap, (int)(1600 * scaledHeight), 1600 , true);
} else {
bm = bitmap;
}
}
// End of question code block.
MemoryStream stream = new MemoryStream ();
bitmap.Compress (Bitmap.CompressFormat.Jpeg, 80, stream);
byte[] bitmapData = stream.ToArray ();
bitmap.Dispose ();
app.api.SendPhoto (Base64.EncodeToString (bitmapData, Base64Flags.Default), album_id);
}
What would be a good and clean way for solving such memory problems?
EDIT 1 :
After reading other posts, it became clear to me that i am doing some inefficient things with my code. This is, in steps, what i have been doing :
Load full bitmap into memory.
Decide wether it is landscape or not.
Then create new bitmap with the right dimensions.
Then converting this bitmap into byte array
Disposing the initial bitmap. (But never remove the scaled bitmap out of memory).
What i really should be doing :
Determine real bitmap dimensions without loading it into memory with :
private void FancyMethodForDeterminingImageDimensions() {
BitmapFactory.Options options = new BitmapFactory.Options();
options.InJustDecodeBounds = true;
BitmapFactory.DecodeFile(fileUri.Path, options);
// Now the dimensions of the bitmap are known without loading
// the bitmap into memory.
// I am not further going to explain this, i think the purpose is
// explaining enough.
int outWidth = options.OutWidth;
int outHeight = options.OutHeight;
}
If set to true, the decoder will return null (no bitmap), but the
out... fields will still be set, allowing the caller to query the
bitmap without having to allocate the memory for its pixels.
Now i know the real dimenions. So i can downsample it before i load it into memory.
(in my case) Convert bitmap to base64 string and send it.
Dispose everything so the memory gets cleared.
I can't currently test this, because i am not on my development machine. Can anyone give me some feedback if this is the right way? It will be appreciated.

private void ResizeBitmapAndSendToWebServer(string album_id) {
BitmapFactory.Options options = new BitmapFactory.Options ();
options.InJustDecodeBounds = true; // <-- This makes sure bitmap is not loaded into memory.
// Then get the properties of the bitmap
BitmapFactory.DecodeFile (fileUri.Path, options);
Android.Util.Log.Debug ("[BITMAP]" , string.Format("Original width : {0}, and height : {1}", options.OutWidth, options.OutHeight) );
// CalculateInSampleSize calculates the right aspect ratio for the picture and then calculate
// the factor where it will be downsampled with.
options.InSampleSize = CalculateInSampleSize (options, 1600, 1200);
Android.Util.Log.Debug ("[BITMAP]" , string.Format("Downsampling factor : {0}", CalculateInSampleSize (options, 1600, 1200)) );
// Now that we know the downsampling factor, the right sized bitmap is loaded into memory.
// So we set the InJustDecodeBounds to false because we now know the exact dimensions.
options.InJustDecodeBounds = false;
// Now we are loading it with the correct options. And saving precious memory.
Bitmap bm = BitmapFactory.DecodeFile (fileUri.Path, options);
Android.Util.Log.Debug ("[BITMAP]" , string.Format("Downsampled width : {0}, and height : {1}", bm.Width, bm.Height) );
// Convert it to Base64 by first converting the bitmap to
// a byte array. Then convert the byte array to a Base64 String.
MemoryStream stream = new MemoryStream ();
bm.Compress (Bitmap.CompressFormat.Jpeg, 80, stream);
byte[] bitmapData = stream.ToArray ();
bm.Dispose ();
app.api.SendPhoto (Base64.EncodeToString (bitmapData, Base64Flags.Default), album_id);
}

Related

C# Image GPS location property disappears after resizing image [duplicate]

Yes yes... I've seen other posts related to this issue, and yes... I've googled about it.
But so far, I was not able to get to the result I need.
I'm loading a large image taken in 300 dpi, and I need to resize it.
I know... I know... dpi is relative and doesn't really matter... what matters are the dimensions in pixels:
DPI is essentially the number of pixels that correspond to an inch when the image is printed not when it is viewed on a screen. Therefore by increasing the DPI of the image, you do not increase the size of the image on the screen. You only increase the quality of print.
Even though the DPI information stored in the EXIF of an image is somewhat useless, it is causing me problems.
The image I'm resizing is losing the original exif information, including the horizontal and vertical resolution (dpi), and thus it is saving with a default of 96 dpi. Possible reason to this is that only JPEG and another format can hold metadata information.
The end image result is should look like this: 275x375 at 300dpi
Instead is looking like this: 275x375 at 96dpi
You can argue that they are they same, and I agree, but we have a corel draw script that used to load these images, and since this dpi information is different, it places it in different sizes on the document.
Here's what I'm using for resizing:
public System.Drawing.Bitmap ResizeImage(System.Drawing.Image image, int width, int height)
{
Bitmap result = new Bitmap(width, height);
// set the resolutions the same to avoid cropping due to resolution differences
result.SetResolution(image.HorizontalResolution, image.VerticalResolution);
//use a graphics object to draw the resized image into the bitmap
using (Graphics graphics = Graphics.FromImage(result))
{
//set the resize quality modes to high quality
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//draw the image into the target bitmap
graphics.DrawImage(image, 0, 0, result.Width, result.Height);
}
//return the resulting bitmap
return result;
}
That does the work very well, but loses the EXIF information.
Setting the SetResolution to SetResolution(300, 300) does not work!
I looked at reading and changing the EXIF information of an image, and I've tried:
public void setImageDpi(string Filename, string NewRes)
{
Image Pic;
PropertyItem[] PropertyItems;
byte[] bDescription = new Byte[NewRes.Length];
int i;
string FilenameTemp;
System.Drawing.Imaging.Encoder Enc = System.Drawing.Imaging.Encoder.Transformation;
EncoderParameters EncParms = new EncoderParameters(1);
EncoderParameter EncParm;
ImageCodecInfo CodecInfo = GetEncoderInfo("image/jpeg");
// copy description into byte array
for (i = 0; i < NewRes.Length; i++) bDescription[i] = (byte)NewRes[i];
// load the image to change
Pic = Image.FromFile(Filename);
foreach (PropertyItem item in Pic.PropertyItems)
{
if (item.Id == 282 || item.Id == 283)
{
PropertyItem myProperty = item;
myProperty.Value = bDescription;
myProperty.Type = 2;
myProperty.Len = NewRes.Length;
Pic.SetPropertyItem(item);
Console.WriteLine(item.Type);
}
}
// we cannot store in the same image, so use a temporary image instead
FilenameTemp = Filename + ".temp";
// for lossless rewriting must rotate the image by 90 degrees!
EncParm = new EncoderParameter(Enc, (long)EncoderValue.TransformRotate90);
EncParms.Param[0] = EncParm;
// now write the rotated image with new description
Pic.Save(FilenameTemp, CodecInfo, EncParms);
// for computers with low memory and large pictures: release memory now
Pic.Dispose();
Pic = null;
GC.Collect();
// delete the original file, will be replaced later
System.IO.File.Delete(Filename);
// now must rotate back the written picture
Pic = Image.FromFile(FilenameTemp);
EncParm = new EncoderParameter(Enc, (long)EncoderValue.TransformRotate270);
EncParms.Param[0] = EncParm;
Pic.Save(Filename, CodecInfo, EncParms);
// release memory now
Pic.Dispose();
Pic = null;
GC.Collect();
// delete the temporary picture
System.IO.File.Delete(FilenameTemp);
}
That didn't work either.
I tried looking and changing the EXIF information for DPI (282 and 283) later in the process as such:
Encoding _Encoding = Encoding.UTF8;
Image theImage = Image.FromFile("somepath");
PropertyItem propItem282 = theImage.GetPropertyItem(282);
propItem282.Value = _Encoding.GetBytes("300" + '\0');
theImage.SetPropertyItem(propItem282);
PropertyItem propItem283 = theImage.GetPropertyItem(283);
propItem283.Value = _Encoding.GetBytes("300" + '\0');
theImage.SetPropertyItem(propItem283);
theImage.Save("somepath");
But the program crashes saying that Property Cannot be Found.
If the property doesn't exist, apparently I can't add it:
A PropertyItem is not intended to be used as a stand-alone object. A PropertyItem object is intended to be used by classes that are derived from Image. A PropertyItem object is used to retrieve and to change the metadata of existing image files, not to create the metadata. Therefore, the PropertyItem class does not have a defined Public constructor, and you cannot create an instance of a PropertyItem object.
I'm stuck... all I need is a resized image with a dpi set to 300, it shouldn't be so hard.
Any help much appreciated. Thanks
The following code worked for me:
const string InputFileName = "test_input.jpg";
const string OutputFileName = "test_output.jpg";
var newSize = new Size(640, 480);
using (var bmpInput = Image.FromFile(InputFileName))
{
using (var bmpOutput = new Bitmap(bmpInput, newSize))
{
foreach (var id in bmpInput.PropertyIdList)
bmpOutput.SetPropertyItem(bmpInput.GetPropertyItem(id));
bmpOutput.SetResolution(300.0f, 300.0f);
bmpOutput.Save(OutputFileName, ImageFormat.Jpeg);
}
}
When I inspect the output file I can see EXIF data and the DPI has been changed to 300.

A generic error occurred in GDI+, for random images

The questions in SO, for example this one or this one have discussed many reason for this exception, but none could help me to find out my problem, and I still cannot figure out why this is happening.
I'm having this error, when trying to save (code is below). And the strangest thing I've got is that - this error is occurring for random images (let's say "random" for now, because I don't know exact problem yet). Only for particular images, out of thousands, I got this problem.
Here is the one successful and one unsuccessful image when trying.
1. 2009-05-23_00.51.39.jpg
Image dimensions: 768x1024
Size: 87KB
Bit depth: 24
Color representation: sRGB
2. 2009-05-23_00.52.50.jpg
Image dimensions: 768x1024
Size: 335KB
Bit depth: 24
Color representation: sRGB
For image #1 the process is going smoothly, with no errors. However, for image #2, I keep getting this error. As you see the images are almost the same. Both are .jpg, same dimensions, and most of the properties are just same.
Here is the method.
public static Stream GetImageBytes(Stream fileStream, int maxWidth, int maxHeight, bool showCopyRight, string link, int fillColor = 0xFFFFFF)
{
var img = Image.FromStream(fileStream);
if (maxHeight != 0 && img.Height > maxHeight)
ResizeByHeight(ref img, maxHeight, fillColor.ToString("X6"));
if (maxWidth != 0 && img.Width > maxWidth)
ResizeByWidth(ref img, maxWidth, fillColor.ToString("X6"));
if (showCopyRight)
img = ApplyWatermark(ref img, "watermark.png", link);
var ms = new MemoryStream();
//problem is here
img.Save(ms, ImageFormat.Jpeg);
img.Dispose();
return ms;
}
Edit after comments.
Ok, here is the Watermark method
private static Bitmap ApplyWatermark(ref Image img, string watermarkPathPng, string link) {
const int x = 0;
var y = img.Height - 20;
string watermarkText;
if (img.Width <= 220) {
watermarkText = "shorter copy";
} else {
if (img.Width < 500)
watermarkText = "longer copy";
else
watermarkText = "full copy " + link;
}
var bp = new Bitmap(img);
img.Dispose();
using (var gr = Graphics.FromImage(bp)) {
var watermark = Image.FromFile(watermarkPathPng);
gr.DrawImage(watermark, x, y, watermark.Width, watermark.Height);
watermark.Dispose();
gr.DrawString(watermarkText, new Font(new FontFamily("Batang"), 13, FontStyle.Regular, GraphicsUnit.Pixel), Brushes.Black, 5, y + 5);
}
return bp;
}
The reason why I've used Bitmap inside this method is that I had Indexed Pixels problem before and that solved the issue.
Try copying the image rather than operating directly on the one you loaded. Often gdi errors are caused by you trying to change something that you think you "own" when the loader thinks it owns it.
As it only happens for some images, check the logic in your three processing methods - perhaps the failure only occurs if you need to do a vertical resize, in which case look for a bug in that method. Our maybe it's images that have to be processed by more than one of your methods.
It is also suspicious that you return img and pass it by reference in ApplyWatermark(). Check your logic here.
As I've not a great knowledge of GDI+, I'm not fully sure what has happened, but changing the following code line:
var ms = new MemoryStream();
//problem is here
img.Save(ms, ImageFormat.Jpeg);
img.Dispose();
return ms;
to this,
var ms = new MemoryStream();
var bit = new Bitmap(img);
img.Dispose();
bit.Save(ms, ImageFormat.Jpeg);
bit.Dispose();
return ms;
Worked well, so I just create a Bitmap from the image, then save the Bitmap to the Memory. Now this function is working well for all the images.

C# Image Resizing - Losing EXIF

Yes yes... I've seen other posts related to this issue, and yes... I've googled about it.
But so far, I was not able to get to the result I need.
I'm loading a large image taken in 300 dpi, and I need to resize it.
I know... I know... dpi is relative and doesn't really matter... what matters are the dimensions in pixels:
DPI is essentially the number of pixels that correspond to an inch when the image is printed not when it is viewed on a screen. Therefore by increasing the DPI of the image, you do not increase the size of the image on the screen. You only increase the quality of print.
Even though the DPI information stored in the EXIF of an image is somewhat useless, it is causing me problems.
The image I'm resizing is losing the original exif information, including the horizontal and vertical resolution (dpi), and thus it is saving with a default of 96 dpi. Possible reason to this is that only JPEG and another format can hold metadata information.
The end image result is should look like this: 275x375 at 300dpi
Instead is looking like this: 275x375 at 96dpi
You can argue that they are they same, and I agree, but we have a corel draw script that used to load these images, and since this dpi information is different, it places it in different sizes on the document.
Here's what I'm using for resizing:
public System.Drawing.Bitmap ResizeImage(System.Drawing.Image image, int width, int height)
{
Bitmap result = new Bitmap(width, height);
// set the resolutions the same to avoid cropping due to resolution differences
result.SetResolution(image.HorizontalResolution, image.VerticalResolution);
//use a graphics object to draw the resized image into the bitmap
using (Graphics graphics = Graphics.FromImage(result))
{
//set the resize quality modes to high quality
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//draw the image into the target bitmap
graphics.DrawImage(image, 0, 0, result.Width, result.Height);
}
//return the resulting bitmap
return result;
}
That does the work very well, but loses the EXIF information.
Setting the SetResolution to SetResolution(300, 300) does not work!
I looked at reading and changing the EXIF information of an image, and I've tried:
public void setImageDpi(string Filename, string NewRes)
{
Image Pic;
PropertyItem[] PropertyItems;
byte[] bDescription = new Byte[NewRes.Length];
int i;
string FilenameTemp;
System.Drawing.Imaging.Encoder Enc = System.Drawing.Imaging.Encoder.Transformation;
EncoderParameters EncParms = new EncoderParameters(1);
EncoderParameter EncParm;
ImageCodecInfo CodecInfo = GetEncoderInfo("image/jpeg");
// copy description into byte array
for (i = 0; i < NewRes.Length; i++) bDescription[i] = (byte)NewRes[i];
// load the image to change
Pic = Image.FromFile(Filename);
foreach (PropertyItem item in Pic.PropertyItems)
{
if (item.Id == 282 || item.Id == 283)
{
PropertyItem myProperty = item;
myProperty.Value = bDescription;
myProperty.Type = 2;
myProperty.Len = NewRes.Length;
Pic.SetPropertyItem(item);
Console.WriteLine(item.Type);
}
}
// we cannot store in the same image, so use a temporary image instead
FilenameTemp = Filename + ".temp";
// for lossless rewriting must rotate the image by 90 degrees!
EncParm = new EncoderParameter(Enc, (long)EncoderValue.TransformRotate90);
EncParms.Param[0] = EncParm;
// now write the rotated image with new description
Pic.Save(FilenameTemp, CodecInfo, EncParms);
// for computers with low memory and large pictures: release memory now
Pic.Dispose();
Pic = null;
GC.Collect();
// delete the original file, will be replaced later
System.IO.File.Delete(Filename);
// now must rotate back the written picture
Pic = Image.FromFile(FilenameTemp);
EncParm = new EncoderParameter(Enc, (long)EncoderValue.TransformRotate270);
EncParms.Param[0] = EncParm;
Pic.Save(Filename, CodecInfo, EncParms);
// release memory now
Pic.Dispose();
Pic = null;
GC.Collect();
// delete the temporary picture
System.IO.File.Delete(FilenameTemp);
}
That didn't work either.
I tried looking and changing the EXIF information for DPI (282 and 283) later in the process as such:
Encoding _Encoding = Encoding.UTF8;
Image theImage = Image.FromFile("somepath");
PropertyItem propItem282 = theImage.GetPropertyItem(282);
propItem282.Value = _Encoding.GetBytes("300" + '\0');
theImage.SetPropertyItem(propItem282);
PropertyItem propItem283 = theImage.GetPropertyItem(283);
propItem283.Value = _Encoding.GetBytes("300" + '\0');
theImage.SetPropertyItem(propItem283);
theImage.Save("somepath");
But the program crashes saying that Property Cannot be Found.
If the property doesn't exist, apparently I can't add it:
A PropertyItem is not intended to be used as a stand-alone object. A PropertyItem object is intended to be used by classes that are derived from Image. A PropertyItem object is used to retrieve and to change the metadata of existing image files, not to create the metadata. Therefore, the PropertyItem class does not have a defined Public constructor, and you cannot create an instance of a PropertyItem object.
I'm stuck... all I need is a resized image with a dpi set to 300, it shouldn't be so hard.
Any help much appreciated. Thanks
The following code worked for me:
const string InputFileName = "test_input.jpg";
const string OutputFileName = "test_output.jpg";
var newSize = new Size(640, 480);
using (var bmpInput = Image.FromFile(InputFileName))
{
using (var bmpOutput = new Bitmap(bmpInput, newSize))
{
foreach (var id in bmpInput.PropertyIdList)
bmpOutput.SetPropertyItem(bmpInput.GetPropertyItem(id));
bmpOutput.SetResolution(300.0f, 300.0f);
bmpOutput.Save(OutputFileName, ImageFormat.Jpeg);
}
}
When I inspect the output file I can see EXIF data and the DPI has been changed to 300.

Image resize optimization causes memory usage to sky rocket

I made a function that takes an original image and resizes it to 3 different zoom scales -> 16x, 10x, and 4x. For a better understanding, continue reading this paragraph. Let's say the original image is 1000x1000. I declare that at 1x zoom it's dimensions will be 50x50. That means 4x zoom will be 200x200, 10x zoom will be 500x500 and 16x zoom will be 800x800. So my function needs to resize the original 1000x1000 down to 800x800, then down to 500x500, then down to 200x200. Please note I have done this successfully and my question is regarding memory usage.
Below I have two methods of doing so. Both methods work, but one causes a HUGE memory usage bloat using approximately 3x/4x more memory than the other... I like the 2nd method better, because it loads significantly faster than the first method because it's not resizing each of the 3 images from the original image, instead it's resizing them from the previously resized image.
Notes: I'm using Xcode Instruments to measure memory usage. The ImageResizer Class contains a function called "Resize" which resizes the Image.
Method 1.)
public List<UIImage> InitImageList_BFObjects ( UIImage image, SizeF frameSize )
{
List<UIImage> listOfImages = new List<UIImage>();
for ( int i = 0; i < 3; i++ )
{
if ( i == 0 )
zoomScale = 16f;
else if ( i == 1 )
zoomScale = 10f;
else// if ( i == 2 )
zoomScale = 4f;
Resizer = new ImageResizer(image);
Resizer.Resize(frameSize.Width * zoomScale, frameSize.Height * zoomScale);
UIImage resizedImage = Resizer.ModifiedImage;
listOfImages.Insert(0, resizedImage);
}
return listOfImages;
}
Method 1 works and uses very little memory usage. I ran this with a group of about 20 images. My app had about 14mb of memory usage after this loaded (using Xcodes Instruments to examine memory usage)
Method 2.)
public List<UIImage> InitImageList_BFObjects ( UIImage image, SizeF frameSize )
{
List<UIImage> listOfImages = new List<UIImage>();
for ( int i = 0; i < 3; i++ )
{
if ( i == 0 )
zoomScale = 16f;
else if ( i == 1 )
zoomScale = 10f;
else// if ( i == 2 )
zoomScale = 4f;
if ( listOfImages.Count == 0 )
{
Resizer = new ImageResizer(image);
Resizer.Resize(frameSize.Width * zoomScale, frameSize.Height * zoomScale);
UIImage resizedImage = Resizer.ModifiedImage;
listOfImages.Insert(0, resizedImage);
}
else
{
// THIS LINE CONTAINS THE MAIN DIFFERENCE BETWEEN METHOD 1 AND METHOD 2
// Notice how it resizes from the most recent image from listOfImages rather than the original image
Resizer = new ImageResizer(listOfImages[0]);
Resizer.Resize(frameSize.Width * zoomScale, frameSize.Height * zoomScale);
UIImage resizedImage = Resizer.ModifiedImage;
listOfImages.Insert(0, resizedImage);
}
}
return listOfImages;
}
Method 2 works but the memory usage sky rockets! I ran this with the same group of about 20 images. My app had over 60mb of memory usage after this loaded (using Xcodes Instruments to examine memory usage) Why is the memory usage so high? What is it about Method 2 that causes the memory to sky rocket? It's almost as if a variable is not getting cleaned up properly
* Additional Information, ImageResizer Class **
I cut out the non-needed functions from my ImageResizer Class and renamed it "ImageResizer_Abridged". I even switched over to using this class to make sure I didn't accidentally cut out anything needed.
public class ImageResizer_Abridged
{
UIImage originalImage = null;
UIImage modifiedImage = null;
public ImageResizer_Abridged ( UIImage image )
{
this.originalImage = image;
this.modifiedImage = image;
}
/// <summary>
/// strech resize
/// </summary>
public void Resize( float width, float height )
{
UIGraphics.BeginImageContext( new SizeF( width, height ) );
//
modifiedImage.Draw( new RectangleF( 0,0, width, height ) );
modifiedImage = UIGraphics.GetImageFromCurrentImageContext();
//
UIGraphics.EndImageContext();
}
public UIImage OriginalImage
{
get
{
return this.originalImage;
}
}
public UIImage ModifiedImage
{
get
{
return this.modifiedImage;
}
}
}
I created a simplified test project showing this problem *
Here is a dropbox link to the project: https://www.dropbox.com/s/4w7d87nn0aafph9/TestMemory.zip
Here is Method 1's Xcode Instruments screen shot as evidence (9 mb memory usage):
http://i88.photobucket.com/albums/k194/lampshade9909/AllImagesResizedFromOriginalImage_zps585228c6.jpg
Here is Method 2's Xcode Instruments screens hot as evidence (55 mb memory usage):
http://i88.photobucket.com/albums/k194/lampshade9909/SignificantIncreaseInMemoryUsage_zps19034bad.jpg
Below is the code block needed to run the test project
// Initialize My List of Images
ListOfImages = new List<UIImage>();
for ( int i = 0; i < 30; i++ )
{
// Create a UIImage Containing my original Image
UIImage originalImage = UIImage.FromFile ("b2Bomber.png");
float newWidth = 100f;
float newHeight = 40f;
float zoomScale;
float resizedWidth, resizedHeight;
UIImage resizedImage1;
UIImage resizedImage2;
// Basically, I want to take the originalImage Image and resize it twice.
// Method 1.) Resize the originalImage and save it as ResizedImage1. Resize the originalImage and save it as ResizedImage2. We're finished!
// Method 2.) Resize the originalImage and save it as ResizedImage1. Resize ResizedImage1 and save it as ResizedImage2. We're finished!
// The pro to Method 1 is that we get the best possible quaility on all resized images. The con is, this takes a long time if we're doing dozens of very large images
// The pro to Method 2 is that it's faster than Method 1. This is why I want to use Method 2, it's speed. But it has a HUGE con, it's memory usage.
// Please run this project on an iPad connected to XCodes Instruments to monitor memory usage and see what I mean
zoomScale = 10f;
resizedWidth = newWidth*zoomScale;
resizedHeight = newHeight*zoomScale;
UIGraphics.BeginImageContext( new SizeF( resizedWidth, resizedHeight ) );
originalImage.Draw( new RectangleF( 0, 0, resizedWidth, resizedHeight ) );
resizedImage1 = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
zoomScale = 4f;
resizedWidth = newWidth*zoomScale;
resizedHeight = newHeight*zoomScale;
UIGraphics.BeginImageContext( new SizeF( resizedWidth, resizedHeight ) );
// Run this project on an iPad and examine the memory usage in XCode's Instruments.
// The Real Memory Usage will be aroud 9 MB.
// Uncomment this "originalImage.Draw" line to see this happening, make sure to comment out the "resizedImage1.Draw" line
// originalImage.Draw( new RectangleF( 0, 0, resizedWidth, resizedHeight ) );
// Run this project on an iPad and examine the memory usage in XCode's Instruments.
// The Real Memory Usage will be aroud 55 MB!!
// My question is, why does the memory sky rocket when doing this, and how can I prevent the memory from sky rocketing??
// My App requires me to resize around a hundred images and I want to be able to resize an already resized image (like in this example) without the memory usage sky rocketing like this...
// Uncomment this "resizedImage1.Draw" line to see this happening, make sure to comment out the "originalImage.Draw" line
resizedImage1.Draw( new RectangleF( 0, 0, resizedWidth, resizedHeight ) );
resizedImage2 = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
// Add my resized images to the list of Images
ListOfImages.Add (resizedImage1);
ListOfImages.Add (resizedImage2);
}
I'm not sure about your Resize code but I have seen Scale do strange thing. It's not really strange, once you dig into it, but it's definitively not obvious.
Creating an UIImage can be very cheap, memory wise, as long as its backing CGImage is not created. IOW iOS might not immediately allocate a new CGImage backing image that match the new size. That allocation will be differed until the CGImage is needed.
In such case it's possible for some code (like your method 1) to require almost no additional memory when scaling up. However your 2nd method is using a scaled-up image (and will need to allocate the backing CGImage to do so) so it end up requiring the memory earlier.
How can you check for this ?
Compare your resizedImage.Size with the resizedImage.CGImage.Size. If they don't match then you're likely hitting caching.
Notes
I say might because the caching logic is unknown (undocumented). I know that this can differ from running on the simulator and devices - and it also vary between iOS versions;
Caching is a good thing - but it can be surprising :-) I just wish this was documented.
Have you checked whether Resizer implements the Dispose() method? I don't see you disposing it anywhere.
I believe your new code line is implementing the Zoom on the entire image, hence the increased memory usage.
Resizer is zooming the ENTIRE image at the new zoomed in scale, so that an incoming 4MB image is zoomed to 8MB, 16MB and 32MB, consuming your memory.
UIImage implements IDisposable, so something will have to Dispose of it eventually. The Resize method appears to "lose" the reference to modifiedImage, so I'm going to Dispose() it. Hopefully the caller is doing the same to all images in the list returned by InitImageList_BFObjects when it's done with them. Or that class implements IDisposable and it's kicked up the line of who has to deal with it. But rest assured, these images being created need to be Dispose()d somewhere sometime.
public class ImageResizer_Abridged
{
private readonly UIImage originalImage;
private UIImage modifiedImage;
public ImageResizer_Abridged(UIImage image)
{
this.originalImage = image;
this.modifiedImage = image;
}
/// <summary>
/// stretch resize
/// </summary>
public void Resize(float width, float height)
{
UIGraphics.BeginImageContext(new SizeF(width, height));
//
var oldImage = this.modifiedImage;
this.modifiedImage.Draw(new RectangleF(0, 0, width, height));
this.modifiedImage = UIGraphics.GetImageFromCurrentImageContext();
oldImage.Dispose();
//
UIGraphics.EndImageContext();
}
public UIImage OriginalImage
{
get
{
return this.originalImage;
}
}
public UIImage ModifiedImage
{
get
{
return this.modifiedImage;
}
}
}

Create thumbnail in .NET C# by defining width

I am using the following code in my website, for thumbnail creation:
string furl = "~/images/thumbs/" + matchString;
lBlogThumb.ImageUrl = GetThumbnailView(furl, 200, 200);
private string GetThumbnailView(string originalImagePath, int height, int width)
{
//Consider Image is stored at path like "ProductImage\\Product1.jpg"
//Now we have created one another folder ProductThumbnail to store thumbnail image of product.
//So let name of image be same, just change the FolderName while storing image.
string thumbnailImagePath = originalImagePath.Replace("thumbs", "thumbs2");
//If thumbnail Image is not available, generate it.
if (!System.IO.File.Exists(Server.MapPath(thumbnailImagePath)))
{
System.Drawing.Image imThumbnailImage;
System.Drawing.Image OriginalImage = System.Drawing.Image.FromFile(Server.MapPath(originalImagePath));
imThumbnailImage = OriginalImage.GetThumbnailImage(width, height,
new System.Drawing.Image.GetThumbnailImageAbort(ThumbnailCallback), IntPtr.Zero);
imThumbnailImage.Save(Server.MapPath(thumbnailImagePath), System.Drawing.Imaging.ImageFormat.Jpeg);
imThumbnailImage.Dispose();
OriginalImage.Dispose();
}
return thumbnailImagePath;
}
public bool ThumbnailCallback() { return false; }
I would like to change this code, and be able to create a thumbnail defining width ONLY. What I have in mind is actually something like cropping/resizing image, using a static width, maintaining it's ratio. Is that possible;
You mention resizing and cropping. If you want the thumbnail heights to vary with a fixed width, the answers provided already will work for you.
The mention of cropping makes me think that you may want a fixed thumbnail size, with the width filled and any overflowing vertical portion cropped off. If that is the case, you'll need to do a bit more work. I needed something similar recently, and this is what I came up with.
This will create a thumbnail of the original that is sized and cropped in such a way that the source image completely fills the target thumbnail, cropping any overflow. There will be no borders within the thumbnail, even if the original and thumbnail aspect ratios are different.
public System.Drawing.Image CreateThumbnail(System.Drawing.Image image, Size thumbnailSize)
{
float scalingRatio = CalculateScalingRatio(image.Size, thumbnailSize);
int scaledWidth = (int)Math.Round((float)image.Size.Width * scalingRatio);
int scaledHeight = (int)Math.Round((float)image.Size.Height * scalingRatio);
int scaledLeft = (thumbnailSize.Width - scaledWidth) / 2;
int scaledTop = (thumbnailSize.Height - scaledHeight) / 2;
// For portrait mode, adjust the vertical top of the crop area so that we get more of the top area
if (scaledWidth < scaledHeight && scaledHeight > thumbnailSize.Height)
{
scaledTop = (thumbnailSize.Height - scaledHeight) / 4;
}
Rectangle cropArea = new Rectangle(scaledLeft, scaledTop, scaledWidth, scaledHeight);
System.Drawing.Image thumbnail = new Bitmap(thumbnailSize.Width, thumbnailSize.Height);
using (Graphics thumbnailGraphics = Graphics.FromImage(thumbnail))
{
thumbnailGraphics.CompositingQuality = CompositingQuality.HighQuality;
thumbnailGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
thumbnailGraphics.SmoothingMode = SmoothingMode.HighQuality;
thumbnailGraphics.DrawImage(image, cropArea);
}
return thumbnail;
}
private float CalculateScalingRatio(Size originalSize, Size targetSize)
{
float originalAspectRatio = (float)originalSize.Width / (float)originalSize.Height;
float targetAspectRatio = (float)targetSize.Width / (float)targetSize.Height;
float scalingRatio = 0;
if (targetAspectRatio >= originalAspectRatio)
{
scalingRatio = (float)targetSize.Width / (float)originalSize.Width;
}
else
{
scalingRatio = (float)targetSize.Height / (float)originalSize.Height;
}
return scalingRatio;
}
To use with your code, you could replace your call to OriginalImage.GetThumbnailImage with this:
imThumbnailImage = CreateThumbnail(OriginalImage, new Size(width, height));
Note that for portrait images, this code will actually shift the thumbnail's viewport slightly higher on the original image. This was done so that portrait shots of people wouldn't result in headless torsos when the thumbnails were created. If you don't want that logic, simply remove the if block following the "portrait mode" comment.
let's have originalWidth=the original image width and thumbWidth. You can simply choose the thumbWidth to your desired value, and calculate the thumbHeigth=originalHeigth*thumbWidth/originalWidth
I got sick of needed to do this and created a lib that does this easily: Link To Documentation & Download
A basic example without rounding with a thumbnail width of 140, below the 'file' is a HttpPostedFile uploaded from an ASP.Net FileUpload control, the HttpPostedFile exposes a stream.
// Save images to disk.
using (System.Drawing.Image image = System.Drawing.Image.FromStream(file.InputStream))
using (System.Drawing.Image thumbnailImage = image.GetThumbnailImage(140, Convert.ToInt32((image.Height / (image.Width / 140))), null, IntPtr.Zero))
{
if (image != null)
{
image.Save(imageFilePath);
thumbnailImage.Save(thumbnailImagePath);
}
else
throw new ArgumentNullException("Image stream is null");
}

Categories