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

帅比萌擦擦* 提交于 2021-02-05 07:31:38

问题


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?


回答1:


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



回答2:


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.



来源:https://stackoverflow.com/questions/24156355/jpegbitmapencoder-save-throws-exception-when-writing-image-with-metadata

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!