Portable Image Format: Go parsing library

The Portable Image Format (PIF) is a basic, bitmap-like image format with the focus on ease of use (implementation) and small size for embedded applications.

See https://github.com/gfcwfzkm/PIF-Image-Format for more info.

File extension

pif

KS implementation details

License: LGPL-2.1
Minimal Kaitai Struct required: 0.9

References

This page hosts a formal specification of Portable Image 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 Portable Image Format

pif.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"
)


/**
 * The Portable Image Format (PIF) is a basic, bitmap-like image format with the
 * focus on ease of use (implementation) and small size for embedded
 * applications.
 * 
 * See <https://github.com/gfcwfzkm/PIF-Image-Format> for more info.
 * @see <a href="https://github.com/gfcwfzkm/PIF-Image-Format/blob/4ec261b/Specification/PIF%20Format%20Specification.pdf">Source</a>
 * @see <a href="https://github.com/gfcwfzkm/PIF-Image-Format/blob/4ec261b/C%20Library/pifdec.c#L300">Source</a>
 */

type Pif_ImageType int
const (
	Pif_ImageType__Rgb332 Pif_ImageType = 7763
	Pif_ImageType__Rgb888 Pif_ImageType = 17212
	Pif_ImageType__IndexedRgb332 Pif_ImageType = 18754
	Pif_ImageType__IndexedRgb565 Pif_ImageType = 18759
	Pif_ImageType__IndexedRgb888 Pif_ImageType = 18770
	Pif_ImageType__BlackWhite Pif_ImageType = 32170
	Pif_ImageType__Rgb16c Pif_ImageType = 47253
	Pif_ImageType__Rgb565 Pif_ImageType = 58821
)

type Pif_CompressionType int
const (
	Pif_CompressionType__None Pif_CompressionType = 0
	Pif_CompressionType__Rle Pif_CompressionType = 32222
)
type Pif struct {
	FileHeader *Pif_PifHeader
	InfoHeader *Pif_InformationHeader
	ColorTable *Pif_ColorTableData
	_io *kaitai.Stream
	_root *Pif
	_parent interface{}
	_raw_ColorTable []byte
	_f_imageData bool
	imageData []byte
}
func NewPif() *Pif {
	return &Pif{
	}
}

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

	tmp1 := NewPif_PifHeader()
	err = tmp1.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.FileHeader = tmp1
	tmp2 := NewPif_InformationHeader()
	err = tmp2.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.InfoHeader = tmp2
	tmp3, err := this.InfoHeader.UsesIndexedMode()
	if err != nil {
		return err
	}
	if (tmp3) {
		tmp4, err := this._io.ReadBytes(int(this.InfoHeader.LenColorTable))
		if err != nil {
			return err
		}
		tmp4 = tmp4
		this._raw_ColorTable = tmp4
		_io__raw_ColorTable := kaitai.NewStream(bytes.NewReader(this._raw_ColorTable))
		tmp5 := NewPif_ColorTableData()
		err = tmp5.Read(_io__raw_ColorTable, this, this._root)
		if err != nil {
			return err
		}
		this.ColorTable = tmp5
	}
	return err
}
func (this *Pif) ImageData() (v []byte, err error) {
	if (this._f_imageData) {
		return this.imageData, nil
	}
	_pos, err := this._io.Pos()
	if err != nil {
		return nil, err
	}
	_, err = this._io.Seek(int64(this.FileHeader.OfsImageData), io.SeekStart)
	if err != nil {
		return nil, err
	}
	tmp6, err := this._io.ReadBytes(int(this.InfoHeader.LenImageData))
	if err != nil {
		return nil, err
	}
	tmp6 = tmp6
	this.imageData = tmp6
	_, err = this._io.Seek(_pos, io.SeekStart)
	if err != nil {
		return nil, err
	}
	this._f_imageData = true
	this._f_imageData = true
	return this.imageData, nil
}
type Pif_PifHeader struct {
	Magic []byte
	LenFile uint32
	OfsImageData uint32
	_io *kaitai.Stream
	_root *Pif
	_parent *Pif
	_f_ofsImageDataMin bool
	ofsImageDataMin int
}
func NewPif_PifHeader() *Pif_PifHeader {
	return &Pif_PifHeader{
	}
}

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

	tmp7, err := this._io.ReadBytes(int(4))
	if err != nil {
		return err
	}
	tmp7 = tmp7
	this.Magic = tmp7
	if !(bytes.Equal(this.Magic, []uint8{80, 73, 70, 0})) {
		return kaitai.NewValidationNotEqualError([]uint8{80, 73, 70, 0}, this.Magic, this._io, "/types/pif_header/seq/0")
	}
	tmp8, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.LenFile = uint32(tmp8)
	tmp9, err := this.OfsImageDataMin()
	if err != nil {
		return err
	}
	tmp10, err := this.OfsImageDataMin()
	if err != nil {
		return err
	}
	if !(this.LenFile >= tmp10) {
		return kaitai.NewValidationLessThanError(tmp9, this.LenFile, this._io, "/types/pif_header/seq/1")
	}
	tmp11, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.OfsImageData = uint32(tmp11)
	tmp12, err := this.OfsImageDataMin()
	if err != nil {
		return err
	}
	tmp13, err := this.OfsImageDataMin()
	if err != nil {
		return err
	}
	if !(this.OfsImageData >= tmp13) {
		return kaitai.NewValidationLessThanError(tmp12, this.OfsImageData, this._io, "/types/pif_header/seq/2")
	}
	if !(this.OfsImageData <= this.LenFile) {
		return kaitai.NewValidationGreaterThanError(this.LenFile, this.OfsImageData, this._io, "/types/pif_header/seq/2")
	}
	return err
}
func (this *Pif_PifHeader) OfsImageDataMin() (v int, err error) {
	if (this._f_ofsImageDataMin) {
		return this.ofsImageDataMin, nil
	}
	this.ofsImageDataMin = int((12 + 16))
	this._f_ofsImageDataMin = true
	return this.ofsImageDataMin, nil
}
type Pif_InformationHeader struct {
	ImageType Pif_ImageType
	BitsPerPixel uint16
	Width uint16
	Height uint16
	LenImageData uint32
	LenColorTable uint16
	Compression Pif_CompressionType
	_io *kaitai.Stream
	_root *Pif
	_parent *Pif
	_f_lenColorTableEntry bool
	lenColorTableEntry int8
	_f_lenColorTableFull bool
	lenColorTableFull int
	_f_lenColorTableMax bool
	lenColorTableMax int
	_f_usesIndexedMode bool
	usesIndexedMode bool
}
func NewPif_InformationHeader() *Pif_InformationHeader {
	return &Pif_InformationHeader{
	}
}

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

	tmp14, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.ImageType = Pif_ImageType(tmp14)
	if !( ((this.ImageType == Pif_ImageType__Rgb888) || (this.ImageType == Pif_ImageType__Rgb565) || (this.ImageType == Pif_ImageType__Rgb332) || (this.ImageType == Pif_ImageType__Rgb16c) || (this.ImageType == Pif_ImageType__BlackWhite) || (this.ImageType == Pif_ImageType__IndexedRgb888) || (this.ImageType == Pif_ImageType__IndexedRgb565) || (this.ImageType == Pif_ImageType__IndexedRgb332)) ) {
		return kaitai.NewValidationNotAnyOfError(this.ImageType, this._io, "/types/information_header/seq/0")
	}
	tmp15, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.BitsPerPixel = uint16(tmp15)
	{
		_it := this.BitsPerPixel
		var tmp16 bool;
		if (this.ImageType == Pif_ImageType__Rgb888) {
			tmp16 = _it == 24
		} else {
			var tmp17 bool;
			if (this.ImageType == Pif_ImageType__Rgb565) {
				tmp17 = _it == 16
			} else {
				var tmp18 bool;
				if (this.ImageType == Pif_ImageType__Rgb332) {
					tmp18 = _it == 8
				} else {
					var tmp19 bool;
					if (this.ImageType == Pif_ImageType__Rgb16c) {
						tmp19 = _it == 4
					} else {
						var tmp20 bool;
						if (this.ImageType == Pif_ImageType__BlackWhite) {
							tmp20 = _it == 1
						} else {
							var tmp21 bool;
							tmp22, err := this.UsesIndexedMode()
							if err != nil {
								return err
							}
							if (tmp22) {
								tmp21 = _it <= 8
							} else {
								tmp21 = true
							}
							tmp20 = tmp21
						}
						tmp19 = tmp20
					}
					tmp18 = tmp19
				}
				tmp17 = tmp18
			}
			tmp16 = tmp17
		}
		if !(tmp16) {
			return kaitai.NewValidationExprError(this.BitsPerPixel, this._io, "/types/information_header/seq/1")
		}
	}
	tmp23, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.Width = uint16(tmp23)
	tmp24, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.Height = uint16(tmp24)
	tmp25, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.LenImageData = uint32(tmp25)
	if !(this.LenImageData <= (this._root.FileHeader.LenFile - this._root.FileHeader.OfsImageData)) {
		return kaitai.NewValidationGreaterThanError((this._root.FileHeader.LenFile - this._root.FileHeader.OfsImageData), this.LenImageData, this._io, "/types/information_header/seq/4")
	}
	tmp26, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.LenColorTable = uint16(tmp26)
	var tmp27 int;
	tmp28, err := this.UsesIndexedMode()
	if err != nil {
		return err
	}
	if (tmp28) {
		tmp29, err := this.LenColorTableEntry()
		if err != nil {
			return err
		}
		tmp27 = (tmp29 * 1)
	} else {
		tmp27 = 0
	}
	var tmp30 int;
	tmp31, err := this.UsesIndexedMode()
	if err != nil {
		return err
	}
	if (tmp31) {
		tmp32, err := this.LenColorTableEntry()
		if err != nil {
			return err
		}
		tmp30 = (tmp32 * 1)
	} else {
		tmp30 = 0
	}
	if !(this.LenColorTable >= tmp30) {
		return kaitai.NewValidationLessThanError(tmp27, this.LenColorTable, this._io, "/types/information_header/seq/5")
	}
	var tmp33 int;
	tmp34, err := this.UsesIndexedMode()
	if err != nil {
		return err
	}
	if (tmp34) {
		var tmp35 int;
		tmp36, err := this.LenColorTableMax()
		if err != nil {
			return err
		}
		tmp37, err := this.LenColorTableFull()
		if err != nil {
			return err
		}
		if (tmp36 < tmp37) {
			tmp38, err := this.LenColorTableMax()
			if err != nil {
				return err
			}
			tmp35 = tmp38
		} else {
			tmp39, err := this.LenColorTableFull()
			if err != nil {
				return err
			}
			tmp35 = tmp39
		}
		tmp33 = tmp35
	} else {
		tmp33 = 0
	}
	var tmp40 int;
	tmp41, err := this.UsesIndexedMode()
	if err != nil {
		return err
	}
	if (tmp41) {
		var tmp42 int;
		tmp43, err := this.LenColorTableMax()
		if err != nil {
			return err
		}
		tmp44, err := this.LenColorTableFull()
		if err != nil {
			return err
		}
		if (tmp43 < tmp44) {
			tmp45, err := this.LenColorTableMax()
			if err != nil {
				return err
			}
			tmp42 = tmp45
		} else {
			tmp46, err := this.LenColorTableFull()
			if err != nil {
				return err
			}
			tmp42 = tmp46
		}
		tmp40 = tmp42
	} else {
		tmp40 = 0
	}
	if !(this.LenColorTable <= tmp40) {
		return kaitai.NewValidationGreaterThanError(tmp33, this.LenColorTable, this._io, "/types/information_header/seq/5")
	}
	tmp47, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.Compression = Pif_CompressionType(tmp47)
	if !( ((this.Compression == Pif_CompressionType__None) || (this.Compression == Pif_CompressionType__Rle)) ) {
		return kaitai.NewValidationNotAnyOfError(this.Compression, this._io, "/types/information_header/seq/6")
	}
	return err
}
func (this *Pif_InformationHeader) LenColorTableEntry() (v int8, err error) {
	if (this._f_lenColorTableEntry) {
		return this.lenColorTableEntry, nil
	}
	var tmp48 int8;
	if (this.ImageType == Pif_ImageType__IndexedRgb888) {
		tmp48 = 3
	} else {
		var tmp49 int8;
		if (this.ImageType == Pif_ImageType__IndexedRgb565) {
			tmp49 = 2
		} else {
			var tmp50 int8;
			if (this.ImageType == Pif_ImageType__IndexedRgb332) {
				tmp50 = 1
			} else {
				tmp50 = 0
			}
			tmp49 = tmp50
		}
		tmp48 = tmp49
	}
	this.lenColorTableEntry = int8(tmp48)
	this._f_lenColorTableEntry = true
	return this.lenColorTableEntry, nil
}
func (this *Pif_InformationHeader) LenColorTableFull() (v int, err error) {
	if (this._f_lenColorTableFull) {
		return this.lenColorTableFull, nil
	}
	tmp51, err := this.LenColorTableEntry()
	if err != nil {
		return 0, err
	}
	this.lenColorTableFull = int((tmp51 * (1 << this.BitsPerPixel)))
	this._f_lenColorTableFull = true
	return this.lenColorTableFull, nil
}
func (this *Pif_InformationHeader) LenColorTableMax() (v int, err error) {
	if (this._f_lenColorTableMax) {
		return this.lenColorTableMax, nil
	}
	tmp52, err := this._root.FileHeader.OfsImageDataMin()
	if err != nil {
		return 0, err
	}
	this.lenColorTableMax = int((this._root.FileHeader.OfsImageData - tmp52))
	this._f_lenColorTableMax = true
	return this.lenColorTableMax, nil
}
func (this *Pif_InformationHeader) UsesIndexedMode() (v bool, err error) {
	if (this._f_usesIndexedMode) {
		return this.usesIndexedMode, nil
	}
	tmp53, err := this.LenColorTableEntry()
	if err != nil {
		return false, err
	}
	this.usesIndexedMode = bool(tmp53 != 0)
	this._f_usesIndexedMode = true
	return this.usesIndexedMode, nil
}

/**
 * See <https://github.com/gfcwfzkm/PIF-Image-Format/blob/4ec261b/Specification/PIF%20Format%20Specification.pdf>:
 * 
 * > Bits per Pixel: Bit size that each Pixel occupies. Bit size for an
 * > Indexed Image cannot go beyond 8 bits.
 */

/**
 * See <https://github.com/gfcwfzkm/PIF-Image-Format/blob/4ec261b/Specification/PIF%20Format%20Specification.pdf>:
 * 
 * > Color Table Size: (...), only used in Indexed mode, otherwise zero.
 * ---
 * > **Note**: The presence of the Color Table is mandatory when Bits per
 * > Pixel <= 8, unless Image Type states RGB332, RGB16C or B/W
 * ---
 * > **Color Table** (semi-optional)
 * >
 * > (...) The amount of Colors has to be same or less than [Bits per
 * > Pixel] allow, otherwise the image is invalid.
 */
type Pif_ColorTableData struct {
	Entries []int
	_io *kaitai.Stream
	_root *Pif
	_parent *Pif
}
func NewPif_ColorTableData() *Pif_ColorTableData {
	return &Pif_ColorTableData{
	}
}

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

	for i := 1;; i++ {
		tmp54, err := this._io.EOF()
		if err != nil {
			return err
		}
		if tmp54 {
			break
		}
		switch (this._root.InfoHeader.ImageType) {
		case Pif_ImageType__IndexedRgb888:
			tmp55, err := this._io.ReadBitsIntLe(24)
			if err != nil {
				return err
			}
			this.Entries = append(this.Entries, tmp55)
		case Pif_ImageType__IndexedRgb565:
			tmp56, err := this._io.ReadBitsIntLe(16)
			if err != nil {
				return err
			}
			this.Entries = append(this.Entries, tmp56)
		case Pif_ImageType__IndexedRgb332:
			tmp57, err := this._io.ReadBitsIntLe(8)
			if err != nil {
				return err
			}
			this.Entries = append(this.Entries, tmp57)
		}
	}
	return err
}