双缓冲绘图技术
- 格式:doc
- 大小:55.00 KB
- 文档页数:20
由双缓冲绘图技术谈起到Delphi源码实现摘要:双缓冲绘图技术在Delphi中的实现关键字:Delphi,双缓冲,Canvas作者:上海翰博数码科技实业有限公司沈小云说明:假设读者熟悉VCL双缓冲绘图也不是什么新技术,简单的说:在绘图实现时不直接绘在窗口上,而是先绘在内存里,再一起“拷贝”至窗口。
实现起来也不复杂,创建一兼容HDC,在此兼容HDC上绘图,最后拷贝到窗口HDC就行了。
本人前段时间把一C++实现该技术的代码改成了Delphi代码,都是用Win32API写的。
今改成了使用Delphi自带的类,试了一下(窗口类Canvas与TImage 的Canvas)。
实现方式大同小异,但不得不提的是在窗口中直接使用Canvas绘图与TImage.Canvas却不相同。
使用TImage.Canvas绘图时,自动使用了双缓冲技术,而窗口的Canvas对像却未实现。
怎么回事呢?看一下代码吧,“源码面前没有秘密”!一.TImage类的CanvasTImage = class(TGraphicControl)...property Canvas: TCanvas read GetCanvas;...function TImage.GetCanvas: TCanvas;varBitmap: TBitmap;beginif Picture.Graphic = nil thenbeginBitmap := TBitmap.Create;tryBitmap.Width := Width;Bitmap.Height := Height;Picture.Graphic := Bitmap;finallyBitmap.Free;end;end;if Picture.Graphic is TBitmap thenResult := TBitmap(Picture.Graphic).Canvaselseraise EInvalidOperation.Create(SImageCanvasNeedsBitmap); end;可知TImage.Canvas来自Bitmap.Canvas,好,那来看看TBitmap.Canvas function TBitmap.GetCanvas: TCanvas;beginif FCanvas = nil thenbeginHandleNeeded;if FCanvas = nil then // possible recursionbeginFCanvas := 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)privateFBitmap: TBitmap;FOldBitmap: HBITMAP;FOldPalette: HPALETTE;procedure FreeContext;protectedprocedure CreateHandle; override;publicconstructor Create(ABitmap: TBitmap);destructor Destroy; override;end;关注一下CreateHandle函数:procedure TBitmapCanvas.CreateHandle;varH: HBITMAP;beginif FBitmap <> nil thenbeginLock;tryFBitmap.HandleNeeded;DeselectBitmap(FBitmap.FImage.FHandle);//!! DeselectBitmap(FBitmap.FImage.FMaskHandle);FBitmap.PaletteNeeded;H := CreateCompatibleDC(0);if FBitmap.FImage.FHandle <> 0 thenFOldBitmap := SelectObject(H, FBitmap.FImage.FHandle) else FOldBitmap := 0;if FBitmap.FImage.FPalette <> 0 thenbeginFOldPalette := SelectPalette(H, FBitmap.FImage.FPalette, True);RealizePalette(H);endelseFOldPalette := 0;Handle := H;BitmapCanvasList.Add(Self);finallyUnlock;end;end;end;读起来也不困难,FBitmap是Create构造函数传进来的。
而我们应该关注的代码位于斜体部份,也很好理解:创建兼容DC,并选进设备。
要的就是这个效果,现在知道为什么使用TImage.Canvas来绘图是使用的双缓冲技术的了吧?那么这个兼容DC是如何从内存“拷贝”到窗口的呢?我们使用上面的分析方法,当TImage基类TGraphicControl收到WM_PAINT消息时,将执行下面的代码:procedure TGraphicControl.WMPaint(var Message: TWMPaint);beginif Message.DC <> 0 thenbeginCanvas.Lock;tryCanvas.Handle := Message.DC; tryPaint;finallyCanvas.Handle := 0;end;finallyCanvas.Unlock;end;end;end;(在此先仅关注Paint函数)而TImage覆盖了此Paint虚函数: procedure TImage.Paint;varSave: Boolean;beginif csDesigning in ComponentState thenwith inherited Canvas dobeginPen.Style := psDash;Brush.Style := bsClear;Rectangle(0, 0, Width, Height);end;Save := FDrawing;FDrawing := True;trywith inherited Canvas do//祖先的CanvasStretchDraw(DestRect, Picture.Graphic);finallyFDrawing := Save;end;end;抛开枝节,关注两个地方,一是斜体部份的Canvas对像,二是StrectchDraw函数。
先看看此Canvas对像,它被显示声明为基类的Canvas对像。
不得不提,此Canvas.Handle即句柄的赋值代码:procedure TGraphicControl.WMPaint(var Message: TWMPaint);beginif Message.DC <> 0 thenbeginCanvas.Lock;tryCanvas.Handle := Message.DC;tryPaint;finallyCanvas.Handle := 0;end;finallyCanvas.Unlock;end;end;end;是消息传递进来的,这里的DC为此TGraphicControl.Parent的DC。
至于如何传递进来的请参考《VCL构架剖析》,在此不费话了。
再看第二个关注点StrectDraw函数:procedure TCanvas.StretchDraw(const Rect: TRect; Graphic: TGraphic);beginif Graphic <> nil thenbeginChanging;RequiredState(csAllValid);Graphic.Draw(Self, Rect);Changed;end;end;这里的Graphic是什么呢?这里是TBitmap!看看第一块代码。
那再看TBitmap.Draw函数吧:procedure TBitmap.Draw(ACanvas: TCanvas; const Rect: TRect);varOldPalette: HPalette;RestorePalette: Boolean;DoHalftone: Boolean;Pt: TPoint;BPP: Integer;MaskDC: HDC;Save: THandle;beginwith Rect, FImage dobeginACanvas.RequiredState(csAllValid);PaletteNeeded;OldPalette := 0;RestorePalette := False;if FPalette <> 0 thenbeginOldPalette := 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));if DoHalftone thenbeginGetBrushOrgEx(ACanvas.FHandle, pt);SetStretchBltMode(ACanvas.FHandle, HALFTONE);SetBrushOrgEx(ACanvas.FHandle, pt.x, pt.y, @pt);end else if not Monochrome thenSetStretchBltMode(ACanvas.Handle, STRETCH_DELETESCANS);try{ Call MaskHandleNeeded prior to creating the canvas handle sinceit causes FreeContext to be called. }if Transparent then MaskHandleNeeded;Canvas.RequiredState(csAllValid);if Transparent thenbeginSave := 0;MaskDC := 0;tryMaskDC := 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);finallyif Save <> 0 then SelectObject(MaskDC, Save);if MaskDC <> 0 then DeleteDC(MaskDC);end;endelseStretchBlt(ACanvas.FHandle, Left, Top, Right - Left, Bottom - Top, Canvas.FHandle, 0, 0, FDIB.dsbm.bmWidth,FDIB.dsbm.bmHeight, ACanvas.CopyMode);finallyif RestorePalette thenSelectPalette(ACanvas.FHandle, OldPalette, True);end;end;不要再深挖了,斜体部份很明了,功能就是将绘图内容从内存拷贝至窗口。