GIMP brush file version 2: Nim 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.

Nim source code to parse GIMP brush file version 2

gimp_brush.nim

import kaitai_struct_nim_runtime
import options

type
  GimpBrush* = ref object of KaitaiStruct
    `lenHeader`*: uint32
    `header`*: GimpBrush_Header
    `parent`*: KaitaiStruct
    `rawHeader`*: seq[byte]
    `lenBodyInst`: int
    `lenBodyInstFlag`: bool
    `bodyInst`: seq[byte]
    `bodyInstFlag`: bool
  GimpBrush_ColorDepth* = enum
    grayscale = 1
    rgba = 4
  GimpBrush_Header* = ref object of KaitaiStruct
    `version`*: uint32
    `width`*: uint32
    `height`*: uint32
    `bytesPerPixel`*: GimpBrush_ColorDepth
    `magic`*: seq[byte]
    `spacing`*: uint32
    `brushName`*: string
    `parent`*: GimpBrush
  GimpBrush_Bitmap* = ref object of KaitaiStruct
    `rows`*: seq[GimpBrush_Row]
    `parent`*: KaitaiStruct
  GimpBrush_Row* = ref object of KaitaiStruct
    `pixels`*: seq[KaitaiStruct]
    `parent`*: KaitaiStruct
  GimpBrush_Row_PixelGray* = ref object of KaitaiStruct
    `gray`*: uint8
    `parent`*: KaitaiStruct
    `redInst`: int8
    `redInstFlag`: bool
    `greenInst`: int8
    `greenInstFlag`: bool
    `blueInst`: int8
    `blueInstFlag`: bool
    `alphaInst`: uint8
    `alphaInstFlag`: bool
  GimpBrush_Row_PixelRgba* = ref object of KaitaiStruct
    `red`*: uint8
    `green`*: uint8
    `blue`*: uint8
    `alpha`*: uint8
    `parent`*: KaitaiStruct

proc read*(_: typedesc[GimpBrush], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): GimpBrush
proc read*(_: typedesc[GimpBrush_Header], io: KaitaiStream, root: KaitaiStruct, parent: GimpBrush): GimpBrush_Header
proc read*(_: typedesc[GimpBrush_Bitmap], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): GimpBrush_Bitmap
proc read*(_: typedesc[GimpBrush_Row], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): GimpBrush_Row
proc read*(_: typedesc[GimpBrush_Row_PixelGray], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): GimpBrush_Row_PixelGray
proc read*(_: typedesc[GimpBrush_Row_PixelRgba], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): GimpBrush_Row_PixelRgba

proc lenBody*(this: GimpBrush): int
proc body*(this: GimpBrush): seq[byte]
proc red*(this: GimpBrush_Row_PixelGray): int8
proc green*(this: GimpBrush_Row_PixelGray): int8
proc blue*(this: GimpBrush_Row_PixelGray): int8
proc alpha*(this: GimpBrush_Row_PixelGray): uint8


##[
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.

@see <a href="https://github.com/GNOME/gimp/blob/441631322b/devel-docs/gbr.txt">Source</a>
]##
proc read*(_: typedesc[GimpBrush], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): GimpBrush =
  template this: untyped = result
  this = new(GimpBrush)
  let root = if root == nil: cast[GimpBrush](this) else: cast[GimpBrush](root)
  this.io = io
  this.root = root
  this.parent = parent

  let lenHeaderExpr = this.io.readU4be()
  this.lenHeader = lenHeaderExpr
  let rawHeaderExpr = this.io.readBytes(int((this.lenHeader - 4)))
  this.rawHeader = rawHeaderExpr
  let rawHeaderIo = newKaitaiStream(rawHeaderExpr)
  let headerExpr = GimpBrush_Header.read(rawHeaderIo, this.root, this)
  this.header = headerExpr

proc lenBody(this: GimpBrush): int = 
  if this.lenBodyInstFlag:
    return this.lenBodyInst
  let lenBodyInstExpr = int(((this.header.width * this.header.height) * ord(this.header.bytesPerPixel)))
  this.lenBodyInst = lenBodyInstExpr
  this.lenBodyInstFlag = true
  return this.lenBodyInst

proc body(this: GimpBrush): seq[byte] = 
  if this.bodyInstFlag:
    return this.bodyInst
  let pos = this.io.pos()
  this.io.seek(int(this.lenHeader))
  let bodyInstExpr = this.io.readBytes(int(this.lenBody))
  this.bodyInst = bodyInstExpr
  this.io.seek(pos)
  this.bodyInstFlag = true
  return this.bodyInst

proc fromFile*(_: typedesc[GimpBrush], filename: string): GimpBrush =
  GimpBrush.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[GimpBrush_Header], io: KaitaiStream, root: KaitaiStruct, parent: GimpBrush): GimpBrush_Header =
  template this: untyped = result
  this = new(GimpBrush_Header)
  let root = if root == nil: cast[GimpBrush](this) else: cast[GimpBrush](root)
  this.io = io
  this.root = root
  this.parent = parent

  let versionExpr = this.io.readU4be()
  this.version = versionExpr

  ##[
  @see <a href="https://github.com/GNOME/gimp/blob/441631322b/app/core/gimpbrush-load.c#L170">Source</a>
  @see <a href="https://github.com/GNOME/gimp/blob/441631322b/app/core/gimpbrush-header.h#L24">Source</a>
  ]##
  let widthExpr = this.io.readU4be()
  this.width = widthExpr

  ##[
  @see <a href="https://github.com/GNOME/gimp/blob/441631322b/app/core/gimpbrush-load.c#L177">Source</a>
  @see <a href="https://github.com/GNOME/gimp/blob/441631322b/app/core/gimpbrush-header.h#L24">Source</a>
  ]##
  let heightExpr = this.io.readU4be()
  this.height = heightExpr
  let bytesPerPixelExpr = GimpBrush_ColorDepth(this.io.readU4be())
  this.bytesPerPixel = bytesPerPixelExpr
  let magicExpr = this.io.readBytes(int(4))
  this.magic = magicExpr

  ##[
  Default spacing to be used for brush. Percentage of brush width.
  ]##
  let spacingExpr = this.io.readU4be()
  this.spacing = spacingExpr
  let brushNameExpr = encode(this.io.readBytesFull().bytesTerminate(0, false), "UTF-8")
  this.brushName = brushNameExpr

proc fromFile*(_: typedesc[GimpBrush_Header], filename: string): GimpBrush_Header =
  GimpBrush_Header.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[GimpBrush_Bitmap], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): GimpBrush_Bitmap =
  template this: untyped = result
  this = new(GimpBrush_Bitmap)
  let root = if root == nil: cast[GimpBrush](this) else: cast[GimpBrush](root)
  this.io = io
  this.root = root
  this.parent = parent

  for i in 0 ..< int(GimpBrush(this.root).header.height):
    let it = GimpBrush_Row.read(this.io, this.root, this)
    this.rows.add(it)

proc fromFile*(_: typedesc[GimpBrush_Bitmap], filename: string): GimpBrush_Bitmap =
  GimpBrush_Bitmap.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[GimpBrush_Row], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): GimpBrush_Row =
  template this: untyped = result
  this = new(GimpBrush_Row)
  let root = if root == nil: cast[GimpBrush](this) else: cast[GimpBrush](root)
  this.io = io
  this.root = root
  this.parent = parent

  for i in 0 ..< int(GimpBrush(this.root).header.width):
    block:
      let on = GimpBrush(this.root).header.bytesPerPixel
      if on == gimp_brush.grayscale:
        let it = GimpBrush_Row_PixelGray.read(this.io, this.root, this)
        this.pixels.add(it)
      elif on == gimp_brush.rgba:
        let it = GimpBrush_Row_PixelRgba.read(this.io, this.root, this)
        this.pixels.add(it)

proc fromFile*(_: typedesc[GimpBrush_Row], filename: string): GimpBrush_Row =
  GimpBrush_Row.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[GimpBrush_Row_PixelGray], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): GimpBrush_Row_PixelGray =
  template this: untyped = result
  this = new(GimpBrush_Row_PixelGray)
  let root = if root == nil: cast[GimpBrush](this) else: cast[GimpBrush](root)
  this.io = io
  this.root = root
  this.parent = parent

  let grayExpr = this.io.readU1()
  this.gray = grayExpr

proc red(this: GimpBrush_Row_PixelGray): int8 = 
  if this.redInstFlag:
    return this.redInst
  let redInstExpr = int8(0)
  this.redInst = redInstExpr
  this.redInstFlag = true
  return this.redInst

proc green(this: GimpBrush_Row_PixelGray): int8 = 
  if this.greenInstFlag:
    return this.greenInst
  let greenInstExpr = int8(0)
  this.greenInst = greenInstExpr
  this.greenInstFlag = true
  return this.greenInst

proc blue(this: GimpBrush_Row_PixelGray): int8 = 
  if this.blueInstFlag:
    return this.blueInst
  let blueInstExpr = int8(0)
  this.blueInst = blueInstExpr
  this.blueInstFlag = true
  return this.blueInst

proc alpha(this: GimpBrush_Row_PixelGray): uint8 = 
  if this.alphaInstFlag:
    return this.alphaInst
  let alphaInstExpr = uint8(this.gray)
  this.alphaInst = alphaInstExpr
  this.alphaInstFlag = true
  return this.alphaInst

proc fromFile*(_: typedesc[GimpBrush_Row_PixelGray], filename: string): GimpBrush_Row_PixelGray =
  GimpBrush_Row_PixelGray.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[GimpBrush_Row_PixelRgba], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): GimpBrush_Row_PixelRgba =
  template this: untyped = result
  this = new(GimpBrush_Row_PixelRgba)
  let root = if root == nil: cast[GimpBrush](this) else: cast[GimpBrush](root)
  this.io = io
  this.root = root
  this.parent = parent

  let redExpr = this.io.readU1()
  this.red = redExpr
  let greenExpr = this.io.readU1()
  this.green = greenExpr
  let blueExpr = this.io.readU1()
  this.blue = blueExpr
  let alphaExpr = this.io.readU1()
  this.alpha = alphaExpr

proc fromFile*(_: typedesc[GimpBrush_Row_PixelRgba], filename: string): GimpBrush_Row_PixelRgba =
  GimpBrush_Row_PixelRgba.read(newKaitaiFileStream(filename), nil, nil)