PCX prakticky - implementace komprimace RLE

23. 11. 2006
Doba čtení: 18 minut

Sdílet

V dnešní části seriálu věnovaného grafickým formátům dokončíme popis struktury souborů typu PCX. Ukážeme si, jakým způsobem je prováděna komprimace pomocí algoritmu RLE, porovnáme účinnost této komprimační metody s dalšími bezeztrátovými metodami a také si předvedeme programové kódy určené pro čtení i zápis obrázků typu PCX.

Obsah

1. Komprimace rastrových dat v souborech typu PCX
2. Porovnání obrázků uložených v souborech typu PCX, GIF, PNG a BMP
3. Knihovna pro práci s obrázky typu PCX
4. Funkce provádějící zápis černo-bílého obrázku
5. Funkce provádějící zápis grayscale obrázku
6. Funkce provádějící zápis plnobarevného obrázku
7. Literatura
8. Odkazy na Internetu
9. Obsah dalšího pokračování tohoto seriálu

1. Komprimace rastrových dat v souborech typu PCX

Již v předchozí části tohoto seriálu jsme si řekli, že rastrová data mohou být v grafickém formátu PCX uložena buď v přímé (tj. nekompri­mované) podobě, nebo v komprimovaném tvaru. Nekomprimované PCX se prakticky nepoužívají, většina obrázků uložených v tomto formátu používá jedinou podporovanou komprimační metodu – modifikovaný algoritmus RLE (Run Length Encoding). Zajímavé je, že algoritmus RLE má u PCX stále stejnou podobu, a to bez ohledu na typ komprimovaného obrázku. Nemusíme tedy rozlišovat, zda se jedná o černo-bílý obrázek, šestnáctibarevný obrázek, obrázek s 256 barvami či plnobarevný (truecolor) obrázek.

RLE použitý u PCX pracuje s proudem bytů, přičemž maximální délka tohoto proudu odpovídá délce obrazového řádku (tato hodnota je uložena v hlavičce). Obrazový řádek se postupně načítá a zjišťuje se, kolik bytů (nikoli pixelů!) má stejnou hodnotu. Blok za sebou jdoucích bytů se stejnou hodnotou se zapíše jako dvojice bytů: první byte udává počet znaků v bloku, druhý byte hodnotu těchto bytů. Počitadlo bytů je inicializováno na hodnotu 0×c0, to znamená, že pokud dekomprimační program narazí na byte větší než 0×c0, ví, že se jedná o dvoubytový komprimovaný blok.

Jednotlivé byty, které nejsou součástí bloku a mají hodnotu menší než 0×c0, jsou do komprimovaného souboru zapsány ve své původní podobě. Horší je to s byty, které mají hodnotu větší než 0×c0. Aby nenastala kolize s počitadlem, musí se tyto byty uložit jako dvojice bytů 0×c1 0×??, tj. jako blok o délce jednoho bytu s barvou pixelu uloženou ve druhém bytu. V tomto případě tedy nenastává komprimace, ale naopak prodloužení výstupního souboru. To vede k zajímavému paradoxu, který se u jiných komprimačních metod neprojevuje: pouhým přeindexováním bytů a úpravou barvové palety je možné měnit komprimační poměr u PCX souborů (ideální je, aby paleta byla setříděna tak, že nejčastěji používané barvy jsou uloženy na začátku, aby se omezil počet bytů s hodnotou větší než 0×c0). Kupodivu velmi málo aplikací tuto zajímavou optimalizaci provádí.

Příklad komprimace tří posloupností bytů:

01 01 01 01 01          => C5 01
01 01 01 01 01 04 01 01 => C5 01 04 C2 01
01 01 01 01 01 FF 01 01 => C5 01 C1 FF C2 01 

Praktická implementace algoritmu RLE bude uvedena ve čtvrté, páté a šesté kapitole.

2. Porovnání obrázků uložených v souborech typu PCX, GIF, PNG a BMP

Při práci s rastrovými obrázky, které jsou komprimované, je celkem přirozené položit si otázku, která komprimační metoda dává nejlepší výsledky, tj. která metoda data co nejvíce „stlačí“. Je to otázka velmi záludná, protože každá bezeztrátová komprimační metoda některé sekvence údajů zkomprimuje dobře a některé naopak (nutně) prodlouží. V případě grafických formátů však vycházíme z představy, že zdaleka ne všechny kombinace barev pixelů se v reálných obrázcích vyskytují a bezeztrátové komprimační metody jsou proto připraveny na to, že se například části obrazu opakují nebo se v obrazu vyskytují jednobarevné plochy.

Pro ilustraci možností několika známých bezeztrátových grafických formátů jsem provedl otestování na čtyřech obrázcích, které se lišily zejména počtem bitů na pixel (bpp) a také svým motivem. Grafickými formáty, které se zúčastnily porovnání jsou:

Grafický formát Komprimace Poznámka
BMP RLE+delta při porovnávání nekomprimováno
GIF LZW maximálně 256 barev
PCX RLE
PNG LZ 77

Obrázky byly zvoleny následovně:

  1. černo-bílý obrázek: dithering, velké změny v obraze
  2. šestnáctibarevný: velké plochy stejné barvy, málo změn
  3. 256barevný obrázek: velká stejnobarevná plocha (pozadí), více změn
  4. true-color obrázek: reálná fotografie, mnoho změn v obraze, barevné přechody

pcx2_1

Obrázek 1: černo-bílý testovací obrázek

pcx2_2

Obrázek 2: šestnáctibarevný testovací obrázek

pcx2_3

Obrázek 3: 256barevný testovací obrázek

pcx2_4

Obrázek 4: plnobarevný testovací obrázek

Výsledky jsou uvedeny v následující tabulce. Tučně je zvýrazněn údaj, při kterém došlo ke ztrátové komprimaci. Grafický formát GIF sice teoreticky zobrazí více než 256 barev (o tom jsem psal v samostatném seriálu), ale většina programů takový obrázek neumí zpracovat. Proto došlo v případě true-color obrázku k redukci počtu barev na 256, tj. de facto ke ztrátové komprimaci.

Jméno souboru Typ souboru Typ obrázku Rozlišení Velikost rastru Velikost po komprimaci Komprimační poměr
01bpp.bmp BMP 1 bpp 256×256 8192 8254 101%
01bpp.gif GIF 1 bpp 256×256 8192 5979 73%
01bpp.pcx PCX 1 bpp 256×256 8192 8460 103%
01bpp.png PNG 1 bpp 256×256 8192 6030 74%
04bpp.bmp BMP 4 bpp 354×520 92040 93718 102%
04bpp.gif GIF 4 bpp 354×520 92040 8419 9%
04bpp.pcx PCX 4 bpp 354×520 92040 14881 16%
04bpp.png PNG 4 bpp 354×520 92040 7310 8%
08bpp.bmp BMP 8 bpp 624×113 70512 71590 102%
08bpp.gif GIF 8 bpp 624×113 70512 20079 28%
08bpp.pcx PCX 8 bpp 624×113 70512 27645 39%
08bpp.png PNG 8 bpp 624×113 70512 17902 25%
24bpp.bmp BMP 24 bpp 256×256 196608 196662 100%
24bpp.gif GIF 24 bpp 256×256 196608 52214 27%
24bpp.pcx PCX 24 bpp 256×256 196608 221261 113%
24bpp.png PNG 24 bpp 256×256 196608 107001 54%

Výše uvedené výsledky si můžeme sepsat pro každý grafický formát samostatně:

Jméno souboru Typ souboru Typ obrázku Rozlišení Velikost rastru Velikost po komprimaci Komprimační poměr
01bpp.bmp BMP 1 bpp 256×256 8192 8254 101%
04bpp.bmp BMP 4 bpp 354×520 92040 93718 102%
08bpp.bmp BMP 8 bpp 624×113 70512 71590 102%
24bpp.bmp BMP 24 bpp 256×256 196608 196662 100%
Celkem: 367352 370224 101%

Podle očekávání došlo u souborů typu BMP k celkovému nárůstu velikosti v porovnání s velikostí původní bitmapy. Kupodivu se však u středně velkých souborů nejedná o nijak velkou režijní položku, protože v našem testovacím souboru se jednalo o pouhé jedno procento celkové velikosti. U menších obrázků (ikon apod.) však bude režie větší, mnohdy i více než 100%.

Jméno souboru Typ souboru Typ obrázku Rozlišení Velikost rastru Velikost po komprimaci Komprimační poměr
01bpp.gif GIF 1 bpp 256×256 8192 5979 73%
04bpp.gif GIF 4 bpp 354×520 92040 8419 9%
08bpp.gif GIF 8 bpp 624×113 70512 20079 28%
24bpp.gif GIF 24 bpp 256×256 196608 52214 27%
Celkem: 367352 86691 24%

Už v předchozím textu jsem se zmínil o tom, že v případě použití grafického formátu GIF došlo ke ztrátové komprimaci původně true-color obrázku na obrázek s 256 barvami. Poslední řádek tabulky i celkové zhodnocení tedy musíme brát s rezervou. V každém případě je zajímavé, že černo-bílý obrázek je menší než při použití formátu PNG. Zde se uplatňuje především menší hlavička souboru typu PNG, samotný algoritmus (LZW vs. LZ77) nemá v případě tak malého obrázku na výslednou velikost velký vliv.

Jméno souboru Typ souboru Typ obrázku Rozlišení Velikost rastru Velikost po komprimaci Komprimační poměr
01bpp.pcx PCX 1 bpp 256×256 8192 8460 103%
04bpp.pcx PCX 4 bpp 354×520 92040 14881 16%
08bpp.pcx PCX 8 bpp 624×113 70512 27645 39%
24bpp.pcx PCX 24 bpp 256×256 196608 221261 113%
Celkem: 367352 272247 74%

Z předchozí tabulky je patrné, že RLE algoritmus použitý u grafického formátu PCX má svoje limity. Jak u černo-bílého, tak i u plnobarevného obrázku nedošlo ke komprimaci, ale naopak ke zvýšení velikosti souboru! Důvod je jasný – u obou zmiňovaných obrázků jsou použity z hlediska RLE náhodné kombinace pixelů, ve kterých se těžko hledá posloupnost stejných hodnot. Naopak u šestnáctiba­revných a 256barevných obrázků takové posloupnosti existují a algoritmus RLE se stává účinným (ale LZW a LZ77 nepřekoná).

Jméno souboru Typ souboru Typ obrázku Rozlišení Velikost rastru Velikost po komprimaci Komprimační poměr
01bpp.png PNG 1 bpp 256×256 8192 6030 74%
04bpp.png PNG 4 bpp 354×520 92040 7310 8%
08bpp.png PNG 8 bpp 624×113 70512 17902 25%
24bpp.png PNG 24 bpp 256×256 196608 107001 54%
Celkem: 367352 138243 38%

Podle očekávání dává algoritmus LZ 77, resp. jeho modifikovaná podoba, nejlepší výsledky. Režijní nárůst velikosti hlavičky se nejvíce projevil u černo-bílého obrázku, není však nijak dramatický.

3. Knihovna pro práci s obrázky typu PCX

Pro účely práce s rastrovými grafickými formáty jsem v minulosti vytvořil jednoduchou céčkovou knihovnu. V této knihovně je implementováno načítání i ukládání obrázků v různých formátech, včetně dnes popisovaného formátu PCX (testováno pouze na 32bitových platformách Linux a Windows). Knihovna je rozdělena do dvou souborů: hlavičky a zdrojového textu v programovacím jazyku C. V dalších třech kapitolách jsou uvedeny ukázky použití této knihovny při ukládání obrázků ve formátu PCX. Testovací program, který tuto knihovnu využívá pro uložení dvanácti obrázků s různým rozlišením (test zarovnávání řádků na dvojice bytů) je velmi jednoduchý (zdrojový text):

#include <stdio.h>
#include <stdlib.h>
#include "pcx_lib.h"

void createBWPcx(int width, int height, const char *filename)
{
    Bitmap *bmp;
    printf("creating black & white PCX %dx%d pixels\n", width, height);
    bmp=bitmapCreateTwoColors(width, height);
    if (bmp) {
        bitmapSetColorBit(bmp, 1);
        bitmapLine(bmp, 0, 0, width-1, height-1);
        bitmapLine(bmp, 0, height-1, width-1, 0);
        bitmapSave2TwoColorPCX(bmp, (char *)filename);
        bitmapDestroy(bmp);
        printf("done file %s\n", filename);
    }
    else {
        puts("creation failed!");
    }
}

void createGrayScalePcx(int width, int height, const char *filename)
{
    Bitmap *bmp;
    printf("creating grayscale PCX %dx%d pixels\n", width, height);
    bmp=bitmapCreateGrayScale(width, height);
    if (bmp) {
        bitmapSetColorInt(bmp, 0xff);
        bitmapLine(bmp, 0, 0, width-1, height-1);
        bitmapSetColorInt(bmp, 0x00);
        bitmapLine(bmp, 0, height-1, width-1, 0);
        bitmapSave2GrayScalePCX(bmp, (char *)filename);
        bitmapDestroy(bmp);
        printf("done file %s\n", filename);
    }
    else {
        puts("creation failed!");
    }
}

void createTrueColorPcx(int width, int height, const char *filename)
{
    Bitmap *bmp;
    printf("creating truecolor PCX %dx%d pixels\n", width, height);
    bmp=bitmapCreateTrueColor(width, height);
    if (bmp) {
        bitmapSetColorRGB(bmp, 0xff, 0x00, 0x00);
        bitmapLine(bmp, 0, 0, width-1, height-1);
        bitmapSetColorRGB(bmp, 0x00, 0x00, 0xff);
        bitmapLine(bmp, 0, height-1, width-1, 0);
        bitmapSave2TrueColorPCX(bmp, (char *)filename);
        bitmapDestroy(bmp);
        printf("done file %s\n", filename);
    }
    else {
        puts("creation failed!");
    }
}

int main(void)
{
    createBWPcx(126, 126, "bw126x126.pcx");
    createBWPcx(127, 127, "bw127x127.pcx");
    createBWPcx(128, 128, "bw128x128.pcx");
    createBWPcx(129, 129, "bw129x129.pcx");

    createGrayScalePcx(126, 126, "g126x126.pcx");
    createGrayScalePcx(127, 127, "g127x127.pcx");
    createGrayScalePcx(128, 128, "g128x128.pcx");
    createGrayScalePcx(129, 129, "g129x129.pcx");

    createTrueColorPcx(126, 126, "rgb126x126.pcx");
    createTrueColorPcx(127, 127, "rgb127x127.pcx");
    createTrueColorPcx(128, 128, "rgb128x128.pcx");
    createTrueColorPcx(129, 129, "rgb129x129.pcx");

    return 0;
} 

Komprimace pomocí algoritmu RLE je v případě PCX poměrně přímočará a zajišťuje ji následující funkce:

//-----------------------------------------------------------------------------
// Subroutine for writing an encoded byte pair (or single byte if it doesn't
// encode) to a file. It returns the count of bytes written, 0 if error.
//-----------------------------------------------------------------------------
int pcxEncPut(unsigned char byt, unsigned int cnt, FILE *file)
{
    if (cnt) {
        if ((cnt == 1) && (0xc0 != (0xc0 & byt))) {
            if (putc((int)byt, file)==EOF)          // disk write error (probably disk full)
                return 0;                           // write failed
            return 1;                               // write ok
        }
        else {
            if (putc((int)0xC0 | cnt, file)==EOF)   // disk write error
                return 0;                           // write failed
            if (putc((int)byt, file)==EOF)          // disk write error
                return 0;                           // write failed
            return 2;                               // write ok
        }
    }
    return 0;                                       // write failed
} 

4. Funkce provádějící zápis černo-bílého obrázku

Strukturu černo-bílých (dvoubarevných) obrázků typu PCX jsme si popisovali v předchozí části tohoto seriálu. Funkce, která provádí jejich zápis, může mít následující tvar:

//-----------------------------------------------------------------------------
// This function saves raster data in black and white only pixel-format to
// fileformat PCX
//-----------------------------------------------------------------------------
int bitmapSave2TwoColorPCX(Bitmap *bitmap, char *filename)
{
    FILE  *fout;                                    // output file
    int    size;
    int    i, j;                                    // loop counters
    int    cnt;                                     // character count
    int    result;
    unsigned char *p;
    typedef struct tagPCXHeader {                   // pcx header
        unsigned char   id;                         // file id
        unsigned char   version;                    // PCX version
        unsigned char   rle;                        // compression
        unsigned char   bpp;                        // bits per pixel
        unsigned short  xstart;                     // starting coordinates
        unsigned short  ystart;
        unsigned short  xend;                       // ending coordinates
        unsigned short  yend;
        unsigned short  hres;                       // horizontal resolution
        unsigned short  vres;                       // vertical resolution
        unsigned char   pal[48];                    // palette (up to 16 colors)
        unsigned char   reserved;                   // reserved byte
        unsigned char   nbitp;                      // number of bitplanes
        unsigned short  bytesPerLine;               // bytes per line
        unsigned short  palType;                    // palette type
        unsigned short  horizontalSize;             // horizontal size
        unsigned short  verticalSize;               // vertical size
        unsigned char   reserved2[54];              // reserved bytes
    } PCXHeader;
    PCXHeader pcxHeader;

    assert(bitmap);                                 // check bitmap structure
    assert(filename);                               // check filename
    assert(bitmap->pixels);                         // check pixels
    assert(bitmap->type==BitmapTypeTwoColors);      // check bitmap type
    if (!bitmap) return false;                      // bitmap does not exists
    if (!bitmap->pixels) return false;              // pixel array does not exists
    if (!filename) return false;                    // empty filename
    if (bitmap->type!=BitmapTypeTwoColors) return false; // invalid bitmap type

    size=bitmap->bytesPerLine;                      // compute scanline size

    // fill in PCX header
    memset(&pcxHeader, 0, sizeof(pcxHeader));       // clear header
    pcxHeader.id=10;                                // manufacturer
    pcxHeader.version=5;                            // Paintbrush version 3.0 and >
    pcxHeader.rle=1;                                // PCX run length encoding
    pcxHeader.bpp=1;                                // 1 bit per pixel
    pcxHeader.xstart=0;                             // window coordinates
    pcxHeader.ystart=0;
    pcxHeader.xend=bitmap->width-1;
    pcxHeader.yend=bitmap->height-1;
    pcxHeader.hres=300;                             // horizontal resolution
    pcxHeader.vres=300;                             // vertical resolution
    for (i=0; i<3; i++)                             // set first color
        pcxHeader.pal[i]=0x00;
    for (i=3; i<6; i++)                             // set second color
        pcxHeader.pal[i]=0xff;
    pcxHeader.reserved=0;                           // should be set to zero
    pcxHeader.nbitp=1;                              // one bitplane
    if (bitmap->bytesPerLine % 1)
        pcxHeader.bytesPerLine=bitmap->bytesPerLine+1; // must be even number
    else
        pcxHeader.bytesPerLine=bitmap->bytesPerLine; // must be even number
    pcxHeader.palType=1;                            // palette type
    pcxHeader.horizontalSize=0;                     // horizontal screen size - new field
    pcxHeader.verticalSize=0;                       // vertical screen size - new field

    fout=fopen(filename,"wb");                      // open output file for writing
    assert(fout);
    if (!fout) return false;                        // when file could not be opened
    cnt=fwrite(&pcxHeader, sizeof(pcxHeader), 1, fout); // try to write file header
    assert(cnt==1);
    if (cnt!=1) {                                   // check file header
        fclose(fout);
        return false;                               // function failed
    }
    for (i=(bitmap->height-1); i>=0; i--) {         // write bitmap data
        unsigned char this;
        unsigned char last;
        int runCount=0;
        p=bitmap->pixels+i*size;                    // pointer to active scanline
        last=~(*p);
        for (j=0; j<(signed int)bitmap->bytesPerLine; j++) { // for all scanlines
            this=*p++;
            if (this==last) {                       // there is a "run" in the data, encode it
                runCount++;
                if (runCount==63) {                 // maximum run length
                    if (!(pcxEncPut(last, runCount, fout))) { // write two bytes
                        result=fclose(fout);        // try to close file
                        assert(result!=EOF);
                        return false;               // function failed
                    }
                    runCount=0;
                }
            }
            else {                                  // no "run"
                if (runCount) {
                    if (!(pcxEncPut(last, runCount, fout))) { // write one or two bytes
                        result=fclose(fout);        // try to close file
                        assert(result!=EOF);
                        return false;               // function failed
                    }
                }
                last=this;
                runCount=1;
            }
        }
        if (runCount) {                             // finish up scanline
            if (!(pcxEncPut(last, runCount, fout))) {
                result=fclose(fout);                // try to close file
                assert(result!=EOF);
                return false;                       // function failed
            }
        }
    }
    result=fclose(fout);                            // try to close file
    assert(result!=EOF);
    if (result==EOF) {                              // when file close failed
        return false;                               // function failed
    }
    return true;                                    // function succesed
} 

5. Funkce provádějící zápis grayscale obrázku

Monochromatické (grayscale) obrázky se ukládají mnohem jednodušším způsobem, protože je použit systém „co byte, to jeden pixel“. Není tedy zapotřebí pixely sdružovat do jednoho bytu či je naopak rozdělovat do více bitových rovin. Jediný problém, který musíme vyřešit, je zarovnání celého obrazového řádku tak, jak je ukázáno v následující funkci:

//-----------------------------------------------------------------------------
// This function saves raster data in grayscale pixel-format to fileformat PCX
//-----------------------------------------------------------------------------
int bitmapSave2GrayScalePCX(Bitmap *bitmap, char *filename)
{
    FILE  *fout;                                    // output file
    int    size;
    int    i, j;                                    // loop counters
    int    cnt;                                     // character count
    int    result;
    unsigned char *p;
    typedef struct tagPCXHeader {                   // pcx header
        unsigned char   id;                         // file id
        unsigned char   version;                    // PCX version
        unsigned char   rle;                        // compression
        unsigned char   bpp;                        // bits per pixel
        unsigned short  xstart;                     // starting coordinates
        unsigned short  ystart;
        unsigned short  xend;                       // ending coordinates
        unsigned short  yend;
        unsigned short  hres;                       // horizontal resolution
        unsigned short  vres;                       // vertical resolution
        unsigned char   pal[48];                    // palette (up to 16 colors)
        unsigned char   reserved;                   // reserved byte
        unsigned char   nbitp;                      // number of bitplanes
        unsigned short  bytesPerLine;               // bytes per line
        unsigned short  palType;                    // palette type
        unsigned short  horizontalSize;             // horizontal size
        unsigned short  verticalSize;               // vertical size
        unsigned char   reserved2[54];              // reserved bytes
    } PCXHeader;
    PCXHeader pcxHeader;

    assert(bitmap);                                 // check bitmap structure
    assert(filename);                               // check filename
    assert(bitmap->pixels);                         // check pixels
    assert(bitmap->type==BitmapTypeGrayScale);      // check bitmap type
    if (!bitmap) return false;                      // bitmap does not exists
    if (!bitmap->pixels) return false;              // pixel array does not exists
    if (!filename) return false;                    // empty filename
    if (bitmap->type!=BitmapTypeGrayScale) return false; // invalid bitmap type

    size=bitmap->bytesPerLine;                      // compute scanline size

    // fill in PCX header
    memset(&pcxHeader, 0, sizeof(pcxHeader));       // clear header
    pcxHeader.id=10;                                // manufacturer
    pcxHeader.version=5;                            // Paintbrush version 3.0 and >
    pcxHeader.rle=1;                                // PCX run length encoding
    pcxHeader.bpp=8;                                // 1 bit per pixel
    pcxHeader.xstart=0;                             // window coordinates
    pcxHeader.ystart=0;
    pcxHeader.xend=bitmap->width-1;
    pcxHeader.yend=bitmap->height-1;
    pcxHeader.hres=300;                             // horizontal resolution
    pcxHeader.vres=300;                             // vertical resolution
    pcxHeader.reserved=0;                           // should be set to zero
    pcxHeader.nbitp=1;                              // one bitplane
    if (bitmap->width & 0x01)
        pcxHeader.bytesPerLine=(bitmap->width)+1;   // must be even number
    else
        pcxHeader.bytesPerLine=(bitmap->width);     // must be even number
    pcxHeader.palType=1;                            // palette type
    pcxHeader.horizontalSize=0;                     // horizontal screen size - new field
    pcxHeader.verticalSize=0;                       // vertical screen size - new field

    fout=fopen(filename,"wb");                      // open output file for writing
    assert(fout);
    if (!fout) return false;                        // when file could not be opened
    cnt=fwrite(&pcxHeader, sizeof(pcxHeader), 1, fout); // try to write file header
    assert(cnt==1);
    if (cnt!=1) {                                   // check file header
        fclose(fout);
        return false;                               // function failed
    }
    for (i=(bitmap->height-1); i>=0; i--) {         // write bitmap data
        unsigned char this;
        unsigned char last;
        int runCount=0;
        p=bitmap->pixels+i*size;
        last=~(*p);
        for (j=0; j<(signed int)(bitmap->width); j++) { // for all scanlines
            this=*p++;
            if (this==last) {                       // there is a "run" in the data, encode it
                runCount++;
                if (runCount==63) {                 // maximum run length
                    if (!(pcxEncPut(last, runCount, fout))) {
                        fclose(fout);               // try to close file
                        return false;               // function failed
                    }
                    runCount=0;
                }
            }
            else {                                  // no "run"
                if (runCount) {
                    if (!(pcxEncPut(last, runCount, fout))) { // write one or two bytes
                        fclose(fout);               // try to close file
                        return false;               // function failed
                    }
                }
                last=this;
                runCount=1;
            }
        }
        if (runCount) {                             // finish up scanline
            if (!(pcxEncPut(last, runCount, fout))) {
                fclose(fout);                       // try to close file
                return false;                       // function failed
            }
        }
        if (bitmap->bytesPerLine & 0x01) fputc(0x00, fout); // even bytes at scanline
    }
    if (fputc(0x0c, fout)==EOF) {                   // palette magic number
        result=fclose(fout);
        assert(result!=EOF);
        return false;
    }
    for (i=0; i<256; i++) {                         // write palette
        result=fputc(i, fout);                      // red color component
        assert(result!=EOF);                        // check write status
        result=fputc(i, fout);                      // green color component
        assert(result!=EOF);                        // check write status
        result=fputc(i, fout);                      // blue color component
        assert(result!=EOF);                        // check write status
    }
    result=fclose(fout);                            // try to close file
    assert(result!=EOF);
    if (result==EOF) {                              // when file close failed
        return false;                               // function failed
    }
    return true;                                    // function succesed
} 

6. Funkce provádějící zápis plnobarevného obrázku

Plnobarevné obrázky se do značné míry podobají obrázkům monochromatickým, ovšem s tím rozdílem, že jsou použity tři obrazové roviny, které jsou do souboru ukládány prokládaně: první řádek první roviny, první řádek druhé roviny atd. Funkce pro práci s tímto formátem má tvar:

//-----------------------------------------------------------------------------
// This function saves raster data in true color pixel-format to fileformat PCX
//-----------------------------------------------------------------------------
int bitmapSave2TrueColorPCX(Bitmap *bitmap, char *filename)
{
    FILE  *fout;                                    // output file
    int    size;
    int    i, j, k;                                 // loop counters
    int    cnt;                                     // character count
    int    result;
    unsigned char *p;
    typedef struct tagPCXHeader {                   // pcx header
        unsigned char   id;                         // file id
        unsigned char   version;                    // PCX version
        unsigned char   rle;                        // compression
        unsigned char   bpp;                        // bits per pixel
        unsigned short  xstart;                     // starting coordinates
        unsigned short  ystart;
        unsigned short  xend;                       // ending coordinates
        unsigned short  yend;
        unsigned short  hres;                       // horizontal resolution
        unsigned short  vres;                       // vertical resolution
        unsigned char   pal[48];                    // palette (up to 16 colors)
        unsigned char   reserved;                   // reserved byte
        unsigned char   nbitp;                      // number of bitplanes
        unsigned short  bytesPerLine;               // bytes per line
        unsigned short  palType;                    // palette type
        unsigned short  horizontalSize;             // horizontal size
        unsigned short  verticalSize;               // vertical size
        unsigned char   reserved2[54];              // reserved bytes
    } PCXHeader;
    PCXHeader pcxHeader;

    assert(bitmap);                                 // check bitmap structure
    assert(filename);                               // check filename
    assert(bitmap->pixels);                         // check pixels
    assert(bitmap->type==BitmapTypeTrueColor);      // check bitmap type
    if (!bitmap) return false;                      // bitmap does not exists
    if (!bitmap->pixels) return false;              // pixel array does not exists
    if (!filename) return false;                    // empty filename
    if (bitmap->type!=BitmapTypeTrueColor) return false; // invalid bitmap type

    size=bitmap->bytesPerLine;                      // compute scanline size

    // fill in PCX header
    memset(&pcxHeader, 0, sizeof(pcxHeader));       // clear header
    pcxHeader.id=10;                                // manufacturer
    pcxHeader.version=5;                            // Paintbrush version 3.0 and >
    pcxHeader.rle=1;                                // PCX run length encoding
    pcxHeader.bpp=8;                                // 1 bit per pixel
    pcxHeader.xstart=0;                             // window coordinates
    pcxHeader.ystart=0;
    pcxHeader.xend=bitmap->width-1;
    pcxHeader.yend=bitmap->height-1;
    pcxHeader.hres=300;                             // horizontal resolution
    pcxHeader.vres=300;                             // vertical resolution
    pcxHeader.reserved=0;                           // should be set to zero
    pcxHeader.nbitp=3;                              // one bitplane
    if (bitmap->width & 0x01)
        pcxHeader.bytesPerLine=(bitmap->width)+1;   // must be even number
    else
        pcxHeader.bytesPerLine=(bitmap->width);     // must be even number
    pcxHeader.palType=1;                            // palette type
    pcxHeader.horizontalSize=0;                     // horizontal screen size - new field
    pcxHeader.verticalSize=0;                       // vertical screen size - new field

    fout=fopen(filename,"wb");                      // open output file for writing
    assert(fout);
    if (!fout) return false;                        // when file could not be opened
    cnt=fwrite(&pcxHeader, sizeof(pcxHeader), 1, fout); // try to write file header
    assert(cnt==1);
    if (cnt!=1) {                                   // check file header
        fclose(fout);
        return false;                               // function failed
    }
    for (i=(bitmap->height-1); i>=0; i--) {         // write bitmap data
        unsigned char this;
        unsigned char last;
        for (k=0; k<3; k++) {
            int runCount=0;
            p=bitmap->pixels+i*size+(2-k);
            last=~(*p);
            for (j=0; j<(signed int)(bitmap->width); j++) { // for all scanlines
                this=*p;
                p+=3;
                if (this==last) {                   // there is a "run" in the data, encode it
                    runCount++;
                    if (runCount==63) {             // maximum run length
                        if (!(pcxEncPut(last, runCount, fout))) {
                            fclose(fout);           // try to close file
                            return false;           // function failed
                        }
                        runCount=0;
                    }
                }
                else {                              // no "run"
                    if (runCount) {
                        if (!(pcxEncPut(last, runCount, fout))) {
                            fclose(fout);           // try to close file
                            return false;           // function failed
                        }
                    }
                    last=this;
                    runCount=1;
                }
            }
            if (runCount) {                         // finish up
                if (!(pcxEncPut(last, runCount, fout))) {
                    fclose(fout);                   // try to close file
                    return false;                   // function failed
                }
            }
            if (bitmap->bytesPerLine & 0x01) fputc(0x00, fout); // even bytes at scanline
        }
    }
    result=fclose(fout);                            // try to close file
    assert(result!=EOF);
    if (result==EOF) {                              // when file close failed
        return false;                               // function failed
    }
    return true;                                    // function succesed
} 

7. Literatura

  1. Graef, G.L.: „Graphics Format“,
    Graphics Format
  2. Johnson Eric: „PCX's format“,
    Boulware Technologies, Inc., Burnsville, MN
  3. Sobota Branislav: „Počítačová grafika a jazyk C“,
    Koop, 1995, České Budějovice
  4. Sobota Branislav, Milián Ján: „Grafické formáty“,
    Kopp, 1996, České Budějovice
  5. Sládeček Hynek a kolektiv: „1000 File Formats“,
    (freeware encyklopedie – hypertextový dokument ve formátu HLP), 1997, 1998
  6. Žára J., Beneš B., Felkel P.: „Moderní počítačová grafika“,
    Computer Press, Praha, 1998, ISBN 80–7226–049–9
  7. Žára J., Limpouch A., Beneš B., Werner T.: „Počítačová grafika – principy a algoritmy“,
    Grada, 1992

ict ve školství 24

8. Odkazy na Internetu

  1. Wikipedia: „PCX“ (stručný popis formátu PCX),
    http://en.wiki­pedia.org/wiki/PCX
  2. Wikipedia: „ZSoft Corporation“ (informace o firmě ZSoft, tvůrci PCX),
    http://en.wiki­pedia.org/wiki/ZSof­t_Corporation
  3. Wikipedia: „PC PaintBrush“ (program, ve kterém se PCX objevil poprvé),
    http://en.wiki­pedia.org/wiki/PC_Pa­intbrush
  4. „PCX Graphic File Format“,
    http://courses­.ece.uiuc.edu/e­ce390/books/lab­manual/graphics-pcx.html 
  5. PCGPE: „ZSoft PCX File Format Technical Reference Manual“,
    http://www.qzx­.com/pc-gpe/pcx.txt
  6. Lukáš Karas: „Práce s grafikou v Pascalu“ (včetně funkcí pro PCX)
    http://progra­mujte.com/view­.php?cisloclan­ku=2006012504-Prace-s-grafikou-640×480@16bit-v-Pascalu.-(4–4)

9. Obsah dalšího pokračování tohoto seriálu

V následující části tohoto seriálu dokončíme popis grafických formátů používajících bezeztrátovou (nebo dokonce žádnou) komprimaci, protože si popíšeme známé (alespoň ve světě Unixu) a interně velmi jednoduché formáty PBM, PGM, PPM a jejich univerzálního následovníka PAM. Poté se již začneme zabývat další velmi populární skupinou rastrových grafických formátů – jedná se o formáty využívající ztrátovou komprimaci, mezi něž patří například známý formát JFIF (JPEG).

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.