Direct2D教程V——位图(Bitmap)和位图笔刷(BitmapBrush)
目前博客园中成系列的Direct2D的教程有
1、万一的 Direct2D 系列,用的是Delphi 2009
2、zdd的 Direct2D 系列,用的是VS中的C++
3、本文所在的 Direct2D教程 系列,用的是VS2010的Visual Basic语言(可以很方便的转为C#),基于Windows API Code Pack 1.1。
还有官方的说明文档 Direct2D ,用的是C++。
本系列的前几篇文章:
Direct2D教程II——绘制基本图形和线型(StrokeStyle)的设置详解
Direct2D中的绘图函数
在Direct2D中,RenderTarget对象的绘图函数是DrawBitmap和DrawBitmapAtOrigin。这两个函数的原型定义如下:
Public Sub DrawBitmap(bitmap As Direct2D1.D2DBitmap)
Public Sub DrawBitmap(bitmap As Direct2D1.D2DBitmap, opacity As Single, interpolationMode As Direct2D1.BitmapInterpolationMode)
Public Sub DrawBitmap(bitmap As Direct2D1.D2DBitmap, opacity As Single, interpolationMode As Direct2D1.BitmapInterpolationMode, destinationRectangle As Direct2D1.RectF)
Public Sub DrawBitmap(bitmap As Direct2D1.D2DBitmap, opacity As Single, interpolationMode As Direct2D1.BitmapInterpolationMode, destinationRectangle As Direct2D1.RectF, sourceRectangle As Direct2D1.RectF)
Public Enum BitmapInterpolationMode
Linear = 1
NearestNeighbor = 0
End Enum
Public Sub DrawBitmapAtOrigin(bitmap As Direct2D1.D2DBitmap, opacity As Single, interpolationMode As Direct2D1.BitmapInterpolationMode, sourceRectangle As Direct2D1.RectF)
从函数的原型定义可以看出,DrawBitmap函数有4个重载函数,我们以最后一个函数说明各个参数的意义。
参数bitmap:要绘制的位图对象。参数类型是D2DBitmap类。
参数opacity:不透明度。范围是0-1,0表示全透明,1表示不透明
参数interpolationMode:缩放时的插值算法。参数类型是BitmapInterpolationMode枚举,分为Linear和NearestNeighbor
参数destinationRectangle:在RenderTarget上绘制图像的范围。参数类型是RectF类型。如果不指定的话,默认绘图在原点(左上角),大小是源位图的大小。
参数sourceRectangle:源位图需要绘制的范围。参数类型是RectF类型。不指定的话,指整个源位图。这个参数可以绘制源位图的一部分。
函数DrawBitmapAtOrigin指的是在原点位置绘制位图,参数意义和上面的一样,这里不再解释了(不过由于不牵涉到缩放操作,似乎参数interpolationMode没什么意义)
从上面的原型定义可以看出,在Direct2D中,有关位图的类是D2DBitmap类。有关位图的操作都和这个有关(绘制位图和位图笔刷(BitmapBrush))。但是,D2DBitmap类没有提供位图和文件转换的函数(从文件读取位图和把位图保存到文件中)。
在Direct2D中,有关位图文件的解码和编码(读取位图文件和保存位图文件)是WIC组件(和Direct2D命名空间平级的WindowsImagingComponent命名空间)
有关WIC(WindowsImagingComponent)组件的介绍如下:
Windows Imaging Component (WIC) 是 Windows Vista 中用于进行数字成像的扩展平台,它可以作为 .NET Framework 3.0 的部件或能够重新发布的独立组件,也用于 Windows XP 和 Windows Server 2003 中。WIC 在应用程序和 CODEC 之间提供一个抽象层,使应用程序不必专门认知特定的图像格式。无论哪种图像格式,只要机器上装有用于该图像格式的支持 WIC 的 CODEC,任何使用 Windows Imaging Component 的应用程序就能够通过一组一致的接口访问、显示、处理、保存和打印图像。
从上可以看出,WIC(WindowsImagingComponent)组件提供了Direct2D和文件系统的交流平台(Direct2D不必关心位图文件的编码和解码,WIC能自动的把各种格式的位图文件转换为Direct2D认可的位图格式)
利用WIC在Direct2D中绘制位图文件
由前面的内容可知,Direct2D中只能绘制D2DBitmap对象。
因此,利用WIC在Direct2D中绘制位图文件的核心内容就是把位图文件转换为D2DBitmap对象。它的操作过程如下:
1、创建WIC的ImagingFactory类。这个和Direct2D中的D2DFactory类类似。总管类,负责WIC的相关操作。很多的WIC中的类都得依靠它的相关函数才能创建
2、利用ImagingFactory类的CreateDecoderFromFileName函数,根据位图文件创建BitmapDecoder对象(实际上调用系统解码器解析位图文件)。
BitmapDecoder对象有1个属性和1个函数
FrameCount属性:只读属性,说明该位图对象包含的帧数。一般gif文件能包含多个帧,其余格式的一般只有1个帧
GetFrame函数:返回指定帧对象。参数index是整形,说明是第几帧(从0开始)。返回的是BitmapFrameDecode对象
3、利用BitmapDecoder对象的GetFrame函数,返回指定帧的BitmapFrameDecode对象。(参数index一般是0,返回第1帧)
BitmapFrameDecode对象只有1个函数。ToBitmapSource函数。将该对象转换为BitmapSource对象。
一般情况下,到此就可以了。但是,位图格式有很多,你可能不是很确定你的位图格式是否兼容Direct2D的D2DBitmap对象。因此,比较好的做法是继续下面的步骤,将位图格式转换为兼容Direct2D的D2DBitmap对象
4、利用ImagingFactory类的CreateFormatConverter函数创建FormatConverter对象。该对象负责进行格式转换。
5、调用FormatConverter对象的Initialize方法,进行格式转换。该对象还有一个ToBitmapSource函数。将转换好的位图对象转换为BitmapSource对象。
6、最后,利用RenderTarget对象的CreateBitmapFromWicBitmap函数将之前的BitmapSource对象转换为Direct2D的D2DBitmap对象
将Direct2D中的绘图内容保存到位图文件
前面提到如何在Direct2D中绘制位图文件。那么,反过来,将Direct2D中的绘图内容保存到位图文件也应该是可以的。
具体的操作过程如下:
1、利用RenderTarget对象的CreateBitmap函数创建D2DBitmap对象
2、利用D2DBitmap对象的CopyFromRenderTarget方法将RenderTarget上的内容复制到D2DBitmap对象中
3、利用WIC中的ImagingFactory类的CreateImagingBitmap函数创建ImagingBitmap对象
4、利用D2DFactory对象的CreateWicBitmapRenderTarget的函数创建基于ImagingBitmap对象的RenderTarget对象。我们命名为wicRenderTarget,以示区别。
5、调用wicRenderTarget对象的BeginDraw方法,准备绘图
6、调用wicRenderTarget对象的DrawBitmap方法,将步骤2中的D2DBitmap对象绘制到wicRenderTarget,实际上是绘制到对应的ImagingBitmap对象。
7、调用wicRenderTarget对象的EndDraw方法,结束绘图(也可以在步骤6中,利用绘图函数添加自己的水印)
8、根据要保存文件的后缀名,获得相对应的Guid对象
9、调用ImagingBitmap对象的SaveToFile方法,将ImagingBitmap中的内容保存到文件中。一共有三个参数:参数imagingFactory,类型是ImagingFactory;参数containerFormat,类型是Guid对象,指明文件的编码方式;参数fileName,要保存的文件名。
需要注意的是,上面的过程中,实际上有两个RenderTarget对象,一个是Direct2D中的RenderTarget和临时创建的wicRenderTarget对象。再利用wicRenderTarget对象的DrawBitmap方法把RenderTarget对象创建的D2DBitmap对象绘制到wicRenderTarget上。这里牵涉到资源共享的问题,在两个(或多个)RenderTarget对象之间共享D2DBitmap对象,则这两个RenderTarget对象创建时的RenderTargetProperties属性中的RenderTargetType的值必须相同(要么是Software,要么是Hardware)。但由于WIC只能是基于Software。因此,如果要用上面的步骤保存到位图文件,则必须把RenderTarget对象的RenderTargetType的值设为Software。但也因此降低了Direct2D的效率。
RenderTargetType的值有Software(用软件方式呈现绘图)、Hardware(用硬件方式呈现绘图)、Default(由系统判断采用软件还是硬件方式呈现绘图)
下面是读取位图文件和保存位图文件的示例代码:
Imports Microsoft.WindowsAPICodePack.DirectX
Imports WIC = Microsoft.WindowsAPICodePack.DirectX.WindowsImagingComponent
Public Class clsDirect2DSample10
Protected _d2DFactory As Direct2D1.D2DFactory
Protected _renderTarget As Direct2D1.RenderTarget
Protected _renderProps As Direct2D1.RenderTargetProperties
Protected _imagingFactory As WIC.ImagingFactory
Public Sub New()
_d2DFactory = Direct2D1.D2DFactory.CreateFactory()
_imagingFactory = WIC.ImagingFactory.Create
End Sub
Public Sub CreateDeviceResource(Target As Control)
If _renderTarget Is Nothing Then
_renderProps = New Direct2D1.RenderTargetProperties( _
Direct2D1.RenderTargetType.Software, _
New Direct2D1.PixelFormat( _
Graphics.Format.B8G8R8A8UNorm, _
Direct2D1.AlphaMode.Ignore), _
0, 0, Direct2D1.RenderTargetUsages.None, Direct3D.FeatureLevel.Default)
_renderTarget = _d2DFactory.CreateHwndRenderTarget( _
_renderProps, _
New Direct2D1.HwndRenderTargetProperties( _
Target.Handle, _
New Direct2D1.SizeU(Target.Width, Target.Height), _
Direct2D1.PresentOptions.None) _
)
End If
End Sub
Public Function LoadBitmapFromFile(fileName As String, Optional frameIndex As Integer = 0) As Direct2D1.D2DBitmap
Dim decoder As WIC.BitmapDecoder = _imagingFactory.CreateDecoderFromFileName( _
fileName, _
WIC.DesiredAccess.Read, _
WIC.DecodeMetadataCacheOption.OnLoad)
If frameIndex > decoder.FrameCount - 1 OrElse frameIndex < 0 Then frameIndex = 0
Dim source As WIC.BitmapFrameDecode = decoder.GetFrame(frameIndex)
Dim converter As WIC.FormatConverter = _imagingFactory.CreateFormatConverter()
converter.Initialize(source.ToBitmapSource(), WIC.PixelFormats.Pbgra32Bpp, WIC.BitmapDitherType.None, WIC.BitmapPaletteType.MedianCut)
Return _renderTarget.CreateBitmapFromWicBitmap(converter.ToBitmapSource())
End Function
Public Sub Render()
If Not _renderTarget Is Nothing Then
With _renderTarget
.BeginDraw()
Dim B As Direct2D1.D2DBitmap = LoadBitmapFromFile("216.png")
.DrawBitmap(B, 1, Direct2D1.BitmapInterpolationMode.Linear, New Direct2D1.RectF(150, 150, 280, 280), New Direct2D1.RectF(0, 0, 260, 260))
.DrawBitmapAtOrigin(B, 1, Direct2D1.BitmapInterpolationMode.Linear, New Direct2D1.RectF(10, 10, 130, 130))
.EndDraw()
End With
SaveToFile("217.png")
End If
End Sub
Public Sub SaveToFile(FileName As String)
Dim size As Direct2D1.SizeU = _renderTarget.PixelSize
Dim d2dBitmap As Direct2D1.D2DBitmap = _renderTarget.CreateBitmap(size, _
New Direct2D1.BitmapProperties( _
New Direct2D1.PixelFormat( _
Graphics.Format.B8G8R8A8UNorm, _
Direct2D1.AlphaMode.Ignore), _
_renderTarget.Dpi.X, _renderTarget.Dpi.Y))
d2dBitmap.CopyFromRenderTarget(_renderTarget)
Dim wicBitmap As WIC.ImagingBitmap = _imagingFactory.CreateImagingBitmap( _
size.Width, size.Height, _
WIC.PixelFormats.Bgr32Bpp, _
WIC.BitmapCreateCacheOption.CacheOnLoad)
Dim wicRenderTarget As Direct2D1.RenderTarget = _d2DFactory.CreateWicBitmapRenderTarget(wicBitmap, _renderProps)
wicRenderTarget.BeginDraw()
wicRenderTarget.DrawBitmap(d2dBitmap)
wicRenderTarget.EndDraw()
Dim fileType As Guid
Select Case FileName.Substring(FileName.LastIndexOf(".") + 1).ToUpper
Case "PNG"
fileType = WIC.ContainerFormats.Png
Case "JPEG", "JPG"
fileType = WIC.ContainerFormats.Jpeg
Case "GIF"
fileType = WIC.ContainerFormats.Gif
Case "TIFF", "TIF"
fileType = WIC.ContainerFormats.Tiff
Case "WMP"
fileType = WIC.ContainerFormats.Wmp
Case Else
fileType = WIC.ContainerFormats.Bmp
End Select
wicBitmap.SaveToFile(_imagingFactory, fileType, FileName)
End Sub
End Class
上面的例子中,添加了一个LoadBitmapFromFile函数,负责把位图文件转换为D2DBitmap对象。在LoadBitmapFromFile函数中,一些枚举参数的设置都是采用默认值,最好不要改成其他值。还添加了一个SaveToFile方法,负责把RenderTarget对象上的内容保存到指定文件.
在Render方法中,先用LoadBitmapFromFile函数加载216.png,然后通过DrawBitmap和DrawBitmapAtOrigin方法绘制图片。并调用SaveToFile方法把RenderTarget上的内容保存到217.png文件中。
先看看216.png图片
来看看示例代码的效果图,来了解DrawBitmap和DrawBitmapAtOrigin方法的区别
位图笔刷(BitmapBrush)
在前文介绍了纯色笔刷(SolidColorBrush)、线性渐变笔刷(LinearGradientBrush)、径向渐变笔刷(RadialGradientBrush)
这里再介绍最后一种笔刷——位图笔刷(BitmapBrush)
先看看位图笔刷(BitmapBrush)对应的RenderTarget对象的CreateBitmapBrush函数的原型定义
Public Function CreateBitmapBrush(bitmap As Direct2D1.D2DBitmap) As Direct2D1.BitmapBrush
Public Function CreateBitmapBrush(bitmap As Direct2D1.D2DBitmap, brushProperties As Direct2D1.BrushProperties) As Direct2D1.BitmapBrush
Public Function CreateBitmapBrush(bitmap As Direct2D1.D2DBitmap, bitmapBrushProperties As Direct2D1.BitmapBrushProperties) As Direct2D1.BitmapBrush
Public Function CreateBitmapBrush(bitmap As Direct2D1.D2DBitmap, bitmapBrushProperties As Direct2D1.BitmapBrushProperties, brushProperties As Direct2D1.BrushProperties) As Direct2D1.BitmapBrush
Direct2D1.BitmapBrushProperties(extendModeX As Direct2D1.ExtendMode, extendModeY As Direct2D1.ExtendMode, interpolationMode As Direct2D1.BitmapInterpolationMode)
从上面的函数的原型定义来看,类型BitmapBrushProperties有三个参数,extendModeX指明位图笔刷(BitmapBrush)在水平扩展区域的扩展模式;extendModeY指明位图笔刷(BitmapBrush)在垂直扩展区域的扩展方式,扩展方式还是有3中,分别是Clamp(延伸:按照笔刷边界点的颜色延伸)、Wrap(换行:按笔刷的方向重新设置颜色)、Mirror(镜像:按笔刷的反方向重新设置颜色)。枚举BitmapInterpolationMode指明位图的插值算法。并且这三个参数在位图笔刷(BitmapBrush)对象中也有对应的属性,分别是ExtendModeX、ExtendModeY、InterpolationMode
我们先看看一个位图笔刷(BitmapBrush)的示例代码
Public Class clsDirect2DSample11
Inherits clsDirect2DSample
Protected _imagingFactory As WIC.ImagingFactory
Public Sub New()
MyBase.New()
_imagingFactory = WIC.ImagingFactory.Create
End Sub
Public Function LoadBitmapFromFile(fileName As String, Optional frameIndex As Integer = 0) As Direct2D1.D2DBitmap
Dim decoder As WIC.BitmapDecoder = _imagingFactory.CreateDecoderFromFileName(fileName, WIC.DesiredAccess.Read, WIC.DecodeMetadataCacheOption.OnLoad)
If frameIndex > decoder.FrameCount - 1 OrElse frameIndex < 0 Then frameIndex = 0
Dim source As WIC.BitmapFrameDecode = decoder.GetFrame(frameIndex)
Dim converter As WIC.FormatConverter = _imagingFactory.CreateFormatConverter()
converter.Initialize(source.ToBitmapSource(), WIC.PixelFormats.Pbgra32Bpp, WIC.BitmapDitherType.None, WIC.BitmapPaletteType.MedianCut)
Return _renderTarget.CreateBitmapFromWicBitmap(converter.ToBitmapSource())
End Function
Public Shadows Sub Render()
If Not _renderTarget Is Nothing Then
With _renderTarget
.BeginDraw()
.Clear(New Direct2D1.ColorF(Color.Chocolate.ToArgb))
Dim B As Direct2D1.D2DBitmap = LoadBitmapFromFile("216.png")
Dim BB As Direct2D1.BitmapBrush = _renderTarget.CreateBitmapBrush(B)
Dim R As New Direct2D1.RectF(130, 130, 390, 390)
Dim SB As Direct2D1.SolidColorBrush = _renderTarget.CreateSolidColorBrush(New Direct2D1.ColorF(0, 0, 0))
.DrawRectangle(R, SB, 3)
.FillRectangle(R, BB)
.EndDraw()
End With
End If
End Sub
End Class
上面的代码中,首先定义了一个位图笔刷(BitmapBrush),然后用该位图笔刷填充一个矩形。下面看看效果
效果有点出乎意料,设置因为,216.png这个位图大小为260*260px,在设置位图笔刷(BitmapBrush)时,默认笔刷的起始位置是画布的原点(左上角)。而由于矩形的范围是(130,130,390,390),因此位图笔刷只有一部分画在矩形的范围里(位图的右下角的部分),而由于默认的扩展模式是Clamp(延伸:按照笔刷边界点的颜色延伸),故在向右和向下延伸了边界的颜色。
如何更改位图笔刷的起始位置?通过位图笔刷(BitmapBrush)的Transform属性来更改起始位置。如下面的示例代码所示(有关Transform的详细内容留待后文再详解)
Public Class clsDirect2DSample12
Inherits clsDirect2DSample11
Public Shadows Sub Render()
If Not _renderTarget Is Nothing Then
With _renderTarget
.BeginDraw()
.Clear(New Direct2D1.ColorF(Color.Chocolate.ToArgb))
Dim B As Direct2D1.D2DBitmap = LoadBitmapFromFile("216.png")
Dim BB As Direct2D1.BitmapBrush = _renderTarget.CreateBitmapBrush(B)
BB.Transform = Direct2D1.Matrix3x2F.Translation(130, 130)
Dim R As New Direct2D1.RectF(130, 130, 390, 390)
Dim SB As Direct2D1.SolidColorBrush = _renderTarget.CreateSolidColorBrush(New Direct2D1.ColorF(0, 0, 0))
.DrawRectangle(R, SB, 3)
.FillRectangle(R, BB)
.EndDraw()
End With
End If
End Sub
End Class
下面是示例代码的效果图,看看效果,了解如何更改位图笔刷(BitmapBrush)的起始位置
下面再看看另一个示例的代码,把前面的内容总结复习一下
Public Class clsDirect2DSample13
Inherits clsDirect2DSample11
Public Shadows Sub Render()
If Not _renderTarget Is Nothing Then
With _renderTarget
.BeginDraw()
.Clear(New Direct2D1.ColorF(Color.Chocolate.ToArgb))
Dim PG As Direct2D1.PathGeometry
Dim sink As Direct2D1.GeometrySink
PG = _d2DFactory.CreatePathGeometry
sink = PG.Open
sink.BeginFigure(New Direct2D1.Point2F(30, 30), Direct2D1.FigureBegin.Filled)
sink.AddLine(New Direct2D1.Point2F(240, 30))
sink.AddArc(New Direct2D1.ArcSegment( _
New Direct2D1.Point2F(240, 240), _
New Direct2D1.SizeF(60, 100), _
45, _
Direct2D1.SweepDirection.Clockwise,
Direct2D1.ArcSize.Large))
sink.AddBezier(New Direct2D1.BezierSegment( _
New Direct2D1.Point2F(170, 120), _
New Direct2D1.Point2F(100, 360), _
New Direct2D1.Point2F(30, 240)))
sink.EndFigure(Direct2D1.FigureEnd.Closed)
sink.Close()
Dim B As Direct2D1.D2DBitmap = LoadBitmapFromFile("216.png")
Dim BB As Direct2D1.BitmapBrush = _renderTarget.CreateBitmapBrush(B)
Dim SB As Direct2D1.SolidColorBrush = _renderTarget.CreateSolidColorBrush(New Direct2D1.ColorF(0, 0, 0))
BB.ExtendModeX = Direct2D1.ExtendMode.Mirror
BB.ExtendModeY = Direct2D1.ExtendMode.Mirror
BB.Transform = Direct2D1.Matrix3x2F.Scale(0.4, 0.4)
.DrawGeometry(PG, SB, 3)
.FillGeometry(PG, BB)
.EndDraw()
End With
End If
End Sub
End Class
上面的示例代码中,把位图笔刷(BitmapBrush)的水平扩展模式和垂直扩展模式都改成Mirror(镜像),并且把位图笔刷的大小改为原来的0.4倍。下面看看效果
最后,介绍一个非常好用的工具:ILSpy。文章中的很多原型定义都是直接从该工具中复制而来,省了不少的功夫。