Ukládání PCX obrázků (MFC a C++)

Jak jsem již říkal v článku o Ukládání Bmp obrázků, tyto soubory nejsou vůbec šetrné k místu na disku, jelikož nepoužívají žádnou komprimaci. Proto jsem se rozhodl napsat článek i o Pcx souborech. Hlavní výhodou obrázků uložených v tomto formátu je asi jejich jednoduchá komprimace (RLE), která je založena na opakujících se datech, které se poměrně jednoduchým postupem zkrátí. Z toho ovšem plyne i malá nevýhoda - pokud máme složitý obrázek, na kterém nejsou souvislé plochy jedné barvy, komprimace nebude tolik účinná. Zde se budu zabývat výhradně 24 - bitovými obrázky, jelikož tyto jsou asi nejpoužívanější.

Struktura souboru

SOUBOR PCX
{
  PCXINFOHEADER   //Hlavička s informacemi o obrázku
  komprimovaná obrazová data
}

Informační hlavička

Začneme tím, že si vytvoříme strukturu hlavičky obrázku. U Bmp obrázků se vytvářet nemusí, protože tento formát používá standardně systém Windows, a proto v něm je hlavička deklarovaná. Struktura tedy vypadá následovně:

typedef struct
{
  char Password;
  char Version;
  char Encoding;
  char BitsPerPixel;
  WORD XMin;
  WORD YMin;
  WORD XMax;
  WORD YMax;
  WORD HRes;
  WORD VRes;
  char Palette[16][3];
  char VMode;
  char ColorPlanes;
  WORD BytesPerLine;
  WORD PaletteInfo;
  char Dummy[58];
} PCXHEADER;

Na začátku souboru se tedy nachází proměnná Passoword , která musí mít hodnotu 0xA (10 decimálně) a indikuje, že se jedná o PCX soubor. Version označuje číslo verze, já tuto hodnotu nastavuji vždy na 5. Encoding určuje způsob komprimace obrázku a může mít buď hodnotu 0 (bez komprimace) nebo 1 (RLE komprimace). BitsPerPixel značí počet bitů na pixel, my budeme používat hodnotu 8. Proměnné XMin, YMin, XMax, YMax označují velikost obrázku, proměnné HRes, VRes určují, jaké bude mít obrázek rozlišení, my nastavíme na 96. Poté následuje 48 bajtů v proměnné Palette , ale jelikož 24-bitové obrázky žádnou paletu nepoužívají, stačí když celé pole vyplníme nulami. VMode je rezervované, tudíž ho nebudeme používat. ColorPlanes označuje, kolik barevných rovin se nachází v obrázku, my použijeme 3 roviny. Poslední proměnná, kterou budeme používat BytesPerLine, určuje, kolik bajtů padne na jeden řádek. Proměnné PaletteInfo a Dummy zde nebudu používat.

Obrazová data

Po informační hlavičce následují komprimovaná obrazová data. Ta jsou uložena po řádcích a každý řádek je rozdělen na tři bitové roviny (R,G,B). Data jsou tedy uložena takto:

1. řádek obrázku, bitová rovina R (red, červená)
1. řádek obrázku, bitová rovina G (green, zelená)
1. řádek obrázku, bitová rovina B (blue, modrá)
2. řádek obrázku, bitová rovina R (red, červená)
2. řádek obrázku, bitová rovina G (green, zelená)
.....

Jednotlivé bitové roviny jsou potom komprimované metodou RLE, kterou si nyní popíšeme.

RLE komprimace

Komprimace spočívá v tom, že se čtou hodnoty jednotlivých pixelu. Ty se zapisují na výstup až do té doby, než se nalezne skupina minimálně tří opakujících se hodnot nebo přečtená hodnota je větší než 192. Pokud je jedna z podmínek splněna, zvětší se vnitřní počítadlo opakujících se znaků do té doby, dokud tyto znaky opakují nebo dokud počítadlo nedosáhne hodnoty 63. Potom se hodnota počítadla zvětší o 192 a zapíše se na výstup společně se znakem, který se opakoval. Tento proces potom probíhá pořád dokola a skončí, až když na vstupu nejsou žádné hodnoty.

Postup při ukládání

Opět zde využiji možnosti třídy CMemDC, jelikož velice urychluje funkce GetPixel a SetPixel. Zde je zdrojový kód, který potom popíši:

#include "memdc.h"		//Pro třídu CMemDC

void SavePCX(CDC* pDC,int x,int y,int sirka,int vyska,LPCTSTR FileName)
{
  CMemDC dcMem;

  dcMem.Create(sirka,vyska,pDC);
  dcMem.BitBlt(x,y,sirka,vyska,pDC,0,0,SRCCOPY);

  typedef struct
  {
    char Password;
    char Version;
    char Encoding;
    char BitsPerPixel;
    WORD XMin;
    WORD YMin;
    WORD XMax;
    WORD YMax;
    WORD HRes;
    WORD VRes;
    char Palette[16][3];
    char VMode;
    char ColorPlanes;
    WORD BytesPerLine;
    WORD PaletteInfo;
    char Dummy[58];
  } PCXHEADER;

  BYTE *plane1, *plane2, *plane3;
  int	a, b;
  int	xx;
  int	XMax, YMax;
  COLORREF color;
  FILE* PCXfp;
  PCXHEADER	PCXHdr;
  int	i;
  int PCXSirkaRadku;
  
  if((PCXfp = fopen(FileName, "wb")) == NULL )
  {
    return;
  }

  XMax = x+sirka-1;
  YMax = y+vyska-1;

  PCXHdr.Password = 0x0A;
  PCXHdr.Version = 0x05;
  PCXHdr.Encoding = 1;
  PCXHdr.BitsPerPixel = 8;
  PCXHdr.XMin = x;
  PCXHdr.YMin = y;
  PCXHdr.XMax = XMax;
  PCXHdr.YMax = YMax;
  PCXHdr.HRes = 96;
  PCXHdr.VRes = 96;
  PCXHdr.ColorPlanes = 3;
  PCXHdr.BytesPerLine = sirka;
  
  PCXSirkaRadku = PCXHdr.BytesPerLine*3;
  plane1 = (BYTE *)malloc( PCXSirkaRadku*4 + 8 );
  plane2 = plane1 + PCXHdr.BytesPerLine;
  plane3 = plane2 + PCXHdr.BytesPerLine;
  if( plane1 == NULL )
  {
    fclose( PCXfp );
    return;
  }

  for( i=0; i<16; i++ )
  {
    PCXHdr.Palette[i][0] = 0;
    PCXHdr.Palette[i][1] = 0;
    PCXHdr.Palette[i][2] = 0;
  }

  for( i=0; i<58; i++ )
    PCXHdr.Dummy[i] = 0x00;
  fwrite( &PCXHdr, sizeof( PCXHEADER ), 1, PCXfp );

  for( a = PCXHdr.YMin; a <= YMax; a++ )
  {
    for( b = PCXHdr.XMin, xx=0; b <= XMax; b++, xx++ )
    {
      color = dcMem.ZjistiBarvuPixelu(b,a);
      plane1[xx] = GetRValue( color );
      plane2[xx] = GetGValue( color );
      plane3[xx] = GetBValue( color );
    }

    BYTE Pocet=1;
    BYTE PCXZnak = plane1[0];
    int AktS;

    for( AktS=1; AktS < PCXSirkaRadku; AktS++ )
    {
      if( PCXZnak == plane1[AktS] )
      {
        if( Pocet == 63 )
        {
          fputc( (Pocet | 0xC0), PCXfp );
          fputc( PCXZnak, PCXfp );
          Pocet = 1;
        }
        else
          Pocet++;
      }
      else
      {
        if( Pocet > 1 )
        {
          fputc( (Pocet | 0xC0), PCXfp );
          fputc( PCXZnak, PCXfp );
        }
        else
        {
          if( PCXZnak >= 0xC0 )
          fputc( 0xC1, PCXfp );
          fputc( PCXZnak, PCXfp );
        }

        Pocet = 1;
        PCXZnak = plane1[AktS];
      }
    }

    if( Pocet > 1 )
    {
      fputc( (Pocet | 0xC0), PCXfp );
      fputc( PCXZnak, PCXfp );
    }
    else
    {
      if( PCXZnak >= 0xC0 )
        fputc( 0xC1, PCXfp );

      fputc( PCXZnak, PCXfp );
    }
  }
  
  free( plane1 );
  fclose( PCXfp );
}

Popis kódu:

Nejprve si tedy nadeklarujeme všechny potřebné proměnné. Potom dosadíme jednotlivé hodnoty do struktury PCXHEADER, kterou uložíme do souboru. Poté si dynamicky vytvoříme proměnné plane1, plane2, plane3, které nám budou reprezentovat jednotlivé bitové roviny. Potom začneme procházet jednotlivé řádky obrázku a hledat stejné hodnoty. Pokud se naleznou alespoň tři takové, tak se začne zvětšovat hodnota počítadla (Pocet), až dokud se neobjeví jiný znak nebo dosáhne hodnoty 63. Takhle se vždy projde každý řádek, který se tak postupně ukládá do souboru. Nakonec se vymaže paměť přidělená dynamickým proměnným a zavře soubor.

Stáhnout zdrojový kód (29 kb)

UPOZORNĚNÍ: Jedná se o archiv článků z let 2003 - 2005, uvedené technologie či postupy již mohou být neaktuální.