Konwersje pikseli RGB

Autor: Wojciech Muła
Dodany:31.08.2001
Aktualizacja:2.06.2008

Treść

Wprowadzenie

Obecnie większość bibliotek graficznych wewnętrznie używa obrazów 32bpp (BPP — Bits Per Pixel), dopiero podczas wyświetlania są one konwertowane do odpowiedniego formatu, zależnego od sprzętu (trybu karty graficznej).

Poniżej przedstawiono formaty rozważanych pikseli.

format piksela 16bpp format piksela 17bpp format piksela 24bpp

Oto struktury opisujące piksele w języku C:

typedef unsigned char        byte; //  8 bitów
typedef unsigned short int   word; // 16 bitów
typedef unsigned int        dword; // 16 bitów

typedef struct {
          word b: 5;
          word g: 5;
          word r: 5;
          word _: 1; // nieużywany
        } pixel15bpp;

typedef struct {
          word b: 5;
          word g: 6;
          word r: 5;
        } pixel16bpp;

typedef struct {
          byte b,g,r;
        } pixel24bpp;

typedef struct {
          byte b,g,r;
          byte _;   // nieużywany
        } pixel24bpp;

Nadmienię tylko, że piksele 24bpp nie są stosowane — po prostu nie sprawdziły się. Oszczędność jednego bajtu nie rekompensowała skomplikowania kodu i opóźnień związanych z brakiem wyrównania.

Tablicowanie

Jest to chyba jedna z najlepszych metod do implementacji na x86. Polega na prekalkulowaniu wartości pikseli. Bynajmniej nie tworzone są tablice dla wszystkich pikseli — proszę wyobrazić sobie rozmiar takiej tablicy dla 24bpp — całe 16MB i te problemy z cache.

Piksel źródłowy jest dzielony na bajty, tak więc dla pikseli 24/32bpp potrzebne będą trzy, a dla pikseli 15/16bpp dwie tablice 256-elementowe.

Ograniczę się tylko do konwersji 16bpp/32bpp, inne konwersje Czytelnik z łatwością sam napisze.

dword pix16to32bpp_lookup[2][256]; // rozmiar 256*4*2 = 2kB
 word pix32to16bpp_lookup[3][256]; // rozmiar 256*2*3 = 1.5kB

void fill_pix16to32bpp()
{
 for (int i=0; i<256; i++)
        {
         //                   |gggbbbbb| -- młodszy bajt piksela 16bpp
         // |00000000|000ggg00|bbbbb000| -- rozkład bitów w pikselu 32bpp

         dword blu  = i & 0x1f;
         dword grn1 = i & 0x1e;
         pix16to32bpp_lookup[0][i] = (blu << 3) | (grn1 << 5);

         //                   |rrrrrggg| -- starszy bajt piksela 16bpp
         // |rrrrr000|ggg00000|00000000|

         dword red  = i & 0xf8;
         dword grn2 = i & 0x07;
         pix16to32bpp_lookup[1][i] = (red << 16) | (grn2 << 13);
        }
}

void fill_pix32to16bpp()
{
 for (int i=0; i<256; i++)
        {
         word blu  = i >> 3;
         word grn  = i >> 2;
         word red  = i >> 3;

         pix32to16bpp_lookup[0][i] = blu;
         pix32to16bpp_lookup[1][i] = grn << 5;
         pix32to16bpp_lookup[2][i] = red << 11;
        }
}

pixel32bpp pix16to32bpp(pixel16bpp SRC)
{
  word src = *(word*)&SRC;
 dword res = pix16to32bpp_lookup[0][src & 0xff] |
             pix16to32bpp_lookup[1][src >> 8];

 return *(pixel32bpp*)&res;
}

pixel16bpp pix32to316pp(pixel32bpp SRC)
{
 dword src = *(dword)&SRC;
  word res = pix32to16bpp_lookup[0][(src >> 0)  & 0xff] |
             pix32to16bpp_lookup[1][(src >> 8)  & 0xff] |
             pix32to16bpp_lookup[2][(src >> 16) & 0xff];

 return *(pixel16bpp*)&res;
}

Zmniejszanie głębi kolorów

Podczas takich konwersji część bitów piksela źródłowego jest tracona, są to najmłodsze bity każdej ze składowych; oto przykład w C.

pixel16bpp convert32_to_16bpp(pixel32bpp src)
{
 pixel16bpp dst;

 dst.r = src.r >> 3; // 8-5
 dst.g = src.g >> 2; // 8-6
 dst.b = src.b >> 3; // 8-5

 return p;
}

Konwersja 24/32bpp na 15/16bpp — MMX

Pokażę funkcję dla konwersji 32bpp na 16bpp, konwersja na 15bpp będzie wymagać nieznacznych zmian w kodzie. Użycie rozkazu pmaddwd wyeliminowało co najmniej dwa przesłania, jedno przesunięcie bitowe i operację logiczną or.

; wto 29 paź 2002 16:43:22 CET
segment .data

mask_rg dd 0x001f001f, 0x001f001f
mask_b  dd 0x00001f00, 0x00001f00
shift   dd 0x00800001, 0x00800001 ; 1, 1<<11, 1, 1<<11

segment .text

; esi -> wskaźnik na obraz 32bpp
conv_32bpp_to_16bpp:

        movq    mm0, [esi]      ; mm0 = |0000 0000|rrrr rrrr|gggg gggg|bbbb bbbb|...
        psrld   mm0, 3          ; mm0 = |0000 0000|000r rrrr|rrrg gggg|gggb bbbb|...
        movq    mm1, mm0

        pand    mm0, [mask_rb]  ; mm0 = |0000 0000|000r rrrr|0000 0000|000b bbbb|...
        pand    mm1, [mask_g]   ; mm1 = |0000 0000|0000 0000|000g gggg|g000 0000|...

        pmaddwd mm0, [shift]    ; mm0 =                     |0000 0000|000b bbbb|
                                ;                           |rrrr r000|0000 0000| +
        psrld   mm1, 2          ; mm1 = |0000 0000|0000 0000|0000 0ggg|ggg0 0000|...

        por     mm0, mm1        ; mm0 = |0000 0000|0000 0000|rrrr rggg|gggb bbbb|...

                                ; mm0 = |pix1| 0 |pix0| 0 |
        ret

Konwersja 32bpp na 15bpp — x86

; wejście: eax - piksel 32bpp
; wyjście:  ax - piksel 15bpp

                  ; eax = |xxxx xxxx|rrrr rrrr|gggg gggg|bbbb bbbb|
and eax, 00f8f8f8 ; eax = |0000 0000|rrrr r000|gggg g000|bbbb b000|
shr eax, 3        ; eax = |0000 0000|000r rrrr|000g gggg|000b bbbb|
shl al , 3        ; eax = |0000 0000|000r rrrr|000g gggg|bbbb b000|
shl ax , 3        ; eax = |0000 0000|000r rrrr|gggg gbbb|bb00 0000|
shr eax, 6        ; eax = |0000 0000|0000 0000|0rrr rrgg|gggb bbbb|

Konwersja 32bpp na 16bpp — x86

; wejście: eax - piksel 32bpp
; wyjście:  ax - piksel 16bpp

                  ; eax = |xxxx xxxx|rrrr rrrr|gggg gggg|bbbb bbbb|
and eax, 00f8fcf8 ; eax = |xxxx xxxx|rrrr r000|gggg gg00|bbbb b000|
shr  ah, 2        ; eax = |xxxx xxxx|rrrr r000|00gg gggg|bbbb b000|
shr eax, 3        ; eax = |xxxx xxxx|000r rrrr|0000 0ggg|gggb bbbb|
shl  ax, 5        ; eax = |xxxx xxxx|000r rrrr|gggg ggbb|bbb0 0000|
shr eax, 5        ; eax = |xxxx xxxx|0000 0000|grrr rggg|gggb bbbb|

Konwersja 16bpp na 15bpp — x86

; wejście: ax - piksel 16bpp
; wyjście: ax - piksel 15bpp
; niszczy: bl

                  ; ax = |rrrr rggg|gggb bbbb|
mov bl, al
and bl, 00011111b
and al, 11011111b ; ax = |rrrr rggg|gg0b bbbb|
add al, bl        ; ax = |rrrr rggg|ggbb bbb0|
shr ax, 1

Zwiększanie głębi kolorów

Podczas konwersji brakuje części najmłodszych bitów, są one wypełniane zerami; oto przykład w C.

pixel32bpp convert(pixel15bpp src)
{
 pixel32bpp dst = {src.r << 3, src.g << 3, src.b << 3, 0};

 return dst;
}

Konwersja 15bpp na 32bpp — x86

; wejście: ax  - piksel 15bpp
; wyjście: eax - piksel 32bpp

            ; eax = |xxxx xxxx|xxxx xxxx|0rrr rrgg|gggb bbbb|
shr eax, 6  ; eax = |xxxx xxxx|xx0r rrrr|gggg gbbb|bb00 0000|
shr  ax, 3  ; eax = |xxxx xxxx|xx0r rrrr|000g gggg|bbbb b000|
shr  al, 3  ; eax = |xxxx xxxx|xx0r rrrr|000g gggg|000b bbbb|
shl eax, 3  ; eax = |xxxx xxxx|rrrr r000|gg0g g000|bbbb b000|

Konwersja 15bpp na 32bpp — MMX

segment .data

mask_rb dw 0b0111110000011111, 0x7c1f, 0x7c1f, 0x7c1f
mask_g  dw 0b0000001111100000, 0x03e0, 0x03e0, 0x03e0
shifts  dw 0b0000001000001000, 0x0208, 0x0208, 0x0208 ; 1<<9 | 1<<3

mask_rb32bpp dd 0x00f800f8, 0x00f800f8

segment .text

; wejście:
;       esi -> wskaźnik na 2 piksele 15bpp
; wyjście:
;       mm0 = 2 piksele 32bpp
; niszczy:
;       mm1, mm2, mm3, mm4

conv_15bpp_to_32bpp:
        pxor      mm1, mm1
        movq      mm0, [esi]       ; mm0 = | 0  | 0  |pix1|pix0|
        punpcklwd mm0, mm1         ; mm0 = | 0  |pix1| 0  |pix0|
        movq      mm1, mm0

        pand      mm0, [mask_rb]   ; mm0 = |00000000 00000000|0rrrrr00 000bbbbb|..
        pand      mm1, [mask_g]    ; mm1 = |00000000 00000000|000000gg ggg00000|..
        pmaddwd   mm0, [shift]     ; mm0 = |00000000 000000rr|rrr00000 bbbbb000| +
                                   ;       |00000000 rrrrr000|00bbbbb0 00000000| =
                                   ;       |00000000 rrrrrxxx|xxxxxxx0 bbbbb000|..

        pslld     mm1, 6           ; mm1 = |00000000 00000000|ggggg000 00000000|
        pand      mm0, [mask_rb32] ; mm0 = |00000000 rrrrr000|00000000 bbbbb000|
        por       mm0, mm1
        ret

Konwersja 16bpp na 32bpp — x86 — tablicowanie

Zamiast „suchego” kodu w C pokażę praktyczną realizację tablicowania w asemblerze.

W jeden pętli przetwarzane będą po dwa piksele, przede wszystkim po to by zminimalizować liczbę odwołań do pamięci.

segment .data

lookup_lo times 256 dd 0 ; tablice są wypełniane "gdzieś tam"
lookup_hi times 256 dd 0

; esi -> wskaźnik na 2 wejściowe piksele 16bpp
; edi -> wskaźnik na 2 wyjściowe piksele 32bpp

conv_16bpp_to_32bpp:
        mov eax, [esi]   ; ładowane są 2 piksele

        movzx ecx, al    ; młodsze
        movzx edx, ah    ; i starsze słowo pierwszego piksela

        bswap eax
        movzx ebx, ah,   ; młodsze
        and   eax, 0xff  ; i starsze słowo drugiego piksela

        mov   ebx, [lookup_lo + ebx*4]
        mov   ecx, [lookup_lo + ecx*4]

        or    ebx, [lookup_hi + edx*4]
        or    ecx, [lookup_hi + eax*4]

        mov   [edi+0], ebx
        mov   [edi+8], ecx
        ret

Konwersja 16bpp na 32bpp — x86

; wejście: ax  - piksel 16bpp
; wyjście: eax - piksel 32bpp

           ; eax = |xxxx xxxx|xxxx xxxx|rrrr rggg|gggb bbbb|
shl eax, 5 ; eax = |xxxx xxxx|xxxr rrrr|gggg ggbb|bbb0 0000|
shr  ax, 5 ; eax = |xxxx xxxx|xxxr rrrr|0000 0ggg|gggb bbbb|
shr eax, 3 ; eax = |xxxx xxxx|rrrr r000|00gg gggg|bbbb b000|
shl  ah, 2 ; eax = |xxxx xxxx|rrrr r000|gggg gg00|bbbb b000|

Konwersja 16bpp na 32bpp — MMX

; czw 24 paź 2002 18:59:18 CEST
segment .data

mask_r db 0b1111100000000000, 0xf800, 0xf800, 0xf800
mask_g db 0b0000011111100000, 0x07e0, 0x07e0, 0x07e0
mask_b db 0b0000000000011111, 0x0x1e, 0x0x1e, 0x0x1e

segment .text

; esi -> wskaźnik do obrazu 16bpp
conv_16bpp_to_32bpp:
        movq  mm0, [esi]    ; mm0 = |pix3|pix2|pix1|pix0|
        movq  mm1, mm0
        movq  mm2, mm0

        pand  mm0, [mask_r] ; mm0 = |rrrrr000 00000000|...
        pand  mm1, [mask_g] ; mm1 = |00000ggg ggg00000|...
        pand  mm2, [mask_b] ; mm2 = |00000000 000bbbbb|...

        psrlw mm0, 8        ; mm0 = |00000000 rrrrr000|...
        psllw mm1, 5        ; mm1 = |gggggg00 00000000|...
        psllw mm2, 3        ; mm2 = |00000000 bbbbb000|...

        por   mm1, mm2      ; mm1 = |gggggg00 bbbbb000|...
        movq  mm2, mm1

        punpcklwd  mm1, mm0 ; mm1 = |00000000 rrrrr000|gggggg00 bbbbb000|...
        punpckhwd  mm2, mm0 ; mm2 = |00000000 rrrrr000|gggggg00 bbbbb000|...
        ret

Po optymalizacji.

; czw 24 paź 2002 19:19:35 CEST
; pią 25 paź 2002 17:41:44 CEST
segment .data

mask_r dw 0b0000000011111000, 0x00f8, 0x00f8, 0x00f8
mask_b dw 0b0000011111100000, 0x07e0, 0x07e0, 0x07e0
mask_g dw 0b0000000011111000, 0x00f8, 0x00f8, 0x00f8

segment .text

conv_16bpp_to_32bpp:
        movq      mm0, [esi]    ; 1
        movq      mm3, [edi]    ; -
        movq      mm1, mm0      ; 2
        movq      mm2, mm0      ; -
        movq      mm4, mm3      ; 3
        movq      mm5, mm3      ; -

        psrlw     mm0, 8        ; 4
        pand      mm2, [mask_b] ; -
        psllw     mm1, 5        ; 5
        pand      mm0, [mask_r] ; -
        psllw     mm2, 3        ; 6
        pand      mm1, [mask_g] ; -

        psrlw     mm3, 8        ; 7
        por       mm1, mm2      ; -
        psllw     mm4, 5        ; 8
        pand      mm3, [mask_r] ; -
        psllw     mm5, 3        ; 9
        movq      mm2, mm1      ; -
        punpcklwd mm1, mm0      ; 10
        pand      mm4, [mask_g] ; -
        punpckhwd mm2, mm0      ; 11
        pand      mm5, [mask_b] ; -
        por       mm4, mm5      ; 13
        movq      mm5, mm4      ; 14
        punpcklwd mm4, mm3      ; 15
        punpckhwd mm5, mm3      ; 16 -- 16 cykli/8 pikseli (2 cykle/piksel)

        ret

Konwersja 15bpp na 16bpp

kod x86 update

Kod jest łatwy do przerobienia na MMX/SSE.

; wejście: ax - piksel 15bpp
; wyjście: ax - piksel 16bpp
; niszczy: bx

                      ; ax = |0rrr rrgg|gggb bbbb|
mov bx, ax
and ax, 7fe0h         ; ax = |0rrr rrgg|ggg0 0000|
and bx, 001fh         ; bx = |0000 0000|000b bbbb|

lea eax, [eax*2 + ebx]

kod x86 — wariant 2. new

Ta wersja jest o wiele szybsza niż przedstawiony wyżej algorytm; kod jest równie łatwy do przerobienia na MMX/SSE.

; wejście: ax - piksel 15bpp
; wyjście: ax - piksel 16bpp
; niszczy: bx

                      ; ax = |0rrr rrgg|gggb bbbb|
mov bx, ax
and ax, 7fe0h         ; ax = |0rrr rrgg|ggg0 0000|
add ax, bx            ; bx = |rrrr rggg|gg0b bbbb|

Porównanie metod konwersji 15bpp/16bpp na 32bpp new

Zobacz w osobnym artykule