Editing a .gbr file with a bash script

Hi,

I’d like to edit and save a .gbr with a bash script, however these files are binary in nature. All I’m hoping to do, is edit the name of the brush that’s stored as a string in the file, something like:

sed -i "s/$source_template/$tool_package_item/g" "$tool_package_item_path.$file_extension"

does this, but as you can see in the C code below, the header size is also influenced by the string length of the name…
…so unless the names are identical in length, the .gbr file becomes corrupted.

I’d love to be able to solve this puzzle, any help appreciated, thanks

gimp_brush_save (GimpData       *data,
                 GOutputStream  *output,
                 GError        **error)
{
  GimpBrush       *brush  = GIMP_BRUSH (data);
  GimpTempBuf     *mask   = gimp_brush_get_mask (brush);
  GimpTempBuf     *pixmap = gimp_brush_get_pixmap (brush);
  GimpBrushHeader  header;
  const gchar     *name;
  gint             width;
  gint             height;

  name   = gimp_object_get_name (brush);
  width  = gimp_temp_buf_get_width  (mask);
  height = gimp_temp_buf_get_height (mask);

  header.header_size  = g_htonl (sizeof (GimpBrushHeader) +
                                 strlen (name) + 1);
  header.version      = g_htonl (2);
  header.width        = g_htonl (width);
  header.height       = g_htonl (height);
  header.bytes        = g_htonl (pixmap ? 4 : 1);
  header.magic_number = g_htonl (GIMP_BRUSH_MAGIC);
  header.spacing      = g_htonl (gimp_brush_get_spacing (brush));

  if (! g_output_stream_write_all (output, &header, sizeof (header),
                                   NULL, NULL, error))
    {
      return FALSE;
    }

  if (! g_output_stream_write_all (output, name, strlen (name) + 1,
                                   NULL, NULL, error))
    {
      return FALSE;
    }

  if (pixmap)
    {
      gsize   size = width * height * 4;
      guchar *data = g_malloc (size);
      guchar *p    = gimp_temp_buf_get_data (pixmap);
      guchar *m    = gimp_temp_buf_get_data (mask);
      guchar *d    = data;
      gint    i;

      for (i = 0; i < width * height; i++)
        {
          *d++ = *p++;
          *d++ = *p++;
          *d++ = *p++;
          *d++ = *m++;
        }

      if (! g_output_stream_write_all (output, data, size,
                                       NULL, NULL, error))
        {
          g_free (data);

          return FALSE;
        }

      g_free (data);
    }
  else
    {
      if (! g_output_stream_write_all (output,
                                       gimp_temp_buf_get_data (mask),
                                       gimp_temp_buf_get_data_size (mask),
                                       NULL, NULL, error))
        {
          return FALSE;
        }
    }

  return TRUE;
}

Technically, you can split the file into several parts using utilities such as dd, edit the parts you want to change and rebuild the result, still using dd.

Or you make your life simpler and use Python.

1 Like

many times I’ve wished to know python, thanks for your pointer

it’s not too much easier to write cli arguments correctly for example for bbe stream editor, than write several lines in python or in c

Solved :slight_smile:

#!/bin/bash

# This script renames a GIMP brush (.gbr)

# Define variables
brush_file="$1"         # Input brush file
brush_name="$2"         # Current brush name
brush_new_name="$3"     # New brush name

debug=1                 # Set to 1 for debug output

echo "Renaming $brush_name to $brush_new_name"

# Check if the source brush exists
if [ ! -f "$brush_file" ]; then
  echo "Error: Source brush file not found: '$brush_file'"
  exit 1
fi

# Calculate the original header size
original_header_size=$(od -An -N 4 -tu4 --endian=big "$brush_file" | tr -d '[:space:]')

# Calculate the original name size
original_name_size=${#brush_name}

# Calculate the new header size
new_name_size=${#brush_new_name}

# Perform arithmetic operation: Subtract original name size from header size
header_minus_name=$(( original_header_size - original_name_size ))
header_plus_new_name=$(( header_minus_name + new_name_size ))

# Replace the first 4 bytes in the destination file with the new header size
printf '%08x\n' "$header_plus_new_name" | xxd -r -p | dd conv=notrunc of="$brush_file" bs=1 seek=0 count=4 > /dev/null

# Verify the replacement by checking the new header size in the destination file
verified_header_size=$(od -An -N 4 -tu4 --endian=big "$brush_file" | tr -d '[:space:]')

if [ $debug -eq 1 ]; then
  echo "Original Header size   $original_header_size"
  echo "Original Name size     $original_name_size"
  echo "Header minus name size $header_minus_name"
  echo "New Name size          $new_name_size"
  echo "New Header size        $header_plus_new_name"
  echo "Verified Header size   $verified_header_size"
fi

# Replace brush name in the renamed file
sed -i "s/$brush_name/$brush_new_name/g" "$brush_file"

Not really. Only works with ASCII characters. Breaks if character!=byte.

$ bash /tmp/rnbrush DejaVu.gbr DejaVu "DéjàVu"
Renaming DejaVu to DéjàVu
4+0 records in
4+0 records out
4 bytes copied, 2.5113e-05 s, 159 kB/s
Original Header size   35
Original Name size     6
Header minus name size 29
New Name size          6  ### <------ Should probably be 8
New Header size        35
Verified Header size   35

Solved for my puzzle needs, you have created a new one, what’s the solution?

wc can count bytes (wc -c) or characters (wc -m).

1 Like
string="DéjàVu"
size=$(echo -n "$string" | wc -c)
echo "Size of the string is: $size"

Size of the string is: 8

Using wc -c in Bash will give you the byte count, which is equivalent to what strlen() in C would return for the string “DéjàVu”. Since each character in “DéjàVu” is represented by one or more bytes in UTF-8 encoding, the byte count will match the count returned by strlen() in C.

#!/bin/bash

# This script renames a GIMP brush (.gbr)

# Define variables
brush_file="$1"         # Input brush file
brush_name="$2"         # Current brush name
brush_new_name="$3"     # New brush name

debug=1                 # Set to 1 for debug output

echo "Renaming $brush_name to $brush_new_name"

# Check if the source brush exists
if [ ! -f "$brush_file" ]; then
  echo "Error: Source brush file not found: '$brush_file'"
  exit 1
fi

# Calculate the original header size
original_header_size=$(od -An -N 4 -tu4 --endian=big "$brush_file" | tr -d '[:space:]')

# Calculate the original name size
original_name_size=$(echo -n "$brush_name" | wc -c)

# Calculate the new name size
new_name_size=$(echo -n "$brush_new_name" | wc -c)

# Perform arithmetic operation: Subtract original name size from header size
header_minus_name=$(( original_header_size - original_name_size ))
header_plus_new_name=$(( header_minus_name + new_name_size ))

# Replace the first 4 bytes in the destination file with the new header size
printf '%08x\n' "$header_plus_new_name" | xxd -r -p | dd conv=notrunc of="$brush_file" bs=1 seek=0 count=4 > /dev/null

# Verify the replacement by checking the new header size in the destination file
verified_header_size=$(od -An -N 4 -tu4 --endian=big "$brush_file" | tr -d '[:space:]')

if [ $debug -eq 1 ]; then
  echo "Original Header size   $original_header_size"
  echo "Original Name size     $original_name_size"
  echo "Header minus name size $header_minus_name"
  echo "New Name size          $new_name_size"
  echo "New Header size        $header_plus_new_name"
  echo "Verified Header size   $verified_header_size"
fi

# Replace brush name in the renamed file
sed -i "s/$brush_name/$brush_new_name/g" "$brush_file"