Draw and tri-paint 3D buildings with GraphicPath class
page 1 of 1
Published: 13 Oct 2003
Unedited - Community Contributed
Abstract
In this article, I'll use GDI+ to group parts of a 3D building so I can simply input 3 colored brushes to paint it over and over till I see what colors I like the best.
by Terry Voss
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 15063/ 24

Adding polygons to paths in the GDI+ can be very productive.

GDI+, GraphicPaths, and Polygons: (vb.net, w/explanation)
Author: Terry Voss

Demo the Building Painter
(Served with DSL so be patient. The application is instant with a T1 line.)
Imports System.Drawing.Imaging
Imports System.Drawing.Drawing2D
Imports System.drawing
Imports System.io
The imaging namespace is needed simply to get use of the ImageFormat enumeration.
The drawing2d namesapce is for the vector graphics drawing and filling.
The drawing namespace gives us the image class since we will be drawing on an image.
Public Class colors : Inherits System.Web.UI.Page

Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
If Not IsPostBack Then
  Response.Expires = 0
  Dim sidingx As String = Session("sidingname") 


  Dim trimx As String = Session("trimname") 
  Dim roofx As String = Session("roofname")
  SetColor(IIf(Session("sidingname") Is Nothing, "s1", sidingx)) 
  SetColor(IIf(Session("trimname") Is Nothing, "t1", trimx)) 
  SetColor(IIf(Session("roofname") Is Nothing, "r1", roofx))
  If File.Exists("c:\inetpub\wwwroot\acb\bg.jpg") Then
    File.Delete("c:\inetpub\wwwroot\acb\bg.jpg")
  End If
End If
If Request.QueryString("image") = "1" Then
  DrawBldg1()
End If
End Sub


Assume the page file is named colors.aspx
In pageload event we must get 3 items from the session since each time we paint the building we are reloading the page.
Then notice the 2 if structures. The 2nd one says if the request for this page is coming from the image then draw the building.
The url of the image server control is: color.aspx?image=1
Therefore when the page is loaded the building is not drawn, but when the image is loaded the building is drawn.
Why? Because the response from codebehind goes back to the request.
If the request is from the image the building goes into the image which is postioned where you want.
This saves using an extra page for such positioning. Your one page is simply used twice.
Private Sub DrawBldg1()
Dim filename As String = "bg.jpg"
Dim destpath As String = Request.PhysicalApplicationPath + "images\" + filename
Dim img As Image = Image.FromFile(destpath)
Dim canvas As Bitmap = New Bitmap(img)
Dim g As Graphics = Graphics.FromImage(canvas)
g.SmoothingMode = SmoothingMode.AntiAlias
g.TranslateTransform(70, 215)
Dim pen1 As New Pen(Color.Black, 1)
Dim brush1 As New SolidBrush(Color.White)
Dim sidingColor As Color = IIf(Session("siding") Is Nothing, Color.White, Session("siding"))
Dim roofColor As Color = IIf(Session("roof") Is Nothing, Color.White, Session("roof"))
Dim trimColor As Color = IIf(Session("trim") Is Nothing, Color.White, Session("trim"))
DrawBldg2(g, New SolidBrush(sidingColor), New SolidBrush(roofColor), New SolidBrush(trimColor))
Try
canvas.Save(Response.OutputStream, ImageFormat.Jpeg)
Catch ex As Exception
Dim msg As String = ex.Message
End Try
drawing.ImageUrl = filename
img.Dispose()
canvas.Dispose()
g.Dispose()
End Sub
Here we are doing all the gymnastics necessary to draw on an existing background jpg image.
We also send the image drawn back to the requester.
In DrawBldg1() method notice that I dispose some objects at the end.
Usually I never do this, but allow the garbage collection to handle it.
In the GDI+ resources are left in use until you dispose.
Private Sub DrawBldg2(ByVal g As Graphics, ByVal bsiding As Brush, ByVal broof As Brush, ByVal btrim As Brush)
Dim fullrect As New Rectangle(-30, -60, 400, 400) : g.SetClip(fullrect)
Dim p As New Pen(Color.Black, 1)
Dim s11 As New Point(0, 0)
Dim s12 As New Point(84, -28)
Dim s13 As New Point(84 + 94, 0)
Dim s14 As New Point(84 + 94, 58)
Dim s15 As New Point(0, 56)
Dim siding1() As Point = {s11, s12, s13, s14, s15} ' front of bldg
Dim s21 As New Point(84 + 94, 0)
Dim s22 As New Point(84 + 94 + 86, 6)
Dim s23 As New Point(84 + 94 + 86, 46)
Dim s24 As New Point(84 + 94, 58)
Dim siding2() As Point = {s21, s22, s23, s24} ' side of blding
Dim t11a As New Point(0, 56)
Dim t12a As New Point(0, 66)
Dim t13a As New Point(42, 67)
Dim t14a As New Point(42, 56)
Dim trim1a() As Point = {t11a, t12a, t13a, t14a} ' front left trim
Dim t11b As New Point(127, 57)
Dim t12b As New Point(127, 68)
Dim t13b As New Point(178, 69)
Dim t14b As New Point(178, 58)
Dim trim1b() As Point = {t11b, t12b, t13b, t14b} ' front right trim
Dim t21 As New Point(178, 58)
Dim t22 As New Point(178 + 86, 46)
Dim t23 As New Point(178 + 86, 52)
Dim t24 As New Point(178, 69)
Dim trim2() As Point = {t21, t22, t23, t24} ' side lower trim
Dim t31 As New Point(42, 67)
Dim t32 As New Point(42, 10)
Dim t33 As New Point(83, 10)
Dim t34 As New Point(83, 67)
Dim trim3() As Point = {t31, t32, t33, t34} ' left front door
Dim t41 As New Point(83, 67)
Dim t42 As New Point(83, 10)
Dim t43 As New Point(127, 10)
Dim t44 As New Point(127, 68)
Dim trim4() As Point = {t41, t42, t43, t44} ' right front door
Dim tr1 As New Point(10, 6)
Dim tr2 As New Point(10, 10)
Dim tr3 As New Point(169, 10)
Dim tr4 As New Point(169, 6)
Dim track() As Point = {tr1, tr2, tr3, tr4} ' tracking over the doors
Dim r11 As New Point(-8, 4)
Dim r12 As New Point(-10, 2)
Dim r13 As New Point(83, -28)
Dim r14 As New Point(184, 2)
Dim r15 As New Point(183, 4)
Dim r16 As New Point(83, -26)
Dim r1() As Point = {r11, r12, r13, r14, r15, r16} ' roof front edge
Dim r21 As New Point(184, 2)
Dim r22 As New Point(183, 4)
Dim r23 As New Point(183 + 89, 6)
Dim r24 As New Point(183 + 89, 4)
Dim r2() As Point = {r21, r22, r23, r24} ' roof side edge
Dim r31 As New Point(83, -28)
Dim r32 As New Point(83 + 108, -16)
Dim r33 As New Point(183 + 89, 4)
Dim r34 As New Point(184, 2)
Dim r3() As Point = {r31, r32, r33, r34} ' roof cover
Dim sdoor1 As New Point(184, 45)
Dim sdoor2 As New Point(196, 45)
Dim sdoor3 As New Point(196, 65)
Dim sdoor4 As New Point(184, 68)
Dim sdoor() As Point = {sdoor1, sdoor2, sdoor3, sdoor4} ' small side door
Dim siding As New GraphicsPath
siding.AddPolygon(siding1)
siding.AddPolygon(siding2)
g.FillPath(bsiding, siding)
Dim trim As New GraphicsPath
trim.AddPolygon(trim1a)
trim.AddPolygon(trim1b)
trim.AddPolygon(trim2)
Dim doors As New GraphicsPath
g.FillPath(btrim, trim)
doors.AddPolygon(trim3)
doors.AddPolygon(trim4)
g.DrawPath(p, siding)
g.FillPath(btrim, doors)
Dim frontx As New GraphicsPath
frontx.AddPolygon(siding1)
frontx.AddPolygon(trim1a)
frontx.AddPolygon(trim1b)
DrawCorrugation(g, frontx, 0, 4)
Dim doorsx As New GraphicsPath
doorsx.AddPolygon(trim3)
doorsx.AddPolygon(trim4)
DrawCorrugation(g, doorsx, 0, 4)
Dim sidex As New GraphicsPath
sidex.AddPolygon(siding2)
sidex.AddPolygon(trim2)
DrawCorrugation(g, sidex, 180, 3)
g.SetClip(fullrect)
g.DrawPath(p, trim)
g.DrawPath(p, doors)
g.DrawPolygon(p, track)
g.FillPolygon(btrim, track)
g.FillRectangle(New SolidBrush(Color.Black), 81, 52, 4, 8) ' big door handles
Dim roof As New GraphicsPath
roof.AddPolygon(r1)
roof.AddPolygon(r2)
roof.AddPolygon(r3)
g.FillPath(broof, roof)
g.DrawPath(p, roof)
g.DrawPolygon(p, sdoor) ' draw and fill small side door
g.FillPolygon(New SolidBrush(Color.White), sdoor)
End Sub
This is the core of drawing and coloring.
A point is coordinates.
A polygon is a collection of points, where the last point is automatically connected to the first point in the set.
This ensures that we get a polygon from each set. The polygon can be one pixel high and one pixel long.
A GraphicPath is a collection of polygons or rectangles or ellipses or etc, but for now notice that all I used was polygons.
For 3d drawings polygons are valuable.
Just as you can draw and/or fill a polygon it is just as easy to draw and/or fill a path.

With this in mind, see how I defined the roof path as the set of polygons drawing the roof.

Doing the same with the siding, and the trim.

This makes it very easy for DrawBldg2 to receive 3 brush objects with the chosen colors embedded and color the building.
One must be careful in what order you draw and fill since a fill can overwrite your drawing of your borders, so generally fill and then draw.
So far I haven't found a command to reduce a path by one pixel so that the fill fills your border that you've drawn.

Private Sub DrawCorrugation(ByVal g As Graphics, ByVal pathx As GraphicsPath, ByVal start As Integer, ByVal stepinc As Integer)
Dim p As New Pen(Color.Gray)
g.SetClip(pathx)
For i As Integer = 1 To 290 Step stepinc
g.DrawLine(p, start + i, -90, start + i, 90)
Next
End Sub
DrawCorrugation is interesting because of its use of the SetClip method.

Besides being able to draw/fill paths, you can use a path as a clipping region.
This means the corrugation will not exist on the background, but only on the building part specified by the path.

Private Function GetSidingColor(ByVal name As String) As Color
Dim currentColor As New Color
Select Case name
Case "s1"
currentColor = Color.White
Case "s2"
currentColor = Color.FromArgb(240, 240, 240)
Case "s3"
currentColor = Color.FromArgb(240, 241, 149)
Case "s4"
currentColor = Color.FromArgb(207, 203, 130)
Case "s5"
currentColor = Color.FromArgb(168, 157, 92)
Case "s6"
currentColor = Color.FromArgb(183, 184, 129)
Case "s7"
currentColor = Color.FromArgb(135, 137, 108)
Case "s8"
currentColor = Color.FromArgb(0, 73, 89)
Case "s9"
currentColor = Color.FromArgb(64, 99, 122)
Case "s10"
currentColor = Color.FromArgb(96, 144, 163)
Case "s11"
currentColor = Color.FromArgb(95, 63, 48)
Case "s12"
currentColor = Color.FromArgb(66, 68, 68)
Case "s13"
currentColor = Color.FromArgb(0, 0, 0)
Case "s14"
currentColor = Color.FromArgb(158, 11, 14)
Case "s15"
currentColor = Color.FromArgb(121, 0, 0)
Case "s16"
currentColor = Color.FromArgb(86, 151, 53)
Case "s17"
currentColor = Color.FromArgb(46, 83, 72)
End Select
Return currentColor
End Function

Private Function GetRoofColor(ByVal name As String) As Color
Dim currentColor As New Color
Select Case name
Case "r1"
currentColor = Color.White
Case "r2"
currentColor = Color.FromArgb(240, 240, 240)
Case "r3"
currentColor = Color.FromArgb(240, 241, 149)
Case "r4"
currentColor = Color.FromArgb(207, 203, 130)
Case "r5"
currentColor = Color.FromArgb(168, 157, 92)
Case "r6"
currentColor = Color.FromArgb(183, 184, 129)
Case "r7"
currentColor = Color.FromArgb(135, 137, 108)
Case "r8"
currentColor = Color.FromArgb(0, 73, 89)
Case "r9"
currentColor = Color.FromArgb(64, 99, 122)
Case "r10"
currentColor = Color.FromArgb(96, 144, 163)
Case "r11"
currentColor = Color.FromArgb(95, 63, 48)
Case "r12"
currentColor = Color.FromArgb(66, 68, 68)
Case "r13"
currentColor = Color.FromArgb(0, 0, 0)
Case "r14"
currentColor = Color.FromArgb(158, 11, 14)
Case "r15"
currentColor = Color.FromArgb(121, 0, 0)
Case "r16"
currentColor = Color.FromArgb(86, 151, 53)
Case "r17"
currentColor = Color.FromArgb(46, 83, 72)
End Select
Return currentColor
End Function

Private Function GetTrimColor(ByVal name As String) As Color
Dim currentColor As New Color
Select Case name
Case "t1"
currentColor = Color.White
Case "t2"
currentColor = Color.FromArgb(240, 240, 240)
Case "t3"
currentColor = Color.FromArgb(240, 241, 149)
Case "t4"
currentColor = Color.FromArgb(207, 203, 130)
Case "t5"
currentColor = Color.FromArgb(168, 157, 92)
Case "t6"
currentColor = Color.FromArgb(183, 184, 129)
Case "t7"
currentColor = Color.FromArgb(135, 137, 108)
Case "t8"
currentColor = Color.FromArgb(0, 73, 89)
Case "t9"
currentColor = Color.FromArgb(64, 99, 122)
Case "t10"
currentColor = Color.FromArgb(96, 144, 163)
Case "t11"
currentColor = Color.FromArgb(95, 63, 48)
Case "t12"
currentColor = Color.FromArgb(66, 68, 68)
Case "t13"
currentColor = Color.FromArgb(0, 0, 0)
Case "t14"
currentColor = Color.FromArgb(158, 11, 14)
Case "t15"
currentColor = Color.FromArgb(121, 0, 0)
Case "t16"
currentColor = Color.FromArgb(86, 151, 53)
Case "t17"
currentColor = Color.FromArgb(46, 83, 72)
End Select
Return currentColor
End Function
The only thing interesting here is a way to use decimal values to create a color.
I had to do this because the client had html colors already defined that I had to use.
Private Sub SetColor(ByVal name As String)
Select Case True
Case name.StartsWith("s")
s1.Checked = False 
s2.Checked = False 
s3.Checked = False 
s4.Checked = False 
s5.Checked = False
s6.Checked = False
s7.Checked = False 
s8.Checked = False 
s8.Checked = False 
s9.Checked = False 
s10.Checked = False 
s11.Checked = False 
s12.Checked = False 
s13.Checked = False 
s14.Checked = False 
s15.Checked = False 
s16.Checked = False 
s17.Checked = False
Case name.StartsWith("r")
r1.Checked = False 
r2.Checked = False 
r3.Checked = False 
r4.Checked = False 
r5.Checked = False 
r6.Checked = False 
r7.Checked = False 
r8.Checked = False 
r8.Checked = False 
r9.Checked = False 
r10.Checked = False 
r11.Checked = False 
r12.Checked = False 
r13.Checked = False 
r14.Checked = False 
r15.Checked = False 
r16.Checked = False 
r17.Checked = False
Case name.StartsWith("t")
t1.Checked = False 
t2.Checked = False 
t3.Checked = False 
t4.Checked = False 
t5.Checked = False 
t6.Checked = False 
t7.Checked = False 
t8.Checked = False 
t9.Checked = False 
t10.Checked = False 
t11.Checked = False 
t12.Checked = False
t13.Checked = False 
t14.Checked = False 
t15.Checked = False 
t16.Checked = False 
t17.Checked = False
End Select
CType(Page.FindControl(name), CheckBox).Checked = True
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Response.Redirect("colors.aspx")
End Sub

Private Sub SidingChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles s1.CheckedChanged, _
s2.CheckedChanged, s3.CheckedChanged, s4.CheckedChanged, s5.CheckedChanged, _
s6.CheckedChanged, s7.CheckedChanged, s8.CheckedChanged, s9.CheckedChanged, _
s10.CheckedChanged, s11.CheckedChanged, s12.CheckedChanged, s13.CheckedChanged, _
s14.CheckedChanged, s15.CheckedChanged, s16.CheckedChanged, s17.CheckedChanged
Session("sidingname") = sender.id
Session("siding") = GetSidingColor(sender.id)
End Sub

Private Sub TrimChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles t1.CheckedChanged, _
t2.CheckedChanged, t3.CheckedChanged, t4.CheckedChanged, t5.CheckedChanged, _
t6.CheckedChanged, t7.CheckedChanged, t8.CheckedChanged, t9.CheckedChanged, _
t10.CheckedChanged, t11.CheckedChanged, t12.CheckedChanged, s13.CheckedChanged, _
t14.CheckedChanged, t15.CheckedChanged, t16.CheckedChanged, t17.CheckedChanged
Session("trimname") = sender.id
Session("trim") = GetTrimColor(sender.id)
End Sub

Private Sub RoofChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles r1.CheckedChanged, _
r2.CheckedChanged, r3.CheckedChanged, r4.CheckedChanged, r5.CheckedChanged, _
r6.CheckedChanged, r7.CheckedChanged, r8.CheckedChanged, r9.CheckedChanged, _
r10.CheckedChanged, r11.CheckedChanged, r12.CheckedChanged, r13.CheckedChanged, _
r14.CheckedChanged, r15.CheckedChanged, r16.CheckedChanged, r17.CheckedChanged
Session("roofname") = sender.id
Session("roof") = GetRoofColor(sender.id)
End Sub

End Class
The rest of the code is fairly straightforward.

The more I have delved into the GDI+ part of the framework, the more I see potential for it. On a web page there are many potentials for a simple diagram indicating more clearly than anything else what a user should be  thinking now to understand something easier. Clicking on certain regions of a highly contextual drawing could evolve the diagram to a state that holds valuable information that would be hard replaced by pages of numbers. Picture a use case drawing program that could keep up with the speed that a client could talk to describe their domain.

So far all my GDI+ experience has been on the web page, and the only limitation that the web page has had versus using GDI+ on the desktop is when drawing a continuous line since each pixel might require a request to the server, so you could generate 100 requests per second this way. There are ways of getting around this that I will go into in the next article. Map server side image maps to regions that can see a point that you have clicked, and other such things allow a discrete drawing system to be developed.

Here are the links and the one book I used to get going on this project in case they may be of use to you.


MSDN documentation: (lots of examples):
Introduction with VB.NET:
Another good introduction:
Brushes with VB.NET:
Doing image overlays:
Creating a drawing program:

GDI+ Programming in C# and VB.NET, by Nick Symmonds: this book definitely helped me, but his constant referring to past versions of GDI+ and to Visual Basic 6 were not helpful. The book explains some things well, but was very skimpy on many topics. At the time of this writing it is the only major book on the topic. Having all examples in VB and C# was nice for my learning of C#.

Send mail to Computer Consulting with questions or comments about this web site.
Last modified: October 11, 2003

 



User Comments

No comments posted yet.

Product Spotlight
Product Spotlight 





Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2017 ASPAlliance.com  |  Page Processed at 2017-09-23 10:43:01 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search