On Apr 3, 2017, at 8:54 PM, Patrick J. Collins <[email protected]>
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 ([email protected])
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 [email protected]