.pcx file format: C++98/STL parsing library

PCX is a bitmap image format originally used by PC Paintbrush from ZSoft Corporation. Originally, it was a relatively simple 128-byte header + uncompressed bitmap format, but latest versions introduced more complicated palette support and RLE compression.

There's an option to encode 32-bit or 16-bit RGBA pixels, and thus it can potentially include transparency. Theoretically, it's possible to encode resolution or pixel density in the some of the header fields too, but in reality there's no uniform standard for these, so different implementations treat these differently.

PCX format was never made a formal standard. "ZSoft Corporation Technical Reference Manual" for "Image File (.PCX) Format", last updated in 1991, is likely the closest authoritative source.

This page hosts a formal specification of .pcx file format using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for C++98/STL generated by Kaitai Struct depends on the C++/STL runtime library. You have to install it before you can parse data.

For C++, the easiest way is to clone the runtime library sources and build them along with your project.

Code

Using Kaitai Struct in C++/STL usually consists of 3 steps.

  1. We need to create an STL input stream (std::istream). One can open local file for that, or use existing std::string or char* buffer.
    #include <fstream>
    
    std::ifstream is("path/to/local/file.pcx", std::ifstream::binary);
    
    #include <sstream>
    
    std::istringstream is(str);
    
    #include <sstream>
    
    const char buf[] = { ... };
    std::string str(buf, sizeof buf);
    std::istringstream is(str);
    
  2. We need to wrap our input stream into Kaitai stream:
    #include "kaitai/kaitaistream.h"
    
    kaitai::kstream ks(&is);
    
  3. And finally, we can invoke the parsing:
    pcx_t data(&ks);
    

After that, one can get various attributes from the structure by invoking getter methods like:

data.hdr() // => get hdr

C++98/STL source code to parse .pcx file format

pcx.h

#ifndef PCX_H_
#define PCX_H_

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "kaitai/kaitaistruct.h"
#include <stdint.h>
#include <vector>

#if KAITAI_STRUCT_VERSION < 9000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required"
#endif

/**
 * PCX is a bitmap image format originally used by PC Paintbrush from
 * ZSoft Corporation. Originally, it was a relatively simple 128-byte
 * header + uncompressed bitmap format, but latest versions introduced
 * more complicated palette support and RLE compression.
 * 
 * There's an option to encode 32-bit or 16-bit RGBA pixels, and thus
 * it can potentially include transparency. Theoretically, it's
 * possible to encode resolution or pixel density in the some of the
 * header fields too, but in reality there's no uniform standard for
 * these, so different implementations treat these differently.
 * 
 * PCX format was never made a formal standard. "ZSoft Corporation
 * Technical Reference Manual" for "Image File (.PCX) Format", last
 * updated in 1991, is likely the closest authoritative source.
 * \sa https://web.archive.org/web/20100206055706/http://www.qzx.com/pc-gpe/pcx.txt Source
 */

class pcx_t : public kaitai::kstruct {

public:
    class header_t;
    class t_palette_256_t;
    class rgb_t;

    enum versions_t {
        VERSIONS_V2_5 = 0,
        VERSIONS_V2_8_WITH_PALETTE = 2,
        VERSIONS_V2_8_WITHOUT_PALETTE = 3,
        VERSIONS_PAINTBRUSH_FOR_WINDOWS = 4,
        VERSIONS_V3_0 = 5
    };

    enum encodings_t {
        ENCODINGS_RLE = 1
    };

    pcx_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, pcx_t* p__root = 0);

private:
    void _read();
    void _clean_up();

public:
    ~pcx_t();

    /**
     * \sa https://web.archive.org/web/20100206055706/http://www.qzx.com/pc-gpe/pcx.txt - "ZSoft .PCX FILE HEADER FORMAT"
     */

    class header_t : public kaitai::kstruct {

    public:

        header_t(kaitai::kstream* p__io, pcx_t* p__parent = 0, pcx_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~header_t();

    private:
        std::string m_magic;
        versions_t m_version;
        encodings_t m_encoding;
        uint8_t m_bits_per_pixel;
        uint16_t m_img_x_min;
        uint16_t m_img_y_min;
        uint16_t m_img_x_max;
        uint16_t m_img_y_max;
        uint16_t m_hdpi;
        uint16_t m_vdpi;
        std::string m_palette_16;
        std::string m_reserved;
        uint8_t m_num_planes;
        uint16_t m_bytes_per_line;
        uint16_t m_palette_info;
        uint16_t m_h_screen_size;
        uint16_t m_v_screen_size;
        pcx_t* m__root;
        pcx_t* m__parent;

    public:

        /**
         * Technically, this field was supposed to be "manufacturer"
         * mark to distinguish between various software vendors, and
         * 0x0a was supposed to mean "ZSoft", but everyone else ended
         * up writing a 0x0a into this field, so that's what majority
         * of modern software expects to have in this attribute.
         */
        std::string magic() const { return m_magic; }
        versions_t version() const { return m_version; }
        encodings_t encoding() const { return m_encoding; }
        uint8_t bits_per_pixel() const { return m_bits_per_pixel; }
        uint16_t img_x_min() const { return m_img_x_min; }
        uint16_t img_y_min() const { return m_img_y_min; }
        uint16_t img_x_max() const { return m_img_x_max; }
        uint16_t img_y_max() const { return m_img_y_max; }
        uint16_t hdpi() const { return m_hdpi; }
        uint16_t vdpi() const { return m_vdpi; }
        std::string palette_16() const { return m_palette_16; }
        std::string reserved() const { return m_reserved; }
        uint8_t num_planes() const { return m_num_planes; }
        uint16_t bytes_per_line() const { return m_bytes_per_line; }
        uint16_t palette_info() const { return m_palette_info; }
        uint16_t h_screen_size() const { return m_h_screen_size; }
        uint16_t v_screen_size() const { return m_v_screen_size; }
        pcx_t* _root() const { return m__root; }
        pcx_t* _parent() const { return m__parent; }
    };

    class t_palette_256_t : public kaitai::kstruct {

    public:

        t_palette_256_t(kaitai::kstream* p__io, pcx_t* p__parent = 0, pcx_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~t_palette_256_t();

    private:
        std::string m_magic;
        std::vector<rgb_t*>* m_colors;
        pcx_t* m__root;
        pcx_t* m__parent;

    public:
        std::string magic() const { return m_magic; }
        std::vector<rgb_t*>* colors() const { return m_colors; }
        pcx_t* _root() const { return m__root; }
        pcx_t* _parent() const { return m__parent; }
    };

    class rgb_t : public kaitai::kstruct {

    public:

        rgb_t(kaitai::kstream* p__io, pcx_t::t_palette_256_t* p__parent = 0, pcx_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~rgb_t();

    private:
        uint8_t m_r;
        uint8_t m_g;
        uint8_t m_b;
        pcx_t* m__root;
        pcx_t::t_palette_256_t* m__parent;

    public:
        uint8_t r() const { return m_r; }
        uint8_t g() const { return m_g; }
        uint8_t b() const { return m_b; }
        pcx_t* _root() const { return m__root; }
        pcx_t::t_palette_256_t* _parent() const { return m__parent; }
    };

private:
    bool f_palette_256;
    t_palette_256_t* m_palette_256;
    bool n_palette_256;

public:
    bool _is_null_palette_256() { palette_256(); return n_palette_256; };

private:

public:

    /**
     * \sa https://web.archive.org/web/20100206055706/http://www.qzx.com/pc-gpe/pcx.txt - "VGA 256 Color Palette Information"
     */
    t_palette_256_t* palette_256();

private:
    header_t* m_hdr;
    pcx_t* m__root;
    kaitai::kstruct* m__parent;
    std::string m__raw_hdr;
    kaitai::kstream* m__io__raw_hdr;

public:
    header_t* hdr() const { return m_hdr; }
    pcx_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
    std::string _raw_hdr() const { return m__raw_hdr; }
    kaitai::kstream* _io__raw_hdr() const { return m__io__raw_hdr; }
};

#endif  // PCX_H_

pcx.cpp

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "pcx.h"
#include "kaitai/exceptions.h"

pcx_t::pcx_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, pcx_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    m_hdr = 0;
    m__io__raw_hdr = 0;
    m_palette_256 = 0;
    f_palette_256 = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void pcx_t::_read() {
    m__raw_hdr = m__io->read_bytes(128);
    m__io__raw_hdr = new kaitai::kstream(m__raw_hdr);
    m_hdr = new header_t(m__io__raw_hdr, this, m__root);
}

pcx_t::~pcx_t() {
    _clean_up();
}

void pcx_t::_clean_up() {
    if (m__io__raw_hdr) {
        delete m__io__raw_hdr; m__io__raw_hdr = 0;
    }
    if (m_hdr) {
        delete m_hdr; m_hdr = 0;
    }
    if (f_palette_256 && !n_palette_256) {
        if (m_palette_256) {
            delete m_palette_256; m_palette_256 = 0;
        }
    }
}

pcx_t::header_t::header_t(kaitai::kstream* p__io, pcx_t* p__parent, pcx_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void pcx_t::header_t::_read() {
    m_magic = m__io->read_bytes(1);
    if (!(magic() == std::string("\x0A", 1))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x0A", 1), magic(), _io(), std::string("/types/header/seq/0"));
    }
    m_version = static_cast<pcx_t::versions_t>(m__io->read_u1());
    m_encoding = static_cast<pcx_t::encodings_t>(m__io->read_u1());
    m_bits_per_pixel = m__io->read_u1();
    m_img_x_min = m__io->read_u2le();
    m_img_y_min = m__io->read_u2le();
    m_img_x_max = m__io->read_u2le();
    m_img_y_max = m__io->read_u2le();
    m_hdpi = m__io->read_u2le();
    m_vdpi = m__io->read_u2le();
    m_palette_16 = m__io->read_bytes(48);
    m_reserved = m__io->read_bytes(1);
    if (!(reserved() == std::string("\x00", 1))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x00", 1), reserved(), _io(), std::string("/types/header/seq/11"));
    }
    m_num_planes = m__io->read_u1();
    m_bytes_per_line = m__io->read_u2le();
    m_palette_info = m__io->read_u2le();
    m_h_screen_size = m__io->read_u2le();
    m_v_screen_size = m__io->read_u2le();
}

pcx_t::header_t::~header_t() {
    _clean_up();
}

void pcx_t::header_t::_clean_up() {
}

pcx_t::t_palette_256_t::t_palette_256_t(kaitai::kstream* p__io, pcx_t* p__parent, pcx_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_colors = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void pcx_t::t_palette_256_t::_read() {
    m_magic = m__io->read_bytes(1);
    if (!(magic() == std::string("\x0C", 1))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x0C", 1), magic(), _io(), std::string("/types/t_palette_256/seq/0"));
    }
    int l_colors = 256;
    m_colors = new std::vector<rgb_t*>();
    m_colors->reserve(l_colors);
    for (int i = 0; i < l_colors; i++) {
        m_colors->push_back(new rgb_t(m__io, this, m__root));
    }
}

pcx_t::t_palette_256_t::~t_palette_256_t() {
    _clean_up();
}

void pcx_t::t_palette_256_t::_clean_up() {
    if (m_colors) {
        for (std::vector<rgb_t*>::iterator it = m_colors->begin(); it != m_colors->end(); ++it) {
            delete *it;
        }
        delete m_colors; m_colors = 0;
    }
}

pcx_t::rgb_t::rgb_t(kaitai::kstream* p__io, pcx_t::t_palette_256_t* p__parent, pcx_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void pcx_t::rgb_t::_read() {
    m_r = m__io->read_u1();
    m_g = m__io->read_u1();
    m_b = m__io->read_u1();
}

pcx_t::rgb_t::~rgb_t() {
    _clean_up();
}

void pcx_t::rgb_t::_clean_up() {
}

pcx_t::t_palette_256_t* pcx_t::palette_256() {
    if (f_palette_256)
        return m_palette_256;
    n_palette_256 = true;
    if ( ((hdr()->version() == pcx_t::VERSIONS_V3_0) && (hdr()->bits_per_pixel() == 8) && (hdr()->num_planes() == 1)) ) {
        n_palette_256 = false;
        std::streampos _pos = m__io->pos();
        m__io->seek((_io()->size() - 769));
        m_palette_256 = new t_palette_256_t(m__io, this, m__root);
        m__io->seek(_pos);
        f_palette_256 = true;
    }
    return m_palette_256;
}