【问题标题】:JpegBitmapEncoder.Save() throws exception when writing image with metadata使用元数据写入图像时,JpegBitmapEncoder.Save() 抛出异常
【发布时间】:2014-08-01 02:56:58
【问题描述】:

我正在构建一个 WPF 桌面应用程序来帮助我组织照片以发布到 Facebook。这是我在新位置创建照片副本的代码,并添加了标题(EXIF + IPTC + XMP):

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

运行它,我得到一个突出显示这一行的“COMException was unhandled”异常:

encoder.Save(memstream);

“System.Runtime.InteropServices.COMException”类型的未处理异常发生在 PresentationCore.dll

附加信息:句柄无效。 (来自 HRESULT 的异常:0x80070006 (E_HANDLE))

我看到here 说这可能是由于线程问题,所以我没有直接从应用程序调用 SaveImageAs,而是添加了这个,但没有效果:

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

我还尝试将 MemoryStream 换成 FileStream 创建一个本地临时文件——这并没有改变任何东西:

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

有什么想法吗?

【问题讨论】:

    标签: c# wpf metadata exif iptc


    【解决方案1】:

    您的代码中有几处错误。

    1. 在将 BitmapFrame 写入目标流之前,源流必须保持打开状态。

    2. 来自 BitmapDecoder 的 BitmapFrame 的图像元数据是只读的。当您想修改元数据时,您必须从原始 BitmapFrame 创建一个新的 BitmapFrame。

    3. 第三个查询似乎被破坏了。异常显示“找不到属性”。

    此代码适用于我:

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

      您收到异常是因为您关闭了用于加载原始 jpeg 的流。注释第一个 stream.Close() (就在 if(setCaption) 上面),它会起作用。就像使用 Image 实例时一样,您必须在 Image 的整个生命周期内保持流打开。

      【讨论】:

      • 使用 BitmapCacheOption.OnLoad 代替 None 会使代码运行速度慢 10 倍以上(在 100 张大约 1M 的图像上进行了测试)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-17
      • 2013-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多