On Apr 3, 2017, at 8:54 PM, Patrick J. Collins <patr...@collinatorstudios.com> wrote: > > I have a NSView in which I am drawing a waveform from a buffer. This > NSView has a child playhead element, that I want to move across the > waveform as it plays. The problem is, drawRect: is expensive and anytime > the playhead moves, the waveform has to draw itself all over again, > causing terrible performance problems. > > My thought was to draw the waveform once, save it as an NSImage, and > then have drawRect: draw itself from the saved image, and when a new > buffer is loaded, discard the image and let drawRect: draw the new > waveform and save it as an NSImage, etc. > > However, when drawRect: is called subsequent times (after new buffers > are set), the image appears to be garbage and therefore this strategy is > not quite working as planned... What am I doing wrong? > > @property (nonatomic, strong) NSImage *image; > > ... > > -(void)drawRect:(NSRect)dirtyRect { > if (self.image) { > [self.image drawInRect:dirtyRect];
You need to draw to self.bounds, here, not dirtyRect. If the dirtyRect is some portion of the view bounds, because only that portion was marked as needing display, the above code would draw the whole image scaled down into that portion. You could also draw using one of the other draw methods. Some will allow you to specify both the source and destination rects, which will allow you to only draw the dirty rect without the scaling problem. Also, specifying the compositing operation as NSCompositingOperationCopy will be more efficient. > return; > } > > [[NSColor blackColor] setFill]; > NSRectFill(dirtyRect); > [super drawRect:dirtyRect]; > > float zero = self.bounds.size.height / 2; > [[NSColor blueColor] set]; > > NSPoint pointA = NSMakePoint(0, zero); > NSPoint pointB = NSMakePoint(self.bounds.size.width, zero); > [self drawLineFromPointA:pointA toPointB:pointB]; > > if (self.buffer) { > float spacing = self.bounds.size.width / self.buffer.size; > for (int i = 0; i < self.buffer.size - 1; i++) { > NSPoint pointA = NSMakePoint(i * spacing, zero - > (self.buffer.samples[i] * zero)); > NSPoint pointB = NSMakePoint((i + 1) * spacing, zero - > (self.buffer.samples[i + 1] * zero)); > [self drawLineFromPointA:pointA toPointB:pointB]; > } > } > > [self lockFocus]; > NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] > initWithFocusedViewRect:[self bounds]]; > self.image = [[NSImage alloc] initWithData:[rep TIFFRepresentation]]; > [self unlockFocus]; > } Don't draw into the view and then attempt to capture the drawing as an image. Draw into an image directly and then draw that image in the view. First, Cocoa locks focus on your view before calling -drawRect:. That's how drawing you do within -drawRect: ends up in your view. So, you don't need to call [self lockFocus] in -drawRect: and thus you shouldn't call [self unlockFocus]. That said, the drawing you do during -drawRect: is not necessarily flushed to the window backing store and/or the window server immediately. Therefore, the image you capture with -initWithFocusedViewRect: doesn't necessarily contain what you just drew. I expect that's why you're getting garbage on a draw after the first. To draw directly into an image, do something like: NSImage* image = [NSImage imageWithSize:self.bounds.size flipped:self.flipped drawingHandler:^BOOL(NSRect dstRect){ // drawing relative to dstRect (not dirtyRect nor the view's bounds) // Note that you can't make use of [super drawRect:…] here. If the super class is NSView, it did nothing, anyway. return TRUE; }]; Note that you want to invalidate the image when the view changes size, in addition to when you change the buffer. Cheers, Ken _______________________________________________ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) 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: https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com