Standard library · Graphics and games

gui.paint

A pure Salam 2D painter for CSS boxes: rounded rectangles, borders, gradients, shadows, images, paths, clipping and layers, with the same pixels on every system.

import gui.paint

This package is the drawing layer of the native user interface: it paints everything a CSS box can look like into a pixel buffer, in pure Salam, with the same pixels on every operating system. A Canvas holds premultiplied 32-bit pixels (0xAARRGGBB, so B, G, R, A bytes in memory), which X11, Win32, Cocoa and Wayland windows can show without a copy. Colours in the API are ordinary straight RGBA, as in CSS.

A box is painted in the CSS order: DrawShadow, then the background with FillRoundedRect, FillGradient or DrawImage, then DrawBorder, the content, and DrawOutline. Rounded corners take a horizontal and a vertical radius per corner and are scaled down the way CSS does when they do not fit. Edges are anti-aliased with exact coverage computed line by line in fixed point, borders have a width, colour and style (solid, dashed, dotted) per side, and shadows are Gaussian blurs computed analytically, outside or inside the box.

Clips (PushClip, PushRoundedClip), transforms and offscreen layers with group opacity (BeginLayer, EndLayer) are stacks. SetRegion limits drawing to a damage region of at most three rectangles, and drawing a scene in parts gives exactly the same pixels as drawing it at once. FillPath and StrokePath draw vector paths with lines, curves, arcs, caps, joins and dashes, and DrawMask draws text glyphs from a coverage mask.

Things to know

  • Coordinates are logical pixels as f64. Fractional edges are anti-aliased; images, clips and layers snap to whole pixels.
  • Rasterization uses integers and fixed point only, so a picture is meant to have the same hash on every system and backend. Tests compare Hash() and a few pixels.
  • Every drawing call follows the whole transform, rotation and skew included. Borders, gradients, shadows and tiled images under a rotation or skew are drawn upright in a scratch layer and then resampled, and a clip under a rotation becomes a coverage mask.
  • Dashed and dotted sides are patterned along their rounded corners too, with the pattern spaced so that each corner gets a whole dash or dot.
  • Filters work on layers: BeginFilterLayer and EndLayerFiltered give CSS filter, and BackdropFilter gives backdrop-filter, compared with Chromium on opaque, translucent and nested backdrops. Colour filters work on straight colours in sRGB, as browsers do, and blurs use three box blurs, as the filter specification describes.
  • DrawNineSlice draws an image cut into nine parts around a rectangle, the way CSS border-image does, with stretched, repeated, rounded or spaced edges.
  • Free every canvas, path and gradient you make. A canvas from WrapCanvas or Sub still needs Free for its scratch memory, but leaves the pixels alone.
  • The benchmark in tools/bench/paint_frame.salam draws 200 rounded boxes with a shadow, a background and a border over a full screen gradient at 1920x1080.

Painting a CSS box

A box is painted in the order CSS uses: the shadow first, then the background, then the border, and the outline last. All of them take the same rectangle and radii, so the shadow, the gradient and the border line up exactly. The outer shadow is cut out under the box, so a translucent background does not show it.

gui-paint-guide-card.salam
import gui.paint

func main:
    mut cv := paint.NewCanvas(160, 100)
    cv.Clear(paint.Rgb(240, 242, 247))
    box := paint.RectOf(20.0, 20.0, 120.0, 60.0)
    radii := paint.Corners(16.0, 16.0, 4.0, 4.0)
    cv.DrawShadow(box, radii, paint.Shadow { offset_y = 4.0, blur = 12.0, color = paint.Rgba(20, 30, 60, 80) })
    mut bg := paint.LinearGradient(180.0)
    bg.AddStop(0.0, paint.Rgb(255, 255, 255))
    bg.AddStop(1.0, paint.Rgb(226, 234, 252))
    cv.FillGradient(box, radii, bg)
    bg.Free()
    cv.DrawBorder(box, radii, paint.SolidBorder(1.5, paint.Rgb(150, 170, 220)))
    cv.DrawOutline(box, radii, 2.0, 2.0, paint.Rgba(60, 110, 230, 160))
    top := cv.Pixel(80, 24)
    bottom := cv.Pixel(80, 75)
    println "top of the card:", top.r, top.g, top.b
    println "bottom of the card:", bottom.r, bottom.g, bottom.b
    println "hash:", cv.Hash()
    cv.Free()
end
Outputtop of the card: 253 253 255 bottom of the card: 228 236 252 hash: a7600df3ad82d161

Repainting only what changed

When something moves, only its old and new places need painting. Put both rectangles in a Region, set it with SetRegion and draw the whole scene as usual: everything outside the region is left alone. Coverage is computed for the whole shape and only then clipped, so the result is the same, pixel for pixel, as painting everything again.

gui-paint-guide-damage.salam
import gui.paint

func draw(cv &: paint.Canvas, x: f64):
    cv.Clear(paint.Rgb(255, 255, 255))
    cv.FillRoundedRect(paint.RectOf(x, 10.0, 30.0, 30.0), paint.Uniform(8.0), paint.Rgb(230, 80, 40))
end

func main:
    mut screen := paint.NewCanvas(100, 50)
    draw(screen, 10.0)
    mut damage := paint.Region {}
    damage.Add(paint.IntRectOf(10, 10, 30, 30))
    damage.Add(paint.IntRectOf(50, 10, 30, 30))
    screen.SetRegion(damage)
    draw(screen, 50.0)
    screen.ClearRegion()
    mut fresh := paint.NewCanvas(100, 50)
    draw(fresh, 50.0)
    println "rectangles repainted:", damage.Count()
    println "same as a full repaint:", screen.Hash() == fresh.Hash()
    fresh.Free()
    screen.Free()
end
Outputrectangles repainted: 2 same as a full repaint: true

Types

BorderStyle

enum BorderStyle
How one side of a border is drawn.
NameTypeDescription
NoneNo line on this side.
SolidA solid line.
DashedDashes as Chromium draws them: twice the side width long with gaps as wide as the side when the side is 3 pixels or wider, and three times long with gaps twice as wide when it is thinner. The gap is stretched or shrunk so that both ends of the side get a whole dash.
DottedRound dots as wide as the side, spaced so that both ends get a dot. On straight sides the dots snap to whole pixels, so thin dotted lines stay crisp.
gui-paint-Box.salam
import gui.paint

func main:
    mut cv := paint.NewCanvas(120, 80)
    cv.Clear(paint.Rgb(245, 246, 250))
    card := paint.RectOf(20.0, 15.0, 80.0, 50.0)
    radii := paint.Uniform(10.0)
    cv.DrawShadow(card, radii, paint.Shadow { offset_y = 3.0, blur = 8.0, color = paint.Rgba(0, 0, 0, 90) })
    cv.FillRoundedRect(card, radii, paint.Rgb(255, 255, 255))
    mut border := paint.SolidBorder(2.0, paint.Rgb(60, 110, 220))
    border.left_width = 6.0
    border.left_color = paint.Rgb(230, 80, 40)
    cv.DrawBorder(card, radii, border)
    cv.DrawOutline(card, radii, 1.0, 3.0, paint.Rgba(60, 110, 220, 120))
    c := cv.Pixel(60, 40)
    println "inside:", c.r, c.g, c.b
    e := cv.Pixel(22, 40)
    println "left border:", e.r, e.g, e.b
    println "hash:", cv.Hash()
    cv.Free()
end
Outputinside: 255 255 255 left border: 230 80 40 hash: 6c34cfff4092b625

GradientKind

enum GradientKind
The shape of a gradient.
NameTypeDescription
LinearColours change along a line, like CSS linear-gradient.
RadialColours change from a centre outwards in an ellipse, like CSS radial-gradient.
ConicColours change around a centre, like CSS conic-gradient.
gui-paint-Gradient.salam
import gui.paint

func main:
    mut cv := paint.NewCanvas(120, 40)
    mut sky := paint.LinearGradient(90.0)
    sky.AddStop(0.0, paint.Rgb(255, 120, 0))
    sky.AddStop(-1.0, paint.Rgb(255, 0, 120))
    sky.AddStop(1.0, paint.Rgb(60, 0, 200))
    cv.FillGradient(paint.RectOf(0.0, 0.0, 60.0, 40.0), paint.Corners(12.0, 0.0, 0.0, 12.0), sky)
    sky.Free()
    size := paint.RadialSize(60.0, 40.0, 30.0, 20.0, true, paint.RadialExtent.ClosestSide)
    mut glow := paint.RadialGradient(30.0, 20.0, size.x, size.y)
    glow.AddStop(0.0, paint.Rgb(255, 255, 255))
    glow.AddStop(1.0, paint.Rgba(0, 160, 120, 255))
    cv.FillGradient(paint.RectOf(60.0, 0.0, 60.0, 40.0), paint.Uniform(0.0), glow)
    glow.Free()
    println "radius:", size.x
    left := cv.Pixel(2, 20)
    right := cv.Pixel(57, 20)
    println "left:", left.r, left.g, left.b
    println "right:", right.r, right.g, right.b
    println "centre:", cv.Pixel(90, 20).r
    cv.Free()
end
Outputradius: 20 left: 255 110 10 right: 77 0 193 centre: 247

RadialExtent

enum RadialExtent
The CSS size keywords of a radial gradient, used by RadialSize.
NameTypeDescription
ClosestSideThe ending shape touches the side nearest to the centre.
FarthestSideThe ending shape touches the side farthest from the centre.
ClosestCornerThe ending shape passes through the nearest corner.
FarthestCornerThe ending shape passes through the farthest corner (the CSS default).

The example for this item is shown with the ones above: gui-paint-Gradient.salam

ObjectFit

enum ObjectFit
The CSS object-fit modes, used by FitImage.
NameTypeDescription
FillStretch the image to the box.
ContainScale the image to fit inside the box, keeping its shape.
CoverScale the image to cover the whole box, keeping its shape.
NoneKeep the natural size.
ScaleDownLike Contain, but never larger than the natural size.
gui-paint-Image.salam
import gui.paint
import os
import filepath

func main:
    mut tile := paint.NewCanvas(2, 2)
    tile.Clear(paint.Rgb(230, 230, 230))
    tile.FillRect(paint.RectOf(0.0, 0.0, 1.0, 1.0), paint.Rgb(90, 90, 90))
    tile.FillRect(paint.RectOf(1.0, 1.0, 1.0, 1.0), paint.Rgb(90, 90, 90))
    mut cv := paint.NewCanvas(80, 60)
    cv.DrawImageTiled(tile, paint.RectOf(0.0, 0.0, 80.0, 60.0), paint.RectOf(0.0, 0.0, 8.0, 8.0), 1.0)
    photo := paint.FitImage(paint.ObjectFit.Contain, 4, 2, paint.RectOf(10.0, 10.0, 60.0, 40.0), 0.5, 0.5)
    println "photo box:", photo.x, photo.y, photo.width, photo.height
    cv.PushRoundedClip(photo, paint.Uniform(6.0))
    cv.BeginLayer(photo)
    cv.FillRect(photo, paint.Rgb(0, 120, 255))
    cv.FillRect(paint.RectOf(30.0, 20.0, 20.0, 20.0), paint.Rgb(255, 60, 60))
    cv.EndLayer(0.8)
    cv.PopClip()
    path := filepath.Join(os.TempDir(), "salam_paint_example.png")
    println "saved:", paint.SavePNG(cv, path)
    mut ok := false
    mut back := paint.LoadPNG(path, ok)
    println "loaded:", ok, back.width, back.height, back.Hash() == cv.Hash()
    os.Remove(path)
    back.Free()
    tile.Free()
    cv.Free()
end
Outputphoto box: 10 15 60 30 saved: true loaded: true 80 60 true

FillRule

enum FillRule
Which parts of a path count as inside.
NameTypeDescription
NonZeroInside where the winding number is not zero.
EvenOddInside where a ray crosses the outline an odd number of times.
gui-paint-Path.salam
import gui.paint

func main:
    mut cv := paint.NewCanvas(100, 60)
    cv.Clear(paint.Rgb(255, 255, 255))
    mut star := paint.NewPath()
    star.MoveTo(30.0, 5.0)
    star.LineTo(42.0, 52.0)
    star.LineTo(5.0, 22.0)
    star.LineTo(55.0, 22.0)
    star.LineTo(18.0, 52.0)
    star.Close()
    cv.FillPath(star, paint.FillRule.EvenOdd, paint.Rgb(250, 190, 0))
    cv.StrokePath(star, paint.Stroke { width = 2.0, join = paint.LineJoin.Round }, paint.Rgb(120, 60, 0))
    star.Free()
    mut wave := paint.NewPath()
    wave.MoveTo(0.0, 0.0)
    wave.CubicTo(10.0, -15.0, 20.0, 15.0, 30.0, 0.0)
    cv.Translate(62.0, 30.0)
    cv.StrokePath(wave, paint.Stroke { width = 3.0, cap = paint.LineCap.Round, dash = 6.0, gap = 4.0 }, paint.Rgb(0, 100, 200))
    cv.PopTransform()
    wave.Free()
    println "middle of the star is a hole:", cv.Pixel(30, 30).b == 255
    println "point of the star:", cv.Pixel(30, 12).g
    println "hash:", cv.Hash()
    cv.Free()
end
Outputmiddle of the star is a hole: true point of the star: 175 hash: 3d0bdde1d3510f18

LineCap

enum LineCap
The ends of an open stroked line.
NameTypeDescription
ButtThe line stops at its end point.
RoundA half circle past the end point.
SquareA half square past the end point.

The example for this item is shown with the ones above: gui-paint-Path.salam

LineJoin

enum LineJoin
The corners of a stroked line.
NameTypeDescription
MiterA sharp corner, cut to a bevel past miter_limit.
RoundA round corner.
BevelA cut corner.

The example for this item is shown with the ones above: gui-paint-Path.salam

FilterKind

enum FilterKind
One CSS filter function, used by Filter.
NameTypeDescription
BlurA Gaussian blur; amount is the standard deviation, as in CSS blur().
BrightnessMultiplies the colour channels by amount.
ContrastMoves the channels away from or towards middle grey by amount.
GrayscaleTurns the colours grey; 1 is fully grey.
SepiaTurns the colours sepia; 1 is fully sepia.
SaturateChanges the saturation; 0 is grey, 1 leaves the colours alone.
HueRotateTurns the hue by amount degrees.
InvertInverts the colours; 1 is fully inverted.
OpacityMultiplies the alpha by amount.
DropShadowA blurred, offset copy of the shape drawn under it, as in CSS drop-shadow().
gui-paint-Filter.salam
import gui.paint

func main:
    mut cv := paint.NewCanvas(120, 60)
    cv.Clear(paint.Rgb(255, 255, 255))
    repeat 6 in i:
        cv.FillRect(paint.RectOf((i * 20) as f64, 0.0, 10.0, 60.0), paint.Rgb(30, 30, 30))
    end
    mut glass := paint.FilterChain {}
    glass.Add(paint.NewFilter(paint.FilterKind.Blur, 3.0))
    glass.Add(paint.NewFilter(paint.FilterKind.Brightness, 1.3))
    cv.BackdropFilter(paint.RectOf(4.0, 4.0, 50.0, 30.0), paint.Uniform(6.0), glass)
    glass.Free()
    mut look := paint.FilterChain {}
    look.Add(paint.NewFilter(paint.FilterKind.Sepia, 1.0))
    look.Add(paint.DropShadowFilter(3.0, 3.0, 2.0, paint.Rgba(0, 0, 0, 160)))
    println "room around the layer:", look.Extent(), "empty:", look.IsEmpty()
    tile := paint.RectOf(70.0, 10.0, 30.0, 30.0)
    cv.BeginFilterLayer(tile, look)
    cv.FillRoundedRect(tile, paint.Uniform(6.0), paint.Rgb(40, 120, 220))
    cv.EndLayerFiltered(look, 1.0)
    look.Free()
    println "frosted glass is grey:", cv.Pixel(14, 20).r > 60
    println "sepia tile:", cv.Pixel(85, 25).r > cv.Pixel(85, 25).b
    println "hash:", cv.Hash()
    cv.Free()
end
Outputroom around the layer: 12 empty: false frosted glass is grey: true sepia tile: true hash: 547e5fb9c6152229

Color

struct Color
A straight (not premultiplied) RGBA colour with channels from 0 to 255, as in CSS.
NameTypeDescription
rint = 0Red, 0 to 255.
gint = 0Green, 0 to 255.
bint = 0Blue, 0 to 255.
aint = 255Alpha (opacity), 0 to 255. The default is 255.

The example for this item is shown with the ones above: gui-paint-Box.salam

Point

struct Point
A point in logical pixels.
NameTypeDescription
xf64 = 0The x coordinate.
yf64 = 0The y coordinate.

The example for this item is shown with the ones above: gui-paint-Path.salam

Rect

struct Rect
A rectangle in logical pixels. Edges may be fractional; they are anti-aliased.
NameTypeDescription
xf64 = 0The left edge.
yf64 = 0The top edge.
widthf64 = 0The width.
heightf64 = 0The height.

The example for this item is shown with the ones above: gui-paint-Box.salam

IntRect

struct IntRect
A rectangle of whole pixels.
NameTypeDescription
xint = 0The left column.
yint = 0The top row.
widthint = 0The number of columns.
heightint = 0The number of rows.

The example for this item is shown with the ones above: gui-paint-Image.salam

Radii

struct Radii
The corner radii of a box, horizontal and vertical per corner, like CSS border-radius.
NameTypeDescription
top_left_xf64 = 0The horizontal radius of the top left corner.
top_left_yf64 = 0The vertical radius of the top left corner.
top_right_xf64 = 0The horizontal radius of the top right corner.
top_right_yf64 = 0The vertical radius of the top right corner.
bottom_right_xf64 = 0The horizontal radius of the bottom right corner.
bottom_right_yf64 = 0The vertical radius of the bottom right corner.
bottom_left_xf64 = 0The horizontal radius of the bottom left corner.
bottom_left_yf64 = 0The vertical radius of the bottom left corner.

The example for this item is shown with the ones above: gui-paint-Box.salam

Border

struct Border
A CSS border: a width, a colour and a style for each side.
NameTypeDescription
top_widthf64 = 0The width of the top side.
right_widthf64 = 0The width of the right side.
bottom_widthf64 = 0The width of the bottom side.
left_widthf64 = 0The width of the left side.
top_colorColorThe colour of the top side.
right_colorColorThe colour of the right side.
bottom_colorColorThe colour of the bottom side.
left_colorColorThe colour of the left side.
top_styleBorderStyleThe style of the top side. The default is Solid.
right_styleBorderStyleThe style of the right side.
bottom_styleBorderStyleThe style of the bottom side.
left_styleBorderStyleThe style of the left side.

The example for this item is shown with the ones above: gui-paint-Box.salam

GradientStop

struct GradientStop
One colour stop of a gradient.
NameTypeDescription
posf64 = -1The position from 0 to 1 along the gradient. A negative value means automatic, spread evenly between its neighbours as in CSS.
colorColorThe colour at this stop.

The example for this item is shown with the ones above: gui-paint-Gradient.salam

Gradient

struct Gradient
A linear, radial or conic gradient with its colour stops.
NameTypeDescription
kindGradientKindLinear, radial or conic.
anglef64 = 180For linear: the CSS angle in degrees (0 points up, 90 right, 180 down). For conic: the start angle.
cxf64 = 0The centre x of a radial or conic gradient, from the left edge of the filled rectangle.
cyf64 = 0The centre y of a radial or conic gradient, from the top edge of the filled rectangle.
rxf64 = 0The horizontal radius of a radial gradient.
ryf64 = 0The vertical radius of a radial gradient.
repeatingbool = falseRepeat the stops, like repeating-linear-gradient.
stopsVectorThe colour stops in order.

The example for this item is shown with the ones above: gui-paint-Gradient.salam

AddStop

func AddStop(pos: f64, color: Color)
Adds a colour stop. Pass a negative position for an automatic one.

The example for this item is shown with the ones above: gui-paint-Gradient.salam

Free

func Free()
Frees the list of stops.

The example for this item is shown with the ones above: gui-paint-Gradient.salam

Shadow

struct Shadow
A CSS box-shadow.
NameTypeDescription
offset_xf64 = 0The horizontal offset.
offset_yf64 = 0The vertical offset.
blurf64 = 0The blur radius. The blur is a Gaussian with a standard deviation of half this value, as in CSS.
spreadf64 = 0Grows (or with a negative value shrinks) the shadow shape before blurring.
colorColorThe shadow colour.
insetbool = falseAn inner shadow, drawn inside the box instead of around it.

The example for this item is shown with the ones above: gui-paint-Box.salam

Mask

struct Mask
An 8-bit coverage mask, such as a rendered glyph.
NameTypeDescription
datavoid*The coverage bytes, one per pixel, row by row.
widthint = 0The width in pixels.
heightint = 0The height in pixels.
strideint = 0The number of bytes from one row to the next.
leftint = 0For glyphs: the distance from the pen position to the left edge of the mask.
topint = 0For glyphs: the distance from the baseline up to the top of the mask.

The example for this item is shown with the ones above: gui-paint-Image.salam

Transform

struct Transform
A 2D affine transform: a point (x, y) becomes (a x + c y + e, b x + d y + f).
NameTypeDescription
af64 = 1The x scale part.
bf64 = 0The y shear part.
cf64 = 0The x shear part.
df64 = 1The y scale part.
ef64 = 0The x translation.
ff64 = 0The y translation.

The example for this item is shown with the ones above: gui-paint-Path.salam

Then

func Then(o: Transform): Transform
Returns the transform that applies this one and then the other.

The example for this item is shown with the ones above: gui-paint-Path.salam

Apply

func Apply(p: Point): Point
Transforms a point.

The example for this item is shown with the ones above: gui-paint-Path.salam

Invert

func Invert(): Transform
Returns the transform that undoes this one, or the identity if it cannot be undone.
gui-paint-Transform.salam
import gui.paint

func main:
    mut cv := paint.NewCanvas(120, 80)
    cv.Clear(paint.Rgb(255, 255, 255))
    turn := paint.Translation(-30.0, -20.0).Then(paint.Rotation(20.0)).Then(paint.Skewing(10.0, 0.0)).Then(paint.Translation(45.0, 40.0))
    cv.PushTransform(turn)
    card := paint.RectOf(0.0, 0.0, 60.0, 40.0)
    cv.DrawShadow(card, paint.Uniform(8.0), paint.Shadow { offset_y = 3.0, blur = 6.0, color = paint.Rgba(0, 0, 0, 90) })
    cv.FillRoundedRect(card, paint.Uniform(8.0), paint.Rgb(250, 240, 200))
    mut b := paint.SolidBorder(3.0, paint.Rgb(200, 60, 0))
    b.top_style = paint.BorderStyle.Dashed
    b.bottom_style = paint.BorderStyle.Dotted
    cv.DrawBorder(card, paint.Uniform(8.0), b)
    cv.PushRoundedClip(paint.RectOf(10.0, 10.0, 40.0, 20.0), paint.Uniform(6.0))
    cv.FillRect(paint.RectOf(0.0, 0.0, 60.0, 40.0), paint.Rgba(0, 90, 200, 120))
    cv.PopClip()
    cv.PopTransform()
    cv.DrawOutlineStyled(paint.RectOf(90.0, 20.0, 20.0, 40.0), paint.Uniform(4.0), 2.0, 3.0, paint.Rgb(0, 0, 0), paint.BorderStyle.Dotted)
    corner := turn.Apply(paint.Point { x = 0.0, y = 0.0 })
    back := turn.Invert().Apply(corner)
    box := turn.MapRect(card)
    println "card corner on screen:", corner.x > 10.0, corner.y < 20.0
    println "back to the card:", back.x > -0.001 and back.x < 0.001
    println "screen box wider than the card:", box.width > 60.0
    println "identity:", paint.Identity().IsIdentity(), turn.IsIdentity()
    println "hash:", cv.Hash()
    cv.Free()
end
Outputcard corner on screen: true true back to the card: true screen box wider than the card: true identity: true false hash: ab06335030f97002

MapRect

func MapRect(r: Rect): Rect
Returns the smallest rectangle that holds a rectangle after this transform.

The example for this item is shown with the ones above: gui-paint-Transform.salam

IsIdentity

func IsIdentity(): bool
Reports whether the transform changes nothing.

The example for this item is shown with the ones above: gui-paint-Transform.salam

Filter

struct Filter
One step of a filter chain.
NameTypeDescription
kindFilterKindWhich filter.
amountf64 = 0The filter amount: a blur standard deviation in pixels, an angle in degrees for HueRotate, or a factor.
offset_xf64 = 0For DropShadow, how far the shadow moves to the right.
offset_yf64 = 0For DropShadow, how far the shadow moves down.
colorColorFor DropShadow, the shadow colour.

The example for this item is shown with the ones above: gui-paint-Filter.salam

FilterChain

struct FilterChain
Filters applied in order, like the CSS filter and backdrop-filter lists.
NameTypeDescription
itemsVectorThe filters, in order.

The example for this item is shown with the ones above: gui-paint-Filter.salam

Add

func Add(f: Filter)
Adds a filter at the end.

The example for this item is shown with the ones above: gui-paint-Filter.salam

Extent

func Extent(): f64
How far the result can reach past the input, in pixels, for blurs and shadows.

The example for this item is shown with the ones above: gui-paint-Filter.salam

IsEmpty

func IsEmpty(): bool
Reports whether the chain has no filters.

The example for this item is shown with the ones above: gui-paint-Filter.salam

Free

func Free()
Frees the chain.

The example for this item is shown with the ones above: gui-paint-Filter.salam

Stroke

struct Stroke
How a path is stroked.
NameTypeDescription
widthf64 = 1The line width.
capLineCapThe ends of open lines.
joinLineJoinThe corners.
miter_limitf64 = 4The longest miter, as a multiple of half the width, before a corner is bevelled.
dashf64 = 0The length of a dash. Zero draws a solid line.
gapf64 = 0The gap between dashes.
dash_offsetf64 = 0How far into the dash pattern the line starts.

The example for this item is shown with the ones above: gui-paint-Path.salam

Path

struct Path
A vector path made of lines, curves and arcs.
NameTypeDescription
verbsVectorThe commands of the path (move, line, quadratic, cubic, close).
pointsVectorThe coordinates the commands use.

The example for this item is shown with the ones above: gui-paint-Path.salam

MoveTo

func MoveTo(x: f64, y: f64)
Starts a new sub-path at a point.

The example for this item is shown with the ones above: gui-paint-Path.salam

LineTo

func LineTo(x: f64, y: f64)
Adds a straight line.

The example for this item is shown with the ones above: gui-paint-Path.salam

QuadTo

func QuadTo(cx: f64, cy: f64, x: f64, y: f64)
Adds a quadratic curve with one control point.

The example for this item is shown with the ones above: gui-paint-Path.salam

CubicTo

func CubicTo(c1x: f64, c1y: f64, c2x: f64, c2y: f64, x: f64, y: f64)
Adds a cubic curve with two control points.

The example for this item is shown with the ones above: gui-paint-Path.salam

Close

func Close()
Closes the current sub-path.

The example for this item is shown with the ones above: gui-paint-Path.salam

Arc

func Arc(cx: f64, cy: f64, rx: f64, ry: f64, start: f64, sweep: f64)
Adds an elliptical arc around a centre, from a start angle through a sweep, both in degrees (0 points right, 90 down). A line joins it to the current point.

The example for this item is shown with the ones above: gui-paint-Path.salam

AddRect

func AddRect(r: Rect)
Adds a closed rectangle.

The example for this item is shown with the ones above: gui-paint-Path.salam

AddRoundedRect

func AddRoundedRect(r: Rect, radii: Radii)
Adds a closed rectangle with rounded corners.

The example for this item is shown with the ones above: gui-paint-Path.salam

AddEllipse

func AddEllipse(cx: f64, cy: f64, rx: f64, ry: f64)
Adds a closed ellipse.

The example for this item is shown with the ones above: gui-paint-Path.salam

Clear

func Clear()
Removes every command, keeping the memory.

The example for this item is shown with the ones above: gui-paint-Path.salam

Free

func Free()
Frees the path.

The example for this item is shown with the ones above: gui-paint-Path.salam

Region

struct Region
A damage region: at most three rectangles. A fourth is merged into the one whose area grows least.

The example for this item is shown with the ones above: gui-paint-Image.salam

Count

func Count(): int
The number of rectangles.

The example for this item is shown with the ones above: gui-paint-Image.salam

At

func At(i: int): IntRect
The rectangle at an index.

The example for this item is shown with the ones above: gui-paint-Image.salam

IsEmpty

func IsEmpty(): bool
Reports whether the region has no rectangles.

The example for this item is shown with the ones above: gui-paint-Image.salam

Clear

func Clear()
Empties the region.

The example for this item is shown with the ones above: gui-paint-Image.salam

Bounds

func Bounds(): IntRect
The rectangle around the whole region.

The example for this item is shown with the ones above: gui-paint-Image.salam

Add

func Add(r: IntRect)
Adds a rectangle. One already covered is ignored; ones it covers are dropped.

The example for this item is shown with the ones above: gui-paint-Image.salam

Union

func Union(o: Region)
Adds every rectangle of another region.

The example for this item is shown with the ones above: gui-paint-Image.salam

Canvas

struct Canvas
A pixel buffer to draw on, with its clip, transform and layer stacks.
NameTypeDescription
pixelsvoid*The pixels: one premultiplied 0xAARRGGBB word each, so B, G, R, A bytes in memory.
widthint = 0The width in pixels.
heightint = 0The height in pixels.
strideint = 0The number of bytes from one row to the next.
ownedbool = falseWhether Free releases the pixels too.
state_StateThe drawing state (clips, transforms, layers and scratch memory). Do not change it directly.

The example for this item is shown with the ones above: gui-paint-Box.salam

Free

func Free()
Frees the scratch memory, and the pixels when the canvas owns them.

The example for this item is shown with the ones above: gui-paint-Box.salam

Clear

func Clear(c: Color)
Sets every pixel inside the clip and the region to a colour, without blending.

The example for this item is shown with the ones above: gui-paint-Box.salam

Pixel

func Pixel(x: int, y: int): Color
Reads a pixel as a straight colour. Outside the canvas it is transparent.

The example for this item is shown with the ones above: gui-paint-Box.salam

RawPixel

func RawPixel(x: int, y: int): u32
Reads a pixel as the stored premultiplied 0xAARRGGBB value.

The example for this item is shown with the ones above: gui-paint-Image.salam

Hash

func Hash(): str
A 64-bit hash of every pixel, as hexadecimal text, for tests.

The example for this item is shown with the ones above: gui-paint-Box.salam

Sub

func Sub(r: IntRect): Canvas
A canvas that draws into a part of this one, sharing its pixels.

The example for this item is shown with the ones above: gui-paint-Image.salam

PushClip

func PushClip(r: Rect)
Limits drawing to a rectangle, on top of the current clip.

The example for this item is shown with the ones above: gui-paint-Image.salam

PushRoundedClip

func PushRoundedClip(r: Rect, radii: Radii)
Limits drawing to a rounded rectangle, anti-aliased at the corners.

The example for this item is shown with the ones above: gui-paint-Image.salam

PopClip

func PopClip()
Removes the last clip.

The example for this item is shown with the ones above: gui-paint-Image.salam

ClipBounds

func ClipBounds(): IntRect
The current clip rectangle in pixels.

The example for this item is shown with the ones above: gui-paint-Image.salam

PushTransform

func PushTransform(t: Transform)
Applies a transform to the drawing that follows, on top of the current one.

The example for this item is shown with the ones above: gui-paint-Path.salam

Translate

func Translate(dx: f64, dy: f64)
Pushes a translation.

The example for this item is shown with the ones above: gui-paint-Path.salam

PopTransform

func PopTransform()
Removes the last transform.

The example for this item is shown with the ones above: gui-paint-Path.salam

SetRegion

func SetRegion(r: Region)
Limits every drawing call to a damage region.

The example for this item is shown with the ones above: gui-paint-Image.salam

ClearRegion

func ClearRegion()
Removes the damage region.

The example for this item is shown with the ones above: gui-paint-Image.salam

FillRect

func FillRect(r: Rect, c: Color)
Fills a rectangle with a colour.

The example for this item is shown with the ones above: gui-paint-Box.salam

FillRoundedRect

func FillRoundedRect(r: Rect, radii: Radii, c: Color)
Fills a rectangle with rounded corners. Radii that do not fit are scaled down as in CSS.

The example for this item is shown with the ones above: gui-paint-Box.salam

DrawBorder

func DrawBorder(r: Rect, radii: Radii, b: Border)
Draws a CSS border inside a rectangle, with per-side widths, colours and styles and the inner radii CSS gives.

The example for this item is shown with the ones above: gui-paint-Box.salam

DrawOutline

func DrawOutline(r: Rect, radii: Radii, width: f64, offset: f64, c: Color)
Draws a solid CSS outline of a width around a rectangle, at an offset.

The example for this item is shown with the ones above: gui-paint-Box.salam

FillGradient

func FillGradient(r: Rect, radii: Radii, g: Gradient)
Fills a (rounded) rectangle with a gradient.

The example for this item is shown with the ones above: gui-paint-Gradient.salam

DrawShadow

func DrawShadow(r: Rect, radii: Radii, s: Shadow)
Draws a box shadow for a (rounded) rectangle. An outer shadow is not drawn under the box itself.

The example for this item is shown with the ones above: gui-paint-Box.salam

DrawImage

func DrawImage(src: Canvas, dst: Rect, opacity: f64)
Draws a whole image into a rectangle with an opacity: bilinear when it grows, area averaging when it shrinks.

The example for this item is shown with the ones above: gui-paint-Image.salam

DrawImagePart

func DrawImagePart(src: Canvas, part: IntRect, dst: Rect, opacity: f64)
Draws part of an image into a rectangle.

The example for this item is shown with the ones above: gui-paint-Image.salam

DrawImageTiled

func DrawImageTiled(src: Canvas, area: Rect, tile: Rect, opacity: f64)
Repeats an image over an area; tile is the position and size of one copy (CSS background-repeat).

The example for this item is shown with the ones above: gui-paint-Image.salam

DrawMask

func DrawMask(m: Mask, x: int, y: int, c: Color)
Draws a coverage mask in a colour with its top left corner at a pixel.

The example for this item is shown with the ones above: gui-paint-Image.salam

DrawGlyph

func DrawGlyph(m: Mask, x: int, baseline: int, c: Color)
Draws a glyph mask at a pen position on a baseline, using its left and top bearings.

The example for this item is shown with the ones above: gui-paint-Image.salam

FillPath

func FillPath(p: Path, rule: FillRule, c: Color)
Fills a path with a colour.

The example for this item is shown with the ones above: gui-paint-Path.salam

StrokePath

func StrokePath(p: Path, s: Stroke, c: Color)
Strokes a path with a colour.

The example for this item is shown with the ones above: gui-paint-Path.salam

BeginLayer

func BeginLayer(r: Rect)
Starts drawing into an offscreen layer covering a rectangle.

The example for this item is shown with the ones above: gui-paint-Image.salam

EndLayer

func EndLayer(opacity: f64)
Composites the last layer onto what is below it with an opacity (CSS opacity).

The example for this item is shown with the ones above: gui-paint-Image.salam

EndLayerMasked

func EndLayerMasked(opacity: f64)
Ends two layers at once: the last one is a mask, and the layer under it is kept only where the mask has alpha, then composited with an opacity. This is how CSS background-clip: text paints a background through the glyphs.

The example for this item is shown with the ones above: gui-paint-Image.salam

BeginFilterLayer

func BeginFilterLayer(r: Rect, chain: FilterChain)
Starts a layer for a filter chain: the layer is grown by the chain extent and is drawn whole even when a damage region is set, so a blur near the region edge is right.

The example for this item is shown with the ones above: gui-paint-Filter.salam

EndLayerFiltered

func EndLayerFiltered(chain: FilterChain, opacity: f64)
Applies a filter chain to the last layer and composites it with an opacity (CSS filter).

The example for this item is shown with the ones above: gui-paint-Filter.salam

DrawNineSlice

func DrawNineSlice(src: Canvas, area: Rect, ns: NineSlice, opacity: f64)
Draws src cut into nine parts around area: the corners are scaled into the corners, the edges fill the sides as repeat_x and repeat_y say, and the middle is drawn when fill is set. This is how CSS border-image is painted.
gui-paint-NineSlice.salam
import gui.paint

func main:
    mut frame := paint.NewCanvas(30, 30)
    frame.Clear(paint.Rgb(40, 90, 200))
    frame.FillRect(paint.RectOf(10.0, 10.0, 10.0, 10.0), paint.Rgb(240, 240, 240))
    frame.FillRect(paint.RectOf(0.0, 0.0, 10.0, 10.0), paint.Rgb(220, 50, 50))
    mut ns := paint.NineSlice { slice_top = 10.0, slice_right = 10.0, slice_bottom = 10.0, slice_left = 10.0 }
    ns.width_top = 6.0
    ns.width_right = 6.0
    ns.width_bottom = 6.0
    ns.width_left = 6.0
    ns.repeat_x = paint.ImageRepeat.Round
    ns.repeat_y = paint.ImageRepeat.Stretch
    ns.fill = true
    mut cv := paint.NewCanvas(100, 60)
    cv.Clear(paint.Rgb(255, 255, 255))
    cv.DrawNineSlice(frame, paint.RectOf(4.0, 4.0, 92.0, 52.0), ns, 1.0)
    c := cv.Pixel(6, 6)
    println "corner:", c.r, c.g, c.b
    m := cv.Pixel(50, 30)
    println "middle:", m.r, m.g, m.b
    println "hash:", cv.Hash()
    cv.Free()
    frame.Free()
end
Outputcorner: 220 50 50 middle: 240 240 240 hash: 2032c6e608aafb05

BackdropFilter

func BackdropFilter(r: Rect, radii: Radii, chain: FilterChain, opacity: f64 = 1)
Filters what is already drawn under a rounded rectangle, like CSS backdrop-filter: the pixels under the rectangle are read with mirrored edges, filtered, clipped to the rounded rectangle and drawn back with opacity (1 when left out) over what was there, the way Chromium composites a backdrop. Inside a layer the backdrop is only what the layer holds.

The example for this item is shown with the ones above: gui-paint-Filter.salam

DrawOutlineStyled

func DrawOutlineStyled(r: Rect, radii: Radii, width: f64, offset: f64, c: Color, style: BorderStyle)
Draws a CSS outline with a border style (solid, dashed or dotted) around a rectangle, at an offset.

The example for this item is shown with the ones above: gui-paint-Transform.salam

ImageRepeat

enum ImageRepeat
How the edges and the middle of a nine-slice image fill their part, like border-image-repeat.
NameTypeDescription
StretchScale the part to fill the space.
RepeatTile the part at its scaled size, centred, cutting the tiles at both ends.
RoundTile the part, scaled so that a whole number of tiles fits.
SpaceTile only whole tiles and spread the space left over evenly between and around them.

The example for this item is shown with the ones above: gui-paint-NineSlice.salam

NineSlice

struct NineSlice
How DrawNineSlice cuts the source image and where the parts go.
NameTypeDescription
slice_topf64 = 0How far from the top of the source the top slice ends, in source pixels.
slice_rightf64 = 0How far from the right of the source the right slice starts, in source pixels.
slice_bottomf64 = 0How far from the bottom of the source the bottom slice starts, in source pixels.
slice_leftf64 = 0How far from the left of the source the left slice ends, in source pixels.
width_topf64 = 0How tall the top edge is drawn, in canvas pixels.
width_rightf64 = 0How wide the right edge is drawn.
width_bottomf64 = 0How tall the bottom edge is drawn.
width_leftf64 = 0How wide the left edge is drawn. Widths that do not fit are all scaled down by the same factor, as CSS does.
repeat_xImageRepeatHow the top and bottom edges and the middle fill the width.
repeat_yImageRepeatHow the left and right edges and the middle fill the height.
fillbool = falseWhether the middle part is drawn too, like the fill keyword of border-image-slice.

The example for this item is shown with the ones above: gui-paint-NineSlice.salam

Functions

LinearGradient

func LinearGradient(angle: f64): Gradient
A linear gradient at a CSS angle in degrees; add stops with AddStop.

The example for this item is shown with the ones above: gui-paint-Gradient.salam

RadialGradient

func RadialGradient(cx: f64, cy: f64, rx: f64, ry: f64): Gradient
A radial gradient with a centre and radii relative to the filled rectangle.

The example for this item is shown with the ones above: gui-paint-Gradient.salam

ConicGradient

func ConicGradient(cx: f64, cy: f64, angle: f64): Gradient
A conic gradient around a centre, starting at an angle.

The example for this item is shown with the ones above: gui-paint-Gradient.salam

RadialSize

func RadialSize(width: f64, height: f64, cx: f64, cy: f64, circle: bool, extent: RadialExtent): Point
The radii CSS gives a radial gradient for a box size, a centre, circle or ellipse, and a size keyword.

The example for this item is shown with the ones above: gui-paint-Gradient.salam

NewCanvas

func NewCanvas(width: int, height: int): Canvas
Makes a transparent canvas of a size that owns its pixels.

The example for this item is shown with the ones above: gui-paint-Box.salam

WrapCanvas

func WrapCanvas(pixels: void*, width: int, height: int, stride: int): Canvas
Makes a canvas over pixel memory that someone else owns, such as a window buffer.

The example for this item is shown with the ones above: gui-paint-Image.salam

RectOf

func RectOf(x: f64, y: f64, width: f64, height: f64): Rect
Makes a Rect.

The example for this item is shown with the ones above: gui-paint-Box.salam

IntRectOf

func IntRectOf(x: int, y: int, width: int, height: int): IntRect
Makes an IntRect.

The example for this item is shown with the ones above: gui-paint-Image.salam

Uniform

func Uniform(r: f64): Radii
Radii with the same circular radius at every corner.

The example for this item is shown with the ones above: gui-paint-Box.salam

Corners

func Corners(top_left: f64, top_right: f64, bottom_right: f64, bottom_left: f64): Radii
Circular radii for the top left, top right, bottom right and bottom left corners.

The example for this item is shown with the ones above: gui-paint-Gradient.salam

FitRadii

func FitRadii(r: Rect, radii: Radii): Radii
Scales radii down the way CSS does when adjacent radii are longer than a side.

The example for this item is shown with the ones above: gui-paint-Gradient.salam

SolidBorder

func SolidBorder(width: f64, c: Color): Border
A solid border of the same width and colour on every side.

The example for this item is shown with the ones above: gui-paint-Box.salam

NewPath

func NewPath(): Path
Makes an empty path.

The example for this item is shown with the ones above: gui-paint-Path.salam

Identity

func Identity(): Transform
The transform that changes nothing.

The example for this item is shown with the ones above: gui-paint-Path.salam

Translation

func Translation(dx: f64, dy: f64): Transform
A transform that moves by (dx, dy).

The example for this item is shown with the ones above: gui-paint-Path.salam

Scaling

func Scaling(sx: f64, sy: f64): Transform
A transform that scales x and y.

The example for this item is shown with the ones above: gui-paint-Path.salam

Skewing

func Skewing(x_degrees: f64, y_degrees: f64): Transform
A transform that slants by angles in degrees along x and y, like CSS skew().

The example for this item is shown with the ones above: gui-paint-Transform.salam

NewFilter

func NewFilter(kind: FilterKind, amount: f64): Filter
Makes a filter of a kind with an amount.

The example for this item is shown with the ones above: gui-paint-Filter.salam

DropShadowFilter

func DropShadowFilter(offset_x: f64, offset_y: f64, blur: f64, c: Color): Filter
Makes a drop-shadow filter from an offset, a blur standard deviation and a colour.

The example for this item is shown with the ones above: gui-paint-Filter.salam

Rotation

func Rotation(degrees: f64): Transform
A transform that rotates clockwise on screen by degrees around the origin.

The example for this item is shown with the ones above: gui-paint-Path.salam

FromImage

func FromImage(img: image.Image): Canvas
A canvas from an ai.image image (straight RGBA).

The example for this item is shown with the ones above: gui-paint-Image.salam

ToImage

func ToImage(cv: Canvas): image.Image
An ai.image image (straight RGBA) from a canvas.

The example for this item is shown with the ones above: gui-paint-Image.salam

SavePNG

func SavePNG(cv: Canvas, path: str): bool
Saves a canvas as a PNG file.

The example for this item is shown with the ones above: gui-paint-Image.salam

LoadPNG

func LoadPNG(path: str, ok &: bool): Canvas
Loads a PNG file into a canvas; ok reports success.

The example for this item is shown with the ones above: gui-paint-Image.salam

FitImage

func FitImage(fit: ObjectFit, image_width: int, image_height: int, box: Rect, pos_x: f64, pos_y: f64): Rect
Where an image of a size goes inside a box for an object-fit mode; pos_x and pos_y are the CSS object-position as fractions (0.5 is centred).

The example for this item is shown with the ones above: gui-paint-Image.salam

ScaleImage

func ScaleImage(src: Canvas, width: int, height: int): Canvas
A new canvas with an image resized, for caching scaled images.

The example for this item is shown with the ones above: gui-paint-Image.salam

Rgb

func Rgb(r: int, g: int, b: int): Color
An opaque colour.

The example for this item is shown with the ones above: gui-paint-Box.salam

Rgba

func Rgba(r: int, g: int, b: int, a: int): Color
A colour with alpha.

The example for this item is shown with the ones above: gui-paint-Box.salam

Premultiply

func Premultiply(c: Color): u32
The premultiplied 0xAARRGGBB value of a colour.

The example for this item is shown with the ones above: gui-paint-Image.salam

Unpremultiply

func Unpremultiply(v: u32): Color
The straight colour of a premultiplied 0xAARRGGBB value.

The example for this item is shown with the ones above: gui-paint-Image.salam