How to smooth joints when calling ofSetLineWidth?

Hi there,

I’ve been scratching my head for a little trying to work out how to apply a scalable stroke to a basic shape (circle, rectangle, ect) with good results. If I apply ofSetLineWidth(float); to a rectangle, curve, etc I can see the joints(see images)… Is there any other way to do it or “smooth” the corners out?

Thanks

ofPolyline can draw more smoothly, though I’m not sure if it does joints quite as precisely as you want. You could also just create two squares and fill between them, that would allow you to do joints however you’d like. Now that I think about it, that’s probably the best option for really precise joints.

Hi Joshua,

Thanks for your quick reply. Yes that’s what I’ve been doing until now but I thought there might be an easier(quicker) solution as in processing’s stroke(). Ok, not a problem. Cheers!

ofSetLineWidth() is internally using opengl to do the line width, which means it’s limited by the opengl implementation. you mention processing’s stroke(), but if you look at strokeCap() and strokeJoin() you’ll notice:

This function is not available with the P2D, P3D, or OPENGL

there might be some implementations that do mitering (line-edge joins) nicely, but i haven’t seen them. the only ‘real’ solution would be to generate your own triangle strips from the lines.

that said, i can think of two ‘quick’ solutions:

1 if you don’t need realtime output, you could try using cairo to render. it might have better mitering algorithms.

2 you could walk along the lines and draw smooth points using glEnable(GL_POINT_SMOOTH). this will get really slow if you have a ton of stuff to draw.

also take a look at the geometry shader example, it transforms a line in a triangle strip using a shader which should be the fastest way of doing it

Hi thanks both for your suggestions.

I’ve tried:

glEnable(GL_POINT_SMOOTH);
glLineWidth(10);
ofRect(0, 0, 300, 300);

with no joy. I’ve also tried to run the geometry shader example as Arturo suggested but I realised I can’t use the 007 pre-release as I’m working on a OSX 10.5. Every time I try to run an example from v007 I get this:

error: There is no SDK at specified SDKROOT path ‘/Developer/SDKs/MacOSX10.6.sdk’

I guess that means I need OSX10.6? Back to double squares.

When I needed something similar a while back (stroking polygons with thick lines and nice joins at the vertices), I ended up writing a small function that uses Apple’s CoreGraphics library to convert the polygon to stroke to a polygon that can be filled using OpenGL (the lines are duplicated, and one copy is offseted inwards and the other outwards, so to speak). If you only need this to work on Mac, I can post the code.

HTH
/Jesper

It’d be great if you could! Thanks

OK, here goes. This code is not well tested at all. It seems to work in the simple cases I have used it for so far, but there are probably problems with it.
Also, this code was written before I started using of 070, so it doesn’t use the nice new ofPolyline class.

The header file, ofxPolygonStroker.h:

  
#ifndef _OFX_POLYGONSTROKER  
#define _OFX_POLYGONSTROKER  
  
#include "ofPoint.h"  
#include <vector>  
  
  
namespace NSPolygonStroker {  
  
  
typedef std::vector<ofPoint> polygonContour;  
typedef std::vector<polygonContour> polygon;  
  
enum EJoinType {  
	kJoinType_Miter,  
//	kJoinType_Round,	// Doesn't work, we cannot return anything other than straight lines  
	kJoinType_Bevel  
};  
  
enum ECapType {  
	kCapType_Butt,  
	kCapType_Square  
//	kCapType_Round	// Doesn't work, we cannot return anything other than straight lines  
};  
  
polygon CreateStrokedPolygon(const polygon& polygonToStroke, bool closePolygon, float strokeWidth, EJoinType joinType = kJoinType_Miter, ECapType capType = kCapType_Butt, float miterLimit = 10.0f);  
  
  
} // namespace NSPolygonStroker  
  
  
#endif // ndef _OFX_POLYGONSTROKER  

And the implementation file, ofxPolygonStroker.cpp:

  
#include "ofxPolygonStroker.h"  
  
#include <cstring>  
#include <cassert>  
#include <cmath>  
  
#include <ApplicationServices/ApplicationServices.h>  
  
namespace NSPolygonStroker {  
  
static CGContextRef GetWorkingContext() {  
	static CGContextRef sWorkingContext = NULL;  
	if (sWorkingContext == NULL) {  
		CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();  
		sWorkingContext = CGBitmapContextCreate(NULL, 16, 16, 8, 16 * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipFirst);  
		CGColorSpaceRelease(colorSpace);  
		colorSpace = NULL;  
	}  
	assert(sWorkingContext != NULL);  
	return sWorkingContext;  
}  
  
static void MyPathApplier(void* info, const CGPathElement* element) {  
	assert(info != NULL);  
	assert(element != NULL);  
	polygon& polygonToMutate = *reinterpret_cast<polygon*>(info);  
	switch (element->type) {  
		case kCGPathElementMoveToPoint:  
			polygonToMutate.push_back(polygonContour(1, ofPoint(element->points[0].x, element->points[0].y)));  
			break;  
		case kCGPathElementAddLineToPoint:  
			assert(polygonToMutate.size() > 0);  
			polygonToMutate.back().push_back(ofPoint(element->points[0].x, element->points[0].y));  
			break;  
		case kCGPathElementAddQuadCurveToPoint:  
		case kCGPathElementAddCurveToPoint:  
			// Should never happen, we only use straight edge polygons, no curves  
			assert(false);  
			break;  
		case kCGPathElementCloseSubpath:  
			assert(polygonToMutate.size() > 0);  
			polygonToMutate.back().push_back(polygonToMutate.back().front());  
			break;  
		default:  
			// What is this?  
			assert(false);  
	}  
}  
  
polygon CreateStrokedPolygon(const polygon& polygonToStroke, bool closePolygon, float strokeWidth, EJoinType joinType, ECapType capType, float miterLimit) {  
	CGContextRef context = GetWorkingContext();  
  
	// Set up context attributes  
	CGContextSetLineWidth(context, strokeWidth);  
	CGContextSetMiterLimit(context, miterLimit);  
	switch (joinType) {  
		case kJoinType_Miter:  
			CGContextSetLineJoin(context, kCGLineJoinMiter);  
			break;  
/*  
		case kJoinType_Round:  
			CGContextSetLineJoin(context, kCGLineJoinRound);  
			break;  
*/  
		case kJoinType_Bevel:  
			CGContextSetLineJoin(context, kCGLineJoinBevel);  
			break;  
		default:  
			assert(false);  
	}  
	switch (capType) {  
		case kCapType_Butt:  
			CGContextSetLineCap(context, kCGLineCapButt);  
			break;  
		case kCapType_Square:  
			CGContextSetLineCap(context, kCGLineCapSquare);  
			break;  
/*  
		case kCapType_Round:  
			CGContextSetLineCap(context, kCGLineCapRound);  
			break;  
*/  
		default:  
			assert(false);  
	}  
  
	// Define the polygon path(s)  
	CGContextBeginPath(context);  
	for (std::size_t contourIndex = 0; contourIndex < polygonToStroke.size(); contourIndex++) {  
		const polygonContour& contour = polygonToStroke[contourIndex];  
		for (std::size_t vertexIndex = 0; vertexIndex < contour.size(); vertexIndex++) {  
			const ofPoint& point = contour[vertexIndex];  
			if (vertexIndex == 0) {  
				CGContextMoveToPoint(context, point.x, point.y);  
			}  
			else {  
				CGContextAddLineToPoint(context, point.x, point.y);  
			}  
		}  
		if (closePolygon) {  
			CGContextClosePath(context);  
		}  
	}  
  
	// Modify the path to a stroked path  
	CGContextReplacePathWithStrokedPath(context);  
  
	// Copy the path from the context  
	CGPathRef strokedPath = CGContextCopyPath(context);  
	assert(strokedPath != NULL);  
  
	// Clear the current path  
	CGContextBeginPath(context);  
  
	polygon resultPolygon;  
  
	CGPathApply(strokedPath, &resultPolygon, &MyPathApplier);  
  
	CGPathRelease(strokedPath);  
  
	return resultPolygon;  
}  
  
  
  
} // namespace NSPolygonStroker  

And finally, sample usage for generating and drawing a stroked rectangle with 10 pixels thick lines:

  
#include "ofxPolygonStroker.h"  
  
// In you draw function  
NSPolygonStroker::polygonContour rectContour;  
rectContour.push_back(ofPoint(100, 100));  
rectContour.push_back(ofPoint(200, 100));  
rectContour.push_back(ofPoint(200, 200));  
rectContour.push_back(ofPoint(100, 200));  
NSPolygonStroker::polygon rectPolygon;  
rectPolygon.push_back(rectContour);  
  
NSPolygonStroker::polygon strokedPolygon = NSPolygonStroker::CreateStrokedPolygon(rectPolygon, true, 10.0f, NSPolygonStroker::kJoinType_Miter, NSPolygonStroker::kCapType_Butt, 2.0f);  
ofSetPolyMode(OF_POLY_WINDING_NONZERO);  
ofBeginShape();  
for (std::size_t contourIndex = 0; contourIndex < strokedPolygon.size(); contourIndex++) {  
	const NSPolygonStroker::polygonContour& contour = strokedPolygon[contourIndex];  
	for (std::size_t vertexIndex = 0; vertexIndex < contour.size(); vertexIndex++) {  
		const ofPoint& vertex = contour[vertexIndex];  
		ofVertex(vertex.x, vertex.y);  
	}  
	if (contourIndex < (strokedPolygon.size() - 1)) {  
		ofNextContour();  
	}  
}  
ofEndShape();  
// Restore the poly mode to the default  
ofSetPolyMode(OF_POLY_WINDING_ODD);  

The example will generate two polygon contours: an outer that will be filled, and an inner that forms a hole in the outer contour. Note that the generated contours are messy and contains lots of redundant points, but it seems to work. :slight_smile:

HTH
/Jesper

wow, super interesting example.

it would be nice to have some built-in mitering in OF in the future though, perhaps using the geometry shader functionality like arturo was mentioning?

i just added an issue to github so we can come back to this/implement it at some point https://github.com/openframeworks/openFrameworks/issues/730

[quote=“kylemcdonald, post:10, topic:7278”]
wow, super interesting example.[/quote]
Thanks! :slight_smile:

[quote=“kylemcdonald, post:10, topic:7278”]
it would be nice to have some built-in mitering in OF in the future though, perhaps using the geometry shader functionality like arturo was mentioning?[/quote]
Yes, that would be awesome! I guess the Cairo renderer already supports it (though there is no OF interface to it).

However, personally I would like to be able to retrieve the stroked geometry too, not just draw it. The reason I want that is mainly that I want to clip stroked paths to other shapes. For example, in one of my OF apps I get blobs from OpenCV , and I then draw stroked paths inside the blobs. I currently do this by generating the stroked paths and then using a polygon clipping library to generate clipped versions of the stroked paths, and finally draw them.
(But if OF would add the ability to clip drawing to an arbitrary polygon, I don’t think I would need to get at the stroked geometry anymore.)

(Also, I asked about this a few months back, and got a few pointers to polygon clipping resources (though no stroking code): http://forum.openframeworks.cc/t/graphical-polygon-operations-stroking,-clipping-etc/6317/1.)

Hi. I need this too. Any news about doing this with “actual oF core stuff” or any good “external” solution?

I am thinking to use one of this:
http://artgrammer.blogspot.de/2011/06/vaserendererdraft12.html
http://artgrammer.blogspot.de/2011/07/drawing-polylines-by-tessellation.html
https://github.com/roymacdonald/ofxFatLines

Use ShivaVG renderer; bgstaal made an addon: https://github.com/bgstaal/ofxShivaVG

works flawlessly

takes a bit more cpu/gpu though, barely noticeable.

Is there a way to do this integrated into the newest oF?

I’m building for iOS, so a lot of the solutions can’t work unfortunately

1 Like