.pcx file format: Go 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.

Go source code to parse .pcx file format

pcx.go

// Code generated by kaitai-struct-compiler from a .ksy source file. DO NOT EDIT.

import (
	"github.com/kaitai-io/kaitai_struct_go_runtime/kaitai"
	"bytes"
	"io"
)


/**
 * 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.
 * @see <a href="https://web.archive.org/web/20100206055706/http://www.qzx.com/pc-gpe/pcx.txt">Source</a>
 */

type Pcx_Encodings int
const (
	Pcx_Encodings__Rle Pcx_Encodings = 1
)
var values_Pcx_Encodings = map[Pcx_Encodings]struct{}{1: {}}
func (v Pcx_Encodings) isDefined() bool {
	_, ok := values_Pcx_Encodings[v]
	return ok
}

type Pcx_Versions int
const (
	Pcx_Versions__V25 Pcx_Versions = 0
	Pcx_Versions__V28WithPalette Pcx_Versions = 2
	Pcx_Versions__V28WithoutPalette Pcx_Versions = 3
	Pcx_Versions__PaintbrushForWindows Pcx_Versions = 4
	Pcx_Versions__V30 Pcx_Versions = 5
)
var values_Pcx_Versions = map[Pcx_Versions]struct{}{0: {}, 2: {}, 3: {}, 4: {}, 5: {}}
func (v Pcx_Versions) isDefined() bool {
	_, ok := values_Pcx_Versions[v]
	return ok
}
type Pcx struct {
	Hdr *Pcx_Header
	_io *kaitai.Stream
	_root *Pcx
	_parent kaitai.Struct
	_raw_Hdr []byte
	_f_palette256 bool
	palette256 *Pcx_TPalette256
}
func NewPcx() *Pcx {
	return &Pcx{
	}
}

func (this Pcx) IO_() *kaitai.Stream {
	return this._io
}

func (this *Pcx) Read(io *kaitai.Stream, parent kaitai.Struct, root *Pcx) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp1, err := this._io.ReadBytes(int(128))
	if err != nil {
		return err
	}
	tmp1 = tmp1
	this._raw_Hdr = tmp1
	_io__raw_Hdr := kaitai.NewStream(bytes.NewReader(this._raw_Hdr))
	tmp2 := NewPcx_Header()
	err = tmp2.Read(_io__raw_Hdr, this, this._root)
	if err != nil {
		return err
	}
	this.Hdr = tmp2
	return err
}

/**
 * @see <a href="https://web.archive.org/web/20100206055706/http://www.qzx.com/pc-gpe/pcx.txt">- "VGA 256 Color Palette Information"</a>
 */
func (this *Pcx) Palette256() (v *Pcx_TPalette256, err error) {
	if (this._f_palette256) {
		return this.palette256, nil
	}
	this._f_palette256 = true
	if ( ((this.Hdr.Version == Pcx_Versions__V30) && (this.Hdr.BitsPerPixel == 8) && (this.Hdr.NumPlanes == 1)) ) {
		_pos, err := this._io.Pos()
		if err != nil {
			return nil, err
		}
		tmp3, err := this._io.Size()
		if err != nil {
			return nil, err
		}
		_, err = this._io.Seek(int64(tmp3 - 769), io.SeekStart)
		if err != nil {
			return nil, err
		}
		tmp4 := NewPcx_TPalette256()
		err = tmp4.Read(this._io, this, this._root)
		if err != nil {
			return nil, err
		}
		this.palette256 = tmp4
		_, err = this._io.Seek(_pos, io.SeekStart)
		if err != nil {
			return nil, err
		}
	}
	return this.palette256, nil
}

/**
 * @see <a href="https://web.archive.org/web/20100206055706/http://www.qzx.com/pc-gpe/pcx.txt">- "ZSoft .PCX FILE HEADER FORMAT"</a>
 */
type Pcx_Header struct {
	Magic []byte
	Version Pcx_Versions
	Encoding Pcx_Encodings
	BitsPerPixel uint8
	ImgXMin uint16
	ImgYMin uint16
	ImgXMax uint16
	ImgYMax uint16
	Hdpi uint16
	Vdpi uint16
	Palette16 []byte
	Reserved []byte
	NumPlanes uint8
	BytesPerLine uint16
	PaletteInfo uint16
	HScreenSize uint16
	VScreenSize uint16
	_io *kaitai.Stream
	_root *Pcx
	_parent *Pcx
}
func NewPcx_Header() *Pcx_Header {
	return &Pcx_Header{
	}
}

func (this Pcx_Header) IO_() *kaitai.Stream {
	return this._io
}

func (this *Pcx_Header) Read(io *kaitai.Stream, parent *Pcx, root *Pcx) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp5, err := this._io.ReadBytes(int(1))
	if err != nil {
		return err
	}
	tmp5 = tmp5
	this.Magic = tmp5
	if !(bytes.Equal(this.Magic, []uint8{10})) {
		return kaitai.NewValidationNotEqualError([]uint8{10}, this.Magic, this._io, "/types/header/seq/0")
	}
	tmp6, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.Version = Pcx_Versions(tmp6)
	tmp7, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.Encoding = Pcx_Encodings(tmp7)
	tmp8, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.BitsPerPixel = tmp8
	tmp9, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.ImgXMin = uint16(tmp9)
	tmp10, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.ImgYMin = uint16(tmp10)
	tmp11, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.ImgXMax = uint16(tmp11)
	tmp12, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.ImgYMax = uint16(tmp12)
	tmp13, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.Hdpi = uint16(tmp13)
	tmp14, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.Vdpi = uint16(tmp14)
	tmp15, err := this._io.ReadBytes(int(48))
	if err != nil {
		return err
	}
	tmp15 = tmp15
	this.Palette16 = tmp15
	tmp16, err := this._io.ReadBytes(int(1))
	if err != nil {
		return err
	}
	tmp16 = tmp16
	this.Reserved = tmp16
	if !(bytes.Equal(this.Reserved, []uint8{0})) {
		return kaitai.NewValidationNotEqualError([]uint8{0}, this.Reserved, this._io, "/types/header/seq/11")
	}
	tmp17, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.NumPlanes = tmp17
	tmp18, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.BytesPerLine = uint16(tmp18)
	tmp19, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.PaletteInfo = uint16(tmp19)
	tmp20, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.HScreenSize = uint16(tmp20)
	tmp21, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.VScreenSize = uint16(tmp21)
	return err
}

/**
 * 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.
 */
type Pcx_Rgb struct {
	R uint8
	G uint8
	B uint8
	_io *kaitai.Stream
	_root *Pcx
	_parent *Pcx_TPalette256
}
func NewPcx_Rgb() *Pcx_Rgb {
	return &Pcx_Rgb{
	}
}

func (this Pcx_Rgb) IO_() *kaitai.Stream {
	return this._io
}

func (this *Pcx_Rgb) Read(io *kaitai.Stream, parent *Pcx_TPalette256, root *Pcx) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp22, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.R = tmp22
	tmp23, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.G = tmp23
	tmp24, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.B = tmp24
	return err
}
type Pcx_TPalette256 struct {
	Magic []byte
	Colors []*Pcx_Rgb
	_io *kaitai.Stream
	_root *Pcx
	_parent *Pcx
}
func NewPcx_TPalette256() *Pcx_TPalette256 {
	return &Pcx_TPalette256{
	}
}

func (this Pcx_TPalette256) IO_() *kaitai.Stream {
	return this._io
}

func (this *Pcx_TPalette256) Read(io *kaitai.Stream, parent *Pcx, root *Pcx) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp25, err := this._io.ReadBytes(int(1))
	if err != nil {
		return err
	}
	tmp25 = tmp25
	this.Magic = tmp25
	if !(bytes.Equal(this.Magic, []uint8{12})) {
		return kaitai.NewValidationNotEqualError([]uint8{12}, this.Magic, this._io, "/types/t_palette_256/seq/0")
	}
	for i := 0; i < int(256); i++ {
		_ = i
		tmp26 := NewPcx_Rgb()
		err = tmp26.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Colors = append(this.Colors, tmp26)
	}
	return err
}