Home > OS >  Pixel format bit endianness
Pixel format bit endianness

Time:03-02

I am currently implementing a program to read and decode image from a BMP file. But I am really confused about the pixel format and especially about bit endianness.

My current understanding is when referring to a pixel format, e.g. RGBA32, we are saying it in our human way, which is using big endian, thus the first byte is R, second G, third B, fourth A:

RGBA32
11111111 22222222 33333333 44444444
R        G        B        A

And when appending big/little endian to this format, such as RGBA32BE/RGBA32LE, this will change the byte order accordingly, as:

RGBA32BE (unchanged)
11111111 22222222 33333333 44444444
R        G        B        A

RGBA32LE (reversed)
11111111 22222222 33333333 44444444
A        B        G        R

This all looks naturally to me, as long as we are assuming the individual value of each component is exactly the value of byte we read.

However things start to confuse me when component size is less than 1 byte or 8 bits. Say RGB555BE, I suppose below is how it is represented as byte array:

RGB555BE
1'22222'33 333'44444
A   R      G     B
0'00001'00 000'00000
  1. Do we read the R component as 10000=16 or 00001=1 ?

  2. I'm even more confused about if the way we read the component (bit endianness) is related to byte endianness?

  3. Which way is RGB555LE format represented in byte array?

    RGB555LE_v1
    333'44444 1'22222'33
    G"    B   A   R   G'
    000'00000 0'00001'00
    
    or RGB555LE_v2 ?
    44444'333 33'22222'1
      B     G      R   A
    00000'000 00'10000'0
    

CodePudding user response:

We may use FFmpeg for testing the different pixel formats.

The conclusion are:

  • There is no bit endianness, only bytes endianness.
  • The endianness is relevant when the base elements is 2 bytes (uint16).
    Little endian: bytes order of uint16 element is: [LSB, MSB].
    Bit endian: bytes order of uint16 element is: [MSB, LSB].

Executing FFmpeg CLI commands for testing:

Start with rgb24 for example:

ffmpeg -y -lavfi color=red:size=8x8:rate=1:duration=1 -pix_fmt rgb24 -f rawvideo red.raw
ffmpeg -y -lavfi color=0x00FF00:size=8x8:rate=1:duration=1 -pix_fmt rgb24 -f rawvideo green.raw
ffmpeg -y -lavfi color=blue:size=8x8:rate=1:duration=1 -pix_fmt rgb24 -f rawvideo blue.raw

Use some hex viewer like HxD for inspecting the raw file content.

red.raw FF 00 00 FF 00 00...
blue.raw 00 FF 00 00 FF 00...
green.raw 00 00 FF 00 00 FF...


rgba pixel format:

ffmpeg -y -lavfi color=red:size=8x8:rate=1:duration=1 -pix_fmt rgba -f rawvideo red.raw
ffmpeg -y -lavfi color=0x00FF00:size=8x8:rate=1:duration=1 -pix_fmt rgba -f rawvideo green.raw
ffmpeg -y -lavfi color=blue:size=8x8:rate=1:duration=1 -pix_fmt rgba -f rawvideo blue.raw

r: FF 00 00 FF
g: 00 FF 00 FF
b: 00 00 FF FF

The FFmpeg convention the order of the bytes.
We can't say if the naming applies little or big endian.
The endianness is relevant when using uint16 components (not uint8 components).


rgba pixel format:

ffmpeg -y -lavfi color=red:size=8x8:rate=1:duration=1 -pix_fmt rgba -f rawvideo red.raw
ffmpeg -y -lavfi color=0x00FF00:size=8x8:rate=1:duration=1 -pix_fmt rgba -f rawvideo green.raw
ffmpeg -y -lavfi color=blue:size=8x8:rate=1:duration=1 -pix_fmt rgba -f rawvideo blue.raw

r: FF 00 00 FF
g: 00 FF 00 FF
b: 00 00 FF FF

A then R then G then B then A...

We may refer rgba as RGBA32BE, but it's too confusing...


rgb555le pixel format:

ffmpeg -y -lavfi color=red:size=8x8:rate=1:duration=1 -pix_fmt rgb555le -f rawvideo red.raw
ffmpeg -y -lavfi color=0x00FF00:size=8x8:rate=1:duration=1 -pix_fmt rgb555le -f rawvideo green.raw
ffmpeg -y -lavfi color=blue:size=8x8:rate=1:duration=1 -pix_fmt rgb555le -f rawvideo blue.raw

r: 00 7C 00 7C
g: E0 03 E0 03
b: 1F 00 1F 00

We may also check the lower bit (for bits ordering):
Set the color to 8 so after shifting the value be 1.

ffmpeg -y -lavfi color=0x000008:size=8x8:rate=1:duration=1 -pix_fmt rgb555le -f rawvideo red.raw
ffmpeg -y -lavfi color=0x000800:size=8x8:rate=1:duration=1 -pix_fmt rgb555le -f rawvideo green.raw
ffmpeg -y -lavfi color=0x080000:size=8x8:rate=1:duration=1 -pix_fmt rgb555le -f rawvideo blue.raw

r: 01 00 01 00 2 bytes in bits: 00000001 00000000
g: 20 00 20 00 2 bytes in bits: 00100000 00000000
b: 00 04 00 04 2 bytes in bits: 00000000 00000100


rgb555be pixel format:

ffmpeg -y -lavfi color=red:size=8x8:rate=1:duration=1 -pix_fmt rgb555be -f rawvideo red.raw
ffmpeg -y -lavfi color=0x00FF00:size=8x8:rate=1:duration=1 -pix_fmt rgb555be -f rawvideo green.raw
ffmpeg -y -lavfi color=blue:size=8x8:rate=1:duration=1 -pix_fmt rgb555be -f rawvideo blue.raw

r: 7C 00 7C 00
g: 03 E0 03 E0
b: 00 1F 00 1F

Checking the lower bit (for bits ordering):

ffmpeg -y -lavfi color=0x000008:size=8x8:rate=1:duration=1 -pix_fmt rgb555be -f rawvideo red.raw
ffmpeg -y -lavfi color=0x000800:size=8x8:rate=1:duration=1 -pix_fmt rgb555be -f rawvideo green.raw
ffmpeg -y -lavfi color=0x080000:size=8x8:rate=1:duration=1 -pix_fmt rgb555be -f rawvideo blue.raw

r: 00 01 00 01
g: 00 20 00 20
b: 04 00 04 00

In big endian format, only the bytes are swapped.
(For every uint16 element (two bytes), order of the first and second byte is swapped).


Answers to questions:

  1. Do we read the R component as 10000=16 or 00001=1 ?

Answer:
For rgb555le: 00000001 00000000
For rgb555be: 00000000 00000001


  1. I'm even more confused about if the way we read the component (bit endianness) is related to byte endianness?

Answer:
In context of pixel format, there is no "bit endianness" there is only "byte endianness".

Bits endianness is more relevant to serial communication, from software perspective, we don't see the order of bits in each byte.


  1. Which way is RGB555LE format represented in byte array?

Answer:

RGB555LE (as RGB555LE_v1):

byte0 byte1
gggbbbbb 0rrrrrgg

(most left bit is the upper bit in each byte).

We better look at the data as one uint16 element:

b: 0000000000011111 (pure blue)
g: 0000001111100000 (pure green)
r: 0111110000000000 (pure red)

We may look at it as uint16 as: 0rrrrrgggggbbbbb

CodePudding user response:

I don't think endianness matters to rgb555, and that's why your link bunches rgb555, rgb555le, and rgb555be together. For that matter, your example with rgba also isn't endian sensitive as its components are all <= 8 bits.

As for how rgb555 is represented in 2 bytes (well, 15 bits), you can search FFmpeg repo for rgb555 and see how encoders/decoders handle such pixel format. Here is one I found rpzaenc.c Line 138:

static uint16_t rgb24_to_rgb555(uint8_t *rgb24)
{
    uint16_t rgb555 = 0;
    uint32_t r, g, b;

    r = rgb24[0] >> 3;
    g = rgb24[1] >> 3;
    b = rgb24[2] >> 3;

    rgb555 |= (r << 10);
    rgb555 |= (g << 5);
    rgb555 |= (b << 0);

    return rgb555;
}

the encoder takes rgb555, a 16-bit unsigned int, an uses put_bits() utility function to push these 15 bits to its bitstream as shown on Line 683

put_bits(&s->pb, 16, rgb24_to_rgb555(avg_color));

Here is the link to put_bits.h

  • Related