Open polygons with Graphics.DrawPolygon

Is it possible to draw open polygons with the Graphics.DrawPolygon method (e.g. last point is not connected with the first point)?

I can’t find anything in the documentation about it.

Bump

Polygons are always closed - by definition
Draw a series of line segments from point to point if you want an open one

EDIT : this is mentioned in the classic framework docs
When passed to the DrawPolygon or FillPolygon method, this array would draw a polygon by drawing a line starting at 10,5 and ending at 40,40 then drawing another line starting from 40,40 ending at 5,60 and finally a line from 5,60 back to 10,5 to complete the polygon. This polygon has only three sets of coordinates so it is a triangle.

Use a Group2d object to hold a collection of curveshapes.

(When you set Order to zero on a CurveShape, you get a straight line from x,y to x2, y2.)

Thank you for the suggestions, it’s much appreciated for I’m currently stuck on this problem.

That is correct.

I should have mentioned that I’m actually trying to draw a polyline, and hoped that I could use the DrawPolygon method. To complicate things further, I’m using a thick line width.

Here is a screenshot of exactly what I’m trying to draw:

However, this is the result I get with g.DrawPolygon:

The last closing line spoils the drawing.

I did try drawing a series of lines segments before making this post. However, with a thick line width it creates ugly corners:

[quote=227205:@Jeff Tullin]Use a Group2d object to hold a collection of curveshapes.

(When you set Order to zero on a CurveShape, you get a straight line from x,y to x2, y2.)[/quote]

I tried the CurveShapes idea. Below is the code that I used to draw from the same points array used by DrawPolygon, but I then again get the same ugly corners as show in the screenshot above.

    Dim c as new CurveShape
    c.BorderColor = &cff0000
    c.Border = 100
    c.BorderWidth = strokeWidth 
    c.Order = 0
    i = 3
    while i < points.Ubound
      c.x = points(i - 2)
      c.y = points(i - 1)
      c.x2 = points(i)
      c.y2 = points(i + 1)
      g.DrawObject c
      i = i + 2
    wend

I’ve even tried the FigureShape to draw the polyline with. Now the corners are drawn correctly, but once again it tries to close the polygon. Not only that, it mangles up the last closing line. Here is the code and a screenshot of it.

    fx.FillColor = &c000000ff
    fx.BorderColor = g.ForeColor
    fx.Border = 100
    fx.BorderWidth = strokeWidth
    i = 1
    while i < (points.Ubound - 2)
      fx.AddLine points(i), points(i + 1), points(i + 2), points(i + 3)
      i = i + 2
    wend
    g.DrawObject fx

I would be sincerely grateful for any other ideas on suggestions what I could try next…

If you’re targeting only Mac you can use CGContext declares which have polylines, more options (round, square and miter line joins and end caps) and faster execution.

There may be similar declares for Windows and Linux.

Also, a brute force solution using DrawPolygon is to retrace backwards the poly line from the end to the beginning.

It seems platform specific declares may be the best solution. For what I’m trying to achieve I will eventually need access to round, sqaure, miter line joins and end caps anyways, so I might as well go through the learning curve.

I would rather avoid a brute force solution, and try to find a proper way to fix this.

Thanks for pointing me in a possible direction Will.

If anyone has a very basic example of how to draw a simple line using CGContext, to get me started, it would be greatly appreciated. I’ve done a lot of work with Windows declares before, but haven’t worked with Mac or Linux declares yet.

This may look complicated but… well maybe it is somewhat. I pulled this from a framework I’ve been developing that wraps all this in a Canvas that provides it’s own g parameter but it’s not finished. MacOSLib has most of the CG functions, Context, Color, Path, etc. There’s quite a lot to the whole family.

Xojo Graphics drawing and CGContext can be mixed. In this example the filling black is done with CGContext but could be regular ForeColor and FillRect

In a Paint Event

[code] declare sub CGContextSetFillColorWithColor lib “Cocoa” (cntxt As Ptr, clr As Ptr)
declare sub CGContextFillRect lib “Cocoa” (cntxt As Ptr, rect As CGRect)
declare sub CGContextSetStrokeColorWithColor lib “Cocoa” (cntxt As Ptr, clr As Ptr)
declare function CGColorGetConstantColor lib “Cocoa” (colorName As CFStringRef) As Ptr
declare sub CGContextBeginPath lib “Cocoa” (context as Ptr)
declare sub CGContextAddLines lib “Cocoa” (context as ptr, pnts as Ptr, count as integer)
declare sub CGContextStrokePath lib “Cocoa” (cntxt As Ptr)
declare sub CGContextSaveGState lib “Cocoa” (c as Ptr)
declare sub CGContextTranslateCTM lib “Cocoa” (cntxt As Ptr, tx As single, ty As single)
declare sub CGContextRestoreGState lib “Cocoa” (c as Ptr)
declare sub CGContextSetLineWidth lib “Cocoa” (c as Ptr, width As single)

//get a Ptr to the CGContext underlying the g parameter
dim gg As Ptr = Ptr(g.Handle(g.HandleTypeCGContextRef))

//fill black
CGContextSetFillColorWithColor(gg, CGColorGetConstantColor(“kCGColorBlack”))
CGContextFillRect(gg, CGRectMake(0, 0, g.Width, g.Height))

//build a 10000 point polyline in a memoryblock
dim resolution As integer = 10000
dim points As new MemoryBlock(1000042)
dim p As Ptr = points
dim a As single
for i As integer = 0 to resolution - 1
a = 6.1 * i / resolution
p.Single(i8) = 200 * cos(a)
p.Single(i
8+4) = 200 * sin(a)
next

//draw path white, 5 pixels wide and centered
CGContextSetStrokeColorWithColor(gg, CGColorGetConstantColor(“kCGColorWhite”))
CGContextSetLineWidth(gg, 5)
CGContextSaveGState(gg)
CGContextTranslateCTM(gg, g.Width/2, g.Height/2)
CGContextBeginPath(gg)
CGContextAddLines(gg, points, resolution)
CGContextStrokePath(gg)
CGContextRestoreGState(gg)[/code]

Also note that the ‘points’ are 32bit singles. On a 64bit build they should be Doubles.

Oh, and you need this Structure and method for the code to work

[code]Structure CGRect
x As Single
y As Single
w As Single
h As Single

Function CGRectMake(x As single, y As single, w As single, h As single) As CGRect
dim r As CGRect
r.x = x
r.y = y
r.w = w
r.h = h
return r
End Function[/code]

Could you not overshoot the vertical lines by strokeWidth/2 in each direction?
(eg Start them strokeWidth/2 higher, and make them strokeWidth longer…

eg
if the strokewidth = 6
vertical line = 30, 20 -(3) , 30, 100 +(6)
horizontal line at top 30,20,50,20

Then the caps should overlap at the actual corners rather than than strokeWidth/2 in each direction.

Thanks for the example Will.

I was also thinking about doing this as an interim solution. It could however get complicated when drawing lines at angles, because then it is not as straight forward anymore as just adding strokeWidth/2 to the horizontal and vertical values. A unit vector would first have to be calculated and then used to determine how much to overshoot each of the horizontal and vertical components.

Another problem I foresee is when using transparent colors for the drawing. Then the overshooting will leave some darker artifacts on the line joints.

But overshooting is easy enough to at least make rectangular opaque drawings look correct for now.
Who wants to draw thick widthed polylines, at angles in a transparent color anyways :slight_smile:

Here’s a simple brute force stop gap. The input array is copied then extended in reverse order so the line traces back on itself and stays open.

//Graphics Extension method
Sub DrawPolyline(extends g As Graphics, points() As integer)
  
  //copy array
  dim xy(0) As integer
  for i As integer = 1 to points.Ubound
    xy.Append points(i)
  next
  
  //add reversed
  for i As integer = points.Ubound - 3 downto 3 step 2
    xy.Append(xy(i))
    xy.Append(xy(i+1))
  next
  
  g.DrawPolygon(xy)

End Sub[/code]

Or if that's too wasteful for your sensibilities you might create and cache the trace back poly

[code]Function makeOpenPolygonCopy(points() As integer) As integer()
  dim xy(0) As integer
  for i As integer = 1 to points.Ubound
    xy.Append points(i)
  next
  makeOpenPolygonInPlace(xy)
  return xy
End Function


Sub makeOpenPolygonInPlace(points() As integer)
  for i As integer = points.Ubound - 3 downto 3 step 2
    points.Append points(i)
    points.Append points(i+1)
  next
End Sub

Put this last closing line little down and clean it with clearrect?

You might also want to check the GraphicsDemo in iosLib where I do some lines() or rects() drawing on CGContext or the CoreGraphics Wiki . It’s pretty much the same in OS X.

It makes sense to extend the points array with itself in reverse.

When drawing with transparent colors though, this technique will not yield the result I’m looking for. However, I think I will use this for now since it is easy to implement and will 90% of the time give the results I need.

Thank you for posting the samples Will.

Just goes to show, many heads are better than one when looking for solutions :wink:

This could work, however, I’m drawing the polyline on existing drawings, and unfortunately clearing the rect would clear those drawings as well. Also, the given polyline is only an example, and it might not always be so easy to clear the unwanted line as cleanly as you illustrated.

Great stuff, thanks Ulrich.

Thank you for all the posts everyone. I’m now back on track with the project, with some interim solutions I can use, while learning about platform specific declares for a more permanent long term solution.

The Xojo forum rocks!

Why not using a picture object where you draw the polys to and then draw it to the actual context. So the solution form Jukka seems the simple one to me. This way, you don’t need to clear rect the context or even a specific region.

@Alwyn Bester why do you say that you have trouble with transparency?
From my test (OS X) I see the same color both with a alpha color than with a transparency value

a simpler extension could be:

sub drawOpenPoly (extends g as Graphics, points() as integer, lineColor as Color=&c000000, lineWidth as integer=1, opacity as integer=100)
  dim xy(0) As integer
  for i As integer = 1 to points.Ubound
    xy.Append points(i)-lineWidth/2
  next
  
  //add reversed
  for i As integer = points.Ubound - 3 downto 3 step 2
    xy.Append(xy(i))
    xy.Append(xy(i+1))
  next
  dim gx as Graphics=g.Clip(0,0,g.Width,g.Height)
  gx.ForeColor=lineColor
  gx.PenHeight=lineWidth
  gx.PenWidth=lineWidth
  gx.Transparency=opacity
end sub

so you can forget about set and reset the graphics context

Clearing the last line won’t work for all poyline shapes.

In the image above you’ll see that final rectangle needed to clear the last polyline will also clear other parts of the polyline. I need a solution that will be able to work with all polyline shapes.

The dark line indicates the part of the polyline that shouldn’t be deleted.