Color matching of NSBitmapImageRep

  • I've been trying to track down some unexpected color shifts in a
    supposedly color-sync'd application. I've discovered that
    NSBitmapImageRep is not rendering its colors at all in the way that I
    expect. It seems to be using a different color profile than the one I
    tell it to. Can anyone tell me how to get a bitmap to draw color-
    matched?

    As an example, let's say that I have an NSColor (or CGColorSpaceRef +
    components, but that's by the way). I want to set some pixels of an
    NSBitmapImageRep to that same color, so that when I draw them side-by-
    side they render as the same color on the screen.

    What I'm doing is this:

    - Convert the color to the calibrated RGB space by calling [foo
    colorUsingColorSpaceName:NSCalibratedRGBColorSpace].
    - Create an NSBitmapImageRep in the NSCalibratedRGBColorSpace, 32-bit
    RGBA:

        NSBitmapImageRep *foo = [[NSBitmapImageRep alloc]
    initWithBitmapDataPlanes:buffer
    pixelsWide:width pixelsHigh:height
    bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
    colorSpaceName:NSCalibratedRGBColorSpace
    bytesPerRow:4 * width bitsPerPixel:32];

    - Extract the color's components using -redComponent, etc.; or -
    getComponents:, or -getRed:green:blue:alpha:. (These all produce the
    same numbers anyway).
    - Set the color on a pixel (or all the pixels) by scaling them to
    [0..255] and stuffing them into 'buffer'.

    This produces a color which is almost, but not quite, the same as the
    color I started with. It's not a matter of converting to integer
    values incorrectly --- for one thing, the error is more than +/- 1
    per component; and for another, I can use NSBitmapImageRep's -
    setColor:atX:xy: method and get exactly the same results as if I'd
    done the conversion myself.

    The color shift has the look that mismatched profiles tend to; it's
    more apparent towards some corners of the gamut, and invisible in
    other areas.

    Interestingly, if I draw into the bitmap image rep by creating a
    context using -graphicsContextWithBitmapImageRep: and setting the
    color on that context and filling a rectangle, then the bitmap will
    draw with the desired color. But if I examine the bitmap I create in
    that way, its color components are not the same as the original color!

    For example, if I start with [NSCalibratedRGBColorSpace 0 0.905991 1
    1] (a sort of teal color), I can convert it to (0, 231, 255) and
    stuff that into the bitmap data, or I can use -setColor:atX:y:, which
    also results in the values (0, 231, 255) in the buffer.

    But if I draw the color into the bitmap via a graphics context, then
    the buffer shows the triple (23, 225, 254), and -colorAtX:y: returns
    the color [NSCalibratedRGBColorSpace 0.0901961 0.882353 0.996078 1].
    The bizarre thing is that it is this altered value which draws
    correctly.

    For other colors, the shift is much smaller. For example, the color
    triplet (255, 0, 114) maps to the triplet (255, 2, 115) when drawn.

    So, what I wonder is:
        - Why isn't the NSBitmapImageRep rendering its contents in the
    color space I ask it to?
        - What color space *is* it using?
  • On Aug 29, 2007, at 7:54 PM, Wim Lewis wrote:

    > I've been trying to track down some unexpected color shifts in a
    > supposedly color-sync'd application. I've discovered that
    > NSBitmapImageRep is not rendering its colors at all in the way that
    > I expect. It seems to be using a different color profile than the
    > one I tell it to. Can anyone tell me how to get a bitmap to draw
    > color-matched?

    Everything that goes through a Quartz context is color matched.

    > As an example, let's say that I have an NSColor (or CGColorSpaceRef
    > + components, but that's by the way). I want to set some pixels of
    > an NSBitmapImageRep to that same color, so that when I draw them
    > side-by-side they render as the same color on the screen.

      Where does your CGColorSpaceRef come from?  Where do the components
    come from?  If this is the color you are trying to match, these hardly
    seem like points of information to leave by the wayside :-)

    > - Convert the color to the calibrated RGB space by calling [foo
    > colorUsingColorSpaceName: NSCalibratedRGBColorSpace].

    If you know the source color space for your components, why don't you
    use that same color space to create your bitmap image?  That way
    you're taking the shortest route.  Here you're adding an what looks to
    be an unnecessary extra conversion that might shift your color slightly.

    > So, what I wonder is:
    > - Why isn't the NSBitmapImageRep rendering its contents in the
    > color space I ask it to?

    The color space you pass in to NSBitmapImageRep is the source color
    space, not the destination color space.  The NSBitmapImageRep is
    rendering it's contents in whatever destination space you draw that
    bitmap into.  The mapping from the NSCalibratedRGBColorSpace, and the
    mapping of whatever mystery color space your original color components
    were found in, to the destination space are not exactly the same.

    > - What color space *is* it using?

    What end of the rendering process are you referring to?  What its the
    source color space for the color samples?  In your example code it
    looks to be the NSCalibratedRGBColorSpace.  The destination color
    space, however, depends on how you've set up your system and what
    display profile you are using.
  • On Aug 29, 2007, at 5:54 PM, Wim Lewis wrote:

    >
    > - Convert the color to the calibrated RGB space by calling [foo
    > colorUsingColorSpaceName:NSCalibratedRGBColorSpace].
    > - Create an NSBitmapImageRep in the NSCalibratedRGBColorSpace, 32-
    > bit RGBA:
    >
    > NSBitmapImageRep *foo = [[NSBitmapImageRep alloc]
    > initWithBitmapDataPlanes:buffer
    > pixelsWide:width pixelsHigh:height
    > bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
    > colorSpaceName:NSCalibratedRGBColorSpace
    > bytesPerRow:4 * width bitsPerPixel:32];

    [snip detailed description, all of which seemed correct]

    Does your image actually have alpha transparency?  If yes, are you
    accounting for the fact that NSBitmapImageRep stores its samples pre-
    multiplied?  (Or have we stopped doing that?)

    Marcel
  • On Aug 30, 2007, at 7:20 AM, Marcel Weiher wrote:
    > On Aug 29, 2007, at 5:54 PM, Wim Lewis wrote:
    >> - Convert the color to the calibrated RGB space by calling [foo
    >> colorUsingColorSpaceName:NSCalibratedRGBColorSpace].
    >> - Create an NSBitmapImageRep in the NSCalibratedRGBColorSpace, 32-
    >> bit RGBA:
    >>
    >> NSBitmapImageRep *foo = [[NSBitmapImageRep alloc]
    >> initWithBitmapDataPlanes:buffer
    >> pixelsWide:width pixelsHigh:height
    >> bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
    >> colorSpaceName:NSCalibratedRGBColorSpace
    >> bytesPerRow:4 * width bitsPerPixel:32];
    >
    > [snip detailed description, all of which seemed correct]

    Stepping through AppKit in the debugger, I find that NSBitmapImageRep
    is always using a color space created with CGColorSpaceCreateDeviceXXX
    (), regardless of whether the colorSpaceName I pass in is NSDeviceXXX
    or NSCalibratedXXX. (Unless I pass in NSDeviceRGBColorSpace, in which
    case it uses CGColorSpaceCreateDisplayRGB() --- still wrong...) If
    anyone at Apple cares to check this, it's in _NSCreateImageRef2() and
    _NSColorSpaceNumFromName(), as well as -[NSNSBitmapGraphicsContext
    _initWithBitmapImageRep:].

    So I guess the not entirely unexpected answer to my question is
    "NSBitmapImageRep is just buggy".  :(

    > Does your image actually have alpha transparency?  If yes, are you
    > accounting for the fact that NSBitmapImageRep stores its samples
    > pre-multiplied?  (Or have we stopped doing that?)

    Alpha is always 1 in my test --- but even so, that wouldn't account
    for the decidedly nonlinear change in the color coordinates. :/
previous month august 2007 next month
MTWTFSS
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    
Go to today