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.
Related
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.
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.
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);
}
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");
}
I'm able to save the captured image from a barcode scanner using this code:
Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
dlg.DefaultExt = ".jpg";
dlg.Filter = "JPEG Images (.jpg)|*.jpg|All files (*.*)|*.*";
if (dlg.ShowDialog() == true)
{
using (FileStream file = File.OpenWrite(dlg.FileName))
{
file.Write(e.ImageBuffer, 0, e.ImageSize);
}
}
However, I would like to display the captured image using WPF but I get a distorted image.
private void _barcodeScannerInstance_SavePhotoEvent(object sender, ImageEventArgs e)
{
SetBitmap(e.ImageBuffer, 350, 263, 96);
}
private void SetBitmap(byte[] image, int width, int height, int dpi)
{
MainWindow.Instance.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate()
{
BitmapSource bitmapSource = BitmapSource.Create(
width, height, (double)dpi, (double)dpi, PixelFormats.Bgr24, null, image, ((width * 24 + 31) & ~31) >> 3);
HwModeScreen.BarcodeImageCanvas.Children.Clear();
Image myImage = new Image();
myImage.Width = HwModeScreen.BarcodeImageCanvas.ActualWidth;
myImage.Height = HwModeScreen.BarcodeImageCanvas.ActualHeight;
myImage.Stretch = Stretch.Fill;
myImage.Source = bitmapSource;
HwModeScreen.BarcodeImageCanvas.Children.Add(myImage);
});
Here is the image I see. It should be a black and white picture of a kleenex box.
Here is the saved jpg file:
did you mix up width and height? are you sure your dpi value is correct?
I suspect the whole problem is this line:
BitmapSource bitmapSource = BitmapSource.Create(
width, height, (double)dpi, (double)dpi, PixelFormats.Bgr24, null, image, ((width * 24 + 31) & ~31) >> 3)
What I would do to debug the issue is to write out the image to file and confirm all the inputs. Use photoshop, paint.net, file properties...
Are you sure you are working with bitmap format?
Are you sure you are working with 24bits per pixel?
Are you sure you have height and width correct, and you are feeding the values into the correct argument
What is this line all about, and why are you doing it? I am slightly suspicious.
((width * 24 + 31) & ~31) >> 3)
Basically, the way I look at this is that you are feeding the bitmap library a stream of bits... it doesn't know what the bits are but it will attempt to create the image from the information you give it: bits per pixel, size, etc. If you give it incorrect information, it will create a corrupted image as you have shown.
I am slightly suspicious that the problem is not with width and height; even if you mix those two values up-- I think you would get at least part of the first row of pixels to be rendered correctly. I see static / noise / snow, which tells me that there is something about the way the stream of bits was interpreted-- it is rendered as random blacks and whites.
Another thing: in your screen cap, I see color. this is another hint that there is something incorrect about your assumptions about the image. The values should probably 1 to 256 ( 8 bits per pixel I think? ) I would try creating a 8 bit per pixel black and white bitmap. Somehow the library thinks this is a color image.
I just noticed that you are assuming jpeg. jpeg is a lossy format-- I would have assumed that you would end up with a bitmap or tiff image. double check that you are indeed getting back a jpeg image (check the barcode api documentation)
The JPEG compression algorithm is quite unsuitable for the kind of image you are capturing. It works well for photos, it behaves poorly on images containing fine lines. The slight artifacts the compression produces makes it a lot harder to properly scan the barcode.
You don't see the Kleenex box because you are writing the raw image bytes. You need to use an image encoder. I recommend you use the PngBitmapEncoder class. GifBitmapEncoder should work too since you don't need a lot of colors, it makes smaller files. A code snippet that shows how to use an encoder is available here.
this is likely distorting it
myImage.Stretch = Stretch.Fill;
I used a jpeg decoder to fix the problem.
private void SetBitmap(byte[] image, int width, int height, int dpi)
{
MainWindow.Instance.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate()
{
BMemoryStream ms = new MemoryStream(image);
JpegBitmapDecoder decoder = new JpegBitmapDecoder(ms, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapSource bitmapSource = decoder.Frames[0];
HwModeScreen.BarcodeImageCanvas.Children.Clear();
Image myImage = new Image();
myImage.Width = HwModeScreen.BarcodeImageCanvas.ActualWidth;
myImage.Height = HwModeScreen.BarcodeImageCanvas.ActualHeight;
myImage.Stretch = Stretch.Fill;
myImage.Source = bitmapSource;
HwModeScreen.BarcodeImageCanvas.Children.Add(myImage);
});