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. nekomprimované) 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ě:
- černo-bílý obrázek: dithering, velké změny v obraze
- šestnáctibarevný: velké plochy stejné barvy, málo změn
- 256barevný obrázek: velká stejnobarevná plocha (pozadí), více změn
- true-color obrázek: reálná fotografie, mnoho změn v obraze, barevné přechody
Obrázek 1: černo-bílý testovací obrázek
Obrázek 2: šestnáctibarevný testovací obrázek
Obrázek 3: 256barevný testovací obrázek
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áctibarevný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
- Graef, G.L.: „Graphics Format“,
Graphics Format - Johnson Eric: „PCX's format“,
Boulware Technologies, Inc., Burnsville, MN - Sobota Branislav: „Počítačová grafika a jazyk C“,
Koop, 1995, České Budějovice - Sobota Branislav, Milián Ján: „Grafické formáty“,
Kopp, 1996, České Budějovice - Sládeček Hynek a kolektiv: „1000 File Formats“,
(freeware encyklopedie – hypertextový dokument ve formátu HLP), 1997, 1998 - Žára J., Beneš B., Felkel P.: „Moderní počítačová grafika“,
Computer Press, Praha, 1998, ISBN 80–7226–049–9 - Žára J., Limpouch A., Beneš B., Werner T.: „Počítačová grafika – principy a algoritmy“,
Grada, 1992
8. Odkazy na Internetu
- Wikipedia: „PCX“ (stručný popis formátu PCX),
http://en.wikipedia.org/wiki/PCX - Wikipedia: „ZSoft Corporation“ (informace o firmě ZSoft, tvůrci PCX),
http://en.wikipedia.org/wiki/ZSoft_Corporation - Wikipedia: „PC PaintBrush“ (program, ve kterém se PCX objevil poprvé),
http://en.wikipedia.org/wiki/PC_Paintbrush - „PCX Graphic File Format“,
http://courses.ece.uiuc.edu/ece390/books/labmanual/graphics-pcx.html - PCGPE: „ZSoft PCX File Format Technical Reference Manual“,
http://www.qzx.com/pc-gpe/pcx.txt - Lukáš Karas: „Práce s grafikou v Pascalu“ (včetně funkcí pro PCX)
http://programujte.com/view.php?cisloclanku=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).