GIMP brush file version 2: C++98/STL parsing library

GIMP brush format is native to the GIMP image editor for storing a brush or a texture. It can be used in all Paint Tools, for example Pencil and Paintbrush. It works by repeating the brush bitmap as you move the tool. The Spacing parameter sets the distance between the brush marks as a percentage of brush width. Its default value can be set in the brush file.

You can also use GIMP to create new brushes in this format. Custom brushes can be loaded into GIMP for use in the paint tools by copying them into one of the Brush Folders - select Edit > Preferences in the menu bar, expand the Folders section and choose Brushes to see the recognized Brush Folders or to add new ones.

Application

GIMP (GNU Image Manipulation Program)

File extension

gbr

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.9

References

This page hosts a formal specification of GIMP brush file version 2 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.gbr", 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:
    gimp_brush_t data(&ks);
    

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

data.len_header() // => get len header

C++98/STL source code to parse GIMP brush file version 2

gimp_brush.h

#ifndef GIMP_BRUSH_H_
#define GIMP_BRUSH_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

/**
 * GIMP brush format is native to the GIMP image editor for storing a brush or a texture.
 * It can be used in all [Paint Tools](https://docs.gimp.org/2.10/en/gimp-tools-paint.html),
 * for example Pencil and Paintbrush. It works by repeating the brush bitmap as you move
 * the tool. The Spacing parameter sets the distance between the brush marks as a percentage
 * of brush width. Its default value can be set in the brush file.
 * 
 * You can also use GIMP to create new brushes in this format. Custom brushes can be loaded
 * into GIMP for use in the paint tools by copying them into one of the Brush Folders -
 * select **Edit** > **Preferences** in the menu bar, expand the **Folders** section
 * and choose **Brushes** to see the recognized Brush Folders or to add new ones.
 * \sa https://github.com/GNOME/gimp/blob/441631322b/devel-docs/gbr.txt Source
 */

class gimp_brush_t : public kaitai::kstruct {

public:
    class header_t;
    class bitmap_t;
    class row_t;

    enum color_depth_t {
        COLOR_DEPTH_GRAYSCALE = 1,
        COLOR_DEPTH_RGBA = 4
    };

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

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

public:
    ~gimp_brush_t();

    class header_t : public kaitai::kstruct {

    public:

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

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

    public:
        ~header_t();

    private:
        uint32_t m_version;
        uint32_t m_width;
        uint32_t m_height;
        color_depth_t m_bytes_per_pixel;
        std::string m_magic;
        uint32_t m_spacing;
        std::string m_brush_name;
        gimp_brush_t* m__root;
        gimp_brush_t* m__parent;

    public:
        uint32_t version() const { return m_version; }

        /**
         * \sa https://github.com/GNOME/gimp/blob/441631322b/app/core/gimpbrush-load.c#L170 Source
         * \sa https://github.com/GNOME/gimp/blob/441631322b/app/core/gimpbrush-header.h#L24 Source
         */
        uint32_t width() const { return m_width; }

        /**
         * \sa https://github.com/GNOME/gimp/blob/441631322b/app/core/gimpbrush-load.c#L177 Source
         * \sa https://github.com/GNOME/gimp/blob/441631322b/app/core/gimpbrush-header.h#L24 Source
         */
        uint32_t height() const { return m_height; }
        color_depth_t bytes_per_pixel() const { return m_bytes_per_pixel; }
        std::string magic() const { return m_magic; }

        /**
         * Default spacing to be used for brush. Percentage of brush width.
         */
        uint32_t spacing() const { return m_spacing; }
        std::string brush_name() const { return m_brush_name; }
        gimp_brush_t* _root() const { return m__root; }
        gimp_brush_t* _parent() const { return m__parent; }
    };

    class bitmap_t : public kaitai::kstruct {

    public:

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

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

    public:
        ~bitmap_t();

    private:
        std::vector<row_t*>* m_rows;
        gimp_brush_t* m__root;
        kaitai::kstruct* m__parent;

    public:
        std::vector<row_t*>* rows() const { return m_rows; }
        gimp_brush_t* _root() const { return m__root; }
        kaitai::kstruct* _parent() const { return m__parent; }
    };

    class row_t : public kaitai::kstruct {

    public:
        class pixel_gray_t;
        class pixel_rgba_t;

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

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

    public:
        ~row_t();

        class pixel_gray_t : public kaitai::kstruct {

        public:

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

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

        public:
            ~pixel_gray_t();

        private:
            bool f_red;
            int8_t m_red;

        public:
            int8_t red();

        private:
            bool f_green;
            int8_t m_green;

        public:
            int8_t green();

        private:
            bool f_blue;
            int8_t m_blue;

        public:
            int8_t blue();

        private:
            bool f_alpha;
            uint8_t m_alpha;

        public:
            uint8_t alpha();

        private:
            uint8_t m_gray;
            gimp_brush_t* m__root;
            kaitai::kstruct* m__parent;

        public:
            uint8_t gray() const { return m_gray; }
            gimp_brush_t* _root() const { return m__root; }
            kaitai::kstruct* _parent() const { return m__parent; }
        };

        class pixel_rgba_t : public kaitai::kstruct {

        public:

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

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

        public:
            ~pixel_rgba_t();

        private:
            uint8_t m_red;
            uint8_t m_green;
            uint8_t m_blue;
            uint8_t m_alpha;
            gimp_brush_t* m__root;
            kaitai::kstruct* m__parent;

        public:
            uint8_t red() const { return m_red; }
            uint8_t green() const { return m_green; }
            uint8_t blue() const { return m_blue; }
            uint8_t alpha() const { return m_alpha; }
            gimp_brush_t* _root() const { return m__root; }
            kaitai::kstruct* _parent() const { return m__parent; }
        };

    private:
        std::vector<kaitai::kstruct*>* m_pixels;
        gimp_brush_t* m__root;
        kaitai::kstruct* m__parent;

    public:
        std::vector<kaitai::kstruct*>* pixels() const { return m_pixels; }
        gimp_brush_t* _root() const { return m__root; }
        kaitai::kstruct* _parent() const { return m__parent; }
    };

private:
    bool f_len_body;
    int32_t m_len_body;

public:
    int32_t len_body();

private:
    bool f_body;
    std::string m_body;

public:
    std::string body();

private:
    uint32_t m_len_header;
    header_t* m_header;
    gimp_brush_t* m__root;
    kaitai::kstruct* m__parent;
    std::string m__raw_header;
    kaitai::kstream* m__io__raw_header;

public:
    uint32_t len_header() const { return m_len_header; }
    header_t* header() const { return m_header; }
    gimp_brush_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
    std::string _raw_header() const { return m__raw_header; }
    kaitai::kstream* _io__raw_header() const { return m__io__raw_header; }
};

#endif  // GIMP_BRUSH_H_

gimp_brush.cpp

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

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

gimp_brush_t::gimp_brush_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, gimp_brush_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    m_header = 0;
    m__io__raw_header = 0;
    f_len_body = false;
    f_body = false;

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

void gimp_brush_t::_read() {
    m_len_header = m__io->read_u4be();
    m__raw_header = m__io->read_bytes((len_header() - 4));
    m__io__raw_header = new kaitai::kstream(m__raw_header);
    m_header = new header_t(m__io__raw_header, this, m__root);
}

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

void gimp_brush_t::_clean_up() {
    if (m__io__raw_header) {
        delete m__io__raw_header; m__io__raw_header = 0;
    }
    if (m_header) {
        delete m_header; m_header = 0;
    }
    if (f_body) {
    }
}

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

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

void gimp_brush_t::header_t::_read() {
    m_version = m__io->read_u4be();
    if (!(version() == 2)) {
        throw kaitai::validation_not_equal_error<uint32_t>(2, version(), _io(), std::string("/types/header/seq/0"));
    }
    m_width = m__io->read_u4be();
    if (!(width() >= 1)) {
        throw kaitai::validation_less_than_error<uint32_t>(1, width(), _io(), std::string("/types/header/seq/1"));
    }
    if (!(width() <= 10000)) {
        throw kaitai::validation_greater_than_error<uint32_t>(10000, width(), _io(), std::string("/types/header/seq/1"));
    }
    m_height = m__io->read_u4be();
    if (!(height() >= 1)) {
        throw kaitai::validation_less_than_error<uint32_t>(1, height(), _io(), std::string("/types/header/seq/2"));
    }
    if (!(height() <= 10000)) {
        throw kaitai::validation_greater_than_error<uint32_t>(10000, height(), _io(), std::string("/types/header/seq/2"));
    }
    m_bytes_per_pixel = static_cast<gimp_brush_t::color_depth_t>(m__io->read_u4be());
    m_magic = m__io->read_bytes(4);
    if (!(magic() == std::string("\x47\x49\x4D\x50", 4))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x47\x49\x4D\x50", 4), magic(), _io(), std::string("/types/header/seq/4"));
    }
    m_spacing = m__io->read_u4be();
    m_brush_name = kaitai::kstream::bytes_to_str(kaitai::kstream::bytes_terminate(m__io->read_bytes_full(), 0, false), std::string("UTF-8"));
}

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

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

gimp_brush_t::bitmap_t::bitmap_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, gimp_brush_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_rows = 0;

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

void gimp_brush_t::bitmap_t::_read() {
    m_rows = new std::vector<row_t*>();
    const int l_rows = _root()->header()->height();
    for (int i = 0; i < l_rows; i++) {
        m_rows->push_back(new row_t(m__io, this, m__root));
    }
}

gimp_brush_t::bitmap_t::~bitmap_t() {
    _clean_up();
}

void gimp_brush_t::bitmap_t::_clean_up() {
    if (m_rows) {
        for (std::vector<row_t*>::iterator it = m_rows->begin(); it != m_rows->end(); ++it) {
            delete *it;
        }
        delete m_rows; m_rows = 0;
    }
}

gimp_brush_t::row_t::row_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, gimp_brush_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_pixels = 0;

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

void gimp_brush_t::row_t::_read() {
    m_pixels = new std::vector<kaitai::kstruct*>();
    const int l_pixels = _root()->header()->width();
    for (int i = 0; i < l_pixels; i++) {
        switch (_root()->header()->bytes_per_pixel()) {
        case gimp_brush_t::COLOR_DEPTH_GRAYSCALE: {
            m_pixels->push_back(new pixel_gray_t(m__io, this, m__root));
            break;
        }
        case gimp_brush_t::COLOR_DEPTH_RGBA: {
            m_pixels->push_back(new pixel_rgba_t(m__io, this, m__root));
            break;
        }
        }
    }
}

gimp_brush_t::row_t::~row_t() {
    _clean_up();
}

void gimp_brush_t::row_t::_clean_up() {
    if (m_pixels) {
        for (std::vector<kaitai::kstruct*>::iterator it = m_pixels->begin(); it != m_pixels->end(); ++it) {
            delete *it;
        }
        delete m_pixels; m_pixels = 0;
    }
}

gimp_brush_t::row_t::pixel_gray_t::pixel_gray_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, gimp_brush_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    f_red = false;
    f_green = false;
    f_blue = false;
    f_alpha = false;

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

void gimp_brush_t::row_t::pixel_gray_t::_read() {
    m_gray = m__io->read_u1();
}

gimp_brush_t::row_t::pixel_gray_t::~pixel_gray_t() {
    _clean_up();
}

void gimp_brush_t::row_t::pixel_gray_t::_clean_up() {
}

int8_t gimp_brush_t::row_t::pixel_gray_t::red() {
    if (f_red)
        return m_red;
    m_red = 0;
    f_red = true;
    return m_red;
}

int8_t gimp_brush_t::row_t::pixel_gray_t::green() {
    if (f_green)
        return m_green;
    m_green = 0;
    f_green = true;
    return m_green;
}

int8_t gimp_brush_t::row_t::pixel_gray_t::blue() {
    if (f_blue)
        return m_blue;
    m_blue = 0;
    f_blue = true;
    return m_blue;
}

uint8_t gimp_brush_t::row_t::pixel_gray_t::alpha() {
    if (f_alpha)
        return m_alpha;
    m_alpha = gray();
    f_alpha = true;
    return m_alpha;
}

gimp_brush_t::row_t::pixel_rgba_t::pixel_rgba_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, gimp_brush_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;

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

void gimp_brush_t::row_t::pixel_rgba_t::_read() {
    m_red = m__io->read_u1();
    m_green = m__io->read_u1();
    m_blue = m__io->read_u1();
    m_alpha = m__io->read_u1();
}

gimp_brush_t::row_t::pixel_rgba_t::~pixel_rgba_t() {
    _clean_up();
}

void gimp_brush_t::row_t::pixel_rgba_t::_clean_up() {
}

int32_t gimp_brush_t::len_body() {
    if (f_len_body)
        return m_len_body;
    m_len_body = ((header()->width() * header()->height()) * header()->bytes_per_pixel());
    f_len_body = true;
    return m_len_body;
}

std::string gimp_brush_t::body() {
    if (f_body)
        return m_body;
    std::streampos _pos = m__io->pos();
    m__io->seek(len_header());
    m_body = m__io->read_bytes(len_body());
    m__io->seek(_pos);
    f_body = true;
    return m_body;
}