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.
Related
This code basically takes an image and crops it according to a detected image (in this case, it detects a decentralized fingerprint, and returns a new Bitmap with the fingerprint centered and cropped).
It turns out that, depending on the image, each resulting Bitmap will have a different size (for example, my test is returning a 425x448 Bitmap, since the identified image has that size), when in fact I need the image to return with a specific size (512x512).
I've already tried to change all the height and width variables of the code, but none satisfy this desired condition. Either it creates a Bitmap with the size of 512x512 and stretches the original image (violating the original ratio), or it creates a 512x512 Bitmap with the cropped image but with a black border on the right and bottom sides.
Any hints of what can be changed or included in the code?
Edit: More clearly, I need to create a 512x512 canvas for the 425x448 image without changing the size or dimensions of the image (it should be 425x448 inside a 512x512 canvas).
private byte[] GetAndCropImage(byte[] image, IEnumerable<YoloItem> yoloItems)
{
byte[] imageRet = null;
var topYoloItem = yoloItems?.Where(x => x.Confidence >= 0.30).OrderByDescending(x => x.Confidence).First();
MemoryStream ms = new MemoryStream(image);
Bitmap src = new Bitmap(ms);
Rectangle cropRect = new Rectangle(topYoloItem.X, topYoloItem.Y, topYoloItem.Width, topYoloItem.Height);
Bitmap target = new Bitmap(cropRect.Width, cropRect.Height);
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(src, new Rectangle(0, 0, target.Width, target.Height),
cropRect,
GraphicsUnit.Pixel);
}
target.SetResolution(512, 512);
ImageConverter converter = new ImageConverter();
imageRet = (byte[])converter.ConvertTo(target, typeof(byte[]));
return imageRet;
}
I have a WPF app which saves out a thumbnail image as png. The code works well enough but when I open the image is very blurry. The image that it grabs comes from the canvas itself. The canvas changes its width and height depending on the image I'm loading. The desired thumbnail size will be 200 x 200 (pixels).
Here is my code
public void CreateThumbail(Canvas canvas, string filename)
{
RenderTargetBitmap rtb = new RenderTargetBitmap(
(int)canvas.ActualWidth,
(int)canvas.ActualHeight,
96, //dip X
96, //dpi Y
PixelFormats.Pbgra32);
rtb.Render(canvas);
PngBitmapEncoder pngImage = new PngBitmapEncoder();
pngImage.Frames.Add(CreateResizedImage(rtb, 200, 200, 0));
using (var filestream = System.IO.File.Create(filename))
{
pngImage.Save(filestream);
}
}
private static BitmapFrame CreateResizedImage(ImageSource source, int width, int height, int margin)
{
var rect = new Rect(margin, margin, width, height);
var group = new DrawingGroup();
RenderOptions.SetBitmapScalingMode(group, BitmapScalingMode.HighQuality);
group.Children.Add(new ImageDrawing(source, rect));
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
drawingContext.DrawDrawing(group);
var resizedImage = new RenderTargetBitmap(
(int)rect.Width, (int)rect.Height,// Resized dimensions
96, 96, // Default DPI values
PixelFormats.Pbgra32); // Default pixel format
resizedImage.Render(drawingVisual);
return BitmapFrame.Create(resizedImage);
}
I saved out the image before I resize it and it looks crisp and sharp. yet when I save out the thumbnail it's ugly and blurry. What am I doing wrong? Am I over-engineering this? Many thanks in advance.
Probably you need to respect the original dimensions of the image. For instance an image that is 400x400 will downscale to 200x200 quite nicely, but an image that is 235x235 will not.
This doesn't consider images that are not square to begin with.
You might try reducing the image height and width by a good factor (I would start by halving) repeatedly until the image is smaller than 200x200 and then padding it with white or transparent.
Image processing can be quite hard. It's not something I'm an expert in either so I'd probably try a 3rd party library like this one I just found on google: https://imageprocessor.org/
I have code which I use to crop an image to a specified size. It does the job apart from the fact that, when I crop a bitonal image, it converts it to 24-bit colour which I don't want. Here's a snippet from the code ...
using (var Bmp = new Bitmap(FileImage.Width, Height))
{
using (var Graphic = Graphics.FromImage(Bmp))
{
var MemoryStreamTemp = new MemoryStream();
Graphic.DrawImage(FileImage, new Rectangle(0, 0, FileImage.Width, Height), x, CheckY, FileImage.Width, Height, GraphicsUnit.Pixel);
Bmp.Save(MemoryStreamTemp, ImageFormat.Tiff);
When bmp is created, it defaults to 24-bit colour. I realise that I can specify 1bpp indexed but that causes an exception when the Graphic object is instantiated.
I could convert the bitmap back to bitonal after it is cropped but that seems an unnecessary step that I would prefer to avoid.
Incidentally, I have a similar problem with the file's compression and resolution but that's for another day.
I have been having a tough time creating a thumbnail that is not horrible quality. So far the best code i've come up with is:
Bitmap bmp = new Bitmap(width, height);
Graphics graphic = Graphics.FromImage(bmp);
graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphic.SmoothingMode = SmoothingMode.HighQuality;
graphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphic.CompositingQuality = CompositingQuality.HighQuality;
graphic.DrawImage(photo, 0, 0, width, height);
return imageToByteArray(bmp);
Which produces this gem:
If I resize the same image in Paint.NET i get this:
Which is WAY better. Everything I've found on line points me to some variation of the code I have above. I know Paint.NET was open source at one point. Does anyone know what magic they were doing to create such nice resize functionality and if that functionality can be reproduced in C#?
UPDATE:
The original image from this example was a jpg
GIFs
I recalled reading that .NET has issues with palette-based formats, like GIF, so I dug up a few articles.
This article describes how to quantize (pick an optimum palette) to improve quality: http://msdn.microsoft.com/en-us/library/aa479306.aspx, as does this (badly formatted) article.
In brief, I believe GDI+ picks a non-optimum palette when performing the resize.
PNGs
PNGs are palette-based, so they may be prone to the same issues as GIFs. I'm not sure if it matters that the palette can be much larger.
JPEG-friendly example
This code should work fine on JPEGs (but does not render GIFs smoothly). If you try it and it pixelates a JPEG, then there is probably something else going on.
private static byte[] GetScaledImage( byte[] inputBytes, int width, int height ) {
Image img = null;
using( MemoryStream ms = new MemoryStream() ) {
ms.Write( inputBytes, 0, inputBytes.Length );
img = Image.FromStream( ms );
}
using( MemoryStream ms = new MemoryStream() ) {
using( Image newImg = new Bitmap( width, height ) ) {
using( Graphics g = Graphics.FromImage( newImg ) ) {
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage( img, 0, 0, width, height );
newImg.Save( ms, img.RawFormat );
return ms.GetBuffer();
}
}
}
}
since Bitmap(int,int) is effectively Bitmap(int,int,PixelFormat.Format32bppArgb) I think the problem is in source image. Try to create another intermediate copy of the image of the same size as source image if using palette, then use that 32bppArgb image source for your resize function.
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.