Re: NSBitmapImageRep caching behavior in Snow Leopard
Re: NSBitmapImageRep caching behavior in Snow Leopard
- Subject: Re: NSBitmapImageRep caching behavior in Snow Leopard
- From: Ken Ferry <email@hidden>
- Date: Mon, 28 Sep 2009 11:33:23 -0700
On Mon, Sep 28, 2009 at 7:52 AM, John Horigan <email@hidden> wrote:
> I understand. Apple has changed the semantics of the NSBitmapImageRep class
> in a way that is optimal for the majority of users but effectively
> deprecates the way that I am using it (as a mutable pixel store).
> Periodically generating new NSBitmapImageReps or CGImages from my mutable
> pixel store that I allocate myself on the heap appears to be the way to go
> moving forward.
>
I thought this was explicitly covered in the release notes, but I guess not.
Oh well!
Your conclusion about what action to take is correct, but what you may not
realize is that you were getting lucky previously. We didn't change the
published semantics of NSImage in SnowLeopard[1]. Rather, much of the
implementation changed. This means that in places where people accidentally
depended on things that _aren't_ part of the API, there is sometimes
trouble.
If you think about it, there's no way calling -[NSBitmapImageRep
bitmapData], stashing the pointer off, and changing the data later could
ever have reliably changed the drawing of the image. What if the image had
to be uploaded to the graphics card? How would the system know that the
data had changed and needed to be reuploaded? I think what's likely to have
gotten you is changes in the color matching pipeline. Down in CoreGraphics,
when an image is drawn such that its color needs to be transformed to a
destination colorspace, the colormatched data is saved off so that doesn't
have to happen again every time. So the difference between 10.5 and 10.6
for you may be that the image requires color transformation now.
Problems like this are always sad, because you were basically getting lucky
on 10.5, but there was no way for you to know it.
-Ken
Cocoa Frameworks
[1]: Okay, there are a couple exceptions. See the relnote section
on drawAtPoint:fromRect:operation:fraction, for example.
>
> -- john
>
>
> On Sep 27, 2009, at 10:07 PM, Scott Anguish wrote:
>
> I strongly suggest reading the release notes on NSImage in the Application
>> Kit release notes for Snow Leopard.
>>
>> it has many, many, changes. These won't translate to the Cocoa Drawing
>> Guide for a bit yet. Sadly.
>>
>>
>> On Sep 25, 2009, at 7:22 PM, John Horigan wrote:
>>
>> I have a drawing program that creates an NSBitmapImageRep and an NSImage:
>>>
>>> mImage = [[NSImage alloc] initWithSize: size];
>>> mBitmap = [[NSBitmapImageRep alloc]
>>> initWithBitmapDataPlanes: NULL
>>> pixelsWide: (int)size.width
>>> pixelsHigh: (int)size.height
>>> bitsPerSample: 8 samplesPerPixel: 4
>>> hasAlpha: YES isPlanar: NO
>>> colorSpaceName: NSCalibratedRGBColorSpace
>>> bytesPerRow: 0 bitsPerPixel: 0];
>>> [mImage addRepresentation: mBitmap];
>>>
>>> later I get the pointer to the bitmap data using the bitmapData method
>>> and hand it over drawing code (AGG) running in another thread. Periodically
>>> the render thread will send a message to the main thread telling it to draw
>>> the current image state to my view:
>>>
>>> [mView performSelectorOnMainThread: @selector(redisplayImage:)
>>> withObject: [NSValue valueWithRect:
>>> NSMakeRect(cropX(), cropY(), cropWidth(), cropHeight())]
>>> waitUntilDone: YES];
>>>
>>>
>>> The redisplayImage method in my view class calls the display method
>>>
>>> - (void)redisplayImage:(NSValue*)rectObj
>>> {
>>> mRenderedRect = [rectObj rectValue];
>>> [self display];
>>> }
>>>
>>> Note that the render thread is waiting for redisplayImage to complete and
>>> the redisplayImage method is calling the display method, not the
>>> setNeedsDisplay method. The views's drawRect method looks like so:
>>>
>>> - (void)drawRect:(NSRect)rect
>>> {
>>> NSSize fSize = [self frame].size;
>>> NSSize rSize = [mBitmap size];
>>>
>>> float scale;
>>> if (rSize.width <= fSize.width && rSize.height <= fSize.height)
>>> {
>>> // rendered area fits within frame, center it
>>> scale = 1.0f;
>>> }
>>> else {
>>> float wScale = fSize.width / rSize.width;
>>> float hScale = fSize.height / rSize.height;
>>> scale = (hScale < wScale) ? hScale : wScale;
>>> }
>>>
>>> NSRect dRect;
>>>
>>> // center scaled image rectangle
>>> dRect.size.width = rSize.width * scale;
>>> dRect.size.height = rSize.height * scale;
>>> dRect.origin.x = floorf((fSize.width - dRect.size.width) / 2.0f);
>>> dRect.origin.y = floorf((fSize.height - dRect.size.height) /
>>> 2.0f);
>>>
>>> [[NSColor colorWithDeviceRed: backgroundColor.r
>>> green: backgroundColor.g
>>> blue: backgroundColor.b
>>> alpha: backgroundColor.a ] set];
>>> [NSBezierPath fillRect: rect];
>>>
>>> [mImage drawInRect: dRect fromRect: NSZeroRect
>>> operation: NSCompositeSourceAtop
>>> fraction: 1.0];
>>> }
>>>
>>> Prior to Snow Leopard this all worked fine. The images drawn onto the
>>> bitmap in the rendering thread would be drawn into the view. If the view was
>>> resized smaller then the NSImage would be shrunk and centered. If the view
>>> was resized larger the NSImage would be centered.
>>>
>>> But with Snow Leopard [mImage drawInRect ...] draws from the bitmap only
>>> the first time it is called. All subsequent calls to the drawInRect method
>>> draw whatever was in the bitmap on the first call, not the current contents
>>> of the bitmap. It's as if the NSBitmapImageRep was being cached on the first
>>> draw and the cached copy was used from then on.
>>>
>>> However, if I resize the window so that the drawing NSRect is smaller
>>> than the NSBitmapImageRep (i.e., the bitmap has to be scaled) then the
>>> current bitmap is drawn. And then if I expand the window to be larger then
>>> it goes back drawing the cached bitmap. Shrinking the window really small
>>> seems to reset something in the NSBitmapImageRep, then it draws from the
>>> current bitmap whether there is scaling or not.
>>>
>>> It seems that under Snow Leopard the NSBitmapImageRep is being cached
>>> somewhere (video memory?) and as long as there is no scaling the cached
>>> version of the NSBitmapImageRep is being used by drawInRect instead of of
>>> the bitmap in system memory. For some reason making the view really small
>>> causes drawInRect to use the data in system memory.
>>>
>>> Here are some things that I tried:
>>> 1) Calling the NSImage recache method: [mImage recache];
>>> 2) Getting rid of the NSImage and using the NSImageRep drawInRect method
>>> to draw in the view.
>>> 3) Call the NSBitmapImageRep bitmapData method before writing to the
>>> bitmap data plane in the rendering thread
>>>
>>> #1 and #2 has no effect. NSImage-level caching does not appear to be the
>>> problem. There appears to be caching by NSBitmapImageRep. #3 was an attempt
>>> to get Snow Leopard to invalidate the NSBitmapImageRep cache. It didn't
>>> work.
>>>
>>> So this is what worked:
>>> 4) Keep the bitmap data plane as a separate data structure, owned by the
>>> view. The redisplayImage method creates a new NSBitmapImageRep and NSImage
>>> every time it is called, using the bitmap data structure that is owned by
>>> the view.
>>>
>>> While creating new NSImages and NSBitmapImageReps all the time works I
>>> hate doing all this object churning. Is there some way to use a single
>>> NSImage and NSBitmapImageRep throughout the life of the view?
>>>
>>> -- john
>>>
>>>
>>> _______________________________________________
>>>
>>> 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
>>>
>>
>>
> _______________________________________________
>
> 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
>
_______________________________________________
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