C#: Preserving Bits per Plane when resizing images - c#

I'm developing a service in which users can upload images and save them in a library. I want to preserve the original file (so people can upload high resolution images), but also make a copy of the image to use as a thumbnail.
The problem I have is that the thumbnail "weights" much more than the original file, proportionally. When I check the basic properties of each file (in XnView), I can see that the original files are for instance saved with 32 bits per Plane, whereas the source file will have, for instance, only 24 bits per plane.
What would be the correct way to make a copy of the original file while still using compression? This is an excerpt of the code:
private void ResizeImage(string originalFile, string NewFile, int NewWidth, int MaxHeight, bool OnlyResizeIfWider, string directory)
{
System.Drawing.Image FullsizeImage = System.Drawing.Image.FromFile(originalFile);
// Prevent using images internal thumbnail
FullsizeImage.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone);
FullsizeImage.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone);
if (OnlyResizeIfWider)
{
if (FullsizeImage.Width <= NewWidth)
{
NewWidth = FullsizeImage.Width;
}
}
int NewHeight = FullsizeImage.Height * NewWidth / FullsizeImage.Width;
if (NewHeight > MaxHeight)
{
// Resize with height instead
NewWidth = FullsizeImage.Width * MaxHeight / FullsizeImage.Height;
NewHeight = MaxHeight;
}
System.Drawing.Image NewImage = FullsizeImage.GetThumbnailImage(NewWidth, NewHeight, null, IntPtr.Zero);
// Clear handle to original file so that we can overwrite it if necessary
FullsizeImage.Dispose();
// Save resized picture
NewImage.Save(directory + Path.DirectorySeparatorChar + NewFile);
}

I would suggest you save the image as a PNG or JPEG stream. That will be much more efficient. Considering that the thumbnail is not the original data, it is probably OK to apply strong compression on it (QF < 60). I once managed to get useful JPEG images in less than 1000 bytes. At that point, they're so small you can consider putting them in a database rather than on disk.
EDIT: (after reading the question fully :) )
And to answer the question, the result of GetThumbnailForImage(), is again an Image so you can call the Save method on it, one of the overloads allows you to specify which compression to use.

Rather than calling GetThumbnailImage, you can create a new image of the size you want and the PixelFormat that you want, and then scale the original image into that.
System.Drawing.Image FullsizeImage = System.Drawing.Image.FromFile(originalFile);
// Create a bitmap that's NewWidth x NewHeight
Bitmap bmp = new Bitmap(NewWidth, NewHeight, PixelFormat.Format24bppRgb);
// Get a Graphics object for that image and draw the original image into it
using (Graphics g = Graphics.FromImage(bmp))
{
g.DrawImage(FullsizeImage, 0, 0, NewWidth, NewHeight);
}
// you now have a 24bpp thumbnail image

Related

Create a bitmap in c# with specific pixelformat from an image without loading the image

sorry if this question is silly, i'm just starting with C#
I have an image in disk which I know the path. The image is 20000x10000 aprox in size and around 400MB in size (i know this looking at the image)
I need to load it in the code and resize it since the progam dies if I try to put in a picture box 400m of image, but if I do
Bitmap b0 = new Bitmap(pathImage);
int newWidth = (int)(b0.Width * escala);
int newHeight = (int)(b0.Height * escala);
// Convert other formats (including CMYK) to RGB.
Bitmap newImage = new Bitmap(newWidth, newHeight, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
// Draws the image in the specified size with quality mode set to HighQuality
using (Graphics graphics = Graphics.FromImage(newImage))
{
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.DrawImage(b0, 0, 0, newWidth, newHeight);
}
But when I do Bitmap b0 = new Bitmap(pathImage); it just dies since 20000x10000x32 it's too much for my poor memory.
The problem is that if not specified, c# just uses as pixel format Format32bppArgb but I want Format1bppIndexed, and the only way i've found to change the format is with the constructor
Bitmap(
int width,
int height,
int stride,
PixelFormat format,
IntPtr scan0
)
But for this constructor I need to know the size of the image, which in the code I don't know because it dies before the code can get it and know the size...
I'm lost. Any ideas?
EDIT:
Doing this
int width;
int height;
Image tif;
using (FileStream file = new FileStream(rutaImagenEntrada, FileMode.Open, FileAccess.ReadWrite))
{
using ( tif = Image.FromStream(stream: file,
useEmbeddedColorManagement: false,
validateImageData: false))
{
width = (int)tif.PhysicalDimension.Width;
height = (int)tif.PhysicalDimension.Height;
}
file.Close();
}
I get the size of the image I cannot load, and can create a bitmap with the pixelformat and size that i wanted, BUT everyway I try to upload it doesn't work, it still crashes on me.
I've tried using graphics .draw(tiff...) and tiff.getThumbnail(new size...) and it just dies on me.
It targets x86 and the .Net 3.5 since those are the requirements of the client...
Any ideas?
If FileStream works to open the file, you might want to give some of the other constructors of Bitmap a try, in particular Bitmap(Stream).
If that doesn't help, you might try loading the raw bmp data, creating a Bitmap with the format you want and copying the data into the Bitmap manually (I'm not familiar with C# Bitmaps, but there should be some way of accessing the data).
Hope this helps.

Image cropping losing quality MVC3 .Net C# Application

I'm having trouble cropping an image in an MVC3 C# application. I am cropping the image but its losing quality when rendered back in a view.
The image to be cropped is loaded from a database and is created from a ByteArray like so...
public static Image ByteArrayToImage(byte[] byteArrayIn)
{
MemoryStream ms = new MemoryStream(byteArrayIn);
Image returnImage = Image.FromStream(ms);
return returnImage;
}
When this image is rendered on a view it looks fine and is off the expected quality.
I am then selecting a region to crop and using the below method to do the crop...
public Image CropImage(System.Drawing.Image Image, int Height, int Width, int StartAtX, int StartAtY)
{
Image outimage;
MemoryStream mm = null;
try
{
//check the image height against our desired image height
if (Image.Height < Height)
{
Height = Image.Height;
}
if (Image.Width < Width)
{
Width = Image.Width;
}
//create a bitmap window for cropping
Bitmap bmPhoto = new Bitmap(Width, Height, PixelFormat.Format24bppRgb);
bmPhoto.SetResolution(Image.VerticalResolution,Image.HorizontalResolution);
//create a new graphics object from our image and set properties
Graphics grPhoto = Graphics.FromImage(bmPhoto);
grPhoto.SmoothingMode = SmoothingMode.AntiAlias;
grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic;
grPhoto.PixelOffsetMode = PixelOffsetMode.HighQuality;
//now do the crop
grPhoto.DrawImage(Image, new Rectangle(0, 0, Width, Height), StartAtX, StartAtY, Width, Height, GraphicsUnit.Pixel);
// Save out to memory and get an image from it to send back out the method.
mm = new MemoryStream();
bmPhoto.Save(mm, System.Drawing.Imaging.ImageFormat.Jpeg);
Image.Dispose();
bmPhoto.Dispose();
grPhoto.Dispose();
outimage = Image.FromStream(mm);
return outimage;
}
catch (Exception ex)
{
throw new Exception("Error cropping image, the error was: " + ex.Message);
}
}
This cropped image is then converted back to a ByteArray and can be saved into a database like so...
public static byte[] ImageToByteArray(System.Drawing.Image imageIn)
{
MemoryStream ms = new MemoryStream();
imageIn.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);
return ms.ToArray();
}
When this image is later rendered on the view its quality is a lot worse than the original image. It looks quite pixilated.
The original image in this case is .jpg but could be any format.
This is an image of the image after it has been loaded from the database and being cropped...
This image is the result of the crop. As you can see its not good.
I have seen some other posts on the topic but they haven’t helped. Can anyone suggest a solution?
Thank you
This is a great blog that sums it all up. I have it working in my application now. http://blog.tallan.com/2011/02/04/using-mvc3-razor-helpers-and-jcrop-to-upload-and-crop-images/
Gunnar Peipman has a blog post about the quality called Resizing images without loss of quality. Gunnar says some trick about what is going wrong;
If the Image contains an embedded thumbnail image, this method
retrieves the embedded thumbnail and scales it to the requested size.
If the Image does not contain an embedded thumbnail image, this method
creates a thumbnail image by scaling the main image.
The GetThumbnailImage method works well when the requested thumbnail
image has a size of about 120 x 120 pixels. If you request a large
thumbnail image (for example, 300 x 300) from an Image that has an
embedded thumbnail, there could be a noticeable loss of quality in the
thumbnail image. It might be better to scale the main image (instead
of scaling the embedded thumbnail) by calling the DrawImage method.
And razor syntax is amicable with JCrop and there is a cool blog post about how you can use it.

Resize Image of any size to fixed dimension using C# ASP.Net web form

I have done image resizing while allowing user to upload a specific size image and then crop them to different dimension i have also used jCrop in project to allow users to upload a image of specific size and then select the image area & crop it accordingly.
In new project i have a requirement where user can upload any size image which is at least larger than 500Px in width and then i have to allow user to select the part of image using jCrop and then save image in different dimension of 475x313 , 310x205 while maintaining the aspect ration.
I can do it with if i allow the used to upload a fixed size image but i am not sure how i can handle variable size image.
I also need to display the image uploaded before cropping in a fixed size box.. let us say 300x200. in this area i have to allow the user to select the part of the image before i can crop.
Issue i am facing is how to handle variable length image and show it is a fixed image box of 300x200px.
I wrote an article on using jCrop with dynamically-resized uploaded images, which seems to be what you're needing.
If you're looking for an open-source ASP.NET control that does it for you, check out cropimage.net.
Want to going to by programmatically than you can try this :
if you are using file upload for upload images
string path = Path.GetFileName(fileuploaderID.PostedFile.FileName);
ConvertThumbnails(width, height, fileuploaderID.FileBytes, path);
your function
public void ConvertThumbnails(int width, int height, byte[] filestream, string path)
{
// create an image object, using the filename we just retrieved
var stream = new MemoryStream(filestream);
System.Drawing.Image image = System.Drawing.Image.FromStream(stream);
try
{
int fullSizeImgWidth = image.Width;
int fullSizeImgHeight = image.Height;
float imgWidth = 0.0F;
float imgHeight = 0.0F;
imgWidth = width;
imgHeight = height;
Bitmap thumbNailImg = new Bitmap(image, (int)imgWidth, (int)imgHeight);
MemoryStream ms = new MemoryStream();
// Save to memory using the Jpeg format
thumbNailImg.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
// read to end
byte[] bmpBytes = ms.GetBuffer();
item.Attachments.Add(path, bmpBytes);
thumbNailImg.Dispose();
ms.Close();
}
catch (Exception)
{
image.Dispose();
}
}

Determine file type from ImageFormat.MemoryBMP

After resizing an image, my resize function returns a newly drawn Image. I'm running into an issue where I need to determine what the file extension of the returned Image should be. I was using the Image.RawFormat property previously but everytime an Image is returned from this function it has ImageFormat.MemoryBMP, rather than ImageFormat.Jpeg or ImageFormat.Gif for example.
So basically my question is, how can I determine what file type the newly resized Image should be?
public static Image ResizeImage(Image imageToResize, int width, int height)
{
// Create a new empty image
Image resizedImage = new Bitmap(width, height);
// Create a new graphic from image
Graphics graphic = Graphics.FromImage(resizedImage);
// Set graphics modes
graphic.SmoothingMode = SmoothingMode.HighQuality;
graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
// Copy each property from old iamge to new image
foreach (var prop in imageToResize.PropertyItems)
{
resizedImage.SetPropertyItem(prop);
}
// Draw the new Image at the resized size
graphic.DrawImage(imageToResize, new Rectangle(0, 0, width, height));
// Return the new image
return resizedImage;
}
The resized image is not in any file based format, it is an uncompressed memory representation of the pixels in the image.
To save this image back to disk the data needs to be encoded in the selected format, which you have to specify. Look at the Save method, it takes an ImageFormat as a second argument, make that Jpeg or whatever format best fits your application.

Image resizing - sometimes very poor quality?

I'm resizing some images to the screen resolution of the user; if the aspect ratio is wrong, the image should be cut.
My code looks like this:
protected void ConvertToBitmap(string filename)
{
var origImg = System.Drawing.Image.FromFile(filename);
var widthDivisor = (double)origImg.Width / (double)System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width;
var heightDivisor = (double)origImg.Height / (double)System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height;
int newWidth, newHeight;
if (widthDivisor < heightDivisor)
{
newWidth = (int)((double)origImg.Width / widthDivisor);
newHeight = (int)((double)origImg.Height / widthDivisor);
}
else
{
newWidth = (int)((double)origImg.Width / heightDivisor);
newHeight = (int)((double)origImg.Height / heightDivisor);
}
var newImg = origImg.GetThumbnailImage(newWidth, newHeight, null, IntPtr.Zero);
newImg.Save(this.GetBitmapPath(filename), System.Drawing.Imaging.ImageFormat.Bmp);
}
In most cases, this works fine. But for some images, the result has an extremely poor quality. It looks like the would have been resized to something very small (thumbnail size) and enlarged again.. But the resolution of the image is correct. What can I do?
Example orig image:
alt text http://img523.imageshack.us/img523/1430/naturaerowoods.jpg
Example resized image:
Note: I have a WPF application but I use the WinForms function for resizing because it's easier and because I already need a reference to System.Windows.Forms for a tray icon.
Change the last two lines of your method to this:
var newImg = new Bitmap(newWidth, newHeight);
Graphics g = Graphics.FromImage(newImg);
g.DrawImage(origImg, new Rectangle(0,0,newWidth,newHeight));
newImg.Save(this.GetBitmapPath(filename), System.Drawing.Imaging.ImageFormat.Bmp);
g.Dispose();
I cannot peek into the .NET source at the moment, but most likely the problem is in the Image.GetThumbnailImage method. Even MSDN says that "it works well when the requested thumbnail image has a size of about 120 x 120 pixels, but it you request a large thumbnail image (for example, 300 x 300) from an Image that has an embedded thumbnail, there could be a noticeable loss of quality in the thumbnail image". For true resizing (i.e. not thumbnailing), you should use the Graphics.DrawImage method. You may also need to play with the Graphics.InterpolationMode to get a better quality if needed.
If you're not creating a thumbnail, using a method called GetThumbnailImage probably isn't a good idea...
For other options, have a look at this CodeProject article. In particular, it creates a new image, creates a Graphics for it and sets the interpolation mode to HighQualityBicubic and draws the original image onto the graphics. Worth a try, at least.
As indicated on MSDN, GetThumbnailImage() is not designed to do arbitrary image scaling. Anything over 120x120 should be scaled manually. Try this instead:
using(var newImg = new Bitmap(origImg, newWidth, newHeight))
{
newImg.Save(this.GetBitmapPath(filename), System.Drawing.Imaging.ImageFormat.Bmp);
}
Edit
As a point of clarification, this overload of the Bitmap constructor calls Graphics.DrawImage, though you do not have any control over the interpolation.
instead of this code:
newImg.Save(this.GetBitmapPath(filename), System.Drawing.Imaging.ImageFormat.Bmp);
use this one :
System.Drawing.Imaging.ImageCodecInfo[] info = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders();
System.Drawing.Imaging.EncoderParameters param = new System.Drawing.Imaging.EncoderParameters(1);
param.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
newImg.Save(dest_img, info[1], param);
For examples, the original image is JPG and the resized image is PNG. Are you converting between formats on purpose? Switching between different lossey compression schemes can cause quality loss.
Are you increasing or decreasing the size of the image when you resize it? If you are creating a larger image from a smaller one, this sort of degradation is to be expected.
Images will definitely be degraded if you enlarge them.
Some camera's put a resized thumbnail into the file itself presumably for preview purposes on the device itself.
The GetThumbnail method actually gets this Thumbnail image which is embedded within the image file instead of getting the higher res method.
The easy solution is to trick .Net into throwing away that thumbnail information before doing your resize or other operation. like so....
img.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipX);
//removes thumbnails from digital camera shots
img.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipX);
If you are attempting to resize constraining proportions I wrote an extension method on System.Drawing.Image that you might find handy.
/// <summary>
///
/// </summary>
/// <param name="img"></param>
/// <param name="size">Size of the constraining proportion</param>
/// <param name="constrainOnWidth"></param>
/// <returns></returns>
public static System.Drawing.Image ResizeConstrainProportions(this System.Drawing.Image img,
int size, bool constrainOnWidth, bool dontResizeIfSmaller)
{
if (dontResizeIfSmaller && (img.Width < size))
return img;
img.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipX);
img.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipX);
float ratio = 0;
ratio = (float)img.Width / (float)img.Height;
int height, width = 0;
if (constrainOnWidth)
{
height = (int)(size / ratio);
width = size;
}
else
{
width = (int)(size * ratio);
height = size;
}
return img.GetThumbnailImage(width, height, null, (new System.IntPtr(0)));
}
This is going to vary widely based on the following factors:
How closely the destination resolution matches a "natural" scale of the original resolution
The source image color depth
The image type(s) - some are more lossy than others

Categories

Resources