Home Upgrade Search Memberlist Extras Hacker Tools Award Goals Help Wiki Contact

.NET 

[WINGLESS] Perfect Rounded Rectangle using WINAPI+MSAA !!

Perfect ???

As some of you know, the usual RoundRect function which uses arcs and ellipses to draw roundrectangles is not very efficient. Artifacts can be seen everywhere, and AA sucks with it.
The top left rounded corner is fine, but the rest of them far from fine. So much that the bottom right seems completely dishtinguishable from the top left. Now this is not symmetrical and human eye praises symmetry so, they are bad.
Most of the time you will find yourself compromising to the situation but no more !

MSAA+WinAPI to the rescue

What is this MSAA ?
MSAA is a method of anti aliasing used to smooth out jagged lines. In this method, the target image is rendered at a higher resolution than normal, and the shrinked to the original size. Downscaling largely removes aliasing, and the render resolution is, smoother is the image.

WinAPI ?
I saw a function in WinAPI that produces roundrects. Named CreateRoundRectRgn, it takes the bounds of the rectangle and radius of the rounds as parameters, and gives to us a perfectly rounded-rectangle region. You can now use this region as you wish.

MSAA + WinAPI ?
Apparently the region that you create with CreateRoundRectRgn is a very jagged one, with harsh lines everywhere. It may be perfectly round, but actually is far from perfect. So I decided to get the best of both worlds, combine CreateRoundRectRgn with 8x MSAA and Voila ! there it was, the PERFECT roundrect.

This one does not have any [ normally visible ] jagged lines, all the rounds are symmetrical, and with the function I have written, you can draw a roundrect in just one statement, complete with border and fill color. (Set the fill/border color to color.transparent if you do not want them to be showed.)


Code
Class RoundedRectangle
<DllImport("gdi32.dll")> _
Shared Function CreateRoundRectRgn(ByVal x1 As Integer, ByVal y1 As Integer, ByVal x2 As Integer, ByVal y2 As Integer, ByVal cx As Integer, ByVal cy As Integer) As IntPtr
    End Function
    Shared Function DrawRoundRectangle(rct As Rectangle, r As Integer, MSAA As Integer, FillColor As Color, BorderColor As Color)
        Dim b As New Bitmap(rct.Width * MSAA + MSAA, rct.Height * MSAA + MSAA)
        Dim g As Graphics = Graphics.FromImage(b)
        Dim RegH As IntPtr = CreateRoundRectRgn(rct.Left, rct.Top, (rct.Width) * MSAA, (rct.Height) * MSAA, r * MSAA, r * MSAA)
        Dim RegH1 As IntPtr = CreateRoundRectRgn(rct.Left + MSAA / 1, rct.Top + MSAA / 1, (rct.Width - 1) * MSAA, (rct.Height - 1) * MSAA, (r - 2) * MSAA, (r - 2) * MSAA)
        Dim Reg As Region = Region.FromHrgn(RegH)
        Dim Reg1 As Region = Region.FromHrgn(RegH1)

        g.InterpolationMode = InterpolationMode.HighQualityBicubic

        g.FillRegion(New SolidBrush(BorderColor), Reg)
        g.SetClip(Reg1, CombineMode.Replace)
        g.Clear(FillColor)
        Return b
    End Function
End Class

The function DrawRoundRectangle returns a bitmap which you can draw as in the following example:

Code
Protected Overrides Sub OnPaint(e As PaintEventArgs)
        Dim g As Graphics = e.Graphics
        g.Clear(Parent.BackColor)

        g.DrawImage(RoundedRectangle.DrawRoundRectangle(New Rectangle(5, 5, Width - 10, Height - 10), 10, 8, Color.FromArgb(28, 28, 28), Color.FromArgb(51, 51, 51)), 5, 5, Width - 10, Height - 10)
    End Sub

Here the base rectangle is rect(5,5,width-10,height-10),
radius is 10 and
MSAA multiplier is 8(x)
Border color is color.fromargb(51,51,51)
Fill Color is color.fromargb(28,28,28)


image:--- i65.tiny pic. com/ 2wp01uc.png


Also, do not forget to use the
G.InterpolationMode = 7
(I have not used it in the code) Without it, your borders may be different in width.
[Image: dKota35.png]
Re-uploaded so I can display it here for you.
Skype: happy.cbb
0x38E6A0912657E
Thx.


Also, if you want to get the region for clipping purposes, you can edit my function to return the region directly.
[Image: 47e652559e.png]

That is far from perfect bro.
(Feb 22, 2016 11:20 PM)Earn Wrote:  That is far from perfect bro.

I believe his rectangle is the dark gray one because in his source code he has dark RGB colors. He probably just drew the red one as an example to show how it is compared to his.
(Feb 22, 2016 11:26 PM)My Computer Wrote:  I believe his rectangle is the dark gray one because in his source code he has dark RGB colors. He probably just drew the red one as an example to show how it is compared to his.

This, especially since the dark grey one has, yknow, perfect rounded edges.
(Feb 22, 2016 11:26 PM)My Computer Wrote:  I believe his rectangle is the dark gray one because in his source code he has dark RGB colors. He probably just drew the red one as an example to show how it is compared to his.

Ah, okay. My bad.

Nice work OP.
Thanks for sharing... Will look into your code... ;)
BTC - bc1q3efew3yzfwhdz290dafykaw0a2w56g2j69g2c6
ETH - 0xf1c873217032468990D3313da6509D1b813c4671
USDT - 0xf1c873217032468990D3313da6509D1b813c4671
PP - [email protected]
First off, great contribution - nice to see something new! In regards to the issue you are referring to though, the solution in GDI is to simply set the Graphics.PixelOffsetMode to HighQuality. Though it is a bit.. quirky. In my experience (if I recall correctly), you should set the PixelOffsetMode when anti-aliasing isn't being used for nice sharp, symmetrical corners, otherwise disable it. That or it was something more bizarre like only using it to 'fill' a rectangle but not for 'drawing' a rectangle. It's one of those two, I'm 75% sure! :D
Thx man.
You contributed to me more than I did! HAHA!


Oh, and do not set the MSAA multiplier to greater than 64x, it will cause an error.
Anyway, 8x is more than enough for anything and for big shapes I do not recommend more than 4x. If I remember correctly, just drawing a text, a roundrect, an ellipse and a line of sizes like 200x100 px took 163 ms with 16x MSAA. That's a LOT of time.
good job man. =) it's perfect tuto
I just realized, if you pass the borderwidth argument as a negative value of the decimal part not equal to 0, it gives you a ready made drop shadow !
Eg- pass borderwidth as 0.5 and the border color as transparent.

This works really well with container controls.
Why are you dividing by 1?

Never knew how MSAA worked so thanks a lot for the concept.
(Feb 23, 2016 10:46 AM)๖ۣۜMrRoot Wrote:  Why are you dividing by 1?

It's nothing, initially I was trying to check what happens if you divided it by 2 or 4 but then I realised it was of no use. Remove it if you want to.
The fix for the problem is really simple with GDI+. This is so silly. You first fill the rounded rectangle and then you draw it. The drawing does the anti-aliasing for you.


Possibly Related Threads...
Thread / Author Replies Views Last Post
[C] by gurlhackerclubApr 7, 2020 04:09 AM 1 91 Apr 7, 2020 04:09 AM
Last Post: HFDone
[C] by 939cv5t8mnNov 22, 2019 09:30 AM 2 184 Nov 22, 2019 09:30 AM
Last Post: this_site_sucks
[C] by VulnTutorialsSep 21, 2019 01:20 AM 9 320 Sep 21, 2019 01:20 AM
Last Post: malpwn