JpegBitmapEncoder.Save() throws exception when writing image with metadata

2

I'm building a WPF desktop app to help me organize photos to post to Facebook. Here's my code for creating a copy of a photo at a new location with a caption (EXIF + IPTC + XMP) added:

    private void SaveImageAs(string currPath, string newPath, bool setCaption = false, string captionToSet = "")
    {
        System.IO.FileStream stream = new System.IO.FileStream(currPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
        JpegBitmapDecoder decoder = new JpegBitmapDecoder(stream, BitmapCreateOptions.None, BitmapCacheOption.None);
        BitmapFrame bitmapFrame = decoder.Frames[0];
        BitmapMetadata metadata = bitmapFrame.Metadata.Clone() as BitmapMetadata;
        stream.Close();

        if (setCaption)
        {
            // if we want to set the caption, do it in EXIF, IPTC, and XMP

            metadata.SetQuery("/app1/ifd/{uint=270}", captionToSet);
            metadata.SetQuery("/app13/irb/8bimiptc/iptc/Caption", captionToSet);
            metadata.SetQuery("/xmp/dc:description/x-default", captionToSet);
        }

        MemoryStream memstream = new MemoryStream();   // create temp storage in memory
        JpegBitmapEncoder encoder = new JpegBitmapEncoder();
        encoder.Frames.Add(BitmapFrame.Create(bitmapFrame, bitmapFrame.Thumbnail, metadata, bitmapFrame.ColorContexts));
        encoder.Save(memstream);   // save in memory
        stream.Close();

        stream = new FileStream(newPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
        memstream.Seek(0, System.IO.SeekOrigin.Begin);    // go to stream start
        byte[] bytes = new byte[memstream.Length + 1];
        memstream.Read(bytes, 0, (int)memstream.Length);
        stream.Write(bytes, 0, bytes.Length);
        stream.Close();
        memstream.Close();
    }

Running that, I get a "COMException was unhandled" exception highlighting this line:

encoder.Save(memstream);

An unhandled exception of type 'System.Runtime.InteropServices.COMException' occurred in PresentationCore.dll

Additional information: The handle is invalid. (Exception from HRESULT: 0x80070006 (E_HANDLE))

I saw here that this could be due to a threading issue, so instead of directly calling SaveImageAs from the app, I added this, to no effect:

        private void _SaveImageAs(string currPath, string newPath, bool setCaption = false, string captionToSet = "")
    {
        Thread saveThread = new Thread(() => SaveImageAs(currPath, newPath, setCaption, captionToSet));
        saveThread.SetApartmentState(ApartmentState.STA);
        saveThread.IsBackground = false;
        saveThread.Start();
    }

I also tried swapping out the MemoryStream for a FileStream creating a local temp file-- that didn't change anything:

FileStream memstream = new FileStream(System.IO.Path.GetDirectoryName(newPath) + @"\" + "temp.jpg", System.IO.FileMode.OpenOrCreate);

Any ideas?

c#
wpf
metadata
exif
iptc
asked on Stack Overflow Jun 11, 2014 by amb9800 • edited May 23, 2017 by Community

2 Answers

4

There are a few things wrong in your code.

  1. The source stream has to be kept open until the BitmapFrame is written to the target stream.

  2. Image metadata of a BitmapFrame from a BitmapDecoder is read-only. You have to create a new BitmapFrame from the original one when you want to modify metadata.

  3. The third query seems to be broken. The exception says "Property cannot be found".

This code works for me:

public static void SaveImageAs(string sourcePath, string targetPath, string caption)
{
    using (var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read))
    {
        var decoder = new JpegBitmapDecoder(sourceStream, BitmapCreateOptions.None, BitmapCacheOption.None);
        var frame = decoder.Frames[0];

        if (!string.IsNullOrWhiteSpace(caption))
        {
            frame = BitmapFrame.Create(frame);
            var metadata = (BitmapMetadata)frame.Metadata;

            metadata.SetQuery("/app1/ifd/{uint=270}", caption);
            metadata.SetQuery("/app13/irb/8bimiptc/iptc/Caption", caption);
            //metadata.SetQuery("/xmp/dc:description/x-default", caption);
        }

        var encoder = new JpegBitmapEncoder();
        encoder.Frames.Add(frame);

        using (var targetStream = new FileStream(targetPath, FileMode.Create))
        {
            encoder.Save(targetStream);
        }
    }
}
answered on Stack Overflow Jun 11, 2014 by Clemens • edited Jun 11, 2014 by Clemens
1

You got the exception because you closed the stream used for loading the original jpeg. Comment the first stream.Close() (just above if(setCaption)) and it will work. Just like when working with a Image instance and you must keep the stream open for the lifetime of the Image.

answered on Stack Overflow Jun 11, 2014 by cdel

User contributions licensed under CC BY-SA 3.0