views:

265

answers:

2

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 :)

+1  A: 

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.

Jonas B
It's kinda mystic oOCan you please archive your whole solution (including all VS files) and mail to me? mephisto123[at]gmail[dot]comI will try to run yours and see what happens.Thank you.
mephisto123
and send me your a.jpg too. or test if program works with the jpg I've linked in first post.
mephisto123
Yeah it is. Sure thing, what version of VS are you running? I made a 2010 project but I can make a 2008 one if it suits you better.
Jonas B
I tried running it with your image as well and it worked. Anyway I'll zip it and send you the whole thing including your image with metadata included.
Jonas B
I'm using 2008.Actually I wonder if it will work for you in 2008
mephisto123
I made it in 2008 now it worked. Sending it to you right now.
Jonas B
Don't forget to set the right paths for input and output file :)
Jonas B
Ops sorry for having that other stuff in the project as well, I use that project for testing stuff ;P
Jonas B
Didnt recieve anything yet :(
mephisto123
I'm starting to think it's cuz I'm developing on my WinXP in VirtualBox. Ppl with Windows 7 can run this program normally without any problems.
mephisto123
Amazing.. it didn't pass gmails spam filter.. <[email protected]>: host gmail-smtp-in.l.google.com[209.85.227.27] said: 552-5.7.0 Our system detected an illegal attachment on your message. Please 552-5.7.0 visit http://mail.google.com/support/bin/answer.py?answer=6590 to 552 5.7.0 review our attachment guidelines. y12si3271009wei.36 (in reply to end of DATA command)
Jonas B
I tried sending it as .zzz instead of .zip.. apparently gmail wont allow .zip attachments.And yes it could have something to do with that
Jonas B
Thank you, got it, will test it now.
mephisto123
I have same exception when running your project :(Looks like it's some WinXP bug ><What's best thing to do ? Use dirty workaround I described is the only option i see :(
mephisto123
Just got information about behavior of this program on Vista: it gives less cryptic error message: "unable to write data to stream"
mephisto123
This is really odd, I run Win 7 x64 and it works flawless
Jonas B
I'm going to download .NET source and try to debug JpegBitmapEncoder.Save() method. If I'll find code causing this bug, I'll subclass JpegBitmapEncoder and fix it.
mephisto123
Good luck, sorry I couldn't help you more. Hard for me to track down the problem when I can't reproduce it :)
Jonas B
Looks like I will have to use my dirty workaround.The bug is in wpfgfx_v300.dll or windowscodecs.dll, and it's not managed code assemblies, its C++ code, I can't debug it since I don't have source :(The problem is fixed in Windows 7 dlls, but I don't see the way to solve it in WinXP/Vista :(
mephisto123
I've found the solution :) check my answer to this question if you're interested :) and thanks for the time you spent trying to help me :)
mephisto123
+1  A: 

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.

mephisto123