Package limn.graphics

Interface Canvas


public interface Canvas
Immediate-mode 2D drawing API: what every widget paints through. All coordinates are logical points; the backend multiplies by the monitor content scale (a float: 1.0, 1.25, 1.5, 2.0…) into physical pixels, applies analytic antialiasing, and batches geometry into as few GPU draw calls as possible.

Pixel snapping: 1-logical-pixel strokes and axis-aligned lines are automatically aligned to the physical pixel grid (odd device widths center on half-pixels, even on integers) so hairlines stay crisp at fractional scales like 125%/150%. Snapping is skipped under rotation.

State (save()/restore()) covers the transform, the opacity and the clip. Clips only intersect (never expand) and are restored by restore().

A Canvas is only valid during a frame callback and is single-threaded (UI thread).

  • Method Summary

    Modifier and Type
    Method
    Description
    default BlendMode
     
    void
    clear(Color color)
    Clears the whole framebuffer (ignores transform/clip/opacity).
    default void
    clearRect(float x, float y, float width, float height, Color color)
    Replaces one rectangle with color: clear(limn.graphics.Color) semantics confined to a rect, in untransformed logical frame coordinates, ignoring transform/clip/opacity.
    default Rect
    The current clip as a rectangle in current coordinates, or null when this canvas cannot say: it does not track clips, or the transform is rotated and the clip's device-space box has no rectangular pre-image.
    void
    clipRect(float x, float y, float width, float height)
    Intersects the clip with an axis-aligned rectangle (in current coordinates).
    default void
    clipRect(Rect rect)
    Intersects the clip with an axis-aligned rectangle (see clipRect(float, float, float, float)).
    void
    Intersects the clip with a rounded rectangle.
    float
     
    default void
    damageScissorHint(float x, float y, float width, float height)
    Backend hint from partial rendering: every draw for the rest of this frame lies inside this rect, in untransformed logical frame coordinates.
    void
    drawCircle(float cx, float cy, float radius, float strokeWidth, Paint paint)
     
    void
    drawEllipse(float cx, float cy, float radiusX, float radiusY, float strokeWidth, Paint paint)
     
    default void
    drawImage(Image image, float x, float y)
    Draws image at its natural pixel size (1 pixel = 1 logical point).
    void
    drawImage(Image image, float x, float y, float w, float h)
    Draws image into the rectangle (x, y, w, h) in logical points, scaled with linear filtering, modulated by the current opacity().
    void
    drawImage(Image image, float srcX, float srcY, float srcW, float srcH, float dstX, float dstY, float dstW, float dstH)
    Draws the source rectangle (srcX, srcY, srcW, srcH), in image pixels, into the destination rectangle (dstX, dstY, dstW, dstH) in logical points.
    void
    drawImage(Image image, float srcX, float srcY, float srcW, float srcH, float dstX, float dstY, float dstW, float dstH, Color tint)
    The sprite primitive and the tint, together: one cell of a sheet, modulated by tint exactly as drawImage(Image, float, float, float, float, Color) modulates a whole image.
    void
    drawImage(Image image, float x, float y, float w, float h, Color tint)
    Draws image tinted by tint (its RGB and alpha are multiplied by the tint's), the path for modulating a picture (e.g.
    default void
    drawImageMask(Image image, float x, float y, float w, float h, Color tint)
    Draws image as a single-color mask: the image's alpha is used as coverage and painted in tint's color; the image's own RGB is ignored.
    void
    drawLine(float x1, float y1, float x2, float y2, float strokeWidth, Paint paint)
    Line segment with round caps.
    void
    drawPath(Path2D path, float strokeWidth, Paint paint)
    Strokes a path with round caps and joins.
    void
    drawRect(float x, float y, float width, float height, float strokeWidth, Paint paint)
    Strokes the rectangle outline, centered on its boundary.
    default void
    drawRoundRect(float x, float y, float width, float height, float radius, float strokeWidth, Paint paint)
    Strokes the rounded-rect outline, centered on its boundary.
    void
    drawRoundRect(RoundRect roundRect, float strokeWidth, Paint paint)
    Strokes the rounded-rect outline, centered on its boundary.
    default void
    drawSurface(GpuSurface surface, float x, float y, float w, float h)
    Composites a GpuSurface (e.g.
    void
    drawText(String text, float x, float y, Font font, Paint paint)
    Draws a single line of text with (x, y) at the baseline origin.
    default void
    drawText(ShapedText text, float x, float y, Paint paint)
    Draws an already-shaped line with (x, y) at the baseline origin of its left edge — for either base direction: right-to-left text fills the same box from the other end rather than growing leftwards from x, so the run covers [x, x + text.metrics().width()] and right-aligning a right-to-left paragraph is a matter of choosing x.
    default void
    fillBackdropRoundRect(float x, float y, float width, float height, float radius, BackdropEffect effect)
    fillBackdropRoundRect(RoundRect, BackdropEffect) with one radius on every corner.
    default void
    Fills roundRect with what this frame has already drawn underneath it, put through effect: a glass panel, a wash over video, a redacted field.
    void
    fillCircle(float cx, float cy, float radius, Paint paint)
     
    void
    fillEllipse(float cx, float cy, float radiusX, float radiusY, Paint paint)
     
    void
    fillPath(Path2D path, Paint paint)
    Fills a path.
    void
    fillRect(float x, float y, float width, float height, Paint paint)
     
    default void
    fillRect(Rect rect, Paint paint)
     
    default void
    fillRoundRect(float x, float y, float width, float height, float radius, Paint paint)
     
    void
    fillRoundRect(RoundRect roundRect, Paint paint)
     
    float
     
    measureText(String text, Font font)
    Measures a single line of text in logical points (baseline-relative, unquantized and therefore independent of the monitor scale).
    float
     
    default boolean
     
    void
    Pops back to the previously saved state.
    void
    restoreToCount(int count)
    Pops saves until saveCount() equals count.
    void
    rotate(float angleRadians)
    Rotates by angleRadians (positive = clockwise, y grows down).
    default Sampling
     
    void
    Pushes a copy of the current state (transform, opacity, clip, blend mode, sampling, pixel snap).
    int
    How many save()s are outstanding, the depth of the state stack.
    void
    scale(float sx, float sy)
     
    default void
    Sets the current state's BlendMode: how every subsequent primitive (shape, text, image) combines with the destination.
    void
    setOpacity(float opacity)
    Sets the current state's opacity multiplier in [0..1]; every subsequent paint's alpha is multiplied by it.
    default void
    setPixelSnap(boolean snap)
    Enables/disables the automatic pixel-grid snapping of image quads under axis-aligned transforms.
    default void
    Sets the current state's image Sampling: how subsequent drawImage calls filter texels (Sampling.PIXELATED keeps pixel art crisp when scaled).
    void
    translate(float dx, float dy)
     
    float
     
  • Method Details

    • width

      float width()
      Returns:
      frame width in logical points
    • height

      float height()
      Returns:
      frame height in logical points
    • contentScale

      float contentScale()
      Returns:
      physical pixels per logical point for this frame
    • clear

      void clear(Color color)
      Clears the whole framebuffer (ignores transform/clip/opacity).
    • clearRect

      default void clearRect(float x, float y, float width, float height, Color color)
      Replaces one rectangle with color: clear(limn.graphics.Color) semantics confined to a rect, in untransformed logical frame coordinates, ignoring transform/clip/opacity. Partial rendering resets each repaint pass with this. The default falls back to fillRect(float, float, float, float, limn.graphics.Paint), which is equivalent only for opaque colors: backends that support translucent framebuffers (popup windows) must override it with a true replace (a blend cannot write alpha back to 0).
    • damageScissorHint

      default void damageScissorHint(float x, float y, float width, float height)
      Backend hint from partial rendering: every draw for the rest of this frame lies inside this rect, in untransformed logical frame coordinates. Backends may confine rasterization to it (e.g. glScissor) so fragments outside are never shaded. Purely an optimization: pixels outside simply keep their previous content, so honoring it must not change what a correct frame produces. Reset at the start of each frame; a non-positive width or height disables it. Default: ignored.
    • save

      void save()
      Pushes a copy of the current state (transform, opacity, clip, blend mode, sampling, pixel snap).
    • restore

      void restore()
      Pops back to the previously saved state.
    • saveCount

      int saveCount()
      How many save()s are outstanding, the depth of the state stack.

      It exists so that a caller can put the canvas back where it found it without knowing how many saves happened in between, which is the only way to be safe against code it does not control: a widget's paint may push clips, may push more of them down a branch, and may stop halfway by throwing. Widget.paintWidget reads this before painting a widget and trims back to it afterwards, so one widget's leak cannot become every ancestor's.

      Returns:
      the number of outstanding saves; 0 on a canvas nobody has saved on
    • restoreToCount

      void restoreToCount(int count)
      Pops saves until saveCount() equals count.

      Does nothing when the depth is already at or below count: it is a trim, not an assertion, and a caller using it to recover from someone else's mistake must not be given a second mistake to handle. Never pops past zero.

      Parameters:
      count - the depth to return to, normally one taken from saveCount() earlier
    • translate

      void translate(float dx, float dy)
    • scale

      void scale(float sx, float sy)
    • rotate

      void rotate(float angleRadians)
      Rotates by angleRadians (positive = clockwise, y grows down).
    • setOpacity

      void setOpacity(float opacity)
      Sets the current state's opacity multiplier in [0..1]; every subsequent paint's alpha is multiplied by it. Restored by restore().
    • opacity

      float opacity()
    • setBlendMode

      default void setBlendMode(BlendMode mode)
      Sets the current state's BlendMode: how every subsequent primitive (shape, text, image) combines with the destination. Restored by restore(). Backends without blend-mode support paint BlendMode.NORMAL (the no-op default).
    • blendMode

      default BlendMode blendMode()
      Returns:
      the current BlendMode (default BlendMode.NORMAL)
    • setSampling

      default void setSampling(Sampling sampling)
      Sets the current state's image Sampling: how subsequent drawImage calls filter texels (Sampling.PIXELATED keeps pixel art crisp when scaled). Restored by restore(). Shapes and text are unaffected.
    • sampling

      default Sampling sampling()
      Returns:
      the current image Sampling (default Sampling.SMOOTH)
    • setPixelSnap

      default void setPixelSnap(boolean snap)
      Enables/disables the automatic pixel-grid snapping of image quads under axis-aligned transforms. Snapping (the default) keeps icons and stills crisp; disable it for sprites that move sub-pixel per frame. A slow smooth scroll snaps into a visible stutter otherwise. Text and stroke snapping are unaffected. Restored by restore().
    • pixelSnap

      default boolean pixelSnap()
      Returns:
      whether image quads snap to the pixel grid (default true)
    • clipRect

      void clipRect(float x, float y, float width, float height)
      Intersects the clip with an axis-aligned rectangle (in current coordinates). V1 limitation: under a rotated transform the clip degrades to the device-space AABB of the transformed corners, a superset of the requested region (content may show in the AABB's corners). Axis-aligned transforms clip exactly.
    • clipRect

      default void clipRect(Rect rect)
      Intersects the clip with an axis-aligned rectangle (see clipRect(float, float, float, float)).
    • clipBounds

      default Rect clipBounds()
      The current clip as a rectangle in current coordinates, or null when this canvas cannot say: it does not track clips, or the transform is rotated and the clip's device-space box has no rectangular pre-image. A caller may use a non-null answer only to skip work whose every pixel would be clipped away, which is what Widget does for a subtree scrolled entirely out of a viewport; nothing may draw more because of it. The default is the null answer, so a canvas that inherits it simply paints everything.
    • clipRoundRect

      void clipRoundRect(RoundRect roundRect)
      Intersects the clip with a rounded rectangle. V1 limitations: the clip state tracks one rounded rect, so nested rounded clips intersect their rectangles exactly, but only the most recent corner radius applies, as a single uniform radius (the max of the four corners, clamped to the clip region's half extents); rotation degrades to the AABB like clipRect(float, float, float, float).
    • fillRect

      void fillRect(float x, float y, float width, float height, Paint paint)
    • fillRect

      default void fillRect(Rect rect, Paint paint)
    • drawRect

      void drawRect(float x, float y, float width, float height, float strokeWidth, Paint paint)
      Strokes the rectangle outline, centered on its boundary.
    • fillRoundRect

      void fillRoundRect(RoundRect roundRect, Paint paint)
    • fillRoundRect

      default void fillRoundRect(float x, float y, float width, float height, float radius, Paint paint)
    • fillBackdropRoundRect

      default void fillBackdropRoundRect(RoundRect roundRect, BackdropEffect effect)
      Fills roundRect with what this frame has already drawn underneath it, put through effect: a glass panel, a wash over video, a redacted field. The shape is opaque within its coverage: the effect replaces the pixels behind it with a transformed copy of them, so a BackdropEffect.tint() with alpha 0 and no displacement is exactly identity.

      It samples the frame at the moment it is called. Anything drawn after it is not in the backdrop and will simply paint on top; anything drawn before it is. That makes paint order load-bearing in a way it is not anywhere else in this interface: a panel must be painted after the content it is meant to sit over, which for a widget means the ordinary child order and for a scene means the ordinary z order.

      Cost. A renderer that batches (this one does) must flush pending geometry and copy a region of the framebuffer before it can sample it, so every call is a batch break plus a copy of the shape's bounds. That is cheap for a control bar and wasteful for a hundred small shapes; it is the reason this is a separate entry point rather than a Paint.

      Where it goes wrong, and it is one place: with partial rendering enabled (Scene.setPartialRendering), a frame repaints only what was invalidated. A shape filled this way depends on pixels that are not its own, so if content behind it changes while it does not itself invalidate, it keeps showing the older backdrop. Over content that repaints anyway (video, an animation, a viewport), the question does not arise. Over static content the effect is correct because nothing moved.

      Rounded rectangles only, which covers rectangles, circles and capsules through the same shape. There is no path-shaped form: the effect needs the shape's signed distance to find its own rim, and a filled path has no analytic distance field.

      Renderers need not implement it. The default fills the shape with BackdropEffect.tint(): a flat translucent panel, the right size in the right place, which is what this toolkit drew before any of this existed. A backdrop effect degrades; it does not fail.

      Throws:
      NullPointerException - if either argument is null
    • fillBackdropRoundRect

      default void fillBackdropRoundRect(float x, float y, float width, float height, float radius, BackdropEffect effect)
      fillBackdropRoundRect(RoundRect, BackdropEffect) with one radius on every corner.
    • drawRoundRect

      void drawRoundRect(RoundRect roundRect, float strokeWidth, Paint paint)
      Strokes the rounded-rect outline, centered on its boundary.
    • drawRoundRect

      default void drawRoundRect(float x, float y, float width, float height, float radius, float strokeWidth, Paint paint)
      Strokes the rounded-rect outline, centered on its boundary.

      A degenerate size (zero or negative width/height) draws nothing rather than throwing, which is the convention every 2D API follows, and a practical necessity: the near-universal border idiom drawRoundRect(0.5f, 0.5f, width() - 1, height() - 1, …) produces a negative size the moment a widget is laid out under 1 pt, which happens routinely while a window is being resized small. Explicitly building a RoundRect still validates, so genuinely malformed geometry (say, swapped corners) is still caught at construction.

    • fillCircle

      void fillCircle(float cx, float cy, float radius, Paint paint)
    • drawCircle

      void drawCircle(float cx, float cy, float radius, float strokeWidth, Paint paint)
    • fillEllipse

      void fillEllipse(float cx, float cy, float radiusX, float radiusY, Paint paint)
    • drawEllipse

      void drawEllipse(float cx, float cy, float radiusX, float radiusY, float strokeWidth, Paint paint)
    • drawLine

      void drawLine(float x1, float y1, float x2, float y2, float strokeWidth, Paint paint)
      Line segment with round caps. Axis-aligned lines are pixel-snapped.
    • fillPath

      void fillPath(Path2D path, Paint paint)
      Fills a path. Each closed subpath is filled independently (see Path2D for the v1 winding limitations).
    • drawPath

      void drawPath(Path2D path, float strokeWidth, Paint paint)
      Strokes a path with round caps and joins. V1 limitation: joins are overlapping round caps, so translucent paints double-blend slightly at each vertex.
    • drawText

      void drawText(String text, float x, float y, Font font, Paint paint)
      Draws a single line of text with (x, y) at the baseline origin. Glyphs are rasterized at font.size() × the effective device scale (content scale × canvas transform) and cached in a glyph atlas keyed by physical pixel size: bitmaps are never scaled, so text stays sharp at 1.0/1.25/1.5/2.0. Under axis-aligned uniform transforms, glyphs are snapped to the physical pixel grid.

      Full code-point support, surrogate pairs included. A code point the font lacks is resolved against the registered fallback faces, so mixed-script text draws; a code point no face has renders as .notdef.

      This is the shaped path with the shaping done for you. A canvas that has a shaper hands the string to the installed TextRuler and draws the ShapedText it gets back, so contextual forms, ligatures, mark attachment and bidirectional ordering are what any caption gets, not only the widgets that hold a value of their own. What that costs is one memo lookup per call on the ruler's side; what it buys is that the string measured by measureText(java.lang.String, limn.graphics.Font) and the string drawn here are the same arithmetic. A caller that draws the same text every frame should still hold a ShapedText and use the overload below — that is the form that pays nothing at all, and the only one that carries a base direction: this one resolves the paragraph from the string alone, left to right where no character is strong, which is wrong exactly for the all-neutral captions a widget could have decided (Widget.shapeText).

      Control characters, \n included, are skipped: multi-line layout belongs to the widget layer. Under anisotropic scale glyphs rasterize at the larger axis and filter on the smaller one.

    • drawText

      default void drawText(ShapedText text, float x, float y, Paint paint)
      Draws an already-shaped line with (x, y) at the baseline origin of its left edge — for either base direction: right-to-left text fills the same box from the other end rather than growing leftwards from x, so the run covers [x, x + text.metrics().width()] and right-aligning a right-to-left paragraph is a matter of choosing x. Rasterization, atlas keying and pixel snapping are exactly as drawText(String, float, float, Font, Paint) describes.

      This is the form that costs nothing per frame: the shaping was already paid for, and this walks runs, resolving a face once each, then glyphs. The String overload above draws the same scripts equally correctly and pays a memo lookup to do it; the difference is only who holds the value. There is no Font parameter because the font is the one the glyphs were chosen for; a font passed here could disagree with it, and the text would then be measured by one face and drawn by another.

      A cluster reported as ShapedText.NO_GLYPH is drawn from the shaped text's own characters instead, which is how a colour-emoji strike keeps working and how a ruler that cannot shape still paints. So does a run whose face this canvas no longer recognizes, which is what a value shaped before an eviction becomes: a stale value draws the right characters by the slower route rather than the wrong glyphs from whichever face inherited the id.

      The whole run draws in one Paint. Text that changes colour partway is two runs today, and two runs shape independently.

      Default, not abstract, for two reasons that point the same way. Canvas is a published interface, and an abstract method added to one breaks every implementation that exists outside this repository as well as every recording and counting canvas inside it. And the honest fallback for a canvas that cannot place glyphs itself is the string the run was shaped from, which is what the default draws.

    • measureText

      TextMetrics measureText(String text, Font font)
      Measures a single line of text in logical points (baseline-relative, unquantized and therefore independent of the monitor scale). Same code-point rules as drawText(java.lang.String, float, float, limn.graphics.Font, limn.graphics.Paint), and the same shaping: a canvas that shapes when it draws measures what it will draw, so a caption is never laid out to one width and painted at another.
    • drawImage

      void drawImage(Image image, float x, float y, float w, float h)
      Draws image into the rectangle (x, y, w, h) in logical points, scaled with linear filtering, modulated by the current opacity(). The GPU texture is created lazily on first use and cached per window.
    • drawImage

      default void drawImage(Image image, float x, float y)
      Draws image at its natural pixel size (1 pixel = 1 logical point).
    • drawImage

      void drawImage(Image image, float srcX, float srcY, float srcW, float srcH, float dstX, float dstY, float dstW, float dstH)
      Draws the source rectangle (srcX, srcY, srcW, srcH), in image pixels, into the destination rectangle (dstX, dstY, dstW, dstH) in logical points. This is the sprite primitive: frames of a sprite sheet, tiles of a tileset and regions of a texture atlas all draw from one shared Image (one GPU texture, so batching stays unbroken across sprites of the same sheet).

      The source rectangle is not clamped: coordinates outside the image sample the edge texels (CLAMP_TO_EDGE). With Sampling.SMOOTH, half a texel of bleed from neighboring atlas cells is possible at the edges; pack sheets with 1px gutters or use Sampling.PIXELATED for exact cells. Gutters only protect mip level 0: SMOOTH minification (drawing a cell well below its pixel size) selects coarser mips where whole cells average together; for atlases that will be minified, use PIXELATED or pad cells generously.

    • drawImage

      void drawImage(Image image, float srcX, float srcY, float srcW, float srcH, float dstX, float dstY, float dstW, float dstH, Color tint)
      The sprite primitive and the tint, together: one cell of a sheet, modulated by tint exactly as drawImage(Image, float, float, float, float, Color) modulates a whole image.

      Both halves already existed and could not be used at once, which left an ordinary thing (a tinted sprite from a sheet) reachable only by splitting the sheet into one Image per cell and losing the batching that is the point of a sheet. Everything the source-rectangle overload says about clamping, bleed and mips applies here unchanged.

    • drawImage

      void drawImage(Image image, float x, float y, float w, float h, Color tint)
      Draws image tinted by tint (its RGB and alpha are multiplied by the tint's), the path for modulating a picture (e.g. ImageView.setTint). A white opaque tint draws it unchanged. A black source stays black: to recolor a mask, use drawImageMask(limn.graphics.Image, float, float, float, float, limn.graphics.Color).
    • drawImageMask

      default void drawImageMask(Image image, float x, float y, float w, float h, Color tint)
      Draws image as a single-color mask: the image's alpha is used as coverage and painted in tint's color; the image's own RGB is ignored. This is the icon-recolor path: a mask authored in any color (black, white, colored) recolors cleanly to the theme, unlike drawImage(Image, float, float, float, float, Color) which multiplies (so a black mask would stay black). The default falls back to that multiply; backends override for a true coverage tint.
    • drawSurface

      default void drawSurface(GpuSurface surface, float x, float y, float w, float h)
      Composites a GpuSurface (e.g. an offscreen 3D viewport) into the rectangle (x, y, w, h) in logical points, as one quad in the 2D paint order, so overlays, dialogs, tooltips and clipping apply to it like any other content. The surface must already have been rendered this frame. The default is a no-op (surfaces need a GPU backend); backends override.