Wang Ke
Wang Ke

Reputation: 339

Why does golang RGBA.RGBA() method use | and <<?

In the golang color package, there is a method to get r,g,b,a values from an RGBA object:

func (c RGBA) RGBA() (r, g, b, a uint32) {
    r = uint32(c.R)
    r |= r << 8
    g = uint32(c.G)
    g |= g << 8
    b = uint32(c.B)
    b |= b << 8
    a = uint32(c.A)
    a |= a << 8
    return
}

If I were to implement this simple function, I would just write this

func (c RGBA) RGBA() (r, g, b, a uint32) {
    r = uint32(c.R)
    g = uint32(c.G)
    b = uint32(c.B)
    a = uint32(c.A)
    return
}

What's the reason r |= r << 8 is used?

Upvotes: 17

Views: 6291

Answers (4)

fl0cke
fl0cke

Reputation: 2884

From the the excellent "The Go image package" blogpost:

[...] the channels have a 16-bit effective range: 100% red is represented by RGBA returning an r of 65535, not 255, so that converting from CMYK or YCbCr is not as lossy. Third, the type returned is uint32, even though the maximum value is 65535, to guarantee that multiplying two values together won't overflow.

and

Note that the R field of an RGBA is an 8-bit alpha-premultiplied color in the range [0, 255]. RGBA satisfies the Color interface by multiplying that value by 0x101 to generate a 16-bit alpha-premultiplied color in the range [0, 65535]

So if we look at the bit representation of a color with the value c.R = 10101010 then this operation

r = uint32(c.R)
r |= r << 8

effectively copies the first byte to the second byte.

   00000000000000000000000010101010 (r)
 | 00000000000000001010101000000000 (r << 8)
--------------------------------------
   00000000000000001010101010101010 (r |= r << 8)

This is equivalent to a multiplication with the factor 0x101 and distributes all 256 possible values evenly across the range [0, 65535].

Upvotes: 14

user862787
user862787

Reputation:

Converting a value in the range 0 to 255 (an 8-bit RGB component) to a value in the range 0 to 65535 (a 16-bit RGB component) would be done by multiplying the 8-bit value by 65535/255; 65535/255 is exactly 257, which is hex 101, so multiplying a one-byte by 65535/255 can be done by shifting that byte value left 8 bits and ORing it with the original value.

(There's nothing Go-specific about this; similar tricks are done elsewhere, in other languages, when converting 8-bit RGB/RGBA components to 16-bit RGB/RGBA components.)

Upvotes: 4

James Henstridge
James Henstridge

Reputation: 43899

The color.RGBA type implements the RGBA method to satisfy the color.Color interface:

type Color interface {
    // RGBA returns the alpha-premultiplied red, green, blue and alpha values
    // for the color. Each value ranges within [0, 0xffff], but is represented
    // by a uint32 so that multiplying by a blend factor up to 0xffff will not
    // overflow.
    //
    // An alpha-premultiplied color component c has been scaled by alpha (a),
    // so has valid values 0 <= c <= a.
    RGBA() (r, g, b, a uint32)
}

Now the RGBA type represents the colour channels with the uint8 type, giving a range of [0, 0xff]. Simply converting these values to uint32 would not extend the range up to [0, 0xffff].

An appropriate conversion would be something like:

r = uint32((float64(c.R) / 0xff) * 0xffff)

However, they want to avoid the floating point arithmetic. Luckily 0xffff / 0xff is 0x0101, so we can simplify the expression (ignoring the type conversions for now):

r = c.R * 0x0101
  = c.R * 0x0100 + c.R
  = (c.R << 8) + c.R    # multiply by power of 2 is equivalent to shift
  = (c.R << 8) | c.R    # equivalent, since bottom 8 bits of first operand are 0

And that's essentially what the code in the standard library is doing.

Upvotes: 8

peterSO
peterSO

Reputation: 166569

To convert from 8- to 16-bits per RGB component, copy the byte into the high byte of the 16-bit value. e.g., 0x03 becomes 0x0303, 0xFE becomes 0xFEFE, so that the 8-bit values 0 through 255 (0xFF) produce 16-bit values 0 to 65,535 (0xFFFF) with an even distribution of values.

Upvotes: 2

Related Questions