Hi everyone!
Guys, I need help... Could a good soul post a working example of an anti-aliased circle using GDI+?
I really tried before posting, and I failed miserably. I gave up trying...
In fact, I am achieving anti-alias using the stretch bitblt trick, and I would be glad to post it here and compare the Antialiasing results.
Cheers
Alex
I cheated and asked ChatGPT
It worked after a couple of minor fixes!
include \masm32\include\masm32rt.inc
include \masm32\include\gdiplus.inc
includelib \masm32\lib\gdiplus.lib
.data
gdiplusStartupInput GdiplusStartupInput <1, 0, 0, 0>
gdiplusToken dd ?
hPen dd ?
hGraphics dd ?
hWnd dd ?
msg MSG <>
wc WNDCLASS <>
className db "CircleWin",0
windowTitle db "Anti-Aliased Circle with GDI+",0
penWidth REAL4 3.0
xPos REAL4 50.0
yPos REAL4 50.0
circleWidth REAL4 200.0
circleHeight REAL4 200.0
.code
start:
; Register window class
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, offset WndProc
mov wc.cbClsExtra, 0
mov wc.cbWndExtra, 0
push 0
call GetModuleHandle
mov wc.hInstance, eax
mov wc.hbrBackground, COLOR_WINDOW+1
mov wc.lpszClassName, offset className
mov wc.lpszMenuName, 0
mov wc.hIcon, 0
mov wc.hCursor, 0
invoke RegisterClass, addr wc
; Create the window
invoke CreateWindowEx, 0, addr className, addr windowTitle,\
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,\
400, 300, 0, 0, wc.hInstance, 0
mov hWnd, eax
; Show and update window
invoke ShowWindow, hWnd, SW_SHOWNORMAL
invoke UpdateWindow, hWnd
; Message loop
msg_loop:
invoke GetMessage, addr msg, 0, 0, 0
test eax, eax
jz end_loop
invoke TranslateMessage, addr msg
invoke DispatchMessage, addr msg
jmp msg_loop
end_loop:
invoke ExitProcess, 0
WndProc proc hWin:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL ps:PAINTSTRUCT
LOCAL hdc:HDC
.if uMsg == WM_PAINT
invoke BeginPaint, hWin, addr ps
mov hdc, eax
; Initialize GDI+
invoke GdiplusStartup, addr gdiplusToken, addr gdiplusStartupInput, 0
; Create graphics object from HDC
invoke GdipCreateFromHDC, hdc, addr hGraphics
; Set anti-aliasing mode
invoke GdipSetSmoothingMode, hGraphics, 2 ; SmoothingModeAntiAlias = 2
; Create a red pen with 3-pixel width
invoke GdipCreatePen1, 0FFFF0000h, penWidth, 2, addr hPen
; Draw ellipse (x, y, width, height)
invoke GdipDrawEllipse, hGraphics, hPen, xPos, yPos, circleWidth, circleHeight
; Cleanup
invoke GdipDeletePen, hPen
invoke GdipDeleteGraphics, hGraphics
invoke GdiplusShutdown, gdiplusToken
invoke EndPaint, hWin, addr ps
ret
.elseif uMsg == WM_DESTROY
invoke PostQuitMessage, 0
ret
.endif
invoke DefWindowProc, hWin, uMsg, wParam, lParam
ret
WndProc endp
end start
(https://i.postimg.cc/6qcHTSBT/untitled.png)
If nothing else, it will give you a starting point.
This code may contain errors of one form or another... AI generated.
Coulda saved a lot of characters there.
The only relevant part seems to be
invoke GdipSetSmoothingMode, hGraphics, 2 ; SmoothingModeAntiAlias = 2
Filled circle
include \masm32\include\masm32rt.inc
include \masm32\include\gdiplus.inc
includelib \masm32\lib\gdiplus.lib
.data
gdiplusStartupInput GdiplusStartupInput <1, 0, 0, 0>
gdiplusToken dd ?
hPen dd ?
hGraphics dd ?
hWnd dd ?
msg MSG <>
wc WNDCLASS <>
className db "CircleWin",0
windowTitle db "Anti-Aliased Circle with GDI+",0
penWidth REAL4 3.0
xPos REAL4 50.0
yPos REAL4 50.0
circleWidth REAL4 200.0
circleHeight REAL4 200.0
hBrush dd 0
.code
start:
; Register window class
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, offset WndProc
mov wc.cbClsExtra, 0
mov wc.cbWndExtra, 0
push 0
call GetModuleHandle
mov wc.hInstance, eax
mov wc.hbrBackground, COLOR_WINDOW+1
mov wc.lpszClassName, offset className
mov wc.lpszMenuName, 0
mov wc.hIcon, 0
mov wc.hCursor, 0
invoke RegisterClass, addr wc
; Create the window
invoke CreateWindowEx, 0, addr className, addr windowTitle,\
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,\
400, 300, 0, 0, wc.hInstance, 0
mov hWnd, eax
; Show and update window
invoke ShowWindow, hWnd, SW_SHOWNORMAL
invoke UpdateWindow, hWnd
; Message loop
msg_loop:
invoke GetMessage, addr msg, 0, 0, 0
test eax, eax
jz end_loop
invoke TranslateMessage, addr msg
invoke DispatchMessage, addr msg
jmp msg_loop
end_loop:
invoke ExitProcess, 0
WndProc proc hWin:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL ps:PAINTSTRUCT
LOCAL hdc:HDC
.if uMsg == WM_PAINT
invoke BeginPaint, hWin, addr ps
mov hdc, eax
; Initialize GDI+
invoke GdiplusStartup, addr gdiplusToken, addr gdiplusStartupInput, 0
; Create graphics object from HDC
invoke GdipCreateFromHDC, hdc, addr hGraphics
; Set anti-aliasing mode
invoke GdipSetSmoothingMode, hGraphics, 2 ; SmoothingModeAntiAlias = 2
; Create a solid brush (ARGB = 0xFF0000FF = opaque blue)
invoke GdipCreateSolidFill, 0FF0000FFh, addr hBrush
; Fill the ellipse
invoke GdipFillEllipse, hGraphics, hBrush, xPos, yPos, circleWidth, circleHeight
; Cleanup
invoke GdipDeletePen, hPen
invoke GdipDeleteGraphics, hGraphics
invoke GdiplusShutdown, gdiplusToken
invoke EndPaint, hWin, addr ps
ret
.elseif uMsg == WM_DESTROY
invoke PostQuitMessage, 0
ret
.endif
invoke DefWindowProc, hWin, uMsg, wParam, lParam
ret
WndProc endp
end start
(https://i.postimg.cc/4d5v19qS/untitled.png)
If nothing else, it will give you a starting point.
This code may contain errors of one form or another... AI generated
Quote from: NoCforMe on April 11, 2025, 09:44:31 AMCoulda saved a lot of characters there.
The only relevant part seems to be
invoke GdipSetSmoothingMode, hGraphics, 2 ; SmoothingModeAntiAlias = 2
You are assuming he is already familiar with setting up and using gdiplus, I assume the opposite unless told otherwise by the OP.
Says the OP:
" I am achieving anti-alias using the
stretch bitblt trick,"
Where'd Alex go?
Hi guys!
Thank you both a bunch!!
I am actually not used to GDI+, but spent this afternoon learning about it.
I must say the stretch bitblt trick actually works! :eusa_dance:
Quote from: LordAdef on April 11, 2025, 10:30:38 AMI must say the stretch bitblt trick actually works!
I have never heard of that trick...
Code? Or at least a screenshot? :smiley:
I myself, am starting to learn some things about gdiplus as well.
Quote from: zedd151 on April 11, 2025, 10:32:04 AMCode? Or at least a screenshot? :smiley:
Code, for sure!
Let me isolate the code and create a proper example and I post here for you. I am using it in a larger project.
In short, you upscale what you want and bitBlt it in a back buffer.
The you Stretchblt scaling down to your original scale, but.. using HALFTONE:
inv SetStretchBltMode, [esi].parent_dc, HALFTONETomorrow I'll post an example.
Quote from: LordAdef on April 11, 2025, 10:37:40 AMQuote from: zedd151 on April 11, 2025, 10:32:04 AMCode? Or at least a screenshot? :smiley:
Code, for sure!
Let me isolate the code and create a proper example and I post here for you. I am using it in a larger project.
In short, you upscale what you want and bitBlt it in a back buffer.
The you Stretchblt scaling down to your original scale, but.. using HALFTONE:
inv SetStretchBltMode, [esi].parent_dc, HALFTONE
Tomorrow I'll post an example.
Oh, ok. That explains enough. No need to post the example unless you want to. Other members especially new ones would probably appreciate it.
Quote from: zedd151 on April 11, 2025, 10:39:22 AMOh, ok. That explains enough. No need to post the example unless you want to. Other members especially new ones would probably appreciate it.
We can compare the two side by side. It will be fun.
In my current code, one can set the amount of scale, so it will be interesting
That's not my code but AI generated code via ChatGPT, I just want to make that clear. There are still some errors in it, like mixing .data and .data? Plus gdiplus startup and shutdown code in WM_PAINT would execute every time WM_PAINT is triggered. Not how I would do it. If I have time tomorrow, I'll clean up that code a bit, and use a dialog box instead of a window, my preferred method lately.
... we can still compare them... :smiley:
Got it.
Let's do this!
Okay, I'm game.
I learned something else new today, by having ChatGPT show me about anti-aliasing and how it's done with gdiplus. It's a win-win! :thumbsup: I helped you and also learned something new for myself in the process ... :biggrin:
Quote from: zedd151 on April 11, 2025, 10:45:36 AMNot how I would do it. If I have time tomorrow, I'll clean up that code a bit, and use a dialog box instead of a window...
Here is my version of gdiplus antialiased filled circle, I had some spare time already tonight.
; This source code written by zedd151 @ masm32.com
include \masm32\include\masm32rt.inc
include \masm32\include\gdiplus.inc
includelib \masm32\lib\gdiplus.lib
DlgProc proto :dword, :dword, :dword, :dword
cwidth equ 300 ; desired client area width
cheight equ 300 ; desired client area height
.data
StartupInfo GdiplusStartupInput <1, 0, 0, 0>
token dd 0
GdiplusStatus dd 0
hGraphics dd 0
hGdiBrush dd 0
xPos REAL4 50.0
yPos REAL4 50.0
circleWidth REAL4 200.0
circleHeight REAL4 200.0
.code
start proc
local hInstance:dword
invoke GetModuleHandle, NULL
mov hInstance, eax
mov eax, offset StartupInfo
mov GdiplusStatus, eax
invoke GdiplusStartup, addr token, addr StartupInfo, 0 ; Initialize gdiplus
invoke DialogBoxParam, hInstance, 100, 0, addr DlgProc, 0 ;; create dialog box, from resource dilalog
invoke GdiplusShutdown, GdiplusStatus ; Shutdown gdiplus
invoke ExitProcess, eax
start endp
DlgProc proc hWin:dword, uMsg:dword, wParam:dword, lParam:dword
local hDC:dword, ps:PAINTSTRUCT, rct:RECT, hBrush:dword, hBrush_old:dword
local x:dword, y:dword, wwid:dword, whgt:dword, cwid:dword, chgt:dword
local mDC:dword, hBmp:dword, hBmp_old:dword
.if uMsg == WM_INITDIALOG
;; resizing Client Area to exact dimensions, specified by cwidth and cheight
invoke GetWindowRect, hWin, addr rct ;; get window current dimensions.
mov eax, rct.right ;; obtain current window width by subtracting left boundary
sub eax, rct.left ;; from the right boundary
mov wwid, eax ;; store current window width
mov eax, rct.bottom ;; obtain current window height by subtracting top boundary
sub eax, rct.top ;; from the bottom boundary
mov whgt, eax ;; store current window height
invoke GetClientRect, hWin, addr rct ;; get client area current dimensions.
mov eax, rct.right ;; obtain current client width by subtracting left boundary
sub eax, rct.left ;; from the right boundary
mov cwid, eax ;; store current client area width
mov eax, rct.bottom ;; obtain current client height by subtracting top boundary
sub eax, rct.top ;; from the bottom boundary
mov chgt, eax ;; store client area height
;; calculate the difference between desired client area width and current client area width
mov eax, cwidth
sub eax, cwid
;; adjust the window width according to the difference calculated above
add wwid, eax
;; calculate the difference between desired client area height and current client area height
mov eax, cheight
sub eax, chgt
;; adjust the window height according to the difference calculated height
add whgt, eax
;; center the main window
;; obtain client area of the desktop, not including the task bar
invoke SystemParametersInfoA, SPI_GETWORKAREA, 0, addr rct, 0
;; center window width
mov eax, rct.right
sub eax, wwid
sar eax, 1
mov x, eax
;; center window height
mov eax, rct.bottom
sub eax, whgt
sar eax, 1
mov y, eax
;; implement the centering of the resized main window
invoke MoveWindow, hWin, x, y, wwid, whgt, TRUE
.elseif uMsg == WM_ERASEBKGND
mov eax, 1
ret
.elseif uMsg == WM_PAINT
invoke BeginPaint, hWin, addr ps
mov hDC, eax ; window client area DC
invoke CreateCompatibleDC, hDC
mov mDC, eax ; memory DC
invoke CreateCompatibleBitmap, hDC, ps.rcPaint.right, ps.rcPaint.bottom
mov hBmp, eax ; compatible bitmap handle
invoke SelectObject, mDC, hBmp
mov hBmp_old, eax
;; ###########################################################
;; Here I change the background color of the main window
invoke CreateSolidBrush, 00FFBF7Fh ;; a nice light blue colr
mov hBrush, eax ;; save the brush handle
;; get client area rectangle
invoke GetClientRect, hWin, addr rct
;; fill the client area rectangle with chosen color
invoke FillRect, mDC, addr rct, hBrush ; fill rectangle in memory DC
;; delete the brush, as it is no longer needed
invoke DeleteObject, hBrush
;; ###########################################################
; Create graphics object from HDC
invoke GdipCreateFromHDC, mDC, addr hGraphics
; Set anti-aliasing mode
invoke GdipSetSmoothingMode, hGraphics, 2 ; SmoothingModeAntiAlias = 2
; Create a solid brush (ARGB = 0xFF0000FF = opaque blue)
invoke GdipCreateSolidFill, 0FF0000FFh, addr hGdiBrush
; Fill the ellipse - xPos, yPos, circleWidth, circleHeight are all REAL4 floating point!!
invoke GdipFillEllipse, hGraphics, hGdiBrush, xPos, yPos, circleWidth, circleHeight
invoke GdipDeleteBrush, hGdiBrush ; Cleanup hGdiBrush
invoke GdipDeleteGraphics, hGraphics ; Cleanup hGraphics
;; ###########################################################
invoke BitBlt, hDC, 0, 0, ps.rcPaint.right, ps.rcPaint.bottom, mDC, 0, 0, SRCCOPY
invoke SelectObject, mDC, hBmp_old
invoke DeleteObject, hBmp
invoke DeleteDC, mDC
invoke EndPaint, hWin, addr ps
.elseif uMsg == WM_CLOSE
invoke EndDialog, hWin, 0
.endif
xor eax, eax
ret
DlgProc endp
end
Result
(https://i.postimg.cc/NFfgy53t/filled-circle.png)
Zoomed in 800%
(https://i.postimg.cc/WbzNTqm6/antialias.png)
As a bonus, here is an antialiased filled rectangle. :smiley:
; This source code written by zedd151 @ masm32.com
include \masm32\include\masm32rt.inc
include \masm32\include\gdiplus.inc
includelib \masm32\lib\gdiplus.lib
DlgProc proto :dword, :dword, :dword, :dword
cwidth equ 240
cheight equ 240
.data
gdiplusStartupInput GdiplusStartupInput <1, 0, 0, 0>
GdiplusToken dd gdiplusStartupInput
rectX real4 20.0
rectY real4 70.0
rectWidth real4 200.0
rectHeight real4 100.0
rectColor dd 0FF0000FFh ; Blue color (ARGB)
.data?
hInstance dd ?
hwndDialog dd ?
.code
start proc
; Get the instance handle
invoke GetModuleHandle, 0
mov hInstance, eax
; Initialize GDI+
invoke GdiplusStartup, addr GdiplusToken, addr gdiplusStartupInput, 0
; Create and display the dialog box
invoke DialogBoxParam, hInstance, 100, 0, addr DlgProc, 0
; DialogBoxParam returns here after the dialog is closed
; Shutdown GDI+
invoke GdiplusShutdown, GdiplusToken
invoke ExitProcess, 0
start endp
; Dialog procedure
DlgProc proc hDlg, uMsg, wParam, lParam
local hdc:dword, ps:PAINTSTRUCT, graphics:dword, brush:dword, rct:RECT
local hBrush:dword, mDC:dword, hBmp:dword, hBmp_old:dword
local x:dword, y:dword, wwid:dword, whgt:dword, cwid:dword, chgt:dword
.if uMsg == WM_INITDIALOG
;; resizing Client Area to exact dimensions, specified by cwidth and cheight
invoke GetWindowRect, hDlg, addr rct ;; get window current dimensions.
mov eax, rct.right ;; obtain current window width by subtracting left boundary
sub eax, rct.left ;; from the right boundary
mov wwid, eax ;; store current window width
mov eax, rct.bottom ;; obtain current window height by subtracting top boundary
sub eax, rct.top ;; from the bottom boundary
mov whgt, eax ;; store current window height
invoke GetClientRect, hDlg, addr rct ;; get client area current dimensions.
mov eax, rct.right ;; obtain current client width by subtracting left boundary
sub eax, rct.left ;; from the right boundary
mov cwid, eax ;; store current client area width
mov eax, rct.bottom ;; obtain current client height by subtracting top boundary
sub eax, rct.top ;; from the bottom boundary
mov chgt, eax ;; store client area height
;; calculate the difference between desired client area width and current client area width
mov eax, cwidth
sub eax, cwid
;; adjust the window width according to the difference calculated above
add wwid, eax
;; calculate the difference between desired client area height and current client area height
mov eax, cheight
sub eax, chgt
;; adjust the window height according to the difference calculated height
add whgt, eax
;; center the main window
;; obtain client area of the desktop, not including the task bar
invoke SystemParametersInfoA, SPI_GETWORKAREA, 0, addr rct, 0
;; center window width
mov eax, rct.right
sub eax, wwid
sar eax, 1
mov x, eax
;; center window height
mov eax, rct.bottom
sub eax, whgt
sar eax, 1
mov y, eax
;; implement the centering of the resized main window
invoke MoveWindow, hDlg, x, y, wwid, whgt, TRUE
.elseif uMsg == WM_ERASEBKGND
mov eax, 1
ret
.elseif uMsg == WM_PAINT
; Get the HDC for the dialog
invoke BeginPaint, hDlg, addr ps
mov hdc, eax
invoke CreateCompatibleDC, hdc
mov mDC, eax ; memory DC
invoke CreateCompatibleBitmap, hdc, ps.rcPaint.right, ps.rcPaint.bottom
mov hBmp, eax ; compatible bitmap handle
invoke SelectObject, mDC, hBmp
mov hBmp_old, eax
;; ###########################################################
;; Here I change the background color of the main window
invoke CreateSolidBrush, 00FFBF7Fh ;; a nice light blue colr
mov hBrush, eax ;; save the brush handle
;; get client area rectangle
invoke GetClientRect, hDlg, addr rct
;; fill the client area rectangle with chosen color
invoke FillRect, mDC, addr rct, hBrush ; fill rectangle in memory DC
;; delete the brush, as it is no longer needed
invoke DeleteObject, hBrush
;; ###########################################################
; Create a Graphics object from the HDC
invoke GdipCreateFromHDC, mDC, addr graphics
; Set antialiasing
invoke GdipSetSmoothingMode, graphics, 2
; Create a solid brush
invoke GdipCreateSolidFill, rectColor, addr brush
; Draw the rectangle
invoke GdipFillRectangle, graphics, brush, rectX, rectY, rectWidth, rectHeight
invoke GdipDeleteBrush, brush
invoke GdipDeleteGraphics, graphics
;; ###########################################################
invoke BitBlt, hdc, 0, 0, ps.rcPaint.right, ps.rcPaint.bottom, mDC, 0, 0, SRCCOPY
invoke SelectObject, mDC, hBmp_old
invoke DeleteObject, hBmp
invoke DeleteDC, mDC
invoke EndPaint, hDlg, addr ps
.elseif uMsg == WM_CLOSE
invoke EndDialog, hDlg, 0 ; Use EndDialog for dialog boxes
.endif
xor eax, eax
ret
DlgProc endp
end
untitled.PNG
Zoomed 800%:
untitled.PNG
Hi guys,
I glued this example a bit in a hurry, but it works at least. Built with UASM. Attached the zip file with source code and .exe
In short, this is a way to use anti aliasing without GDI+. Purely with GDI.
The .exe file in the zip is using an upscale factor of 4. Meaning... how much 'bigger' we are drawing the thing in the back buffer.
If interested, all the fun happens in the upscale_draw procedure.
GDI tends to be faster than GDI+. But I didn't benchmark comparing the two systems (mine, and Zedd's).
The left circle is in pure GDI. On the right, the same circle with the process applied:
(https://i.postimg.cc/MnsH9dB0/Anti-aliasing.png) (https://postimg.cc/MnsH9dB0)
That looks pretty good. :thumbsup:
No worries about the timing. Where I would use it the speed limiting factor are mostly the users mouse clicks. Unless it will be used for a fast paced game. Then the time it takes could be critical to game performance.
So it appears that you're really not processing the image at all, apart from scaling it up and back down again, amiright? I mean, you're not applying Bresenham's algorithm or anything like that.
Looks like all the magic happens here
inv SetStretchBltMode, dc, HALFTONE
due to the HALFTONE parameter.
Pretty slick.
Quote from: NoCforMe on April 13, 2025, 06:25:30 AMSo it appears that you're really not processing the image at all, apart from scaling it up and back down again, amiright? I mean, you're not applying Bresenham's algorithm or anything like that.
Looks like all the magic happens here
inv SetStretchBltMode, dc, HALFTONE
due to the HALFTONE parameter.
Pretty slick.
Yep,
Print the shape upscaled in the back buffer, than print it to your main dc. No processing.
Quote from: NoCforMe on April 13, 2025, 06:25:30 AMSo it appears that you're really not processing the image at all
I'd call it minimal processing, since it does do more than nothing.
QuotePretty slick.
Yes, and very good results. I would have to compare several different image sizes between gdi and gdiplus to make a better determination of how good the results are. But so far, looks really good.
Quote from: zedd151 on April 13, 2025, 06:23:16 AMNo worries about the timing.
It's masm land after all, we want 'fast' :dazzled:
Zedd, I've been doing this for quite a while. But for shapes.
Two things to bare in mind:
1. line thickness expands inward and outwards, and must be scaled. We need to calculate the offset of this expansion outwards, or it may hit the rect boundary.
2. the Drawback: for larger shapes, you need a larger back buffer. I am currently using it to create buttons
Ok. brb I gotta go...
Quote from: LordAdef on April 13, 2025, 06:18:04 AMall the fun happens in the upscale_draw procedure
The docs say you should add this:
inv SetStretchBltMode, dc, HALFTONE ;-> downsize back to original anti-aliasing
inv SetBrushOrgEx, dc, 800, 800, 0
However, I can't see any difference. Well done, Alex :thumbsup:
Quote from: jj2007 on April 13, 2025, 05:21:25 PMQuote from: LordAdef on April 13, 2025, 06:18:04 AMall the fun happens in the upscale_draw procedure
The docs say you should add this:
inv SetStretchBltMode, dc, HALFTONE ;-> downsize back to original anti-aliasing
inv SetBrushOrgEx, dc, 800, 800, 0
However, I can't see any difference. Well done, Alex :thumbsup:
Thanks Johen!
inv SetBrushOrgEx, dc, 800, 800, 0
No clue... not broken..didn't fixed it :dazzled:
I am very curious to time both systems and see whats happening
Hi guys,
I've created a new thread in the Laboratory (since is timing related), where I compare the 2 system.
thread is here: https://masm32.com/board/index.php?topic=12708.0 (https://masm32.com/board/index.php?topic=12708.0)