Re: Advice for drawing complex, line-based designs
Re: Advice for drawing complex, line-based designs
- Subject: Re: Advice for drawing complex, line-based designs
- From: Graham Cox <email@hidden>
- Date: Wed, 6 May 2009 14:21:23 +1000
On 06/05/2009, at 12:28 PM, Tom Roehl wrote:
I'm working on a project where I'm reading in a file that contains a
list of coordinates that are used to define a stitch pattern for
embroidery machines. Think of those machines you see at the mall
that stitch names and logos on hats and t-shirts. To display the
images, I created a document based application that uses each
coordinate to build up a NSBezierPath and draw the image using
NSBezierPath and lineToPoint. It seems pretty straight forward but
there are a few wrinkles that are causing me problems so I thought I
would ask for help. I tried to find some answers online with no luck
so I apologize if this is basic stuff!
The first issue is the actual drawing of the lines. Since I'm trying
to represent stitch patterns, I want to be able to show layers such
that if one stitch crosses over another that there is some
indication of which line is over the other. Think of way the laces
in your shoes look, and that is the effect I am looking for. I
thought I could solve it by finding a way to stroke the line with an
outline but I couldn't figure out a way to do that. My first
solution was to stroke the path in black a couple extra pixels wide
and then to stroke the path again with a lighter color.
You can get the outline of a stroked path if you drop down to the
CoreGraphics functions. There is CGContextReplacePathWithStrokedPath()
which allows you to clip and paint a stroked path in a variety of
ways, though if you want to recover that path back into a
NSBezierPath, it's somewhat tricky. It can be done but requires use of
a non-public function in CG (CGContextCopyPath).
However, stroking a path twice, first in a wider stroke, is a good way
to do this if it creates the effect you need. You can deal with the
the ends of a path in different ways - use a square cap style instead
of butt for the lower stroke for example will cap the ends, or you can
easily calculate a line that can be drawn at the right angle across
the ends. Of course doing that all adds up in terms of drawing time.
This gave me a nice 3-D effect and was pretty close to what I wanted
but it wasn't quite perfect. The problem was in the mitered ends.
The stitches are typically back and forth with very narrow angles
and the outline only showed up on the edges, but I really needed it
to follow the full length of each stitch if that makes sense. My
next solution was to create a NSBezierPath for each stitch. This
solution gave me the look I wanted but I'm worried that it is a very
expensive solution. Since a design can be made up of potentially
tens of thousands of stitches I'm creating a huge amount of
NSBezierPaths which strikes me as a problem.
It could be expensive, but you won't know until you can measure it.
Presumably it would be unusual to redraw the entire view every time,
so ensure you only draw what intersects the update rectangles. use -
needsToDrawRect: and -getRectsBeingDrawn:count: to obtain these - just
using the rect passed to -drawRect: is probably not good enough when
you want to squeeze every bit of performance. There are also ways to
store the paths so that only those needing to be drawn can be quickly
looked up - iterating a linear array works but can get slow when you
have thousands of objects. BSP and R*-Trees are two ways that can help
there (though unfortunately there's no built-in support for either).
The next problem is that once I've got the design drawn, I want to
be able to zoom in and out and do other types of dynamic
modifications of the image. For example, I may want to show or hide
individual colors in the design. And of course, the image should be
able to be scrolled if it is too big for the window. I had
originally used -readFromURL to parse the file and store the line
segments in my own internal structure. I then used -drawRect of the
NSView to build the NSBezierPaths and draw the image. While this
basically worked, I'm pretty sure this is the wrong way to do it.
Why? Sounds entirely reasonable. However, you definitely want to avoid
creating the paths from the data in drawRect - instead try to do that
somewhere else and then cache the paths resulting. Only draw in
drawRect, as far as possible. You only need to compute the drawing
paths once, or when the original data changes.
For starters, I think I'm recreating/drawing the image way too much
by doing it in -drawRect. Should I be doing my drawing in -
initWithFrame instead? Also, I'm struggling with zooming. Since I
have all the x/y coordinates stored in an internal structure, should
I be recreating the path with an appropriate scale factor? Or should
I be trying to scale the created image? If I had all of the image in
a single path it would be easy to transform it but how would I do it
if I have thousands of paths?
Scale the view instead. This has the effect of automatically scaling
any paths you draw, and the drawing code doesn't need to be aware of
the zoom scale. Take care to only draw what's needed to be drawn, and
you'll find that drawing speed should actually increase as you zoom in
(because fewer paths have to be drawn).
NSView's -scaleUnitSquareToSize: is great for implementing zooming, or
I have a simple NSView subclass that wraps that up nicely:
http://apptree.net/gczoomview.htm
Any help or suggestions would be greatly appreciated!
If you can reuse individual stitches by moving them to different
places, then one way to gain a lot of speed is to use a CGLayer to
cache the image. (note: NOT CALayer). This allows you to cache the
image on the GPU and stamp it to different places in your view.
--Graham
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden