[RESOLVED] Differences in the way GDI32 vs GDI+ draws circles (i.e., ellipses)
Attached is a little test program I'm working on to develop understandings of this stuff. And here's a screenshot of me playing with it:
When you "draw" on the left PictureBox, it uses GDI32 to do things on the left and also GDI+ to do things on the right.
I'm basically using CreateEllipticRgn (along with FillRgn) to create the circles on the left, and GdipFillEllipse to create the circles on the right. They're both the correct size; however the individual pixels are different. Let's take a closer look at one of the 7-pixel dots. I've blown it up 16x and also colored (red) some of the pixels so they're easier to count/see. The one on the left is GDI32, and then one on the right is GDI+:
As we can see, they're both the correct size. However, pixel-for-pixel, they're quite a bit different.
And I'd like to "fix" this.
I actually like the GDI+ version of circles better. Therefore, I see two possible avenues to fix this:
1) I could explore using the GDI+ to draw directly onto the hDC of a PictureBox or Form.
or
2) I could explore somehow using the GDI+ circle to create a GDI32 region, and then continue using FillRgn as I'm now doing for the PictureBox (or Form).
Any/all advice and opinions are welcome.
Best Regards,
Elroy
EDIT1: And as a further note, I can't (or don't want to) do what I'm doing to produce the picture on the right ... because I'm rewriting the entire picture into the PictureBox on each mouse-stroke, and I'd really rather not do this. Therefore, the task is how to get GDI+ style circles to show in a PictureBox in real-time (i.e., as the mouse moves) without copying the whole picture over-and-over.
Last edited by Elroy; Apr 15th, 2018 at 12:23 PM.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
Re: Differences in the way GDI32 vs GDI+ draws circles (i.e., ellipses)
Ahhh, this was way easier than I thought.
A bit of creative use of GdipCreateFromHDC, and the problem is solved.
Thanks for giving me a place to "think out loud".
Everyone Have a Great Sunday,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
Re: [RESOLVED] Differences in the way GDI32 vs GDI+ draws circles (i.e., ellipses)
Couple things.
1. The GdipSetPixelOffsetMode function can change the "GDI+ circle" slightly also when the PixelOffsetModeHalf constant is passed to that API. Whether this is more desirable or not, is up to you.
2. Why can't you draw GDI+ directly to the picturebox? You know how to do that. It would also solve your problem of repainting three times: once to GDI, once to GDI+, then once from GDI+ to GDI (which can be made more efficient by tracking and painting only the "dirty" area of the target DC vs. the entire bitmap. Granted this gets more complex if the target is zoomed in/out because of a need to scale target X,Y coordinates and potentially pen sizes or other graphics objects.
P.S. Not sure why you are tweaking the X,Y coordinates for GDI+ when filling the circle? If it's because the end result appears to 1/2 pixel off when compared to GDI, then look at GdipSetPixelOffsetMode. There's some documentation out on the net regarding GDI+ preference for rendering and the 1/2 pixel issue. If I recall, don't hold me to this, GDI+ prefers to render from the center of a pixel vs the top/left corner of the pixel. By playing with that API, you can get results closer to GDI.
Edited: You replied while I was preparing this one. Ignore what no longer applies.
Last edited by LaVolpe; Apr 15th, 2018 at 01:06 PM.
Insomnia is just a byproduct of, "It can't be done"
Re: [RESOLVED] Differences in the way GDI32 vs GDI+ draws circles (i.e., ellipses)
For interested others, here's the code I used to draw on a PictureBox with GDI+. I left the GDI32 code, but commented it out. And attached is a little test project. Focus on the UsePaintBrush procedure to see the two different approaches.
Code for Form1 (and that's all there is):
Code:
Option Explicit
'
'Private Declare Function CreateEllipticRgn Lib "gdi32" (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long) As Long
'Private Declare Function FillRgn Lib "gdi32" (ByVal hdc As Long, ByVal hRgn As Long, ByVal hBrush As Long) As Long
'Private Declare Function GetStockObject Lib "gdi32" (ByVal nIndex As Long) As Long
'Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
'Private Declare Function CreateSolidBrush Lib "gdi32" (ByVal crColor As Long) As Long
Private Declare Function GetDC Lib "user32" (ByVal hWnd As Long) As Long
Private Declare Function ReleaseDC Lib "user32" (ByVal hWnd As Long, ByVal hdc As Long) As Long
Private Declare Function GetSysColor Lib "user32" (ByVal nIndex As Long) As Long
'Private Declare Sub GetMem1 Lib "msvbvm60" (ByRef Source As Any, ByRef Dest As Any)
'
' GDI+ stuff.
Private Type GdiplusStartupInputType
GdiplusVersion As Long
DebugEventCallback As Long
SuppressBackgroundThread As Long
SuppressExternalCodecs As Long
End Type
'
Private Declare Function GdipFillEllipse Lib "GdiPlus" (ByVal hGraphics As Long, ByVal hBrush As Long, ByVal X As Single, ByVal Y As Single, ByVal nWidth As Single, ByVal nHeight As Single) As Long
Private Declare Function GdipCreateSolidFill Lib "GdiPlus" (ByVal srcColor As Long, ByRef hBrush As Long) As Long
Private Declare Function GdipDeleteBrush Lib "GdiPlus" (ByVal hBrush As Long) As Long
'
Private Declare Function GdiplusStartup Lib "GdiPlus" (Token As Long, inputbuf As GdiplusStartupInputType, Optional ByVal outputbuf As Long) As Long
Private Declare Sub GdiplusShutdown Lib "GdiPlus" (ByVal Token As Long)
Private Declare Function GdipDeleteGraphics Lib "GdiPlus" (ByVal hGraphics As Long) As Long
Private Declare Function GdipCreateFromHDC Lib "GdiPlus" (ByVal hdc As Long, hGraphics As Long) As Long
'
Dim hGdipBrushRed As Long
Dim hGdipBrushGreen As Long
Dim hGdipBrushBlue As Long
Dim hGdipBrushWhite As Long
Dim hGdipBrushBlack As Long
'
Dim hTheGdipBrush As Long
'
Private Enum GpStatusEnum
Ok = 0
GenericError = 1
InvalidParameter = 2
OutOfMemory = 3
ObjectBusy = 4
InsufficientBuffer = 5
NotImplemented = 6
Win32Error = 7
WrongState = 8
Aborted = 9
FileNotFound = 10
ValueOverflow = 11
AccessDenied = 12
UnknownImageFormat = 13
FontFamilyNotFound = 14
FontStyleNotFound = 15
NotTrueTypeFont = 16
UnsupportedGdiplusVersion = 17
GdiplusNotInitialized = 18
PropertyNotFound = 19
PropertyNotSupported = 20
ProfileNotFound = 21
End Enum
'
'****************************************************
'****************************************************
'****************************************************
'
'Const BLACK_BRUSH = &H4&
'Const WHITE_BRUSH = &H0&
'
Dim bLeftMouseDown As Boolean
'
'Dim hBrushRed As Long
'Dim hBrushGreen As Long
'Dim hBrushBlue As Long
'Dim hBrushWhite As Long
'Dim hBrushBlack As Long
'
Private Enum ColorCodeEnum
Red = 0&
Green = 1&
Blue = 2&
White = 3&
Black = 4&
End Enum
'
Dim lBrushSize As Long
Dim hTheBrush As Long
'
Private Sub Form_Load()
'hBrushBlack = GetStockObject(BLACK_BRUSH)
'hBrushWhite = GetStockObject(WHITE_BRUSH)
'hBrushRed = CreateSolidBrush(&HFF&)
'hBrushGreen = CreateSolidBrush(&HFF00&)
'hBrushBlue = CreateSolidBrush(&HFF0000)
'
pic.ScaleMode = vbPixels
pic.AutoRedraw = True
'
lBrushSize = SelectedOptIndex(optSize)
'
Me.Caption = "Draw on me with paintbrush ... mouse-down and draw."
'
' The GDI+ stuff.
Gdip_Start
'
GErr GdipCreateSolidFill(Gdip_Color(&HFF&), hGdipBrushRed)
GErr GdipCreateSolidFill(Gdip_Color(&HFF00&), hGdipBrushGreen)
GErr GdipCreateSolidFill(Gdip_Color(&HFF0000), hGdipBrushBlue)
GErr GdipCreateSolidFill(Gdip_Color(&HFFFFFF), hGdipBrushWhite)
GErr GdipCreateSolidFill(Gdip_Color(&H0&), hGdipBrushBlack)
'
'
SetCorrectBrush SelectedOptIndex(optColor)
End Sub
Private Sub optColor_Click(Index As Integer)
SetCorrectBrush SelectedOptIndex(optColor)
End Sub
Private Sub optSize_Click(Index As Integer)
lBrushSize = SelectedOptIndex(optSize)
End Sub
Private Sub SetCorrectBrush(lColorCode As ColorCodeEnum)
'Select Case lColorCode
'Case Red: hTheBrush = hBrushRed
'Case Green: hTheBrush = hBrushGreen
'Case Blue: hTheBrush = hBrushBlue
'Case White: hTheBrush = hBrushWhite
'Case Black: hTheBrush = hBrushBlack
'End Select
Select Case lColorCode
Case Red: hTheGdipBrush = hGdipBrushRed
Case Green: hTheGdipBrush = hGdipBrushGreen
Case Blue: hTheGdipBrush = hGdipBrushBlue
Case White: hTheGdipBrush = hGdipBrushWhite
Case Black: hTheGdipBrush = hGdipBrushBlack
End Select
End Sub
Private Sub Form_Unload(Cancel As Integer)
'DeleteObject hBrushRed
'DeleteObject hBrushGreen
'DeleteObject hBrushBlue
'
' The GDI+ stuff.
GErr GdipDeleteBrush(hGdipBrushRed)
GErr GdipDeleteBrush(hGdipBrushGreen)
GErr GdipDeleteBrush(hGdipBrushBlue)
GErr GdipDeleteBrush(hGdipBrushWhite)
GErr GdipDeleteBrush(hGdipBrushBlack)
'
Gdip_Stop
End Sub
Private Sub pic_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
If Button And 1 = 1 Then
bLeftMouseDown = True
UsePaintBrush CLng(X), CLng(Y)
End If
End Sub
Private Sub pic_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
If bLeftMouseDown Then UsePaintBrush CLng(X), CLng(Y)
End Sub
Private Sub pic_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
If Button And 1 = 1 Then
bLeftMouseDown = False
End If
End Sub
Private Sub UsePaintBrush(X As Long, Y As Long)
'Dim hRgn As Long
Dim hTheDC As Long
Dim X1 As Long
Dim X2 As Long
Dim Y1 As Long
Dim Y2 As Long
'
X1 = X - lBrushSize \ 2&
X2 = X1 + lBrushSize + 1&
Y1 = Y - lBrushSize \ 2&
Y2 = Y1 + lBrushSize + 1&
'
'hRgn = CreateEllipticRgn(X1, Y1, X2, Y2)
'hTheDC = GetDC(pic.hWnd)
'FillRgn hTheDC, hRgn, hTheBrush ' The memory DC.
'FillRgn pic.hDC, hRgn, hTheBrush ' The form's screen DC. With AutoRedraw=True, this will be automatic.
'ReleaseDC pic.hWnd, hTheDC
'DeleteObject hRgn
'
' The GDI+ way of doing it.
Dim hGraphics As Long
'
hTheDC = GetDC(pic.hWnd)
GErr GdipCreateFromHDC(hTheDC, hGraphics)
GErr GdipFillEllipse(hGraphics, hTheGdipBrush, X1 - 0.75!, Y1 - 0.75!, lBrushSize + 0.5!, lBrushSize + 0.5!)
GErr GdipDeleteGraphics(hGraphics)
ReleaseDC pic.hWnd, hTheDC
'
End Sub
Public Function SelectedOptIndex(opt As Object) As Long
' Returns the index number of a selected option button in an option button array.
' Returns -1 if none selected.
Dim o As OptionButton
'
For Each o In opt
If o.Value Then
SelectedOptIndex = o.Index
Exit Function
End If
Next o
SelectedOptIndex = -1&
End Function
Public Sub Gdip_Start()
' This should be called only once, unless Gdip_Stop is called.
Gdip_StartStop True
End Sub
Public Sub Gdip_Stop()
' This should be perfectly paired with Gdip_Start.
Gdip_StartStop False
End Sub
Private Sub Gdip_StartStop(Optional bStartIt As Boolean)
' Be sure bStartIt = True to get going.
' We use this so we can keep GdipToken locally.
'
Static GdipToken As Long
Dim GdipStartupInfo As GdiplusStartupInputType
'
If bStartIt Then
GdipStartupInfo.GdiplusVersion = 1&
GErr GdiplusStartup(GdipToken, GdipStartupInfo)
Else
If GdipToken Then GdiplusShutdown GdipToken ' This one is a Sub, so don't "Call".
End If
End Sub
Public Sub GErr(GdipReturn As Long)
' Just to check for any errors.
'
If GdipReturn = Ok Then Exit Sub
Debug.Print "GDI+ Error: ";
Select Case GdipReturn
Case GenericError: Debug.Print "Generic Error"
Case InvalidParameter: Debug.Print "Invalid Parameter/Argument"
Case OutOfMemory: Debug.Print "Out Of Memory"
Case ObjectBusy: Debug.Print "Object Busy, already in use in another thread"
Case InsufficientBuffer: Debug.Print "Insufficient Buffer, buffer specified as an argument in the API call is not large enough"
Case NotImplemented: Debug.Print "Method Not Implemented"
Case Win32Error: Debug.Print "Win32 Error"
Case WrongState: Debug.Print "Wrong State"
Case Aborted: Debug.Print "Method Aborted"
Case FileNotFound: Debug.Print "File Not Found"
Case ValueOverflow: Debug.Print "Value Overflow, arithmetic operation that produced a numeric overflow"
Case AccessDenied: Debug.Print "Access Denied"
Case UnknownImageFormat: Debug.Print "Unknown Image Format"
Case FontFamilyNotFound: Debug.Print "Font Family Not Found"
Case FontStyleNotFound: Debug.Print "Font Style Not Found"
Case NotTrueTypeFont: Debug.Print "Not TrueType Font"
Case UnsupportedGdiplusVersion: Debug.Print "Unsupported Gdiplus Version"
Case GdiplusNotInitialized: Debug.Print "Gdiplus Not Initialized"
Case PropertyNotFound: Debug.Print "Property Not Found, does not exist in the image"
Case PropertyNotSupported: Debug.Print "Property Not Supported, not supported by the format of the image"
Case ProfileNotFound: Debug.Print "Profile Not Found, color profile required to save an image in CMYK format was not found"
Case Else: Debug.Print "Error Not Specified"
End Select
'
Stop
End Sub
Private Function Gdip_Color(ByVal lColor As Long, Optional ByVal AlphaByte As Byte = &HFF) As Long
' This turns any VB6-type color (including system colors) into a GDI+ type color.
If lColor < 0& Then lColor = GetSysColor(lColor And &HFF&)
'
Gdip_Color = (lColor And &HFF00&) _
Or (lColor And &HFF) * &H10000 _
Or (lColor And &HFF0000) \ &H10000 _
Or (AlphaByte And &H7F) * &H1000000
If (AlphaByte And &H80) Then Gdip_Color = Gdip_Color Or &H80000000
'
'GetMem1 lColor, ByVal VarPtr(Gdip_Color) + 2&
'GetMem1 ByVal VarPtr(lColor) + 1&, ByVal VarPtr(Gdip_Color) + 1&
'GetMem1 ByVal VarPtr(lColor) + 2&, Gdip_Color
'GetMem1 AlphaByte, ByVal VarPtr(Gdip_Color) + 3&
End Function
Enjoy,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
However, when I did that, I didn't even get a circle that was the correct width & height. Also, I didn't even get a circle! What I got (especially for smaller diameter ellipses), was a blob-looking thing with more pixels filled out on one side than the other. I tweaked with it a while and finally "forced" it to give me a circle with the correct diameter (in pixels). That's the +0.5! tweaking. And then, to make it stop giving me a skewed circle, the -0.75! tweak seemed to be necessary.
Also, another criteria was that the cursor's hot-spot always be on the center pixel (particularly when the diameter is an ODD number).
I tried it for all of my "canned" circle sizes, and it worked perfectly with those tweaks. However, obviously, I'm not 100% confident that it'll work for any/all circle sizes.
I'll definitely read up on the GdipSetPixelOffsetMode. It sounds like that's a superior "tweak" to what I did.
Thank You,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
Re: [RESOLVED] Differences in the way GDI32 vs GDI+ draws circles (i.e., ellipses)
In latest M2000 version 9.3 I make all draw commands Draw, Polygon, Curve, Circle (this draw arc also) to use GDI or GDI+, with a change in a Smooth command. (Smooth On/Off).
Also I make an easy way to draw transparent png, by loading in memory as files (in standard memory block) and allow GDI+ to take it from there, and draw it, using anything they have for transparency.
I use this module: https://github.com/M2000Interpreter/...PlusResize.bas
This is for Memory block https://github.com/M2000Interpreter/...r/MemBlock.cls
and there you can find
a Public Function DrawSpriteToHdc(bstack As basetask, sprt As Boolean, angle!, zoomfactor!, blend!, Optional backcolor As Long
(basetask is an object which among other things hold a reference to a form or picture box, in a variable named owner, and from that we get the Hdc to draw)
I Have own sprite functions, and a make same functions for work with GDI+, so I can handle sprites PNG now.
The two most difficult was the Circle command and the Curve.
Also graphics for M2000 are combined with blocs With {} and Path {} (path or color, can be used as equal). And the latest is the automatic change to GDI if we use a combination in Path {} where we get Xor paint, (GDI+ has no Xor)
All commands works in Twips (as preferred for this language) , and angles in Radians for polar coordinates, but in degree in sprites or just bitmap rotation.
I have to thank you Elroy and LaVolpe for your programs, here in vbforums, to study and make my implementations.
... and everything works perfectly: round circles, the correct diameter, and centered on the cursor's hot-spot. All as expected/desired, and all analogous to the way GDI32 works.
I think I've now got all the concepts down to implement this in my little PngTga utility. I'll need to work out the scaling, but that's just a bit of math, and should be fairly straightforward.
LaVolpe, Perfect, Thank You,
Elroy
EDIT1: I also swapped out GdipFillEllipse with GdipFillEllipseI, which I thought should have worked to begin with, and now does work.
Last edited by Elroy; Apr 15th, 2018 at 07:29 PM.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.