Skip to content

Writing a Custom Brush

Cameron White edited this page Apr 20, 2023 · 8 revisions

This guide assumes that you have set up a basic add-in as described in Writing an Add-in.

This guide will demonstrate how to write a custom brush that can be used by the Paintbrush tool. The source code for this example is available at https://github.com/PintaProject/BlockBrush, and the brush can be installed from Pinta's add-in repository.

The Basics

Ensure that the add-in description file (.addin.xml) has the category set to Brushes. This will make it easier for users to find your brush in the Add-in Gallery. The add-in description file should now look like:

<?xml version="1.0" encoding="UTF-8"?>
<Addin id="BlockBrush" version="0.1" category="Brushes">
    <Header>
        <Name>Block Brush</Name>
        <Description>A brush similar to the block brush in GIMP.</Description>
        <Author>Cameron White</Author>
        <Url>https://github.com/PintaProject/BlockBrush</Url>
    </Header>
    <Dependencies>
        <Addin id="Pinta" version="1.5" />
    </Dependencies>
</Addin>

All brushes inherit from Pinta.Core.BasePaintBrush. There are two required methods that you must implement:

  • You must provide the Name of the brush. This should be a user-friendly (and translatable) name, as it will be displayed to users in the toolbar of the Paintbrush tool.
  • You must implement the OnMouseMove method. This is called whenever the user moves the mouse, and is where we will be drawing onto the canvas. The method must return a rectangle containing the area of the canvas that should be redrawn.

Let's add these - for now, we won't do anything in the OnMouseMove method, so we'll just return the empty rectangle:

using System;
using Mono.Addins;
using Pinta.Core;

namespace BlockBrush
{
    public class BlockBrush : BasePaintBrush
    {
        public override string Name {
            get { return AddinManager.CurrentLocalizer.GetString ("Block"); }
        }

        protected override RectangleI OnMouseMove (Cairo.Context g, Cairo.Color strokeColor,
                                                   Cairo.ImageSurface surface,int x, int y,
                                                   int lastX, int lastY)
        {
            return RectangleI.Zero;
        }
    }
}

Implementing the Brush

Let's make our brush a little more useful. To implement the Block brush, we need to draw a thick parallelogram that is slanted in the direction of the stroke. The arguments to the OnMouseMove function give us the current and previous mouse coordinates, as well as a Cairo drawing context, so we have everything we need to draw the brush strokes. We also need to ensure that we return a rectangle enclosing the area we just drew to, in order for Pinta to be able to redraw that area on the screen.

protected override Gdk.Rectangle OnMouseMove (Cairo.Context g, Cairo.Color strokeColor,
                                              Cairo.ImageSurface surface,int x, int y,
                                              int lastX, int lastY)
{
    // Use the brush width as the width of the block.
    double width = g.LineWidth;
    // When moving the brush horizontally, avoid having a zero-height line.
    if (lastY == y)
        y++;

    // Draw a parallelogram.
    g.MoveTo (lastX - width, lastY);
    g.LineTo (lastX + width, lastY);
    g.LineTo (x + width, y);
    g.LineTo (x - width, y);
    g.LineTo (lastX - width, lastY);

    var dirty = g.StrokeExtents ().ToInt ();
    g.Fill ();
    return dirty;
}

Other Examples

For other examples of brushes, you can look at the source code of the default brushes that are shipped with Pinta: https://github.com/PintaProject/Pinta/tree/master/Pinta.Tools/Brushes

Registering the Brush

We've now written a brush, but we haven't told Pinta about it yet. In the IExtension subclass, we need to call PintaCore.PaintBrushes.AddPaintBrush to register the brush, and call PintaCore.PaintBrushes.RemoveInstanceOfPaintBrush to unregister the brush:

using System;
using Pinta.Core;

namespace BlockBrush
{
    [Mono.Addins.Extension]
    public class BlockBrushExtension : IExtension
    {
        public void Initialize ()
        {
            PintaCore.PaintBrushes.AddPaintBrush (new BlockBrush ());
        }

        public void Uninitialize ()
        {
            PintaCore.PaintBrushes.RemoveInstanceOfPaintBrush (typeof (BlockBrush));
        }
    }
}

Now, we can load Pinta and see our brush in the toolbar of the Paintbrush tool:

Paintbrush Toolbar

Let's test it out!

Brush Demo