BadImage Error When Loading Tiff CCITTv4 Via SharpDX.Direct2D1.Bitmap.FromWicBitmap - c#

I'm using SharpDX and its accompanying WIC and Direct2D wrappers to do some serverside image manipulation.
The following code works great with JPEG images and is modeled after the SharpDX docs and this Microsoft sample using D2D directly via C++.
However, I get a BadImage error when I try to load a TIFF CCITT (bitonal 1bpp) image. The BadImage error is only thrown at EndDraw, (which happens later on in the commented DrawEndorsement function), or at this line of code which I inserted to make the point at which the issue occurs more obvious:
SharpDX.Direct2D1.Bitmap bitmap = SharpDX.Direct2D1.Bitmap.FromWicBitmap(_renderTarget, _wicBitmap);
The JPEG image I pass in gets to this point and continues with no issues, but the TIFF I pass in gets to this point and causes FromWicBitmap to barf with a BadImage error.
I'm using FormatConverter to convert the TIFF/JPEG pixel formats to an appropriate and supported D2D pixel format, and the converter does change the pixel format GUID for both images, but, again, FromWicBitmap barfs only on the TIFF.
I assumed I was doing something wrong with conversion or misusing SharpDX/D2D, but when I built and ran the aforementioned Microsoft C++ D2D image viewer sample, it loaded and rendered this same TIFF file with no errors. I double checked the sample's code to verify that I was using all the same pixel formats, options, etc, and it looks like I'm doing almost exactly the same thing with SharpDX that the sample is doing with D2D directly.
Clearly Direct2D doesn't like the pixel format of the TIFF image that WIC is handing it, but why didn't the MS sample exhibit the same behavior, and why didn't FormatConverter fix it?
Am I missing something that the D2D sample code is doing?
Am I missing some trick with SharpDX?
Is this a SharpDX bug?
Thanks!
public byte[] BuildImage(byte[] image, Format saveFormat)
{
SharpDX.WIC.Bitmap _wicBitmap;
WicRenderTarget _renderTarget;
BitmapFrameDecode bSource;
FormatConverter converter = new FormatConverter(_factoryManager.WicFactory);
using (MemoryStream systemStream = new MemoryStream(image))
using (WICStream wicStream = new WICStream(_factoryManager.WicFactory, systemStream))
{
BitmapDecoder inDecoder = new BitmapDecoder(_factoryManager.WicFactory, wicStream, DecodeOptions.CacheOnLoad);
if (inDecoder.FrameCount > 0)
{
bSource = inDecoder.GetFrame(0);
converter.Initialize(bSource, SharpDX.WIC.PixelFormat.Format32bppPRGBA, BitmapDitherType.Solid, null, 0.0f, BitmapPaletteType.MedianCut);
_imageWidth = bSource.Size.Width;
_imageHeight = bSource.Size.Height;
}
else
{
throw new Exception("No frames found!");
}
}
_wicBitmap = new SharpDX.WIC.Bitmap(
_factoryManager.WicFactory,
converter,
BitmapCreateCacheOption.CacheOnDemand
);
_renderTarget = new WicRenderTarget(_factoryManager.D2DFactory, _wicBitmap, new RenderTargetProperties());
SharpDX.Direct2D1.Bitmap bitmap = SharpDX.Direct2D1.Bitmap.FromWicBitmap(_renderTarget, _wicBitmap);
//DrawEndorsement(_renderTarget);
_renderTarget.Dispose();
bSource.Dispose();
converter.Dispose();
return SaveImage(saveFormat, _wicBitmap);
}

As xoofx pointed out, turns out that this was caused by my disposing of the WIC/MemoryStreams underlying the FormatConverter while it was still in use.
This was causing JPEGs to be corrupted on write, and weirdly causing the TIFFs to fail even before that.
Extended the using scope accordingly and that fixed it.

Related

Converting WriteableBitmap to Bitmap for use in EmguCV

In my code, I'm receiving WriteableBitmaps from a byte array (in turn from a Kinect) and I'd like to turn them into bitmaps for use with EmguCV. Currently this is the code I have:
// Copy the pixel data from the image to a temporary array
colorFrame.CopyPixelDataTo(this.colorPixels);
// Write the pixel data into our bitmap
this.colorBitmap.WritePixels(
new Int32Rect(0, 0, this.colorBitmap.PixelWidth, this.colorBitmap.PixelHeight),
this.colorPixels,
this.colorBitmap.PixelWidth * colorFrame.BytesPerPixel,
0);
BitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(colorBitmap));
MemoryStream ms = new MemoryStream();
encoder.Save(ms);
Bitmap b=new Bitmap(ms);
Image<Gray, Byte> img = new Image<Gray, Byte>(b);
img = img.ThresholdBinary(new Gray(200), new Gray(255));
I got the bottom half of the code from here.The code compiles and everything, but hangs when I'm trying to run the program (it's supposed to perform some operations on the image and then convert it back to a format that can be presented as an image.) Pausing my code and then using IntelliTrace in VS 2013, I get the following Exception at Image<Gray, Byte> img = new Image<Gray, Byte>(b); "A System.ArgumentException was thrown: URI formats are not supported." Using alternate code, from where I go directly from byte to bitmap gives me the same error. (Code can be found here.)
Anyone got tips on how to resolve this error, or alternate ways of casting to bitmap? I'm a newbie with C# & EmguCV and I'd greatly appreciate it.
Turns out all the code is fine. I'm not too sure on the technical details of the error, but the error is received when trying to write a Gray16 image in the WriteableBitmap (which is to be transformed into a Emgu Image.) Bgr565 or other formats are supported and I believe Gray16 wasn't fully implemented by MS. If doing a WinForms application, Format16bppGray will also give the same error.
I resolved to using a Gray Emgu image while writing the Bitmap as a Bgr555, which is a lot more noisy, but better than nothing.
I had the same issue. The exception with "URI formats are not supported" had nothing to do with the bitmap but with loading needed opencv dlls. I just copied the x86 and x64 folders including opencv_core290.dll and others to my executable directory.

Converting Bitmap to GDK# PixBuff

How can I convert a System.Drawing.Bitmap to GDK# Image so that I can set to the image widget.
I have tried this...
System.Drawing.Bitmap b = new Bitmap (1, 1);
Gdk.Image bmp = new Gdk.Image (b);
UPDATE:
Bitmap bmp=new Bitmap(50,50);
Graphics g=Graphics.FromImage(bmp);
System.Drawing.Font ff= new System.Drawing.Font (System.Drawing.FontFamily.GenericMonospace, 12.0F, FontStyle.Italic, GraphicsUnit.Pixel);
g.DrawString("hello world",ff,Brushes.Red,new PointF(0,0));
MemoryStream ms = new MemoryStream ();
bmp.Save (ms, ImageFormat.Png);
Gdk.Pixbuf pb= new Gdk.Pixbuf (ms);
image1.Pixbuf=pb;
Exception:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> GLib.GException: Unrecognized image file format
at Gdk.PixbufLoader.Close()
at Gdk.PixbufLoader.InitFromStream(Stream stream)
at Gdk.PixbufLoader..ctor(Stream stream)
at Gdk.Pixbuf..ctor(Stream stream)
One ugly, but working, way is to store the bitmap as a PNG in a MemoryStream.
To save the Bitmap, you can use the Save method:
b.Save(myMemoryStream, ImageFormat.Png);
That was easy enough. Loading the PNG data into the Gdk# Pixbuf is also rather easy; you can use the appropriate constructor:
Pixbuf pb = new Gdk.Pixbuf(myMemoryStream);
You may need to reset the memory stream so the reading position is at the start of the stream before creating the Pixbuf.
A word of caution: I do not consider this the best, or even a "good" solution. Transferring data between two object-oriented data structures by serializing and deserializing the data has a certain code smell to it. I genuinely hope someone else can come up with a better solution.
EDIT: As for the used libraries: This answer uses only plain GDI+ (System.Drawing.Bitmap) and Gdk# (Gdk.Pixbuf). Note that a Gtk.Image is a widget that displays a Gdk.Pixbuf. As such, Gtk.Image is the equivalent of Windows Forms' PictureBox, whereas Gdk.Pixbuf is roughly equivalent to Windows Forms' System.Drawing.Bitmap.
EDIT2: After testing your code, I have found that there are three additional preconditions to ensure before you can run your minimum example:
As suspected above, you must reset the stream position to the beginning of the after saving your Bitmap and before loading your Pixbuf: ms.Position = 0;
You must compile the application for x86 CPUs.
You must invoke Gtk.Application.Init(); before you do anything with Pixbuf.
You may draw in Gtk# like in Winforms. For this you must obtain System.Drawing.Graphics object and then you may draw lines, images and text on it. You may do it like this: 1. Create new Widget. 2. Subscribe on ExposeEvent. 3. On event handler write some code:
protected void OnExposeEvent(object o, ExposeEventArgs e)
{
Gdk.Window window = e.Event.Window;
using (System.Drawing.Graphics graphics =
Gtk.DotNet.Graphics.FromDrawable(window))
{
// draw your stuff here...
graphics.DrawLine(new System.Drawing.Pen(System.Drawing.Brushes.Black), 0, 0, 30, 40);
}
}
Also you need to add reference on gtk-dotnet.dll.
try this ....
Gdk.Pixbuf pixbufImage = mew Gdk.Pixbuf(#"images/test.png");
Gtk.Image gtkImage = new Gtk.Image(pixbufImage);
Gdk.Image gdkImage = gtkImage.ImageProp;

Convert bitmap from 16bpp to 8bpp in C#

I have a Bitmap with 16bpp. I want to convert that image in my ASP.NET side in a 8bpp image.
I tried a lot of options which I found in the internet but nothing works for me.
I also tried that way: C# Converting 32bpp image to 8bpp
But if I want to save the file, I get the following error:
Exception Details: System.Runtime.InteropServices.ExternalException: A generic error occurred in GDI+.
Line 278: System.Drawing.Image img2 = Convert(bm_resize);//byteArrayToImage(gray);
Line 279:
Line 280: img2.Save(helper+"grey2.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
Line 281: }
Line 282:
Is there any correct way for my problem?
Full Code:
System.Drawing.Image img2 = Convert(bm_resize);
img2.Save(path+"test.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
public static System.Drawing.Image Convert(Bitmap oldbmp)
{
using (MemoryStream ms = new MemoryStream())
{
oldbmp.Save(ms, ImageFormat.Gif);
ms.Position = 0;
return System.Drawing.Image.FromStream(ms);
}
}
The problem is caused by disposing the memory stream before the image is saved.
I believe GDI+ requires the memory stream to persist while you are still working with an Image created from the memory stream.
See the Microsoft Support article.
I use a little helper function to create 1 bpp monochrome bitmaps in .NET. Check out this link, it also works great for 8 bpp
http://www.wischik.com/lu/programmer/1bpp.html
hope this could help!
AForge.Net has a good collection of free (under Lesser GPL License) routines for such changes and a lot more. Conversion of 16bpp to 8bpp is as simple as this.

Image.Save crashing: {"Value cannot be null.\r\nParameter name: encoder"}

I am trying to save an image into a MemoryStream but it is failing under certain conditions.
Here is the code:
The following code succeeds:
Image img = Bitmap.FromStream(fileStream);
MemoryStream ms = new MemoryStream();
img.Save(ms, img.RawFormat); // This succeeds.
The following code fails:
Image img = Bitmap.FromStream(fileStream);
Image thumb = img.GetThumbnailImage(thumbWidth, thumbHeight, null, System.IntPtr.Zero);
MemoryStream ms = new MemoryStream();
thumb.Save(ms, thumb.RawFormat); // This fails.
Notice that the second snippet is using an image created using Image.GetThumbnailImage.
What is the difference? Does anyone have any idea why is it failing?
I believe the problem has to do with this part of the GetThumbnailImage documentation:
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.
This probably accounts for the intermittent behaviour (AKA "certain conditions"). The explanation is in the following Microsoft Connect ticket:
The underlying API is not able to locate an encoder for the MemoryBmp image type. We will need to investigate this will the GDI+ team. In the meantime, you should be able to simply change your ImageFormat to ImageFormat.Bmp rather than ImageFormat.MemoryBmp and it should work. It will still be saved to the MemoryStream using the BMP format.
In all likelihood, if there is no embedded thumbnail, the new thumbnail generated by the GetThumbnailImage API is in fact going to have a RawFormat of MemoryBmp which has no associated encoder - thus the specific error message you're seeing.
Just don't use thumb.RawFormat; since you know it's a bitmap anyway, use ImageFormat.Bmp instead.
P.S. Please note that although I deleted my earlier answer because it turned out to not to be relevant to this particular issue, it is still important to use the GetThumbnailImage API properly as the documentation specifies; you must pass a valid delegate for the callback parameter instead of null, otherwise it can fail, and you still need to be wrapping disposables in using clauses.

How can I get .NET to save this image?

I have this JPEG image, that opens fine in Picasa, Photoshop, web browser, etc., but in .NET it just refuses to work.
Image image = Image.FromFile(#"myimage.jpg");
image.Save(#"myimage2.jpg");
// ExternalException - A generic error occurred in GDI+.
Is there a way to recover it in .NET so I can work with it (I need to resize it), without fixing the problem at the source?
Full exception details:
source: System.Drawing
type: System.Runtime.InteropServices.ExternalException
message: A generic error occurred in GDI+.
stack trace:
at System.Drawing.Image.Save(String filename, ImageCodecInfo encoder, EncoderParameters encoderParams)
at System.Drawing.Image.Save(String filename, ImageFormat format)
at System.Drawing.Image.Save(String filename)
at ConsoleApplication20.Program.Main(String[] args) in C:\Users\sam\Desktop\S
ource\ConsoleApplication20\ConsoleApplication20\Program.cs:line 16
This issue is reproducible on Windows 7.
This seems to work:
using (Image image = Image.FromFile(#"c:\dump\myimage.jpg"))
using (Image clone = new Bitmap(image))
{
clone.Save(#"c:\dump\myimage2.jpg", ImageFormat.Jpeg);
}
image is actually a Bitmap anyway, so it should be similar. Oddly myimage2 is 5k smaller - the joys of jpeg ;-p
A nice thing about this is that you can resize at the same time (your actual intent):
using (Image image = Image.FromFile(#"c:\dump\myimage.jpg"))
using (Image clone = new Bitmap(image,
new Size(image.Size.Width / 2, image.Size.Height / 2)))
{
clone.Save(#"c:\dump\myimage2.jpg", ImageFormat.Jpeg);
}
Try explicitly specifying the format:
using (Image image = Image.FromFile(#"test.jpg"))
{
image.Save(#"myimage2.gif", ImageFormat.Gif);
}
All ImageFormat.Png, ImageFormat.Bmp and ImageFormat.Gif work fine. ImageFormat.Jpeg throws an exception.
The original image is in the JFIF format as it starts with FF D8 FF E0 bytes.
This is a long-standing bug in the .NET framework itself that I don't expect to see fixed any time soon. There are several related bugs I've also come across that throw the 'generic error occurred in GDI+' including if you access the PropertyItem collection for some JPEGs (to examine the EXIF codes) then from that point on you won't be able to save the image. Also, for no rhyme or reason some JPEGs, like the one you have here, simply won't save at all. Note that just saving as a different format won't always work either, although it does in this case.
The workaround I've employed in an app of mine that consumes pictures from all over the web (and therefore sees more than its fair share of these types of problems) is to catch the ExternalException and copy the image into a new Bitmap as in one of the previous answers, however simply saving this as a new JPEG will drop the quality rather a lot so I use code much like that below to keep the quality high:
namespace ImageConversionTest
{
using System.Drawing;
using System.Runtime.InteropServices;
using System.Drawing.Imaging;
using System.Globalization;
class Program
{
static void Main( string[] args )
{
using( Image im = Image.FromFile( #"C:\20128X.jpg" ) )
{
string saveAs = #"C:\output.jpg";
EncoderParameters encoderParams = null;
ImageCodecInfo codec = GetEncoderInfo( "image/jpeg" );
if( codec != null )
{
int quality = 100; // highest quality
EncoderParameter qualityParam = new EncoderParameter(
System.Drawing.Imaging.Encoder.Quality, quality );
encoderParams = new EncoderParameters( 1 );
encoderParams.Param[0] = qualityParam;
}
try
{
if( encoderParams != null )
{
im.Save( saveAs, codec, encoderParams );
}
else
{
im.Save( saveAs, ImageFormat.Jpeg );
}
}
catch( ExternalException )
{
// copy and save separately
using( Image temp = new Bitmap( im ) )
{
if( encoderParams != null )
{
temp.Save( saveAs, codec, encoderParams );
}
else
{
temp.Save( saveAs, ImageFormat.Jpeg );
}
}
}
}
}
private static ImageCodecInfo GetEncoderInfo( string mimeType )
{
// Get image codecs for all image formats
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
// Find the correct image codec
foreach( ImageCodecInfo codec in codecs )
{
if( string.Compare( codec.MimeType, mimeType, true, CultureInfo.InvariantCulture ) == 0 )
{
return codec;
}
}
return null;
}
}
}
Note that you'll lose the EXIF information but I'll leave that for now (you'll generally still be able to read the EXIF codes, even when saving the source image fails). I have long since given up attempting to figure out what it is that .NET doesn't like about particular images (and have samples of various failure cases should anybody ever want to take up the challenge) but the approach above works, which is nice.
It seems to work fine on Windows XP and Vista, but not Windows 7.
I was able to find this issue on Microsoft Connect. It's not identical to your issue but it looks very similar - an ExternalException occurring when trying to resave a JPEG file. Currently at 16 repros including some comments that the issue still exists in the final release.
So it looks not to be a bug in the .NET Framework, but a bug in Windows 7 - specifically, a bug in the GdipSaveImageToStream API.
The workaround, as others have mentioned, is to force a conversion to another format. That's what both Marc and Darin's answers are doing. There is obviously some "extra" information in the JPEG file that is triggering the bug in Win7. When you convert to a different format or make a bitmap copy, that information (EXIF maybe?) is eliminated.
Try using WPF's BitmapSource instead of the WinForm's Image, it supports more pixel, image and file formats.
I tried yesterday on 32-bit Windows XP and can't reproduce the problem. Today I tried on 64-bit Windows 7 and get exactly the error you described, which is great for my debugging.
I investigated the header and the JPEG is a standard JPEG, but with an EXIF header. From what I read, it is not uncommon that the EXIF header maybe corrupt and some programs will just ignore it. In .NET it allows reading and (may even allow writing at some point), but not writing. You can read more about it in blog post GDI+ can’t handle some malformed JPG files.
One way to remove it is to clone the image like suggested by Marc, which will create the new image without an EXIF header, hence that explains why the file size is actually smaller. There are methods for removing an EXIF header programmatically, and some has been suggested in Stack Overflow
like Simple way to remove EXIF data from a JPEG with .NET.
The suggested problem with reading the byte marker and skip the stream will not work well consistently as we are dealing with a corrupted EXIF header. I tried using RemovePropertyItem is an option, but it still doesn't work either, and my guess is because there are corrupted property items that are being referred to (when I inspect the JPEG header there are six properties, and .NET only loads four). They are other library like exiv2net which can be explored, but I suspect the outcome will be similar.
In short, the answer will be to clone the image as suggested by Marc. This is perhaps not the solution, but an insight into the problem.
Try checking your permissions. Not being able to save can be caused by not having the right permission to write/modify, and could generate this error.
That error occurrs when you either have
a. no permissions to write the file
b. you are trying to write an image that is locked
(often, by a UI control ie. picturebox)
You'll need to dispose the original, and then save over it with your resized clone as in Marc's answer. I just figured I'd tell you why it's happening.

Categories

Resources