Skip to content

API Rendering

RadiatorTwo edited this page Jun 28, 2026 · 2 revisions

Rendering with IRenderCanvas

Several SDK contracts hand the plugin an IRenderCanvas instead of asking it for raw bytes: side strips, strip segments, IDisplayImageCommand and the animated IAnimatedDisplayCommand. The canvas is the host's own drawing surface — when you draw text or a symbol on it, the host renders it with the same font and symbol library it uses everywhere else, so plugin output stays visually consistent with the core. For anything the primitives don't cover, DrawImage blits bytes you rasterized yourself.

This replaces the older "return a PNG" model: render methods now draw onto a canvas and return booltrue when you drew, false to let the host fall back to its default rendering for that region.

The canvas is valid only during the synchronous render call that received it. Do not cache it or use it after the method returns. Coordinates are canvas-local: (0,0) is the top-left of this region and Width×Height is the region size in device pixels (e.g. 60×270 for a whole Razer strip, 60×90 for one segment, 90×90 for a touch button).

IRenderCanvas

public interface IRenderCanvas
{
    int  Width  { get; }
    int  Height { get; }

    void Clear(PluginColor color);

    // Rectangles
    void FillRectangle(int x, int y, int width, int height, PluginColor color);
    void DrawRectangle(int x, int y, int width, int height, int strokeWidth, PluginColor color);
    void FillRoundedRectangle(int x, int y, int width, int height, int radius, PluginColor color);
    void DrawRoundedRectangle(int x, int y, int width, int height, int radius, int strokeWidth, PluginColor color);

    // Circles / ellipses / arcs
    void FillCircle(int centerX, int centerY, int radius, PluginColor color);
    void DrawCircle(int centerX, int centerY, int radius, int strokeWidth, PluginColor color);
    void FillEllipse(int x, int y, int width, int height, PluginColor color);
    void DrawEllipse(int x, int y, int width, int height, int strokeWidth, PluginColor color);
    void DrawArc(int x, int y, int width, int height, float startAngle, float sweepAngle, int strokeWidth, PluginColor color);
    void FillArc(int x, int y, int width, int height, float startAngle, float sweepAngle, PluginColor color);

    // Lines
    void DrawLine(int x1, int y1, int x2, int y2, int strokeWidth, PluginColor color);

    // Text
    void DrawText(string text, int x, int y, int width, int height, PluginColor color,
        float fontSize, bool bold = false, bool italic = false,
        bool centered = true, bool outlined = false, PluginColor outlineColor = default);
    void DrawText(string text, int x, int y, int width, int height, PluginColor color,
        float fontSize, TextHAlign hAlign, TextVAlign vAlign,
        bool bold = false, bool italic = false, bool outlined = false, PluginColor outlineColor = default);
    float MeasureText(string text, float fontSize, bool bold = false, bool italic = false);

    // Symbols
    void DrawSymbol(string symbolId, int x, int y, int width, int height, PluginColor tint);
    void DrawSymbol(string symbolId, int x, int y, int width, int height, SymbolStyle style);

    // Images
    void DrawImage(byte[] imageBytes, int x, int y, int width, int height);
    void DrawImage(byte[] imageBytes, int x, int y, int width, int height, byte opacity, PluginColor tint = default);

    // Transform
    void PushTransform();
    void PopTransform();
    void Translate(float dx, float dy);
    void Rotate(float degrees);
    void Scale(float sx, float sy);
}

Shapes

All shape primitives take integer device-pixel coordinates and a PluginColor. Each has a Fill… (solid) and a Draw… (stroke, with an explicit strokeWidth) form.

Group Members
Rectangles FillRectangle / DrawRectangle, FillRoundedRectangle / DrawRoundedRectangle (corner radius)
Circles & ellipses FillCircle / DrawCircle (center + radius), FillEllipse / DrawEllipse (bounding box)
Arcs DrawArc (stroked) / FillArc (pie wedge) — angles in degrees, clockwise, at 3 o'clock. Good for round gauges.
Lines DrawLine

Clear(color) fills the whole region with one color — it does not touch pixels outside this canvas's region, so clearing a segment never wipes the rest of the strip.

Text

canvas.DrawText("75 %", 0, 0, canvas.Width, canvas.Height,
    PluginColor.White, fontSize: 14f, centered: true);

Both DrawText overloads word-wrap inside the box at (x, y, width, height) in the host's UI font. The first overload toggles centered-vs-top-left with the centered flag; the second takes independent TextHAlign / TextVAlign. Pass outlined: true (with an outlineColor) to stroke an outline behind the fill — the standard trick for keeping a label legible over an arbitrary page wallpaper.

MeasureText returns the single-line advance width (no wrapping) so you can fit or ellipsize text before drawing it — essential on the narrow 60 px strip:

static string Fit(string text, IRenderCanvas canvas, float size, float maxWidth)
{
    if (canvas.MeasureText(text, size) <= maxWidth) return text;
    var t = text;
    while (t.Length > 1 && canvas.MeasureText(t + "…", size) > maxWidth) t = t[..^1];
    return t + "…";
}

Symbols

DrawSymbol(id, x, y, w, h, tint) draws a glyph from the host's symbol library, tinted and fitted into the box; an unknown id draws a placeholder. The SymbolStyle overload adds outline, drop shadow, linear gradient fill and rotation — see SymbolStyle.

Images

DrawImage(bytes, x, y, w, h) decodes bytes (PNG or any host-decodable format) and blits it aspect-fit into the box. This is the escape hatch for fully custom, plugin-rasterized content. The opacity/tint overload multiplies an alpha (0–255) and an optional tint over the image.

Decode caching. Repeated calls with the same byte[] instance reuse a cached decode, so a plugin holding a static image does not pay the decode cost on every frame. Keep your image bytes in a field and pass that same array each frame — don't re-allocate it per call.

Transform stack

PushTransform / PopTransform save and restore the current transform; Translate, Rotate (degrees, clockwise) and Scale compose onto it. Rotation and scaling are about the current origin — Translate to your pivot first to rotate about a point:

canvas.PushTransform();
canvas.Translate(cx, cy);
canvas.Rotate(angle);
canvas.DrawText(label, -w / 2, -h / 2, w, h, color, size);
canvas.PopTransform();

PluginColor

public readonly record struct PluginColor(byte R, byte G, byte B, byte A = 255)
{
    public static PluginColor Black       => new(0, 0, 0);
    public static PluginColor White       => new(255, 255, 255);
    public static PluginColor Transparent => new(0, 0, 0, 0);
    public static PluginColor FromRgb(byte r, byte g, byte b) => new(r, g, b);
}

A plain RGBA color. The SDK is UI-framework agnostic, so it exposes neither Avalonia nor SkiaSharp color types — the host converts as needed. Define your palette as static readonly fields and reuse them:

private static readonly PluginColor Track      = new(48, 48, 48);
private static readonly PluginColor FillActive = new(0x4C, 0xAF, 0x50); // green

SymbolStyle

public readonly record struct SymbolStyle
{
    public SymbolStyle(PluginColor tint);

    public PluginColor Tint     { get; init; } = PluginColor.White; // ignored when UseGradient
    public float       Rotation { get; init; }                      // degrees, clockwise

    public bool        Outlined     { get; init; }
    public PluginColor OutlineColor { get; init; }
    public float       OutlineWidth { get; init; }

    public bool        Shadow        { get; init; }
    public PluginColor ShadowColor   { get; init; }
    public float       ShadowBlur    { get; init; }
    public int         ShadowOffsetX { get; init; }
    public int         ShadowOffsetY { get; init; }

    public bool        UseGradient   { get; init; }
    public PluginColor GradientStart { get; init; }
    public PluginColor GradientEnd   { get; init; }
    public float       GradientAngle { get; init; }
}

Optional styling for the DrawSymbol overload. Construct it with a tint and set only the extras you need:

canvas.DrawSymbol("volume-mute", x, y, 16, 16,
    new SymbolStyle(MuteColor) { Outlined = true, OutlineColor = PluginColor.Black, OutlineWidth = 1.5f });

TextHAlign / TextVAlign

public enum TextHAlign { Left, Center, Right }
public enum TextVAlign { Top,  Middle, Bottom }

Passed to the second DrawText overload for independent horizontal/vertical alignment within the text box.

When you only need to know one thing

Draw with host primitives so your output matches the core, return true when you painted something and false to defer to the host's default. Reach for DrawImage(bytes) only when the primitives can't express what you need.

Clone this wiki locally