双缓冲绘图技术.docx
《双缓冲绘图技术.docx》由会员分享,可在线阅读,更多相关《双缓冲绘图技术.docx(18页珍藏版)》请在冰豆网上搜索。
![双缓冲绘图技术.docx](https://file1.bdocx.com/fileroot1/2023-5/24/e7f668ec-5c14-4d9b-b5c3-f5a19ddbf4ce/e7f668ec-5c14-4d9b-b5c3-f5a19ddbf4ce1.gif)
双缓冲绘图技术
由双缓冲绘图技术谈起到Delphi源码实现
摘要:
双缓冲绘图技术在Delphi中的实现
关键字:
Delphi,双缓冲,Canvas
作者:
上海翰博数码科技实业有限公司沈小云
说明:
假设读者熟悉VCL
双缓冲绘图也不是什么新技术,简单的说:
在绘图实现时不直接绘在窗口上,而是先绘在内存里,再一起“拷贝”至窗口。
实现起来也不复杂,创建一兼容HDC,在此兼容HDC上绘图,最后拷贝到窗口HDC就行了。
本人前段时间把一C++实现该技术的代码改成了Delphi代码,都是用Win32API写的。
今改成了使用Delphi自带的类,试了一下(窗口类Canvas与TImage的Canvas)。
实现方式大同小异,但不得不提的是在窗口中直接使用Canvas绘图与TImage.Canvas却不相同。
使用TImage.Canvas绘图时,自动使用了双缓冲技术,而窗口的Canvas对像却未实现。
怎么回事呢?
看一下代码吧,“源码面前没有秘密”!
一.TImage类的Canvas
TImage=class(TGraphicControl)
...
propertyCanvas:
TCanvasreadGetCanvas;
...
functionTImage.GetCanvas:
TCanvas;
var
Bitmap:
TBitmap;
begin
ifPicture.Graphic=nilthen
begin
Bitmap:
=TBitmap.Create;
try
Bitmap.Width:
=Width;
Bitmap.Height:
=Height;
Picture.Graphic:
=Bitmap;
finally
Bitmap.Free;
end;
end;
ifPicture.GraphicisTBitmapthen
Result:
=TBitmap(Picture.Graphic).Canvas
else
raiseEInvalidOperation.Create(SImageCanvasNeedsBitmap);
end;
可知TImage.Canvas来自Bitmap.Canvas,好,那来看看TBitmap.Canvas
functionTBitmap.GetCanvas:
TCanvas;
begin
ifFCanvas=nilthen
begin
HandleNeeded;
ifFCanvas=nilthen//possiblerecursion
begin
FCanvas:
=TBitmapCanvas.Create(Self);
FCanvas.OnChange:
=Changed;
FCanvas.OnChanging:
=Changing;
end;
end;
Result:
=FCanvas;
end;
显而易见TBitmap.Canvas=TBitmapCanvas.Create;也就是说TImage.Canvas=TBitmapCanvas.Create.即使用TImage.Canvas绘图时,实际是在TBitmapCanvas上绘图的。
让我们再来看看TBitmapCanvas类:
TBitmapCanvas=class(TCanvas)
private
FBitmap:
TBitmap;
FOldBitmap:
HBITMAP;
FOldPalette:
HPALETTE;
procedureFreeContext;
protected
procedureCreateHandle;override;
public
constructorCreate(ABitmap:
TBitmap);
destructorDestroy;override;
end;
关注一下CreateHandle函数:
procedureTBitmapCanvas.CreateHandle;
var
H:
HBITMAP;
begin
ifFBitmap<>nilthen
begin
Lock;
try
FBitmap.HandleNeeded;
DeselectBitmap(FBitmap.FImage.FHandle);
//!
!
DeselectBitmap(FBitmap.FImage.FMaskHandle);
FBitmap.PaletteNeeded;
H:
=CreateCompatibleDC(0);
ifFBitmap.FImage.FHandle<>0then
FOldBitmap:
=SelectObject(H,FBitmap.FImage.FHandle)else
FOldBitmap:
=0;
ifFBitmap.FImage.FPalette<>0then
begin
FOldPalette:
=SelectPalette(H,FBitmap.FImage.FPalette,True);
RealizePalette(H);
end
else
FOldPalette:
=0;
Handle:
=H;
BitmapCanvasList.Add(Self);
finally
Unlock;
end;
end;
end;
读起来也不困难,FBitmap是Create构造函数传进来的。
而我们应该关注的代码位于斜体部份,也很好理解:
创建兼容DC,并选进设备。
要的就是这个效果,现在知道为什么使用TImage.Canvas来绘图是使用的双缓冲技术的了吧?
那么这个兼容DC是如何从内存“拷贝”到窗口的呢?
我们使用上面的分析方法,当TImage基类TGraphicControl收到WM_PAINT消息时,将执行下面的代码:
procedureTGraphicControl.WMPaint(varMessage:
TWMPaint);
begin
ifMessage.DC<>0then
begin
Canvas.Lock;
try
Canvas.Handle:
=Message.DC;
try
Paint;
finally
Canvas.Handle:
=0;
end;
finally
Canvas.Unlock;
end;
end;
end;
(在此先仅关注Paint函数)
而TImage覆盖了此Paint虚函数:
procedureTImage.Paint;
var
Save:
Boolean;
begin
ifcsDesigninginComponentStatethen
withinheritedCanvasdo
begin
Pen.Style:
=psDash;
Brush.Style:
=bsClear;
Rectangle(0,0,Width,Height);
end;
Save:
=FDrawing;
FDrawing:
=True;
try
withinheritedCanvasdo//祖先的Canvas
StretchDraw(DestRect,Picture.Graphic);
finally
FDrawing:
=Save;
end;
end;
抛开枝节,关注两个地方,一是斜体部份的Canvas对像,二是StrectchDraw函数。
先看看此Canvas对像,它被显示声明为基类的Canvas对像。
不得不提,此Canvas.Handle即句柄的赋值代码:
procedureTGraphicControl.WMPaint(varMessage:
TWMPaint);
begin
ifMessage.DC<>0then
begin
Canvas.Lock;
try
Canvas.Handle:
=Message.DC;
try
Paint;
finally
Canvas.Handle:
=0;
end;
finally
Canvas.Unlock;
end;
end;
end;
是消息传递进来的,这里的DC为此TGraphicControl.Parent的DC。
至于如何传递进来的请参考《VCL构架剖析》,在此不费话了。
再看第二个关注点StrectDraw函数:
procedureTCanvas.StretchDraw(constRect:
TRect;Graphic:
TGraphic);
begin
ifGraphic<>nilthen
begin
Changing;
RequiredState(csAllValid);
Graphic.Draw(Self,Rect);
Changed;
end;
end;
这里的Graphic是什么呢?
这里是TBitmap!
看看第一块代码。
那再看TBitmap.Draw函数吧:
procedureTBitmap.Draw(ACanvas:
TCanvas;constRect:
TRect);
var
OldPalette:
HPalette;
RestorePalette:
Boolean;
DoHalftone:
Boolean;
Pt:
TPoint;
BPP:
Integer;
MaskDC:
HDC;
Save:
THandle;
begin
withRect,FImagedo
begin
ACanvas.RequiredState(csAllValid);
PaletteNeeded;
OldPalette:
=0;
RestorePalette:
=False;
ifFPalette<>0then
begin
OldPalette:
=SelectPalette(ACanvas.FHandle,FPalette,True);
RealizePalette(ACanvas.FHandle);
RestorePalette:
=True;
end;
BPP:
=GetDeviceCaps(ACanvas.FHandle,BITSPIXEL)*
GetDeviceCaps(ACanvas.FHandle,PLANES);
DoHalftone:
=(BPP<=8)and(BPP<(FDIB.dsbm.bmBitsPixel*FDIB.dsbm.bmPlanes));
ifDoHalftonethen
begin
GetBrushOrgEx(ACanvas.FHandle,pt);
SetStretchBltMode(ACanvas.FHandle,HALFTONE);
SetBrushOrgEx(ACanvas.FHandle,pt.x,pt.y,@pt);
endelseifnotMonochromethen
SetStretchBltMode(ACanvas.Handle,STRETCH_DELETESCANS);
try
{CallMaskHandleNeededpriortocreatingthecanvashandlesince
itcausesFreeContexttobecalled.}
ifTransparentthenMaskHandleNeeded;
Canvas.RequiredState(csAllValid);
ifTransparentthen
begin
Save:
=0;
MaskDC:
=0;
try
MaskDC:
=GDICheck(CreateCompatibleDC(0));
Save:
=SelectObject(MaskDC,FMaskHandle);
TransparentStretchBlt(ACanvas.FHandle,Left,Top,Right-Left,
Bottom-Top,Canvas.FHandle,0,0,FDIB.dsbm.bmWidth,
FDIB.dsbm.bmHeight,MaskDC,0,0);
finally
ifSave<>0thenSelectObject(MaskDC,Save);
ifMaskDC<>0thenDeleteDC(MaskDC);
end;
end
else
StretchBlt(ACanvas.FHandle,Left,Top,Right-Left,Bottom-Top,
Canvas.FHandle,0,0,FDIB.dsbm.bmWidth,
FDIB.dsbm.bmHeight,ACanvas.CopyMode);
finally
ifRestorePalettethen
SelectPalette(ACanvas.FHandle,OldPalette,True);
end;
end;
不要再深挖了,斜体部份很明了,功能就是将绘图内容从内存拷贝至窗口。
ACanvas.FHandle即上面所说的消息传递进来的HDC。
(ACanvas是TImage的祖先TGraphicControl的内部对像,Canvas在此为TBitmapCanvas实例)。
可能有点乱,因为我整理好了之后,再次阅读时,自已也迷糊了,仔细多看两遍吧。
再提一下:
TGraphicControl.Canvas与TImage.Canvas是两个实例,虽然TImage继承自TGraphicControl。
好了,我们再来看看为何使用窗口Canvas属性进行绘画时,没有使用双缓冲技术吧
二.窗口类的Canvas
其实也不能决对说窗口Canvas没有使用双缓冲技术,它有使用,但有限制。
条件是在将窗口TForm.DoubleBuffered设为TRUE的前提下,在Paint事件函数里使用Canvas对像进行绘图动作。
下面还是按照上面的方法来找出其中的缘由。
先看一下TCustomForm.WMPaint消息处理函数:
procedureTCustomForm.WMPaint(varMessage:
TWMPaint);
var
DC:
HDC;
PS:
TPaintStruct;
begin
ifnotIsIconic(Handle)then
begin
ControlState:
=ControlState+[csCustomPaint];
inherited;
ControlState:
=ControlState-[csCustomPaint];
end
else
begin
DC:
=BeginPaint(Handle,PS);
DrawIcon(DC,0,0,GetIconHandle);
EndPaint(Handle,PS);
end;
end;
这个简单,基本只用考滤斜体部份代码,即调用基类同名函数,在此要追溯到TWinControl.WMPaint函数:
procedureTWinControl.WMPaint(varMessage:
TWMPaint);
var
DC,MemDC:
HDC;
MemBitmap,OldBitmap:
HBITMAP;
PS:
TPaintStruct;
begin
ifnotFDoubleBufferedor(Message.DC<>0)then
begin
ifnot(csCustomPaintinControlState)and(ControlCount=0)then
inherited
else
PaintHandler(Message);
end
else
begin
DC:
=GetDC(0);
MemBitmap:
=CreateCompatibleBitmap(DC,ClientRect.Right,ClientRect.Bottom);
ReleaseDC(0,DC);
MemDC:
=CreateCompatibleDC(0);
OldBitmap:
=SelectObject(MemDC,MemBitmap);
try
DC:
=BeginPaint(Handle,PS);
Perform(WM_ERASEBKGND,MemDC,MemDC);
Message.DC:
=MemDC;
WMPaint(Message);
Message.DC:
=0;
BitBlt(DC,0,0,ClientRect.Right,ClientRect.Bottom,MemDC,0,0,SRCCOPY);
EndPaint(Handle,PS);
finally
SelectObject(MemDC,OldBitmap);
DeleteDC(MemDC);
DeleteObject(MemBitmap);
end;
end;
end;
好,先看下面的斜体部份,若将TForm.DoubleBuffered设为TRUE,那么将创建兼容DC即使用双缓冲技术。
完了之后还是将会调用此函数,这时将会执行第一个斜体部份代码。
也就是说,不管是否设置Form.DoubleBuffered为何值,始终会执行PaintHandler函数。
只不过传递的参数的属性(DC)不同罢了。
那就看看PaintHandler都干了些什么吧:
procedureTWinControl.PaintHandler(varMessage:
TWMPaint);
var
I,Clip,SaveIndex:
Integer;
DC:
HDC;
PS:
TPaintStruct;
begin
DC:
=Message.DC;
ifDC=0thenDC:
=BeginPaint(Handle,PS);
try
ifFControls=nilthenPaintWindow(DC)else
begin
SaveIndex:
=SaveDC(DC);
Clip:
=SimpleRegion;
forI:
=0toFControls.Count-1do
withTControl(FControls[I])do
if(Visibleor(csDesigninginComponentState)and
not(csNoDesignVisibleinControlStyle))and
(csOpaqueinControlStyle)then
begin
Clip:
=ExcludeClipRect(DC,Left,Top,Left+Width,Top+Height);
ifClip=NullRegionthenBreak;
end;
ifClip<>NullRegionthenPaintWindow(DC);
RestoreDC(DC,SaveIndex);
end;
PaintControls(DC,nil);
finally
ifMessage.DC=0thenEndPaint(Handle,PS);
end;
end;
PaintWindow是虚函数,TCustomForm覆盖了它:
procedureTCustomForm.PaintWindow(DC:
HDC);
begin
FCanvas.Lock;
try
FCanvas.Handle:
=DC;
try
ifFDesigner<>nilthenFDesigner.PaintGridelsePaint;
finally
FCanvas.Handle:
=0;
end;
finally
FCanvas.Unlock;
end;
end;
可以看到,DC值赋给了FCanvas,触发了Paint事件。
由此可以知,只有在将TForm.DoubleBuffered设为TRUE的情况下,并在OnPaint事件中调用Canvas属性时,才会使用双缓冲技术。
那在TForm的其它事件中使用Canvas绘图是什么情况呢:
constructorTCustomForm.CreateNew(AOwner:
TComponent;Dummy:
Integer);
begin
inheritedCreate(AOwner);
ControlStyle:
=[csAcceptsControls,csCaptureMouse,csClickEvents,
csSetCaption,csDoubleClicks];
Left:
=0;
Top:
=0;
Width:
=320;
Height:
=240;
FIcon:
=TIcon.Create;
FIcon.Width:
=GetSystemMetrics(SM_CXSMICON);
FIcon.Height:
=GetSystemMetrics(SM_CYSMICON);
FIcon.OnChange:
=IconChanged;
FCanvas:
=TControlCanvas.Create;
FCanvas.Control:
=Self;
FBorderIcons:
=[biSystemMenu,biMinimize,biMaximize];
FBorderStyle:
=bsSizeable;
…
再看一下TControlCanvas类的定义:
procedureTControlCanvas.CreateHandle;
begin
ifFControl=niltheninheritedCreateHandleelse
begin
ifFDeviceContext=0then
begin
withCanvasList.LockListdo
try
ifCount>=CanvasListCacheSizethenFreeDeviceContext;
FDeviceContext:
=FControl.GetDeviceContext(FWindowHandle);
Add(Self);
finally
Ca