将带有元数据的图像写入 MemoryStream 时,JpegBitmapEncoder.Save() 抛出异常

发布于 2024-08-29 23:28:07 字数 2863 浏览 1 评论 0原文

我正在尝试在 JPG 图像上设置没有元数据的元数据。在这种情况下,您不能使用就地编写器(InPlaceBitmapMetadataWriter),因为图像中没有元数据的位置。

如果我使用 FileStream 作为输出 - 一切正常。但如果我尝试使用 MemoryStream 作为输出 - JpegBitmapEncoder.Save() 会引发异常(来自 HRESULT 的异常:0xC0000005)。 经过一番调查后,我还发现如果我提供 null 而不是元数据,什么编码器可以将图像保存到内存流。

我制作了一个非常简单且简短的示例来重现问题:

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

它是简单的控制台应用程序。 要运行它,请将文件名变量内容替换为任何不带元数据的 .jpg 文件的路径(或使用 我的)。

Ofc 我可以先将图像保存到临时文件,关闭它,然后打开并复制到 MemoryStream,但它太肮脏且缓慢的解决方法。 欢迎任何关于让它工作的想法:)

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

吃兔兔 2024-09-05 23:28:07

如果有人遇到同样的问题,解决方案如下:

如果您尝试从主应用程序线程 .Save() jpeg,请在 Main() 之前添加 [STAThread]。

如果没有,请为调用 JpegBitmapEncoder.Save() 的线程调用 .SetApartmentState(ApartmentState.STA)

WinXP 和 WinVista 版本的 windowscodecs.dll 不可重新输入,因此如果您将使用默认 MTA 模型(自 .NET Framework 2.0 以来为默认值)对于调用 JpegBitmapEncoder.Save() 函数的线程,它可能表现得很奇怪并抛出所描述的异常。
Win7版本的windowscodecs.dll没有这个问题。

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.

少年亿悲伤 2024-09-05 23:28:07

我没有修改就运行了您的代码,并且没有引发错误。
我什至尝试将修改后的数据保存到磁盘,并且图像本身没有损坏。

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

除了我在下面添加的 s = SetTagsInMemory(...) 写入磁盘之外,其余代码未修改。

编辑:哦,元数据最终出现在新文件中,前一个文件没有我所看到的任何元数据。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文