views:

557

answers:

2

Simple Painting:
Fingering the iPhone screen paints temporary graphics to a UIView. These temporary graphics are erased after a touch ends, and are then stored into an underlying UIView.

The process is simple:
1) Touch Starts & Moves >>
2) Paint Temporary Graphics on top UIView>>
3) Touch Ends >>
4) Pass Temporary Graphics To underlying UIView >>
5) Underlying UIView adds Temporary Graphics to Stored Graphics >>
6) Underlying UIView Re-Draws all Stored Graphics >>
7) Delete Temporary Graphics on top UIView.

In this manner, I can accumulate graphics on the underlying UIView while maintaining responsive painting of the temporary graphics on the top UIView.

(Sidenote: Each "Drawing" is simply an NSArray of custom "Point" Objects which are just NSObject containers for CGPoints. And the underlying UIView has a seperate NSArray, where it stores these NSArrays of CGPoints)

The Problem Is:
When a great deal of graphics has accumulated on the underlying UIView, it takes time to draw it all out on the screen. And any new drawings on the top UIView will not be displayed until the drawing of the underlying stored graphics is complete. Thus, there is a noticeable lag when many graphics are on the screen.

Question:
Can anyone think of a good way to improve performance here, so that there is no noticable lag between drawings when there are a lot of graphics on the screen?

+1  A: 

An NSArray of CGPoints? You mean an NSArray of NSValues holding CGPoints? That's an incredibly time-expensive way to hold what has to be a huge number of values that you access constantly. You could store this information in many better ways. A 2-dimensional C-array representing the entire screen is the most obvious. You may also want to look into bitmap image representations, and draw directly into a CGImage rather than maintaining a bunch of CGPoints. Take a look at the Quartz 2D Programming Guide.

EDIT:

Your object (below) is the equivalent of an NSValue, just a little more specialized. There's a lot of overhead going on here when you have many, many objects (~100,000 I'm guessing when the screen is nearly full; more if you're not removing duplicates; run Instruments to profile it). Old-style C data structures are likely to be much faster for this, because you can avoid all the retains/releases, allocations, etc. There are other options, though. Duplicate point checking would be much faster with an NSMutableSet if you pixel-align your CGPoints, and overload -isEqual on your Point object.

Do make sure you're pixel-aligning your data. Drawing on fractional pixels (and storing them all), could dramatically increase the number of objects involved and the amount of drawing you're doing. Even if you want the anti-aliasing, at least round the pixels to .5 (or .25 or something). A CGPoint is made up of two doubles. You don't need that kind of precision to draw to the screen.

Rob Napier
No, I didn't use NSValue to hold the CGPoints. I subclassed NSObject and gave it two members: xCoord and yCoord, which are CGPoints. Actually performance does not seem that bad until the screen is almost covered in graphics. I don't access the stored graphics until the user lifts his/her finger (in-between drawings only), at which time I draw everything that is stored...I am maintaining the points so that I can backtrace and perform undos when needed. Would a C-array be faster?
RexOnRoids
Edited answer based on comment.
Rob Napier
+1  A: 

Why not just draw everything onto a CGBitmapContextRef buffer so the drawing operations will accumulate, and then draw that to the screen in your drawRect:? You will be able to perform arbitrary graphics operations without slowing down as the total number of operations increases.

If undo support is necessary, one could always keep a copy for each change made and invalidate the oldest copies when a memory warning is received. (or for an even fancier solution, store the operations as you do now, but keep a cached copy every dozen user operations or so)

rpetrich
thanks for this answer. How do I draw multiple CGImageRefs (each representing a pen stroke) together in the -drawRect: method?
RexOnRoids
You should use one CGBitmapContextRef as your backing buffer. As you draw a new path onto the context it paints on top of the old contents. In the view's drawRect: use CGBitmapContextCreateImage to get the image and then draw that onto your view. Only one CGBitmapContextRef is necessary to implement this
rpetrich