Elements

A thing to insert that the plugin draws itself, and that stays adjustable.

An element becomes a picture layer that remembers what made it. Its settings appear in the layer's properties, and the drawing function is called again whenever one changes.

elements: [{
  key: 'burst', name: 'Burst', w: 240, h: 240, group: 'Shapes', icon: 'star', about: 'Rays round a middle',
  fields: [
    { key: 'rays', label: 'Rays', low: 4, high: 48, step: 1, usual: 16 },
    { key: 'fill', label: 'Color', usual: '#F7C56B', color: true },
  ],
  draw(pen, settings) {
    const cx = settings.w / 2, cy = settings.h / 2, far = Math.min(cx, cy) - 4;
    pen.strokeStyle = settings.fill;
    pen.lineWidth = 6;
    pen.lineCap = 'round';
    for (let ray = 0; ray < settings.rays; ray++) {
      const angle = (ray / settings.rays) * Math.PI * 2;
      pen.beginPath();
      pen.moveTo(cx + far * 0.35 * Math.cos(angle), cy + far * 0.35 * Math.sin(angle));
      pen.lineTo(cx + far * Math.cos(angle), cy + far * Math.sin(angle));
      pen.stroke();
    }
  },
}],
import math

from rassam import color, slider, toggle


@plugin.element('Star', w=240, h=240, group='Shapes', icon='star', about='A star with as many points as you like',
                fields=[slider('points', 'Points', 3, 24, 5), slider('depth', 'Depth', 0.05, 0.95, 0.5, step=0.01),
                        color('fill', 'Color', '#F7C56B'), toggle('outline', 'Outline only')])
def star(pen, settings):
    cx, cy, far = settings.w / 2, settings.h / 2, min(settings.w, settings.h) / 2 - 6
    pen.beginPath()
    for corner in range(int(settings.points) * 2):
        angle = -math.pi / 2 + corner * math.pi / settings.points
        reach = far if corner % 2 == 0 else far * (1 - settings.depth)
        pen.lineTo(cx + reach * math.cos(angle), cy + reach * math.sin(angle))
    pen.closePath()
    if settings.outline:
        pen.strokeStyle, pen.lineWidth, pen.lineJoin = settings.fill, 6, 'round'
        pen.stroke()
    else:
        pen.fillStyle = settings.fill
        pen.fill()

Its size and its place in the menu

Fetching before drawing

The drawing function must not wait. When it needs something fetched first (a texture, a font), give the element a ready: it runs before every drawing and is waited for (a promise, or in Python an async def).

const pictures = new Map<string, HTMLImageElement>();

// ...in the plugin:
elements: [{
  key: 'stamp', name: 'Stamp', w: 256, h: 256,
  fields: [{ key: 'url', label: 'Picture', usual: 'https://example.com/stamp.png', text: true }],
  async ready(settings) {
    if (pictures.has(settings.url)) return;
    const image = new Image();
    image.crossOrigin = 'anonymous';
    image.src = settings.url;
    await image.decode();
    pictures.set(settings.url, image);
  },
  draw(pen, settings) {
    const image = pictures.get(settings.url);
    if (image) pen.drawImage(image, 0, 0, settings.w, settings.h);
  },
}],
from js import Image

from rassam import text

pictures = {}


async def fetch(settings):
    if settings.url in pictures:
        return
    image = Image.new()
    image.crossOrigin = 'anonymous'
    image.src = settings.url
    await image.decode()
    pictures[settings.url] = image


@plugin.element('Stamp', w=256, h=256, ready=fetch, fields=[text('url', 'Picture', 'https://example.com/stamp.png')])
def stamp(pen, settings):
    image = pictures.get(settings.url)
    if image:
        pen.drawImage(image, 0, 0, settings.w, settings.h)