如何以原始格式保存图像?

发布于 2024-08-31 17:59:00 字数 784 浏览 0 评论 0原文

如何使用原始编码保存图像?

似乎保存图像的唯一方法是使用 BitmapEncoder,但我不知道如何从图像中获取正确的格式。

示例:

Clipboard.GetImage() 返回一个 InteropBitmap,它似乎不包含有关原始格式的任何信息。

我还尝试使用扩展方法:

public static void Save(this BitmapImage image, System.IO.Stream stream)
{
    var decoder = BitmapDecoder.Create(image.StreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
    var encoder = BitmapEncoder.Create(decoder.CodecInfo.ContainerFormat);
    foreach (var frame in decoder.Frames)
    {
        encoder.Frames.Add(BitmapFrame.Create(frame));
    }
    encoder.Save(stream);
}

但问题是

  1. ImageSource 并不总是 BitmapImage (Clipboard.GetImage()) 例如,并且
  2. image.StreamSource 在某些情况下可能为空(似乎是通过加载图像时)相对 Uri)

有什么建议吗?

How can I save an Image with its original encoding?

It seems that the only way to save an Image is by using a BitmapEncoder but I don't know how I can get the correct format from the image.

Example:

Clipboard.GetImage() returns a InteropBitmap which doesn't seem to contain any information about the original format.

I also tried using an Extension method:

public static void Save(this BitmapImage image, System.IO.Stream stream)
{
    var decoder = BitmapDecoder.Create(image.StreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
    var encoder = BitmapEncoder.Create(decoder.CodecInfo.ContainerFormat);
    foreach (var frame in decoder.Frames)
    {
        encoder.Frames.Add(BitmapFrame.Create(frame));
    }
    encoder.Save(stream);
}

but the problem is that

  1. the ImageSource is not always a BitmapImage (Clipboard.GetImage()) for example and
  2. the image.StreamSource can be null in some cases (seems to be when the image is loaded via a relative Uri)

Any suggestions?

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

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

发布评论

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

评论(1

一杯敬自由 2024-09-07 17:59:02

这里的基本问题是“如何获取任意 ImageSource 并了解它最初编码的格式?”

答案是,您不能总是这样做,但对于大多数实际目的,有一个解决方法可用。正如上面的代码所示,一旦您了解了使用什么格式,其余的就很容易了。

您不能总是找出原始格式的原因是,可以(使用一些非常棘手的代码!)子类化 BitmapSource 以创建一个新的 ImageSource,该 ImageSource 从您喜欢的任何地方获取其数据。例如,我可以实现一个返回随机像素的 PseudoRandomBitmapSource。在这种情况下,“原始格式”可能是用于随机数生成器的种子。

如果您正在处理内置的 ImageSource 类之一,则查找原始编码的方法取决于您使用的具体类:

  1. 对于 BitmapImage,您可以使用 StreamSource 或 UriSource,无论设置哪一个。其中任何一个都可以传递给 BitmapDecoder.Create 重载。您的示例代码展示了如何为 StreamSource 执行此操作。它与 UriSource 完全相同,只是您需要 new Uri(BaseUri, UriSource) 并将其传递给 BitmapDecoder。创建采用 Uri 的重载。

  2. 对于 ColorConvertedBitmap、CroppedBitmap、FormatConvertedBitmap 和 TransformedBitmap,有一个公共“Source”属性,您可以使用它来获取基础源,然后使用此算法递归检查其编码。

  3. 对于CachedBitmap,您只能通过内部字段获取源位图。如果你有足够的权限,你可以使用反射来访问,否则你就不走运了。

  4. 对于 RenderTargetBitmap、WritableBitmap、D3DImage 和 DrawingImage,没有原始编码,因为图像是根据矢量格式或算法“即时”构造的。

  5. 对于 BitmapFrame,使用 Decoder 属性获取解码器,然后使用 CodecInfo.ContainerFormat。

  6. 对于 InteropBitmap 或 UnmanagedBitmapWrapper 来说,它非常复杂。基本上,您需要使用反射来读取内部 WicSourceHandle 属性,然后调用 DangerousGetHandle() 来获取 IntPtr,它实际上是一个非托管 IUnkown。使用非托管代码、IWICBitmapDecoder 的 QueryInterface。如果成功,您可以调用 IWICBitmapDecoder.GetContainerFormat 来获取格式 Guid(这仍然是非托管代码)。如果没有,有关原始源的所有信息都将丢失。

正如您所看到的,在很多情况下您无法获取源(例如,完全解码的位图的 InteropBitmap),或者获取源需要特殊的技术和权限(InteropBitmap 的非托管代码,或内部字段的反射)对于缓存位图)。

由于通常很难甚至不可能获得原始格式,因此最好在将这些信息传递到代码中时寻找保留此信息的方法。如果您控制图像的来源,则可能就像创建 ImageSourceAndFormat 类一样简单:

public class BitmapSourceAndFormat
{
  public ImageSource Source { get; set; }
  public Guid OriginalFormat { get; set; }
}

如果您将图像放在剪贴板上,这尤其有用。除了通常将图像添加到 DataObject 之外,还可以添加 BitmapSourceAndFormat 对象。如果执行此操作,粘贴操作将收到包含这两种格式的 DataObject。然后,您的代码只需首先检查 BitmapSourceAndFormat 对象。如果找到,它可以简单地访问它以获得原始格式。如果没有,则必须诉诸上述手段。

最后一点:对于剪贴板粘贴,您可以检查可用的数据格式字符串。一些有用的有:Bitmap、Dib、Tiff、Gif、Jpeg 和 FileName。对于“Tiff”、“Gif”和“Jpeg”,您可以硬编码所需的格式。对于“FileName”,您可以自己打开文件以获取要传递给 BitmapDecoder 的流。对于没有任何其他内容的“Dib”或“Bitmap”,您知道原始格式已丢失。

The fundamental question here is "How do I take an arbitrary ImageSource and learn what format it was originally encoded in?"

The answer is that you can't always do this, but for most practical purposes there is a workaround available. As your code above shows, once you've learned what format to use the rest is easy.

The reason you can't always find out the original format is that it is possible (with some very tricky code!) to subclass BitmapSource to create a new ImageSource that gets its data from anywhere you like. For example, I could implement a PseudoRandomBitmapSource that returns random pixels. In this case the "original format" is probably the seed used for the random number generator.

If you are dealing with one of the built in ImageSource classes, the way to find out the original encoding depends on which exact class you are using:

  1. For BitmapImage, you can use StreamSource or UriSource, whichever one is set. Either of these can be passed to a BitmapDecoder.Create overload. Your example code shows how to do this for StreamSource. It is exactly the same for UriSource except you need to new Uri(BaseUri, UriSource) and pass that to the BitmapDecoder.Create overload that takes a Uri.

  2. For ColorConvertedBitmap, CroppedBitmap, FormatConvertedBitmap, and TransformedBitmap there is a public "Source" property which you can use to get the underlying source, then recursively check its encoding using this algorithm.

  3. For CachedBitmap, you can only get to the source bitmap through an internal field. You can access using reflection if you have enough privileges, otherwise you're out of luck.

  4. For RenderTargetBitmap, WritableBitmap, D3DImage, and DrawingImage there is no original encoding since the image is being constructed "on the fly" from a vector format or an algorithm.

  5. For BitmapFrame, use the Decoder property to get the decoder, then use CodecInfo.ContainerFormat.

  6. For InteropBitmap or UnmanagedBitmapWrapper it is very complicated. Basically you need to use reflection to read the internal WicSourceHandle property, then call DangerousGetHandle() to get an IntPtr which is actually an unmanaged IUnkown. Using unmanaged code, QueryInterface for IWICBitmapDecoder. If successful you can call IWICBitmapDecoder.GetContainerFormat to get the format Guid (this is still unmanaged code). If not, all information about the original source has been lost.

As you can see, there are quite a few situations where you can't get the source (eg an InteropBitmap for a fully decoded bitmap) or where getting the source requires special techniques and privileges (unmanaged code for InteropBitmap, or reflection on internal fields for CachedBitmap).

Because it is often difficult to impossible to get the original format, it is a good idea to look for ways to preserve this information as it is passed into your code. If you control the source of the image, it may be as simple as creating a ImageSourceAndFormat class:

public class BitmapSourceAndFormat
{
  public ImageSource Source { get; set; }
  public Guid OriginalFormat { get; set; }
}

This is particularly useful if you are placing the image on the clipboard. In addition to adding the image to the DataObject normally, you can also add a BitmapSourceAndFormat object. If you do this, the Paste operation will receive a DataObject containing both of these formats. Your code then only needs to check for the BitmapSourceAndFormat object first. If found it can simply access it to get the original format. If not, it must resort to the means described above.

One last note: For clipboard pastes, you can check the available data format strings. Some useful ones are: Bitmap, Dib, Tiff, Gif, Jpeg, and FileName. For "Tiff", "Gif", and "Jpeg" you can hard-code the required format. For "FileName" you can open the file yourself to get a stream to pass to a BitmapDecoder. For "Dib" or "Bitmap" without anything else, you know the original format has been lost.

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