由雙緩衝繪圖技術談起到Delphi源碼實現

來源:互聯網
上載者:User
由雙緩衝繪圖技術談起到Delphi源碼實現(本文原創,轉載請申明)xiaoyunsoft@163.com 摘要:雙緩衝繪圖技術在Delphi中的實現說明:假設讀者熟悉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;var Bitmap: TBitmap;begin if Picture.Graphic = nil then begin    Bitmap := TBitmap.Create;    try      Bitmap.Width := Width;      Bitmap.Height := Height;      Picture.Graphic := Bitmap;    finally      Bitmap.Free;    end; end;  if Picture.Graphic is TBitmap then    Result := TBitmap(Picture.Graphic).Canvas else    raise EInvalidOperation.Create(SImageCanvasNeedsBitmap);end; 可知TImage.Canvas來自Bitmap.Canvas,好,那來看看TBitmap.Canvasfunction TBitmap.GetCanvas: TCanvas;begin if FCanvas = nil then begin    HandleNeeded;    if FCanvas = nil then    // possible recursion    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;    procedure FreeContext; protected    procedure CreateHandle; override; public    constructor Create(ABitmap: TBitmap);    destructor Destroy; override; end;  關注一下CreateHandle函數:procedure TBitmapCanvas.CreateHandle;var H: HBITMAP;begin if FBitmap <> nil then begin    Lock;    try      FBitmap.HandleNeeded;      DeselectBitmap(FBitmap.FImage.FHandle);//!!      DeselectBitmap(FBitmap.FImage.FMaskHandle);      FBitmap.PaletteNeeded;      H := CreateCompatibleDC(0);      if FBitmap.FImage.FHandle <> 0 then        FOldBitmap := SelectObject(H, FBitmap.FImage.FHandle) else        FOldBitmap := 0;      if FBitmap.FImage.FPalette <> 0 then      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訊息時,將執行下面的代碼:procedure TGraphicControl.WMPaint(var Message: TWMPaint);begin if Message.DC <> 0 then begin    Canvas.Lock;    try      Canvas.Handle := Message.DC;      try        Paint;      finally        Canvas.Handle := 0;      end;    finally      Canvas.Unlock;    end; end;end;  (在此先僅關注Paint函數)  而TImage覆蓋了此Paint虛函數:procedure TImage.Paint;var Save: Boolean;begin if csDesigning in ComponentState then    with inherited Canvas do    begin      Pen.Style := psDash;      Brush.Style := bsClear;      Rectangle(0, 0, Width, Height);    end; Save := FDrawing; FDrawing := True; try    with inherited Canvas do//祖先的Canvas      StretchDraw(DestRect, Picture.Graphic); finally    FDrawing := Save; end;end;  拋開枝節,關注兩個地方,一是斜體部份的Canvas對像,二是StrectchDraw函數。先看看此Canvas對像,它被顯示聲明為基類的Canvas對像。不得不提,此Canvas.Handle即控制代碼的賦值代碼:procedure TGraphicControl.WMPaint(var Message: TWMPaint);begin if Message.DC <> 0 then 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函數:procedure TCanvas.StretchDraw(const Rect: TRect; Graphic: TGraphic);begin if Graphic <> nil then begin    Changing;    RequiredState(csAllValid);    Graphic.Draw(Self, Rect);    Changed; end;end; 這裡的Graphic是什麼呢?這裡是TBitmap!看看第一塊代碼。那再看TBitmap.Draw函數吧:procedure TBitmap.Draw(ACanvas: TCanvas; const Rect: TRect);var OldPalette: HPalette; RestorePalette: Boolean; DoHalftone: Boolean; Pt: TPoint; BPP: Integer; MaskDC: HDC; Save: THandle;begin with Rect, FImage do begin    ACanvas.RequiredState(csAllValid);    PaletteNeeded;    OldPalette := 0;    RestorePalette := False;      if FPalette <> 0 then    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));    if DoHalftone then    begin      GetBrushOrgEx(ACanvas.FHandle, pt);      SetStretchBltMode(ACanvas.FHandle, HALFTONE);      SetBrushOrgEx(ACanvas.FHandle, pt.x, pt.y, @pt);    end else if not Monochrome then      SetStretchBltMode(ACanvas.Handle, STRETCH_DELETESCANS);    try      { Call MaskHandleNeeded prior to creating the canvas handle since        it causes FreeContext to be called. }      if Transparent then MaskHandleNeeded;      Canvas.RequiredState(csAllValid);      if Transparent then      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          if Save <> 0 then SelectObject(MaskDC, Save);          if MaskDC <> 0 then DeleteDC(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      if RestorePalette then        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訊息處理函數: procedure TCustomForm.WMPaint(var Message: TWMPaint);var DC: HDC; PS: TPaintStruct;begin if not IsIconic(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函數:procedure TWinControl.WMPaint(var Message: TWMPaint);var DC, MemDC: HDC; MemBitmap, OldBitmap: HBITMAP; PS: TPaintStruct;begin if not FDoubleBuffered or (Message.DC <> 0) then begin    if not (csCustomPaint in ControlState) 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都幹了些什麼吧: procedure TWinControl.PaintHandler(var Message: TWMPaint);var I, Clip, SaveIndex: Integer; DC: HDC; PS: TPaintStruct;begin DC := Message.DC; if DC = 0 then DC := BeginPaint(Handle, PS); try    if FControls = nil then PaintWindow(DC) else    begin      SaveIndex := SaveDC(DC);      Clip := SimpleRegion;      for I := 0 to FControls.Count - 1 do        with TControl(FControls[I]) do          if (Visible or (csDesigning in ComponentState) and            not (csNoDesignVisible in ControlStyle)) and            (csOpaque in ControlStyle) then          begin            Clip := ExcludeClipRect(DC, Left, Top, Left + Width, Top + Height);            if Clip = NullRegion then Break;          end;      if Clip <> NullRegion then PaintWindow(DC);      RestoreDC(DC, SaveIndex);    end;    PaintControls(DC, nil); finally    if Message.DC = 0 then EndPaint(Handle, PS); end;end; PaintWindow是虛函數,TCustomForm覆蓋了它:procedure TCustomForm.PaintWindow(DC: HDC);begin FCanvas.Lock; try    FCanvas.Handle := DC;    try      if FDesigner <> nil then       FDesigner.PaintGrid       else Paint;    finally      FCanvas.Handle := 0;    end; finally    FCanvas.Unlock; end;end; 可以看到,DC值賦給了FCanvas,觸發了Paint事件。由此可以知,只有在將TForm.DoubleBuffered設為TRUE的情況下,並在OnPaint事件中調用Canvas屬性時,才會使用雙緩衝技術。 那在TForm的其它事件中使用Canvas繪圖是什麼情況呢:constructor TCustomForm.CreateNew(AOwner: TComponent; Dummy: Integer);begin inherited Create(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類的定義:procedure TControlCanvas.CreateHandle;begin if FControl = nil then inherited CreateHandle else begin    if FDeviceContext = 0 then    begin      with CanvasList.LockList do      try        if Count >= CanvasListCacheSize then FreeDeviceContext;        FDeviceContext := FControl.GetDeviceContext(FWindowHandle);        Add(Self);      finally        CanvasList.UnlockList;      end;    end;    Handle := FDeviceContext;    UpdateTextFlags; end;end;  上面代碼中的FControl此時為TCustomForm,那麼再看看TCustomForm.GetDeviceContext函數,它位於TWinControl類中定義:function TWinControl.GetDeviceContext(var WindowHandle: HWnd): HDC;begin if csDesigning in ComponentState then    Result := GetDCEx(Handle, 0, DCX_CACHE or DCX_CLIPSIBLINGS) else    Result := GetDC(Handle); if Result = 0 then raise EOutOfResources.CreateRes(@SWindowDCError); WindowHandle := FHandle;end; 這段代碼不解釋也能看的明白了,只是使用了GetDC函數,而未使用我們想要的雙緩衝。 結尾:在Delphi中繪圖時可以使用TImage類來實現,比較方便。若要追求效率,推薦使用Win32Api。聲明:轉載請保持此文檔的完整性。參考文獻: 1.李維 《VCL構架剖析》      2.周愛民 《Delphi原始碼分析》

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.