设为首页 收藏本站
查看: 718|回复: 0

[经验分享] Windows Imaging Component 基础知识

[复制链接]

尚未签到

发表于 2016-5-20 10:34:23 | 显示全部楼层 |阅读模式
  来自网络:http://dev.21tx.com/2009/02/09/14033.html
  
  
目录
  入门
  解码图像
  编码图像
  WIC 图像工厂
  使用流
  通过 WPF 使用 WIC
  下一步是什么?
  Microsoft® Windows® Imaging Component (WIC) 是用于编码、解码和操控图像的可扩展框架。WIC 最初是为 Windows Vista® 和 Windows Presentation Foundation (WPF) 而设计的,但现在,不仅 Windows Vista 和 Microsoft .net Framework 3.0 及更高版本附带此框架,而且它还是 Windows XP 和 Windows Server® 2003 的一个下载项,可供于本机应用程序使用。
  作为支持 WPF 的多个功能强大的本机框架之一,本文中所说的 WIC 是用于实现 System.Windows.Media.Imaging 命名空间的框架。但是,它也非常适合于以 C++ 编写的本机应用程序,因为它提供了通过一组 COM 接口呈现的简单但功能强大的 API。
  WIC 使用一组可扩展的图像编解码器支持多种图像格式。每个编解码器支持一种不同的图像格式,并且通常同时提供编码器和解码器。WIC 包括用于所有主要图像格式的一组内置编解码器,这些格式包括 PNG、JPEG、GIF、TIFF、HD Photo (HDP)、ICO,当然还有 Windows BMP。
  HDP 可能是唯一您没有听过的格式。它最初称为 Windows Media Photo 且是配合 Windows Vista 开发的,用于克服现有格式的一些限制并提供更好的性能和更高的图像质量。如需有关 HDP 的更多信息,请查看 microsoft.com/whdc/xps/wmphoto.mspx 上的规范。幸运地是,WIC 可很好地支持这一新图像格式,因此应用程序不必知道格式的具体细节即可使用它们。
  本月,我将向您展示如何使用 WIC 来编码和解码不同的图像格式以及其间的多项事宜。下一次,我将讲述一些更高级的功能,并向您展示如何使用自己的图像编解码器来扩展 WIC。
  入门
  WIC API 包含 COM 接口、函数、结构和错误代码,以及标识各种编解码器、容器和格式的 GUID。需要的所有声明均包括在 wincodec.h 和 wincodecsdk.h 头文件中,这些文件是 Windows SDK 的一部分(Visual Studio® 2008 中附带这些文件)。您还必须链接到 WindowsCodecs.lib 库,该库提供了您可能需要的各种定义。可将以下代码添加到项目的预编译头文件中以使其完全可用:
#include <wincodec.h>
#include <wincodecsdk.h>
#pragma comment(lib, "WindowsCodecs.lib")

  由于 WIC API 主要包含的是 COM 接口,因此我使用活动模板库 (ATL) CComPtr 类来处理接口指针的创建和管理。如果要执行相同的操作,还需包括定义 CComPtr 模板类的 atlbase.h 头文件:
#include <atlbase.h>
  WIC API 还使用 COM 库,因此使用此 API 的任何线程均必须调用 CoInitializeEx 函数。
  最后,WIC API 使用 HRESULT 来描述错误。本文中的示例使用 HR 宏来清晰地识别方法在何处返回需要检查的 HRESULT。可将它替换为自己的错误处理策略—由它引发异常或是您自己返回 HRESULT。
  解码图像
  解码器由 IWICBitmapDecoder 接口表示。WIC 提供了多种用于创建解码器对象的方法,但可仅使用特定解码器的 CLSID 来创建一个实例。以下示例为 TIFF 图像创建了一个解码器:
CComPtr<IWICBitmapDecoder> decoder;
HR(decoder.CoCreateInstance(CLSID_WICTiffDecoder));

  图 1 列出了 WIC 所包含的编解码器以及可用于创建不同解码器的 CLSID。创建完解码器后,需使用包含像素和可选元数据(它们使用解码器可理解的格式)的流对其进行初始化:
http://img2.21tx.com/2009/02/09/22894.jpgFigure1内置 WIC 编解码器的 CLSID
 
格式解码器编码器
BMPCLSID_WICBmpDecoderCLSID_WICBmpEncoder
PNGCLSID_WICPngDecoderCLSID_WICPngEncoder
ICOCLSID_WICIcoDecoder不可用
JPEGCLSID_WICJpegDecoderCLSID_WICJpegEncoder
GIFCLSID_WICGIfDecoderCLSID_WICGifEncoder
TIFFCLSID_WICTiffDecoderCLSID_WICTiffEncoder
HDPCLSID_WICWmpDecoderCLSID_WICWmpEncoder
 
CComPtr<IStream> stream;
// Create stream object here...
HR(decoder->Initialize(
 stream,
 WICDecodeMetadataCacheOnDemand));

  我将在本文后面的内容中讨论流,但 IStream 只是许多 API 使用的传统 COM 流接口,包括我在 2007 年 4 月出版的《MSDN® 杂志》(msdn.microsoft.com/msdnmag/issues/07/04/XML) 中所介绍的 XmlLite 分析器。
  Initialize 方法的第二个参数介绍您希望解码器如何从流中读取图像信息。WICDecodeMetadataCacheOnDemand 指示解码器应仅在需要时从流中读取图像信息。如果图像格式恰好包含或支持多个帧,则此标记将特别有用。与此相对的标记是 WICDecodeMetadataCacheOnLoad,它指示解码器应立即缓存所有图像信息。然后,对解码器的所有后续请求都应直接从内存执行。它对于托管代码具有更大的意义,我将在稍后进行介绍。
  初始化解码器后,即可自由查询解码器以获取信息。您最可能询问的是构成图像的一组帧。帧是包含像素的实际位图。可将图像格式看作一个帧容器。正如我所提到的,有些图像格式支持多个帧。
  GetFrameCount 函数用于确定图像中的帧数:
UINT frameCount = 0;
HR(decoder->GetFrameCount(&frameCount));

  给定帧数后,即可使用 GetFrame 方法来检索单个帧:
for (UINT index = 0; index < frameCount; ++index)
{
  CComPtr<IWICBitmapFrameDecode> frame;
  HR(decoder->GetFrame(index, &frame));
}

  GetFrame 所返回的 IWICBitmapFrameDecode 接口是从代表只读位图的 IWICBitmapSource 接口派生而来的。IWICBitmapFrameDecode 提供与帧相关的信息,如元数据和色彩配置文件。IWICBitmapSource 提供位图的大小和分辨率、像素格式以及其他可选特性(如色彩表)。IWICBitmapSource 还提供 CopyPixels 方法,此方法可用于从位图中实际读取像素。
  可使用 GetSize 方法获取以像素表示的帧尺寸:
UINT width = 0;
UINT height = 0;
HR(frame->GetSize(&width, &height));

  并且可使用 GetResolution 方法获取以每英寸点数 (dpi) 表示的帧分辨率:
double dpiX = 0;
double dpiY = 0;
HR(frame->GetResolution(&dpiX, &dpiY));

  尽管分辨率对于像素本身没有影响,但在使用逻辑坐标系统(如 WPF 所使用的)时,它的确会影响图像的显示效果。
  最后一个重要帧属性是像素格式。像素格式描述像素在内存中的布局,并且还暗示支持的颜色或颜色空间的范围。GetPixelFormat 方法返回像素格式:
GUID pixelFormat = { 0 };
HR(frame->GetPixelFormat(&pixelFormat));

  像素格式定义为 GUID,其名称非常清楚地描述了内存布局。例如,GUID_WICPixelFormat24bppRGB 格式表示每个像素使用 24 位(3 字节)存储,其中每个颜色通道 1 个字节。此外,红色 (R)、绿色 (G) 和蓝色 (B) 字母的顺序表示从最不重要到最重要的字节顺序。例如,GUID_WICPixelFormat32bpPBGRA 格式表示每个像素使用 32 位(4 字节)存储,其中每个颜色通道 1 个字节,阿尔法通道 1 个字节。在此示例中,通道的顺序是蓝色 (B) 通道最不重要,阿尔法 (A) 通道最重要。
  可使用 CopyPixels 方法来检索实际的像素。
HRESULT CopyPixels(
 const WICRect* rect,
 UINT stride,
 UINT bufferSize,
 BYTE* buffer);

  rect 参数指定位图中要复制的一个矩形。可将此参数设置为 0,此时会复制整个位图。我马上会谈到跨距。buffer 和 bufferSize 参数指示将像素写到何处以及可用空间有多大。
  跨距是位图比较麻烦的一个方面。跨距是扫描线之间的字节数。一般而言,构成位图像素的位会被打包成行。单个行的长度应足够存储一行位图像素。跨距是一行的长度(以字节为单位),向上取整为最接近的 DWORD(4 个字节)。从而允许每像素少于 32 位 (bpp) 的位图占用更少的内存,同时仍提供良好的性能。可使用以下函数来计算指定位图的跨距:
UINT GetStride(
 const UINT width, // image width in pixels
 const UINT bitCount) { // bits per pixel
 ASSERT(0 == bitCount % 8);
 const UINT byteCount = bitCount / 8;
 const UINT stride = (width * byteCount + 3) & ~3;
 ASSERT(0 == stride % sizeof(DWORD));
 return stride;
}

  除此之外,可按如下调用 CopyPixels 方法,假定帧代表一个 32 bpp 位图:
const UINT stride = GetStride(width, 32);
CAtlArray<BYTE> buffer;
VERIFY(buffer.SetCount(stride * height));
HR(frame->CopyPixels(
 0, // entire bitmap
 stride,
 buffer.GetCount(),
 &buffer[0]));

  我的示例使用的是 ATL CAtlArray 集合类来分配缓存,当然您可以使用所喜欢的任何存储。要更有效地处理更大的位图,可多次调用 CopyPixels 以读取位图的不同部分。
  编码图像
  编码器由 IWICBitmapEncoder 接口表示。与解码器一样,WIC 提供了多种用于创建编码器的方法,但可仅使用特定编码器的 CLSID 来创建它。例如,以下代码为 PNG 图像创建了一个编码器:
CComPtr<IWICBitmapEncoder> encoder;
HR(encoder.CoCreateInstance(CLSID_WICPngEncoder));

  图 1 列出了可用于创建 WIC 所包含的各种编码器的 CLSID。创建完编码器后,需使用将最终接收已编码像素和可选元数据的流对其进行初始化:
CComPtr<IStream> stream;
// Create stream object here...
HR(encoder->Initialize(
 stream,
 WICBitmapEncoderNoCache));

  Initialize 方法的第二个参数就没那么有趣了,因为 WICBitmapEncoderNoCache 是目前支持的唯一标记。
  初始化编码器后,现在开始添加帧。CreateNewFrame 方法会创建一个新帧,然后可对其进行配置并向其写入像素:
CComPtr<IWICBitmapFrameEncode> frame;
CComPtr<IPropertyBag2> properties;
HR(encoder->CreateNewFrame(
 &frame,
 &properties));

  CreateNewFrame 返回代表新帧的 IWICBitmapFrameEncode 接口和 IPropertyBag2 接口。后者为可选项,可用于指定任何特定于编码器的属性,如 JPEG 的图像质量或 TIFF 的压缩算法。例如,以下显示了可如何设置 JPEG 图像的图像质量:
PROPBAG2 name = { 0 };
name.dwType = PROPBAG2_TYPE_DATA;
name.vt = VT_R4;
name.pstrName = L"ImageQuality";
CComVariant value(0.75F);
HR(properties->Write(
 1, // property count
 &name,
 &value));

  图像质量的值必须在 0.0(表示可能的最低质量)和 1.0(表示可能的最高质量)之间。
  设完编码器属性后,需先调用 Initialize 方法再进行配置和写入帧:
HR(frame->Initialize(properties));
  下一步是为新帧设置尺寸和像素格式,然后才能向它写入像素:
HR(frame->SetSize(width, height));
GUID pixelFormat = GUID_WICPixelFormat24bppBGR;
HR(frame->SetPixelFormat(&pixelFormat));
ASSERT(GUID_WICPixelFormat24bppBGR == pixelFormat);

  SetPixelFormat 参数为一个 [in, out] 参数。在输入时,它指定所需的像素格式。在输出时,它包含所支持的最接近像素格式。这通常不是问题,除非格式是在运行时设置的,可能基于另一位图的像素格式。
  使用 WritePixels 方法将像素写入到帧中,如下所示:
HRESULT WritePixels(
 UINT lineCount,
 UINT stride,
 UINT bufferSize,
 BYTE* buffer);

  lineCount 参数指定将写入的像素行数。这意味着可多次调用 WritePixels 来写入整个帧。stride 参数指示如何将缓存中的像素打包到行中。我已在上一小节中介绍了如何计算跨距。
  在一次或多次调用 WritePixels 来写入整个帧之后,需通过调用帧的 Commit 方法来告诉编码器帧已做好准备。并且,在提交了构成图像的所有帧后,需通过调用编码器的 Commit 方法来告诉编码器图像已做好被保存的准备。
  到此为止,我已介绍完编码和解码图像的基础知识。在介绍后续内容之前,我想通过一个简单的示例来将其表达清楚。图 2 显示一个 CopyIconToTiff 函数,它展示了如何读取构成图标的各个位图,以及如何将它们复制到多帧 TIFF 图像。
http://img2.21tx.com/2009/02/09/22894.jpgFigure2CopyIconToTiff
HRESULT CopyIconToTiff(
  IStream* sourceStream,
  IStream* targetStream) {
 // Prepare the ICO decoder
 CComPtr<IWICBitmapDecoder> decoder;
 HR(decoder.CoCreateInstance(CLSID_WICIcoDecoder));
 HR(decoder->Initialize(
  sourceStream,
  WICDecodeMetadataCacheOnDemand));
 // Prepare the TIFF encoder
 CComPtr<IWICBitmapEncoder> encoder;
 HR(encoder.CoCreateInstance(CLSID_WICTiffEncoder));
 HR(encoder->Initialize(
  targetStream,
  WICBitmapEncoderNoCache));
 UINT frameCount = 0;
 HR(decoder->GetFrameCount(&frameCount));
 for (UINT index = 0; index < frameCount; ++index) {
  // Get the source frame info
  CComPtr<IWICBitmapFrameDecode> sourceFrame;
  HR(decoder->GetFrame(index, &sourceFrame));
  UINT width = 0;
  UINT height = 0;
  HR(sourceFrame->GetSize(&width, &height));
  GUID pixelFormat = { 0 };
  HR(sourceFrame->GetPixelFormat(&pixelFormat));
  // Prepare the target frame
  CComPtr<IWICBitmapFrameEncode> targetFrame;
  HR(encoder->CreateNewFrame(
   &targetFrame,
   0)); // no properties
  HR(targetFrame->Initialize(0)); // no properties
  HR(targetFrame->SetSize(width, height));
  HR(targetFrame->SetPixelFormat(&pixelFormat));
  // Copy the pixels and commit frame
  HR(targetFrame->WriteSource(sourceFrame, 0));
  HR(targetFrame->Commit());
 }
 // Commit image to stream
 HR(encoder->Commit());
 return S OK;
}

  在该示例中,我通过利用 WritePixels 方法的替代项进一步简化了流程。不是先从源帧中复制像素然后将其写入到目标帧中,而是使用 WriteSource 方法来从给定的 IWICBitmapSource 接口直接读取像素。由于 IWICBitmapFrameDecode 接口是从 IWICBitmapSource 派生而来,因而提供了一个完善的解决方案。
  WIC 图像工厂
  WIC 提供了一个图像工厂来创建各种与 WIC 相关的对象。它通过 IWICImagingFactory 接口公开,并且可按如下所示进行创建:
CComPtr<IWICImagingFactory> factory;
HR(factory.CoCreateInstance(CLSID_WICImagingFactory));

  我已展示了如果特定图像格式给定了指出实现的 CLSID,该如何创建解码器。当然,正如您可能猜想到,如果不必指定特定实现,或者可以硬编码图像的格式,那它会更有用途。
  幸运的是,WIC 提供了解决方案。在创建解码器之前,WIC 可检查给定流有无可标识图像格式的模式。一旦找到最佳的匹配项,它会创建适当的解码器并使用相同流对其进行初始化。CreateDecoderFromStream 方法提供了这一功能:
CComPtr<IWICBitmapDecoder> decoder;
HR(factory->CreateDecoderFromStream(
 stream,
 0, // vendor
 WICDecodeMetadataCacheOnDemand,
 &decoder));

  第二个参数标识解码器的供应商。它是可选项,如果您偏好特定供应商的编解码器,这个参数就会派上用场。请记住,它仅是一个提示,如果特定供应商并未安装适合的解码器,则仍会选择一个解码器,而不管供应商是谁。
  IWICImagingFactory 还提供 CreateDecoderFromFilename 和 CreateDecoderFromFileHandle 方法,它们分别提供到文件和文件句柄的路径。也可以不指定 CLSID 或流,而是指出图像格式来创建解码器。CreateDecoder 方法正好可以完成这项工作:
CComPtr<IWICBitmapDecoder> decoder;
HR(factory->CreateDecoder(
 GUID_ContainerFormatIco,
 0, // vendor
 &decoder));
HR(decoder->Initialize(
 stream,
 WICDecodeMetadataCacheOnDemand));

  与此类似,CreateEncoder 方法为特定图像格式创建编码器时也不必考虑其实现,如下所示:
CComPtr<IWICBitmapEncoder> encoder;
HR(factory->CreateEncoder(
 GUID_ContainerFormatBmp,
 0, // vendor
 &encoder));
HR(encoder->Initialize(
 stream,
 WICBitmapEncoderNoCache));

  图 3 列出了标识独立于实现的图像格式的 GUID,也称为容器格式。
http://img2.21tx.com/2009/02/09/22894.jpgFigure3容器格式 GUID
 
格式GUID
BMPGUID_ContainerFormatBmp
PNGGUID_ContainerFormatPng
ICOGUID_ContainerFormatIco
JPEGGUID_ContainerFormatJpeg
GIFGUID_ContainerFormatGif
TIFFGUID_ContainerFormatTiff
HDPGUID_ContainerFormatWmp
 
  使用流
  可随时提供任何有效的 IStream 实现以用于 WIC。例如,可使用我在 XmlLite 这篇文章中所介绍的 CreateStreamOnHGlobal 或 SHCreateStreamOnFile 函数,也可以编写您自己的实现。WIC 还提供非常方便且灵活的 IStream 实现。
  通过使用我在上一小节中介绍的图像工厂,可按如下所示创建未初始化的流对象:
CComPtr<IWICStream> stream;
HR(factory->CreateStream(&stream));

  IWICStream 接口继承自 Istream,并提供了多种方法来将流与不同的存储支持相关联。例如,可使用 InitializeFromFilename 来创建特定文件所支持的流:
HR(stream->InitializeFromFilename(
 L"file path",
 GENERIC_READ));

  也可使用 InitializeFromIStreamRegion 将某个流创建为另一个流的子集,或使用 InitializeFromMemory 在一个内存块上创建流。
  通过 WPF 使用 WIC
  正如我已提到的,WIC 提供了作为 WPF 图像处理功能基础的框架。System.Windows.Media.Imaging 命名空间中定义了各种图像处理类。为向您展示从托管代码使用是多么地简单,图 4 显示了原本在图 2 中的 CopyIconToTiff 函数,它使用 WPF 包装类以 C# 重新进行了编写。
http://img2.21tx.com/2009/02/09/22894.jpgFigure4以 C# 和 WPF 重新编写的 CopyIconToTiff
static void CopyIconToTiff(Stream sourceStream,
              Stream targetStream) {
 IconBitmapDecoder decoder = new IconBitmapDecoder(
  sourceStream,
  BitmapCreateOptions.None,
  BitmapCacheOption.OnDemand);
 TiffBitmapEncoder encoder = new TiffBitmapEncoder();
 foreach (BitmapFrame frame in decoder.Frames) {
  encoder.Frames.Add(frame);
 }
 encoder.Save(targetStream);
}

  BitmapCacheOption.OnDemand 值对应于本机代码中使用的 WICDecodeMetadataCacheOnDemand 解码器选项。另一个 BitmapCacheOption.OnLoad 值则对应 WICDecodeMetadataCacheOnLoad 解码器选项。
  我已介绍了当解码器将图像信息读入内存时,这些选项分别有何影响。但是,在托管代码中处理这些选项时,还有一个应了解的副作用。考虑一下指定 BitmapCacheOption.OnDemand 时可能发生什么情况。解码器会持有对基础流的引用,并且可能在创建位图解码器对象后的某个时间从其中执行读取。它的前提是流仍然可用。您需要注意,应用程序不会过早地关闭流。因此需要管理流的生存期,以便不会在解码器完成使用之前将其关闭。
  它不会影响本机代码,因为 IStream 接口为标准 COM 接口,其生存期是由引用计数控制的。应用程序可释放对它的所有引用,但只要有必要,解码器都会保存一个引用。并且,仅当释放了所有接口指针后才会关闭流。
  下一步是什么?
  WIC 提供了一个功能极其强大且灵活的框架,它可满足您的各种图像处理需求。通过使用一组丰富的编解码器和一个简单的 API,您即可立刻开始利用它的多项功能。
  在下一专栏中,我将探讨 WIC 所提供的一些更高级的功能。我将向您展示如何开发自己的编解码器,并详细地说明注册和发现过程,包括模式匹配功能。到时,我还会更正一个内置编解码器中的一个限制。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-219475-1-1.html 上篇帖子: Windows性能管理解析 下篇帖子: 读取windows中网络配置信息
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表