When I try to Serialize some images using the BinaryFormatter, I'll get a ExternalException - A generic error occurred in GDI+." After scratching my head for awhile, I decided to create a simple test project to narrow down the problem:
static void Main(string[] args)
{
string file = #"C:\temp\delme.jpg";
//Image i = new Bitmap(file);
//using(FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
byte[] data = File.ReadAllBytes(file);
using(MemoryStream originalms = new MemoryStream(data))
{
using (Image i = Image.FromStream(originalms))
{
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream())
{
// Throws ExternalException on Windows 7, not Windows XP
bf.Serialize(ms, i);
}
}
}
}
For specific images, I've tried all sorts of ways of loading the image and I could not get it to work under Windows 7, even when running the program as Administrator.
I've copied the exact same executable and image into my Windows XP VMWare instance and I have no problems.
Anyone have any idea of why for some images it doesn't work under Windows 7, but works under XP?
Here's one of the images:
http://www.2shared.com/file/7wAXL88i/SO_testimage.html
delme.jpg md5: 3d7e832db108de35400edc28142a8281
As the OP pointed out, the code provided throws an exception that seems to be occurring only with the image he provided but works fine with other images on my machine.
Option 1
static void Main(string[] args)
{
string file = #"C:\Users\Public\Pictures\delme.jpg";
byte[] data = File.ReadAllBytes(file);
using (MemoryStream originalms = new MemoryStream(data))
{
using (Image i = Image.FromStream(originalms))
{
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream())
{
// Throws ExternalException on Windows 7, not Windows XP
//bf.Serialize(ms, i);
i.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp); // Works
i.Save(ms, System.Drawing.Imaging.ImageFormat.Png); // Works
i.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); // Fails
}
}
}
}
It could be that the image in question was created with a tool that added some additional information that is interfering with the JPEG serialization.
P.S. The image can be saved to memory stream using BMP or PNG format. If changing the format is an option, then you can try out either of these or any other format defined in ImageFormat.
Option 2
If your goal is just to get the contents of the image file into a memory stream, then doing just the following would help
static void Main(string[] args)
{
string file = #"C:\Users\Public\Pictures\delme.jpg";
using (FileStream fileStream = File.OpenRead(file))
{
MemoryStream memStream = new MemoryStream();
memStream.SetLength(fileStream.Length);
fileStream.Read(memStream.GetBuffer(), 0, (int)fileStream.Length);
}
}
Although the Bitmap class is marked as [Serializable], it does not actually support serialisation. The best you can do is serialise the byte[] containing the raw image data and then re-create it using a MemoryStream and the Image.FromStream() method.
I can't explain the inconsistent behaviour you're experiencing; for me, it fails unconditionally (although I first discovered this when trying to marshal images between different app domains, rather than manually serialising them).
I don't know for sure but I would lean towards different security models for XP vs Windows 7. Image inherits from System.MarshalByRefObject. There is probably proxying going on between application domains when serialization is performed. This proxying might be forbidden in Windows 7.
http://msdn.microsoft.com/en-us/library/system.marshalbyrefobject%28v=vs.71%29.aspx
Related
I am using System.Speech.Synthesis.SpeechSynthesizer to convert text to speech. And due to Microsoft's anemic documentation (see my link, there's no remarks or code examples) I'm having trouble making heads or tails of the difference between two methods:
SetOutputToAudioStream and SetOutputToWaveStream.
Here's what I have deduced:
SetOutputToAudioStream takes a stream and a SpeechAudioFormatInfo instance that defines the format of the wave file (samples per second, bits per second, audio channels, etc.) and writes the text to the stream.
SetOutputToWaveStream takes just a stream and writes a 16 bit, mono, 22kHz, PCM wave file to the stream. There is no way to pass in SpeechAudioFormatInfo.
My problem is SetOutputToAudioStream doesn't write a valid wave file to the stream. For example I get a InvalidOperationException ("The wave header is corrupt") when passing the stream to System.Media.SoundPlayer. If I write the stream to disk and attempt to play it with WMP I get a "Windows Media Player cannot play the file..." error but the stream written by SetOutputToWaveStream plays properly in both. My theory is that SetOutputToAudioStream is not writing a (valid) header.
Strangely the naming conventions for the SetOutputTo*Blah* is inconsistent. SetOutputToWaveFile takes a SpeechAudioFormatInfo while SetOutputToWaveStream does not.
I need to be able to write a 8kHz, 16-bit, mono wave file to a stream, something that neither SetOutputToAudioStream or SetOutputToWaveStream allow me to do. Does anybody have insight into SpeechSynthesizer and these two methods?
For reference, here's some code:
Stream ret = new MemoryStream();
using (SpeechSynthesizer synth = new SpeechSynthesizer())
{
synth.SelectVoice(voiceName);
synth.SetOutputToWaveStream(ret);
//synth.SetOutputToAudioStream(ret, new SpeechAudioFormatInfo(8000, AudioBitsPerSample.Sixteen, AudioChannel.Mono));
synth.Speak(textToSpeak);
}
Solution:
Many thanks to #Hans Passant, here is the gist of what I'm using now:
Stream ret = new MemoryStream();
using (SpeechSynthesizer synth = new SpeechSynthesizer())
{
var mi = synth.GetType().GetMethod("SetOutputStream", BindingFlags.Instance | BindingFlags.NonPublic);
var fmt = new SpeechAudioFormatInfo(8000, AudioBitsPerSample.Sixteen, AudioChannel.Mono);
mi.Invoke(synth, new object[] { ret, fmt, true, true });
synth.SelectVoice(voiceName);
synth.Speak(textToSpeak);
}
return ret;
For my rough testing it works great, though using reflection is a bit icky it's better than writing the file to disk and opening a stream.
Your code snippet is borked, you're using synth after it is disposed. But that's not the real problem I'm sure. SetOutputToAudioStream produces the raw PCM audio, the 'numbers'. Without a container file format (headers) like what's used in a .wav file. Yes, that cannot be played back with a regular media program.
The missing overload for SetOutputToWaveStream that takes a SpeechAudioFormatInfo is strange. It really does look like an oversight to me, even though that's extremely rare in the .NET framework. There's no compelling reason why it shouldn't work, the underlying SAPI interface does support it. It can be hacked around with reflection to call the private SetOutputStream method. This worked fine when I tested it but I can't vouch for it:
using System.Reflection;
...
using (Stream ret = new MemoryStream())
using (SpeechSynthesizer synth = new SpeechSynthesizer()) {
var mi = synth.GetType().GetMethod("SetOutputStream", BindingFlags.Instance | BindingFlags.NonPublic);
var fmt = new SpeechAudioFormatInfo(8000, AudioBitsPerSample.Eight, AudioChannel.Mono);
mi.Invoke(synth, new object[] { ret, fmt, true, true });
synth.Speak("Greetings from stack overflow");
// Testing code:
using (var fs = new FileStream(#"c:\temp\test.wav", FileMode.Create, FileAccess.Write, FileShare.None)) {
ret.Position = 0;
byte[] buffer = new byte[4096];
for (;;) {
int len = ret.Read(buffer, 0, buffer.Length);
if (len == 0) break;
fs.Write(buffer, 0, len);
}
}
}
If you're uncomfortable with the hack then using Path.GetTempFileName() to temporarily stream it to a file will certainly work.
i have problems during parsing request files.
my file size is 1338521 bytes, but Nancy says, that file size is some times 1751049 or 3200349.
on my windows pc it works fine, on linux server this problem appears, so i can't save file.
string result = Convert.ToBase64String(Core.ReadBytesFromStream(file.Value));
using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(result)))
{
using (Bitmap bm2 = new Bitmap(ms))
{
bm2.Save(path);
}
}
any ideas?
You don't need to convert the file like that.
var filename = Path.Combine(storagePath, Request.Files[0].Name);
using (var fileStream = new FileStream(filename, FileMode.Create))
{
Request.Files[0].Value.CopyTo(fileStream);
}
Validate the file when it comes in to ensure the extension is accepted, create a save path, and copy the stream to a new file on the filesystem.
That's it.
Hey, I am trying to load an Image control from a byte array, I've tried multiple solutions found online (particularly this site) but nothing seems to work.
My main goal was to obtain an ImageSource from the byte array and return it from a converter.
I've tried:
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = new MemoryStream(lBytes);
bi.EndInit();
But this fails with:
NotSupportedException
No imaging component suitable to complete this operation was found.
Also tried first loading a Bitmap and from there try to get the ImageSource.
using (MemoryStream lMem = new MemoryStream(lBytes))
{
TypeConverter tc = TypeDescriptor.GetConverter(typeof(System.Drawing.Bitmap));
System.Drawing.Bitmap b = (System.Drawing.Bitmap)tc.ConvertFrom(lBytes);
lResult = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
b.GetHbitmap(),
IntPtr.Zero,
System.Windows.Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
b.Dispose();
}
But this fails on the ConvertFrom with "Parameter is not valid."
All of this when loading a valid PNG file in my filesystem.
I am running out of ideas, any clue?
Thanks.
Edit:
Alright, the problem was my way of loading the file...
I was using
using (FileStream lFileStream = new FileStream(pFilePath, FileMode.Open))
{
using (StreamReader lReader = new StreamReader(lFileStream))
{
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream())
{
string lString = lReader.ReadToEnd();
bf.Serialize(ms, lString);
ms.Seek(0, 0);
lImage = ms.ToArray();
}
lResult = new Graphic(lImage);
}
}
But then read that I could use:
lImage = File.ReadAllBytes(pFilePath);
And that's it.
Thank you.
First solution works correctly but I think your problem is about how to read array byte not how to convert the array to bitmap.
I've used that solution many times. The difference between my solution and your solution is how to read the file and convert it to an array.
I simply use:
System.IO.File.ReadAllBytes(filepath)
I am trying to set up metadata on JPG image what does not have it. You can't use in-place writer (InPlaceBitmapMetadataWriter) in this case, cuz there is no place for metadata in image.
If I use FileStream as output - everything works fine. But if I try to use MemoryStream as output - JpegBitmapEncoder.Save() throws an exception (Exception from HRESULT: 0xC0000005).
After some investigation I also found out what encoder can save image to memory stream if I supply null instead of metadata.
I've made a very simplified and short example what reproduces the problem:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Media.Imaging;
namespace JpegSaveTest
{
class Program
{
public static JpegBitmapEncoder SetUpMetadataOnStream(Stream src, string title)
{
uint padding = 2048;
BitmapDecoder original;
BitmapFrame framecopy, newframe;
BitmapMetadata metadata;
JpegBitmapEncoder output = new JpegBitmapEncoder();
src.Seek(0, SeekOrigin.Begin);
original = JpegBitmapDecoder.Create(src, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
if (original.Frames[0] != null) {
framecopy = (BitmapFrame)original.Frames[0].Clone();
if (original.Frames[0].Metadata != null) metadata = original.Frames[0].Metadata.Clone() as BitmapMetadata;
else metadata = new BitmapMetadata("jpeg");
metadata.SetQuery("/app1/ifd/PaddingSchema:Padding", padding);
metadata.SetQuery("/app1/ifd/exif/PaddingSchema:Padding", padding);
metadata.SetQuery("/xmp/PaddingSchema:Padding", padding);
metadata.SetQuery("System.Title", title);
newframe = BitmapFrame.Create(framecopy, framecopy.Thumbnail, metadata, original.Frames[0].ColorContexts);
output.Frames.Add(newframe);
}
else {
Exception ex = new Exception("Image contains no frames.");
throw ex;
}
return output;
}
public static MemoryStream SetTagsInMemory(string sfname, string title)
{
Stream src, dst;
JpegBitmapEncoder output;
src = File.Open(sfname, FileMode.Open, FileAccess.Read, FileShare.Read);
output = SetUpMetadataOnStream(src, title);
dst = new MemoryStream();
output.Save(dst);
src.Close();
return (MemoryStream)dst;
}
static void Main(string[] args)
{
string filename = "Z:\\dotnet\\gnom4.jpg";
MemoryStream s;
s = SetTagsInMemory(filename, "test title");
}
}
}
It is simple console application.
To run it, replace filename variable content with path to any .jpg file without metadata (or use mine).
Ofc I can just save image to temporary file first, close it, then open and copy to MemoryStream, but its too dirty and slow workaround.
Any ideas about getting this working are welcome :)
In case someone will encounter same issue, here is the solution:
If you try to .Save() jpeg from main application thread, add [STAThread] before Main().
If not, call .SetApartmentState(ApartmentState.STA) for the thread calling JpegBitmapEncoder.Save()
WinXP and WinVista versions of windowscodecs.dll are not reenterable, so if you will use default MTA model (it is default since .NET framework 2.0) for threads calling JpegBitmapEncoder.Save() function, it can behave strangely and throw described exception.
Win7 version of windowscodecs.dll does not have this issue.
I ran your code without modifications and it didn't throw an error.
I even tried saving the modified data to disk and the image itself was uncorrupted.
string filename = "e:\\a.jpg";
MemoryStream s;
s = SetTagsInMemory(filename, "test title");
FileStream fs = new FileStream("e:\\b.jpg", FileMode.CreateNew, FileAccess.ReadWrite);
BinaryWriter sw = new BinaryWriter(fs);
s.Seek(0, SeekOrigin.Begin);
while (s.Position < s.Length)
{
byte[] data = new byte[4096];
s.Read(data, 0, data.Length);
sw.Write(data);
}
sw.Flush();
sw.Close();
fs.Close();
Other than what I added below s = SetTagsInMemory(...) to write to disk, the rest of your code is unmodifed.
Edit: oh and the metadeta definatly ended up in the new file, previous one didn't have any metadata from what I could see.
i've got some binary data which i want to save as an image. When i try to save the image, it throws an exception if the memory stream used to create the image, was closed before the save. The reason i do this is because i'm dynamically creating images and as such .. i need to use a memory stream.
this is the code:
[TestMethod]
public void TestMethod1()
{
// Grab the binary data.
byte[] data = File.ReadAllBytes("Chick.jpg");
// Read in the data but do not close, before using the stream.
Stream originalBinaryDataStream = new MemoryStream(data);
Bitmap image = new Bitmap(originalBinaryDataStream);
image.Save(#"c:\test.jpg");
originalBinaryDataStream.Dispose();
// Now lets use a nice dispose, etc...
Bitmap2 image2;
using (Stream originalBinaryDataStream2 = new MemoryStream(data))
{
image2 = new Bitmap(originalBinaryDataStream2);
}
image2.Save(#"C:\temp\pewpew.jpg"); // This throws the GDI+ exception.
}
Does anyone have any suggestions to how i could save an image with the stream closed? I cannot rely on the developers to remember to close the stream after the image is saved. In fact, the developer would have NO IDEA that the image was generated using a memory stream (because it happens in some other code, elsewhere).
I'm really confused :(
As it's a MemoryStream, you really don't need to close the stream - nothing bad will happen if you don't, although obviously it's good practice to dispose anything that's disposable anyway. (See this question for more on this.)
However, you should be disposing the Bitmap - and that will close the stream for you. Basically once you give the Bitmap constructor a stream, it "owns" the stream and you shouldn't close it. As the docs for that constructor say:
You must keep the stream open for the
lifetime of the Bitmap.
I can't find any docs promising to close the stream when you dispose the bitmap, but you should be able to verify that fairly easily.
A generic error occurred in GDI+.
May also result from incorrect save path!
Took me half a day to notice that.
So make sure that you have double checked the path to save the image as well.
Perhaps it is worth mentioning that if the C:\Temp directory does not exist, it will also throw this exception even if your stream is still existent.
Copy the Bitmap. You have to keep the stream open for the lifetime of the bitmap.
When drawing an image: System.Runtime.InteropServices.ExternalException: A generic error occurred in GDI
public static Image ToImage(this byte[] bytes)
{
using (var stream = new MemoryStream(bytes))
using (var image = Image.FromStream(stream, false, true))
{
return new Bitmap(image);
}
}
[Test]
public void ShouldCreateImageThatCanBeSavedWithoutOpenStream()
{
var imageBytes = File.ReadAllBytes("bitmap.bmp");
var image = imageBytes.ToImage();
image.Save("output.bmp");
}
I had the same problem but actually the cause was that the application didn't have permission to save files on C. When I changed to "D:\.." the picture has been saved.
You can try to create another copy of bitmap:
using (var memoryStream = new MemoryStream())
{
// write to memory stream here
memoryStream.Position = 0;
using (var bitmap = new Bitmap(memoryStream))
{
var bitmap2 = new Bitmap(bitmap);
return bitmap2;
}
}
This error occurred to me when I was trying from Citrix. The image folder was set to C:\ in the server, for which I do not have privilege. Once the image folder was moved to a shared drive, the error was gone.
A generic error occurred in GDI+. It can occur because of image storing paths issues,I got this error because my storing path is too long, I fixed this by first storing the image in a shortest path and move it to the correct location with long path handling techniques.
I was getting this error, because the automated test I was executing, was trying to store snapshots into a folder that didn't exist. After I created the folder, the error resolved
One strange solution which made my code to work.
Open the image in paint and save it as a new file with same format(.jpg). Now try with this new file and it works. It clearly explains you that the file might be corrupted in someway.
This can help only if your code has every other bugs fixed
It has also appeared with me when I was trying to save an image into path
C:\Program Files (x86)\some_directory
and the .exe wasn't executed to run as administrator, I hope this may help someone who has same issue too.
For me the code below crashed with A generic error occurred in GDI+on the line which Saves to a MemoryStream. The code was running on a web server and I resolved it by stopping and starting the Application Pool that was running the site.
Must have been some internal error in GDI+
private static string GetThumbnailImageAsBase64String(string path)
{
if (path == null || !File.Exists(path))
{
var log = ContainerResolver.Container.GetInstance<ILog>();
log.Info($"No file was found at path: {path}");
return null;
}
var width = LibraryItemFileSettings.Instance.ThumbnailImageWidth;
using (var image = Image.FromFile(path))
{
using (var thumbnail = image.GetThumbnailImage(width, width * image.Height / image.Width, null, IntPtr.Zero))
{
using (var memoryStream = new MemoryStream())
{
thumbnail.Save(memoryStream, ImageFormat.Png); // <= crash here
var bytes = new byte[memoryStream.Length];
memoryStream.Position = 0;
memoryStream.Read(bytes, 0, bytes.Length);
return Convert.ToBase64String(bytes, 0, bytes.Length);
}
}
}
}
I came across this error when I was trying a simple image editing in a WPF app.
Setting an Image element's Source to the bitmap prevents file saving.
Even setting Source=null doesn't seem to release the file.
Now I just never use the image as the Source of Image element, so I can overwrite after editing!
EDIT
After hearing about the CacheOption property(Thanks to #Nyerguds) I found the solution:
So instead of using the Bitmap constructor I must set the Uri after setting CacheOption BitmapCacheOption.OnLoad.(Image1 below is the Wpf Image element)
Instead of
Image1.Source = new BitmapImage(new Uri(filepath));
Use:
var image = new BitmapImage();
image.BeginInit();
image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = new Uri(filepath);
image.EndInit();
Image1.Source = image;
See this: WPF Image Caching
Try this code:
static void Main(string[] args)
{
byte[] data = null;
string fullPath = #"c:\testimage.jpg";
using (MemoryStream ms = new MemoryStream())
using (Bitmap tmp = (Bitmap)Bitmap.FromFile(fullPath))
using (Bitmap bm = new Bitmap(tmp))
{
bm.SetResolution(96, 96);
using (EncoderParameters eps = new EncoderParameters(1))
{
eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
bm.Save(ms, GetEncoderInfo("image/jpeg"), eps);
}
data = ms.ToArray();
}
File.WriteAllBytes(fullPath, data);
}
private static ImageCodecInfo GetEncoderInfo(string mimeType)
{
ImageCodecInfo[] encoders = ImageCodecInfo.GetImageEncoders();
for (int j = 0; j < encoders.Length; ++j)
{
if (String.Equals(encoders[j].MimeType, mimeType, StringComparison.InvariantCultureIgnoreCase))
return encoders[j];
}
return null;
}
I used imageprocessor to resize images and one day I got "A generic error occurred in GDI+" exception.
After looked up a while I tried to recycle the application pool and bingo it works. So I note it here, hope it help ;)
Cheers
I was getting this error today on a server when the same code worked fine locally and on our DEV server but not on PRODUCTION. Rebooting the server resolved it.
public static byte[] SetImageToByte(Image img)
{
ImageConverter converter = new ImageConverter();
return (byte[])converter.ConvertTo(img, typeof(byte[]));
}
public static Bitmap SetByteToImage(byte[] blob)
{
MemoryStream mStream = new MemoryStream();
byte[] pData = blob;
mStream.Write(pData, 0, Convert.ToInt32(pData.Length));
Bitmap bm = new Bitmap(mStream, false);
mStream.Dispose();
return bm;
}