Skip to content

Commit f67affb

Browse files
committed
Add double buffering to class, also add a small fix to argb function
1 parent 4be52a4 commit f67affb

File tree

1 file changed

+138
-11
lines changed

1 file changed

+138
-11
lines changed

src/WIP/stdCanvas.cls

Lines changed: 138 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ End Enum
2929
#If VBA7 Then
3030
Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hWnd As LongPtr) As LongPtr
3131
Private Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hWnd As LongPtr, ByVal hDC As LongPtr) As LongPtr
32+
Private Declare PtrSafe Function CreateCompatibleDC Lib "gdi32" (ByVal hDC As LongPtr) As LongPtr
33+
Private Declare PtrSafe Function CreateCompatibleBitmap Lib "gdi32" (ByVal hDC As LongPtr, ByVal nWidth As Long, ByVal nHeight As Long) As LongPtr
34+
Private Declare PtrSafe Function SelectObject Lib "gdi32" (ByVal hDC As LongPtr, ByVal hObject As LongPtr) As LongPtr
35+
Private Declare PtrSafe Function DeleteObject Lib "gdi32" (ByVal hObject As LongPtr) As Long
36+
Private Declare PtrSafe Function DeleteDC Lib "gdi32" (ByVal hDC As LongPtr) As Long
37+
Private Declare PtrSafe Function BitBlt Lib "gdi32" (ByVal hDestDC As LongPtr, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As LongPtr, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
38+
Private Declare PtrSafe Function GetClientRect Lib "user32" (ByVal hWnd As LongPtr, lpRect As rect) As Long
3239
Private Declare PtrSafe Function GdiplusStartup Lib "gdiplus" (ByRef token As LongPtr, ByRef inputbuf As GdiplusStartupInput, ByRef outputbuf As GdiplusStartupOutput) As Long
3340
Private Declare PtrSafe Sub GdiplusShutdown Lib "gdiplus" (ByVal token As LongPtr)
3441
Private Declare PtrSafe Function GdipCreateFromHDC Lib "gdiplus" (ByVal hDC As LongPtr, ByRef graphics As LongPtr) As Long
@@ -47,6 +54,13 @@ End Enum
4754
End Enum
4855
Private Declare Function GetDC Lib "user32" (ByVal hWnd As LongPtr) As LongPtr
4956
Private Declare Function ReleaseDC Lib "user32" (ByVal hWnd As LongPtr, ByVal hDC As LongPtr) As LongPtr
57+
Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hDC As LongPtr) As LongPtr
58+
Private Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hDC As LongPtr, ByVal nWidth As Long, ByVal nHeight As Long) As LongPtr
59+
Private Declare Function SelectObject Lib "gdi32" (ByVal hDC As LongPtr, ByVal hObject As LongPtr) As LongPtr
60+
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As LongPtr) As Long
61+
Private Declare Function DeleteDC Lib "gdi32" (ByVal hDC As LongPtr) As Long
62+
Private Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As LongPtr, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As LongPtr, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
63+
Private Declare Function GetClientRect Lib "user32" (ByVal hWnd As LongPtr, lpRect As rect) As Long
5064
Private Declare Function GdiplusStartup Lib "gdiplus" (ByRef token As LongPtr, ByRef inputbuf As GdiplusStartupInput, ByRef outputbuf As GdiplusStartupOutput) As Long
5165
Private Declare Sub GdiplusShutdown Lib "gdiplus" (ByVal token As LongPtr)
5266
Private Declare Function GdipCreateFromHDC Lib "gdiplus" (ByVal hdc As LongPtr, ByRef graphics As LongPtr) As Long
@@ -61,6 +75,8 @@ End Enum
6175
Private Declare Function GdipGraphicsClear Lib "gdiplus" (ByVal graphics As LongPtr, ByVal color As Long) As Long
6276
#End If
6377

78+
Private Const SRCCOPY As Long = &HCC0020
79+
6480
Private Type GdiplusStartupInput
6581
GdiplusVersion As Long
6682
DebugEventCallback As LongPtr
@@ -115,6 +131,12 @@ Private Type TThis
115131
gdipToken As LongPtr
116132
objectsIndex As Long
117133
objects() As CanvasObject
134+
135+
' Double buffering components
136+
bufferDC As LongPtr
137+
bufferBitmap As LongPtr
138+
bufferWidth As Long
139+
bufferHeight As Long
118140
End Type
119141
Private This As TThis
120142

@@ -129,6 +151,7 @@ Public Function CreateFromHwnd(ByVal hWnd As LongPtr, Optional ByVal obj As Obje
129151
Set CreateFromHwnd = New stdCanvas
130152
Call CreateFromHwnd.protInit(hWnd, obj)
131153
End Function
154+
132155
Public Sub protInit(ByVal hWnd As LongPtr, ByVal obj As Object)
133156
Set This.obj = obj
134157
This.hWnd = hWnd
@@ -147,21 +170,39 @@ Public Sub protInit(ByVal hWnd As LongPtr, ByVal obj As Object)
147170
If GdiplusStartup(This.gdipToken, gdip, gdipo) <> 0 Then
148171
Err.Raise 5, "stdCanvas#protInit", "Failed to start GDI+"
149172
End If
173+
174+
' Initialize double buffer
175+
Dim clientRect As rect
176+
Call GetClientRect(This.hWnd, clientRect)
177+
This.bufferWidth = clientRect.Right - clientRect.Left
178+
This.bufferHeight = clientRect.Bottom - clientRect.Top
179+
180+
This.bufferDC = CreateCompatibleDC(This.hDC)
181+
If This.bufferDC <> 0 Then
182+
This.bufferBitmap = CreateCompatibleBitmap(This.hDC, This.bufferWidth, This.bufferHeight)
183+
If This.bufferBitmap <> 0 Then
184+
Call SelectObject(This.bufferDC, This.bufferBitmap)
185+
End If
186+
End If
150187
End Sub
151188

189+
190+
152191
Private Sub Class_Terminate()
153192
'TODO: When sprites/fonts added
154193
'Dim i As Long
155194
'For i = 1 To UBound(This.objects)
156195
' Erase bitmaps/fonts etc.
157196
'Next
158197

198+
' Cleanup double buffer
199+
If This.bufferBitmap <> 0 Then Call DeleteObject(This.bufferBitmap)
200+
If This.bufferDC <> 0 Then Call DeleteDC(This.bufferDC)
201+
159202
If This.gdipToken <> 0 Then Call GdiplusShutdown(This.gdipToken)
160203
If This.hDC <> 0 Then Call ReleaseDC(This.hWnd, This.hDC)
161204
End Sub
162205

163-
164-
165206
'Draw a rectangle
166207
'@param x1 - X Coord of top left corner
167208
'@param y1 - Y Coord of top left corner
@@ -228,9 +269,77 @@ End Property
228269
Public Sub Redraw()
229270
Const UnitPixel As Long = 2
230271

272+
' Check if buffer needs resizing
273+
Dim clientRect As rect
274+
Call GetClientRect(This.hWnd, clientRect)
275+
Dim newWidth As Long: newWidth = clientRect.Right - clientRect.Left
276+
Dim newHeight As Long: newHeight = clientRect.Bottom - clientRect.Top
277+
278+
If This.bufferBitmap = 0 Or newWidth > This.bufferWidth Or newHeight > This.bufferHeight Then
279+
' Recreate buffer if needed
280+
If This.bufferBitmap <> 0 Then Call DeleteObject(This.bufferBitmap)
281+
If This.bufferDC <> 0 Then Call DeleteDC(This.bufferDC)
282+
283+
This.bufferWidth = newWidth + 50 ' Some padding to reduce frequent resizing
284+
This.bufferHeight = newHeight + 50
285+
This.bufferDC = CreateCompatibleDC(This.hDC)
286+
If This.bufferDC <> 0 Then
287+
This.bufferBitmap = CreateCompatibleBitmap(This.hDC, This.bufferWidth, This.bufferHeight)
288+
If This.bufferBitmap <> 0 Then Call SelectObject(This.bufferDC, This.bufferBitmap)
289+
End If
290+
End If
291+
292+
' Use double buffering if available, otherwise fall back to direct
293+
If This.bufferBitmap <> 0 Then
294+
' Create GDI+ graphics object from buffer DC
295+
Dim G As LongPtr
296+
If GdipCreateFromHDC(This.bufferDC, G) = 0 Then
297+
' Clear the buffer
298+
Call GdipGraphicsClear(G, argb(0, 255, 255, 255))
299+
300+
' Draw all objects to the buffer
301+
Dim i As Long
302+
For i = 1 To This.objectsIndex
303+
With This.objects(i)
304+
Select Case .ObjectType
305+
Case cvtRectangle
306+
If .ObjectStyle(CanvasObjectStyles.[_cvsFillVisible]) Then
307+
Dim fillBrush As LongPtr
308+
If GdipCreateSolidFill(.ObjectStyle(cvsFillColor), fillBrush) = 0 Then
309+
Call GdipFillRectangle(G, fillBrush, .ObjectParams(cvfRectLeft), .ObjectParams(cvfRectTop), .ObjectParams(cvfRectRight) - .ObjectParams(cvfRectLeft), .ObjectParams(cvfRectBottom) - .ObjectParams(cvfRectTop))
310+
Call GdipDeleteBrush(fillBrush)
311+
End If
312+
End If
313+
If .ObjectStyle(CanvasObjectStyles.[_cvsLineVisible]) Then
314+
Dim strokePen As LongPtr
315+
If GdipCreatePen1(.ObjectStyle(cvsLineColor), .ObjectStyle(cvsLineWidth), UnitPixel, strokePen) = 0 Then
316+
Call GdipDrawRectangle(G, strokePen, .ObjectParams(cvfRectLeft), .ObjectParams(cvfRectTop), .ObjectParams(cvfRectRight) - .ObjectParams(cvfRectLeft), .ObjectParams(cvfRectBottom) - .ObjectParams(cvfRectTop))
317+
Call GdipDeletePen(strokePen)
318+
End If
319+
End If
320+
End Select
321+
End With
322+
Next
323+
324+
Call GdipDeleteGraphics(G)
325+
326+
' Copy buffer to screen
327+
Call BitBlt(This.hDC, 0, 0, newWidth, newHeight, This.bufferDC, 0, 0, SRCCOPY)
328+
Exit Sub
329+
End If
330+
End If
331+
332+
' Fallback to direct rendering
333+
Call RedrawDirect
334+
End Sub
335+
336+
' Fallback method for direct rendering (without double buffering)
337+
Private Sub RedrawDirect()
338+
Const UnitPixel As Long = 2
339+
231340
Dim G As LongPtr
232341
If GdipCreateFromHDC(This.hDC, G) <> 0 Then Exit Sub
233-
'Call GdipGraphicsClear(G, 0)
342+
234343
If Not This.obj Is Nothing Then Call This.obj.Repaint
235344

236345
Dim i As Long
@@ -259,6 +368,10 @@ Public Sub Redraw()
259368
Call GdipDeleteGraphics(G)
260369
End Sub
261370

371+
' Property to check if double buffering is available
372+
Public Property Get DoubleBuffered() As Boolean
373+
DoubleBuffered = (This.bufferBitmap <> 0)
374+
End Property
262375

263376
Private Function getNewObject() As Long
264377
This.objectsIndex = This.objectsIndex + 1
@@ -269,16 +382,30 @@ Private Function getNewObject() As Long
269382
getNewObject = This.objectsIndex
270383
End Function
271384

272-
273-
274-
385+
' Convert RGB to ARGB
386+
' @param A - Alpha channel (0-255)
387+
' @param R - Red channel (0-255)
388+
' @param G - Green channel (0-255)
389+
' @param B - Blue channel (0-255)
390+
' @returns - ARGB color
275391
Public Function argb(A As Byte, R As Byte, G As Byte, B As Byte) As Long
276-
Dim d As Double: d = CDbl(A) * &H1000000 + R * &H10000 + G * &H100 + B
277-
If d > 2147483647# Then d = d - 4294967296#
278-
argb = d
392+
Const AlphaShift As Double = &H1000000
393+
Const RedShift As Long = &H10000
394+
Const GreenShift As Long = &H100
395+
396+
' 2^31–1 = max signed 32-bit; 2^32 = wrap modulus
397+
Const INT32_MAX As Long = 2 ^ 31 - 1
398+
Const UINT32_MODULO As Double = 2 ^ 32
399+
400+
'Shift the colors without causing an overflow, by shifting in the double domain
401+
Dim rawValue As Double: rawValue = A * AlphaShift + R * RedShift + G * GreenShift + B
402+
403+
' If rawValue exceeds INT32_MAX, wrap into the signed 32-bit range
404+
If rawValue > INT32_MAX Then rawValue = rawValue - UINT32_MODULO
405+
argb = rawValue
279406
End Function
407+
280408
Private Function getAlpha(ByVal u As Double) As Byte
281409
If u < 0 Then u = u + 4294967296#
282410
getAlpha = CByte(Fix(u / 16777216#) Mod 256)
283-
End Function
284-
411+
End Function

0 commit comments

Comments
 (0)