JpegBitmapEncoder.Save () throws an exception when writing an image with metadata to a MemoryStream

I'm trying to set up metadata on a JPG image that doesn't have it. You cannot use the inline writer (InPlaceBitmapMetadataWriter) in this case, because there is no place for metadata in the 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 doing some research, I also found out which encoder can save the image to a memory stream if I put zero instead of metadata.

I made a very simplified and short example that 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");
        }
    }
}

      

This is a simple console application. To run it, replace the contents of the pathname variable with any .jpg file without metadata (or use mine ).

Ofc I can first save the image to a temporary file, close it, then open and copy to a MemoryStream, but its a messy and slow workaround. Any ideas on getting this work are appreciated :)

+2


a source to share


2 answers


If anyone comes across the same problem, here's the solution:

If you try to execute .Save () jpeg from the main thread of your application, add [STAThread] before Main ().



If not, call .SetApartmentState (ApartmentState.STA) on the thread calling JpegBitmapEncoder.Save ()

WinXP and WinVista versions of WindowsCodecs.dll are not returned, so if you use the default MTA model (default with .NET framework 2.0) for threads that call JpegBitmapEncoder.Save (), it may behave strangely and throw an exception. The Windows7 version of WindowsCodecs.dll does not have this problem.

+2


a source


I ran your code unchanged and it didn't throw an error. I even tried saving the modified data to disk and the image itself was not corrupted.

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();

      



Apart from what I added below s = SetTagsInMemory (...) for writing to disk, the rest of your code is unchanged.

Edit: oh and the metadata definitely ended up in a new file, the previous one didn't have any metadata from what I could see.

+1


a source







All Articles