[squeak-dev] Color from pixel value bug

Juan Vuletich juan at jvuletich.org
Mon Oct 3 13:10:31 UTC 2011


Stéphane Rollandin wrote:
> Hello,
>
> Working in Squeak 4.1, I stumbled upon a PNG image saving bug, which I 
> traced down to the fact that
>
> ((Color black pixelWordForDepth: 32) asColorOfDepth: 32)
>  = Color black
>
> is false.
>
> I found something looking like a fix, which is attached.
>
> Then I looked at Squeak 4.3 (update 11636) to see if someone else 
> already fixed that bug, and indeed Nicolas Cellier did, in December 
> 2010, by modifying Color>>pixelValueForDepth:
>
> But we still have
>
> ((Color black pixelWordForDepth: 32) asColorOfDepth: 32)
>  = Color black
>
> false, because black is being replaced with, as the comment says, the 
> "closest non-transparent black"
>
> At this point I guess I missed something, since I have no idea of what 
> this business of "transparent black" is about.
>
> Nicolas, could you please tell if my fix is correct ?
>
> Best,
>
>
> Stef

Hi Stef,

BitBlt supports Color transparent in depths < 32bpp, where no alpha 
channel is available. To do this, it considers any pixel with rgb = 0 as 
transparent and not black. So, the convention is to use the darkest 
possible blue instead of black.
See that the following draws nothing.
| f |
f := Form extent: 32 at 32 depth: 16.
f bits atAllPut: 16r00000000.
Display getCanvas image: f at: 10 at 10.    "This is the method for 
displaying a possibly translucent form in Cuis. Not sure about Squeak."

This "zero means transparent" trick is not needed in 32bpp, because real 
transparent is available (alpha=0), and especially because BitBlt 
considers transparent only pixels with _all_ the bits at zero. So, in 
32bpp, pixels with r=g=b=0 but alpha>0 are not considered transparent. 
There have been bugs historically with respect to this detail in the image.
See that this draws a black box
| f |
f := Form extent: 32 at 32 depth: 32.
f bits atAllPut: 16rFF000000.
Display getCanvas image: f at: 10 at 10    "This is the method for 
displaying a possibly translucent form in Cuis. Not sure about Squeak."
This shows that in the 32bpp case it is not needed to replace black by 
darkest blue, plain black can be used.

I think your fix is not correct, as Nicolas' code isn't either. It is 
true that both methods must match, but this special fake black is not 
needed for 32bpp.
After playing a bit with this today, I updated the Cuis code to reflect 
all this. Find it below. Thanks for bringing this up!

Cheers,
Juan Vuletich
--------------------------------------------
colorFromPixelValue: p depth: d
    "Convert a pixel value for the given display depth into a color."
    "Details: For depths of 8 or less, the pixel value is simply looked 
up in a table. For greater depths, the color components are extracted 
and converted into a color."
    "Warning: In BitBlt, a pixel with pixelValue = 0 is transparent.
    Squeak usually assumes that r=g=b=0 => transparent. But this is 
false if we have alpha (opacity).
    A color with r=g=b=0 and opacity = 255 is BLACK, not TRANSPARENT.
    Squeak also answers darkest possible blue when asked for black. 
Again, this is not needed in 32 bits (with alpha).
    The real rule is that pixelValue=0 means transparent.
    And that darkest blue must be used instead of black, but only for 
depths >8 and < 32 (no indexed colors, no alpha)
    This method is updated to reflect that."

    | r g b alpha |
    d = 8 ifTrue: [^ IndexedColors at: (p bitAnd: 16rFF) + 1].
    d = 4 ifTrue: [^ IndexedColors at: (p bitAnd: 16r0F) + 1].
    d = 2 ifTrue: [^ IndexedColors at: (p bitAnd: 16r03) + 1].
    d = 1 ifTrue: [^ IndexedColors at: (p bitAnd: 16r01) + 1].

    d = 32 ifTrue: [
        "eight bits per component; 8 bits of alpha"
        alpha := p bitShift: -24.
        alpha = 0 ifTrue: [ ^Color transparent ].
        r := (p bitShift: -16) bitAnd: 16rFF.
        g := (p bitShift: -8) bitAnd: 16rFF.
        b := p bitAnd: 16rFF.
        ^alpha < 255
            ifTrue: [ (Color r: r g: g b: b range: 255) alpha: alpha 
asFloat / 255.0 ]
            ifFalse: [ Color r: r g: g b: b range: 255 ]].

    "For the rest of the depths, pixelValue = 0 means transparent, and 
darkest blue is considered to be black."
    p = 0 ifTrue: [ ^Color transparent ].

    (d = 16) | (d = 15) ifTrue: [
        "five bits per component"
        r := (p bitShift: -10) bitAnd: 16r1F.
        g := (p bitShift: -5) bitAnd: 16r1F.
        b := p bitAnd: 16r1F.
        (r = 0 and: [ g = 0 and: [ b = 1]]) ifTrue: [
            ^Color black ].
        ^ Color r: r g: g b: b range: 31].

    d = 12 ifTrue: [
        "four bits per component"
        r := (p bitShift: -8) bitAnd: 16rF.
        g := (p bitShift: -4) bitAnd: 16rF.
        b := p bitAnd: 16rF.
        (r = 0 and: [ g = 0 and: [ b = 1]]) ifTrue: [
            ^Color black ].
        ^ Color r: r g: g b: b range: 15].

    d = 9 ifTrue: [
        "three bits per component"
        r := (p bitShift: -6) bitAnd: 16r7.
        g := (p bitShift: -3) bitAnd: 16r7.
        b := p bitAnd: 16r7.
        (r = 0 and: [ g = 0 and: [ b = 1]]) ifTrue: [
            ^Color black ].
        ^ Color r: r g: g b: b range: 7].

    self error: 'unknown pixel depth: ', d printString
---------------------
pixelValueForDepth: d
    "Returns an integer representing the bits that appear in a single 
pixel of this color in a Form of the given depth. The depth must be one 
of 1, 2, 4, 8, 16, or 32. Contrast with pixelWordForDepth: and 
bitPatternForDepth:, which return either a 32-bit word packed with the 
given pixel value or a multiple-word Bitmap containing a pattern. The 
inverse is the class message colorFromPixelValue:depth:"
    "Details: For depths of 8 or less, the result is a colorMap index. 
For depths of 16 and 32, it is a direct color value with 5 or 8 bits per 
color component."
    "Transparency: The pixel value zero is reserved for transparent. For 
depths greater than 8 and less than 32 (no Indexed colors, no real 
alpha), black maps to the darkest possible blue.
    Note that
        Color transparent class = TranslucentColor
    this special case is handled in TranslucentColor >> #pixelValueForDepth:
    "

    | bitBltFakeBlack val |
    d = 8 ifTrue: [^ self closestPixelValue8].  "common case"
    d < 8 ifTrue: [
        d = 4 ifTrue: [^ self closestPixelValue4].
        d = 2 ifTrue: [^ self closestPixelValue2].
        d = 1 ifTrue: [^ self closestPixelValue1]].

    d = 32 ifTrue: [
        "eight bits per component; top 8 bits set to all ones (opaque 
alpha)"
        val := LargePositiveInteger new: 4.
        val at: 3 put: ((rgb bitShift: -22) bitAnd: 16rFF).
        val at: 2 put: ((rgb bitShift: -12) bitAnd: 16rFF).
        val at: 1 put: ((rgb bitShift: -2) bitAnd: 16rFF).
        val at: 4 put: 16rFF.  "opaque alpha"
        ^ val normalize].

    "For the rest of the depths, pixelValue = 0 means transparent, and 
darkest blue is considered to be black."
    bitBltFakeBlack := 1.  "closest black that is not transparent in RGB 
- Not for depths <=8 (Indexed) or = 32 (RGBA)"

    d = 16 ifTrue: [
        "five bits per component; top bits ignored"
        val := (((rgb bitShift: -15) bitAnd: 16r7C00) bitOr:
             ((rgb bitShift: -10) bitAnd: 16r03E0)) bitOr:
             ((rgb bitShift: -5) bitAnd: 16r001F).
        ^ val = 0 ifTrue: [bitBltFakeBlack] ifFalse: [val]].

    d = 12 ifTrue: [  "for indexing a color map with 4 bits per color 
component"
        val := (((rgb bitShift: -18) bitAnd: 16r0F00) bitOr:
             ((rgb bitShift: -12) bitAnd: 16r00F0)) bitOr:
             ((rgb bitShift: -6) bitAnd: 16r000F).
        ^ val = 0 ifTrue: [bitBltFakeBlack] ifFalse: [val]].

    d = 9 ifTrue: [  "for indexing a color map with 3 bits per color 
component"
        val := (((rgb bitShift: -21) bitAnd: 16r01C0) bitOr:
             ((rgb bitShift: -14) bitAnd: 16r0038)) bitOr:
             ((rgb bitShift: -7) bitAnd: 16r0007).
        ^ val = 0 ifTrue: [bitBltFakeBlack] ifFalse: [val]].

    self error: 'unknown pixel depth: ', d printString




More information about the Squeak-dev mailing list