Interface Canvas
- All Known Implementing Classes:
NoopCanvas
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(); an opacity multiplies the one in force. A
layer is a save whose drawing is composited as one
picture by its restore().
A Canvas is only valid during a frame callback and is single-threaded (UI thread).
-
Method Summary
Modifier and TypeMethodDescriptiondefault BlendModevoidClears the whole framebuffer (ignores transform/clip/opacity).default voidReplaces one rectangle withcolor:clear(limn.graphics.Color)semantics confined to a rect, in untransformed logical frame coordinates, ignoring transform/clip/opacity.default @Nullable RectThe current clip as a rectangle in current coordinates, ornullwhen 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.voidclipRect(float x, float y, float width, float height) Intersects the clip with an axis-aligned rectangle (in current coordinates).default voidIntersects the clip with an axis-aligned rectangle (seeclipRect(float, float, float, float)).voidclipRoundRect(RoundRect roundRect) Intersects the clip with a rounded rectangle.default voidclipRoundRectInsideBorder(float x, float y, float width, float height, float radius, float borderWidth) Intersects the clip with the shapefillRoundRectInsideBorder(float, float, float, float, float, float, limn.graphics.Paint)fills, for a surface whose fill is a gradient, an image or several shapes: clip, paint them over the whole box, restore, then draw the border.floatdefault voiddamageScissorHint(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.voiddrawCircle(float cx, float cy, float radius, float strokeWidth, Paint paint) voiddrawEllipse(float cx, float cy, float radiusX, float radiusY, float strokeWidth, Paint paint) default voidDrawsimageat its natural pixel size (1 pixel = 1 logical point).voidDrawsimageinto the rectangle(x, y, w, h)in logical points, scaled with linear filtering, modulated by the currentopacity().voiddrawImage(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.voiddrawImage(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 bytintexactly asdrawImage(Image, float, float, float, float, Color)modulates a whole image.voidDrawsimagetinted bytint(its RGB and alpha are multiplied by the tint's), the path for modulating a picture (e.g.default voiddrawImageMask(Image image, float x, float y, float w, float h, Color tint) Drawsimageas a single-color mask: the image's alpha is used as coverage and painted intint's color; the image's own RGB is ignored.voidLine segment with round caps.voidStrokes a path with round caps and joins.voidStrokes the rectangle outline, centered on its boundary.default voiddrawRoundRect(float x, float y, float width, float height, float radius, float strokeWidth, Paint paint) Strokes the rounded-rect outline, centered on its boundary.voiddrawRoundRect(RoundRect roundRect, float strokeWidth, Paint paint) Strokes the rounded-rect outline, centered on its boundary.default voiddrawRoundRectBorder(float x, float y, float width, float height, float radius, float borderWidth, Paint paint) Strokes a borderborderWidthwide lying wholly inside the rounded box, its outer edge exactly the box's outline.default voiddrawShadow(RoundRect shape, Shadow shadow) Draws one layer of the shadow a rounded rectangle casts: the shape moved by the layer's offset, its edge softened over the layer's blur, in the layer's colour.voiddrawSurface(GpuSurface surface, float x, float y, float w, float h) Composites aGpuSurface(e.g.voidDraws a single line of text with(x, y)at the baseline origin.default voiddrawText(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 fromx, so the run covers[x, x + text.metrics().width()]and right-aligning a right-to-left paragraph is a matter of choosingx.default voidfillBackdropRoundRect(float x, float y, float width, float height, float radius, BackdropEffect effect) fillBackdropRoundRect(RoundRect, BackdropEffect)with one radius on every corner.default voidfillBackdropRoundRect(RoundRect roundRect, BackdropEffect effect) FillsroundRectwith what this frame has already drawn underneath it, put througheffect: a glass panel, a wash over video, a redacted field.voidfillCircle(float cx, float cy, float radius, Paint paint) voidfillEllipse(float cx, float cy, float radiusX, float radiusY, Paint paint) voidFills a path.voiddefault voiddefault voidfillRoundRect(float x, float y, float width, float height, float radius, Paint paint) voidfillRoundRect(RoundRect roundRect, Paint paint) default voidfillRoundRectInsideBorder(float x, float y, float width, float height, float radius, float borderWidth, Paint paint) Fills the box inside the borderdrawRoundRectBorder(float, float, float, float, float, float, limn.graphics.Paint)draws with the same arguments, as far as the border's centre line: the box inset by half the border, with the radius reduced by as much.default voidfillRoundRectWithBorder(float x, float y, float width, float height, float radius, Paint fill, float borderWidth, Paint border) Paints a bordered surface:fillover the rounded box, and a borderborderWidthwide inborderlying wholly inside the box, its outer edge exactly the box's outline.floatheight()measureText(String text, Font font) Measures a single line of text in logical points (baseline-relative, unquantized and therefore independent of the monitor scale).floatopacity()default booleandefault @Nullable ImagereadBack(float x, float y, float width, float height) Reads back, now, a part of what has been drawn on this canvas in this frame, with its transparency: what was cleared to nothing and not painted over is transparent in the image.voidrestore()Pops back to the previously saved state.voidrestoreToCount(int count) Pops saves untilsaveCount()equalscount.voidrotate(float angleRadians) Rotates byangleRadians(positive = clockwise, y grows down).default Samplingsampling()voidsave()Pushes a copy of the current state (transform, opacity, clip, blend mode, sampling, pixel snap).intHow manysave()s are outstanding, the depth of the state stack.default voidsaveLayer(float x, float y, float width, float height, float opacity) Opens a layer: until the matchingrestore(), everything drawn goes into an offscreen picture, and the restore composites that picture over what is beneath it withopacity, once.default voidOpens a layer drawn in a space of its own and composited throughtransform: until the matchingrestore(), current coordinates are the layer's, upright, and its picture is laid where the canvas as it stood drawstransformtimes each of its points.voidscale(float sx, float sy) default voidsetBlendMode(BlendMode mode) Sets the current state'sBlendMode: how every subsequent primitive (shape, text, image) combines with the destination.voidsetOpacity(float opacity) Multiplies the current state's opacity byopacity, clamped to [0..1]; every subsequent paint's alpha is multiplied by the result.default voidsetPixelSnap(boolean snap) Enables/disables the automatic pixel-grid snapping of image quads under axis-aligned transforms.default voidsetSampling(Sampling sampling) Sets the current state's imageSampling: how subsequentdrawImagecalls filter texels (Sampling.PIXELATEDkeeps pixel art crisp when scaled).voidtranslate(float dx, float dy) floatwidth()
-
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
Clears the whole framebuffer (ignores transform/clip/opacity). -
readBack
Reads back, now, a part of what has been drawn on this canvas in this frame, with its transparency: what was cleared to nothing and not painted over is transparent in the image. It is how a picture of one thing is made, painted alone on a cleared corner of a frame before the frame itself is painted over it; a drag's picture is.On the canvas, and not on the renderer behind it, because whoever paints a scene has the canvas and may have nothing else: an application that renders its scene from a frame callback of its own hands the scene a canvas and no renderer.
- Parameters:
x- the part's left edge, in logical pointsy- the part's top edge, in logical pointswidth- its width, in logical pointsheight- its height, in logical points- Returns:
- the pixels, straight alpha, rows top-down, at the canvas's own resolution; null where the canvas keeps no transparency or cannot be read
-
clearRect
Replaces one rectangle withcolor: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 tofillRect(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 manysave()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.paintWidgetreads 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 untilsaveCount()equalscount.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 fromsaveCount()earlier
-
translate
void translate(float dx, float dy) -
scale
void scale(float sx, float sy) -
rotate
void rotate(float angleRadians) Rotates byangleRadians(positive = clockwise, y grows down). -
setOpacity
void setOpacity(float opacity) Multiplies the current state's opacity byopacity, clamped to [0..1]; every subsequent paint's alpha is multiplied by the result. Restored byrestore().It multiplies rather than replaces so that an opacity set inside a fading surface, or inside a layer, combines with the one around it instead of undoing it: a widget at one half inside a panel fading at one half draws at a quarter. On a fresh
save()the current opacity is whatever the enclosing state carries, which is 1 at the top of a frame and inside a layer, so a caller that saves and sets once gets the number it asked for. A caller that wants an absolute value has to know what it is multiplying.This is a per-draw alpha: each draw is translucent on its own, so where one draw covers another the one beneath shows through it. To fade overlapping draws as one picture, open a
layerinstead.- Parameters:
opacity- the factor, clamped to [0..1]
-
opacity
float opacity()- Returns:
- the current state's opacity, the product of every
setOpacity(float)still in force
-
saveLayer
default void saveLayer(float x, float y, float width, float height, float opacity) Opens a layer: until the matchingrestore(), everything drawn goes into an offscreen picture, and the restore composites that picture over what is beneath it withopacity, once. Overlapping draws inside it therefore fade as one picture: a row's highlight does not show through the row's icon, and a panel's shadow does not darken the panel. This is what anOpacitycontainer draws through, and what a widget that paints overlapping parts of its own uses to fade them together.The rectangle, in current coordinates, is what the picture keeps: whatever is drawn outside it is lost. A canvas sizes the picture to the rectangle as far as its current clip lets it be seen, on whole device pixels, so a layer is never larger than what can show. Like
save()it pushes the state (transform, clip, blend mode, sampling, pixel snap), and inside it the opacity starts again from 1, since the composite carries it; the blend mode in force when it is opened is the one the composite uses, and drawing inside starts fromBlendMode.NORMAL.A blend mode inside a layer blends with the layer, which starts transparent, and not with what is beneath it, as an isolated group of a web page does: a draw in
BlendMode.MULTIPLYorBlendMode.ADDITIVEcombines with what was drawn before it inside the layer, and where nothing was, it is drawn as itself, as a draw inBlendMode.NORMALwould be. An opacity of 1 underBlendMode.NORMALopens no layer, so there the same draw combines with what is beneath: a tint that multiplies inside a group fading in shows over the page as itself during the fade and multiplies it once the fade arrives at 1, which a page's own fade does too. Draw what should blend with what is beneath outside the group, or keep the group below 1. Inside a layerBlendMode.MULTIPLYis drawn in two passes: draws in it that cannot cover a pixel in common share them, shapes a pixel or more apart included, and each that can, shapes that touch included, costs two draw calls of its own, a line of text about one pair for every eight to ten glyphs (BlendModehas the measured numbers).What it costs, measured on the renderer this toolkit ships: two batch breaks and one composited quad per layer, about 0.03 to 0.1 ms of GPU for a popup-sized layer on a GPU and a percent or two of the frame on a software renderer; a window-sized one costs 0.1 to 0.15 ms of GPU, and about 10% of a frame on a software renderer. A canvas keeps the layer's memory, four bytes a device pixel, only while consecutive frames open layers. An opacity of 1 under
BlendMode.NORMALneeds none, and a canvas opens none for it; under another mode it does, since the rule above holds at every opacity: drawing at 1 blend by blend would not be the picture blended once, and a fade arriving at 1 would jump.A canvas that cannot make layers (a recording or measuring one) falls back to what the default does: a
save()and a per-drawsetOpacity(float), which draws the same things in the same places and lets overlapping draws show through each other, each in the blend mode in force, at every opacity alike.- Parameters:
x- the left edge of what the layer keeps, in current coordinatesy- its top edgewidth- its widthheight- its heightopacity- the opacity the picture is composited with, clamped to [0..1]
-
saveLayer
Opens a layer drawn in a space of its own and composited throughtransform: until the matchingrestore(), current coordinates are the layer's, upright, and its picture is laid where the canvas as it stood drawstransformtimes each of its points. The layer's space is the planez = 0: a point(x, y)of it is drawn at the matrix's x and y of(x, y, 0, 1), each divided by its w, and a point whose w is not above 0 lies behind the eye and is not drawn. This is how a rotated, skewed or turned subtree keeps every clip inside it an exact rectangle (a scroll view turned a few degrees shows nothing in its corners), andclipBounds()answering inside it, so that what scrolled away is still skipped.The rectangle, in the layer's own coordinates, is what the picture keeps. A canvas sizes the picture to the part of the rectangle that the clip in force lets be seen once it is turned, in front of the eye, at the largest magnification the transform gives any point of it, the nearest under a perspective, so text and shapes are drawn into it at the size they are seen at; past the window's size the picture is drawn smaller and composited softer. Seen in perspective, the picture is laid perspective-correctly, and a glass drawn inside reads only what the subtree drew beneath it. Its edges are antialiased by a clear ring of one pixel round what is drawn, which the composite's filtering ramps over about a pixel. The clip in force when it is opened, rounded or not, cuts the composite. Otherwise as
saveLayer(float, float, float, float, float): the opacity and the blend mode in force are the composite's, and drawing inside starts again from an opacity of 1 andBlendMode.NORMAL.What it costs: what a layer of the same size costs, since it is one picture and one quad, and the frame after the last one that opens it frees the picture. A transform that only moves and scales along the axes needs no picture, and a canvas draws through it straight, as
translate(float, float)andscale(float, float)would.A canvas that cannot make layers applies the transform's flat part, its x, y and w rows and columns without the w row's tilt, straight to its own transform, through
translate(float, float),rotate(float)andscale(float, float), and the opacity per draw: the same commands in nearly the same places, which is what counting them needs.- Parameters:
x- the left edge of what the layer keeps, in the layer's own coordinatesy- its top edgewidth- its widthheight- its heightopacity- the opacity the picture is composited with, clamped to [0..1]transform- where the layer's space is drawn, applied after the canvas's own transform
-
setBlendMode
Sets the current state'sBlendMode: how every subsequent primitive (shape, text, image) combines with the destination. Restored byrestore(). Backends without blend-mode support paintBlendMode.NORMAL(the no-op default).Inside a layer the destination is the layer's own picture, which starts transparent: a draw in another mode combines with what the layer drew before it, and where it drew nothing is drawn as itself.
-
blendMode
- Returns:
- the current
BlendMode(defaultBlendMode.NORMAL)
-
setSampling
Sets the current state's imageSampling: how subsequentdrawImagecalls filter texels (Sampling.PIXELATEDkeeps pixel art crisp when scaled). Restored byrestore(). Shapes and text are unaffected. -
sampling
- Returns:
- the current image
Sampling(defaultSampling.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 byrestore(). -
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
Intersects the clip with an axis-aligned rectangle (seeclipRect(float, float, float, float)). -
clipBounds
The current clip as a rectangle in current coordinates, ornullwhen 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 whatWidgetdoes 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
Intersects the clip with a rounded rectangle. The corners are this rectangle's own: aclipRect(float, float, float, float)made before or after it cuts the region straight and never rounds where it cuts, so a repaint confined to part of a rounded surface draws that part exactly as a whole frame does.V1 limitations: the clip state tracks one rounded rect, so nested rounded clips intersect their rectangles exactly and the region they leave is rounded by the most recent corner radius alone, as a single uniform radius (the max of the four corners, clamped to that region's half extents). That is exact when the inner rounded rect lies inside the outer one; where the two only overlap, every corner of the region they leave is rounded by the inner radius, whichever rectangle it came from. A rounded rect whose corners are all square clips like
clipRectand leaves an earlier one's corners as they were. Rotation degrades to the AABB likeclipRect. -
fillRect
-
fillRect
-
drawRect
Strokes the rectangle outline, centered on its boundary. -
fillRoundRect
-
fillRoundRect
-
drawShadow
Draws one layer of the shadow a rounded rectangle casts: the shape moved by the layer's offset, its edge softened over the layer's blur, in the layer's colour. Drawn whole, under where the shape will be: paint the shape after it. Ink reachesShadow.reach()past the shape's box.The default draws nothing: a shadow is decoration a canvas may go without, and a recording or counting canvas has nothing to say about it.
- Parameters:
shape- the shape that casts itshadow- the layer- Throws:
NullPointerException- if either argument is null
-
fillBackdropRoundRect
FillsroundRectwith what this frame has already drawn underneath it, put througheffect: 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 aBackdropEffect.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
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: a border drawn inside its box (
drawRoundRectBorder(float, float, float, float, float, float, limn.graphics.Paint), a stroke on the box inset by half the border) produces a negative size the moment a widget is laid out narrower than its border, which happens routinely while a window is being resized small. Explicitly building aRoundRectstill validates, so genuinely malformed geometry (say, swapped corners) is still caught at construction. -
fillRoundRectWithBorder
default void fillRoundRectWithBorder(float x, float y, float width, float height, float radius, Paint fill, float borderWidth, Paint border) Paints a bordered surface:fillover the rounded box, and a borderborderWidthwide inborderlying wholly inside the box, its outer edge exactly the box's outline. The one call for a field, a card, a panel or a swatch;drawRoundRectBorder(float, float, float, float, float, float, limn.graphics.Paint)explains the geometry, andfillRoundRectInsideBorder(float, float, float, float, float, float, limn.graphics.Paint)andclipRoundRectInsideBorder(float, float, float, float, float, float)are its two halves, for a surface that paints something between its fill and its border or whose fill is not one paint.The defaults here build on the scalar
fillRoundRectanddrawRoundRect, so they allocate nothing. A renderer may draw the pair in one pass instead, as long as it keeps the geometry the defaults define, moved onto its pixel grid as the two halves say. -
drawRoundRectBorder
default void drawRoundRectBorder(float x, float y, float width, float height, float radius, float borderWidth, Paint paint) Strokes a borderborderWidthwide lying wholly inside the rounded box, its outer edge exactly the box's outline.The geometry. A stroke is centred on its path, so a border inside a box is a stroke on the box inset by half the border. The inset path's corner radius has to shrink by the same half border, and that is what makes it concentric with the box: every point of an arc of radius
radius - borderWidth / 2is half a border from the box's own arc, so the stroke's outer edge follows the box's outline all the way round. Insetting the rectangle but keeping the box's radius, the obviousdrawRoundRect(0.5f, 0.5f, w - 1, h - 1, radius, 1, …), draws a corner whose outer edge is an arc of radiusradius + 0.5about a centre half a point further in: on the diagonal it falls0.5 × (√2 − 1), about 0.2 pt, short of the box's corner, and whatever fills the box shows outside the border there.A radius under half the border leaves the path square, and the stroke's outer corner is then rounded by half the border, which is as tight as a centred stroke can draw. A box no wider or taller than its border draws nothing.
-
fillRoundRectInsideBorder
default void fillRoundRectInsideBorder(float x, float y, float width, float height, float radius, float borderWidth, Paint paint) Fills the box inside the borderdrawRoundRectBorder(float, float, float, float, float, float, limn.graphics.Paint)draws with the same arguments, as far as the border's centre line: the box inset by half the border, with the radius reduced by as much. Reaching exactly that far is what hides the fill's antialiased edge under the border's opaque middle: a fill of the whole box puts that edge on the border's own outer edge, and the partly covered pixels there mix the fill into the border's fringe, which a strong fill under a grey border shows as a coloured rim. Draw the border after it.A renderer that moves a stroke's centre line onto its pixel grid (see the class notes) moves this fill's edge with it, to where the border's centre line is drawn rather than where it was asked for: left behind, the fill's corner arc no longer shares a centre with the border's: wherever the border moved inward the fill shows past it on the diagonal, and wherever it moved outward the background shows between the two.
A border that fades out, leaving the fill to stand alone, is the one case this is wrong for: the fill has to reach the box then, and is a plain
fillRoundRect(float, float, float, float, float, Paint)of it. -
clipRoundRectInsideBorder
default void clipRoundRectInsideBorder(float x, float y, float width, float height, float radius, float borderWidth) Intersects the clip with the shapefillRoundRectInsideBorder(float, float, float, float, float, float, limn.graphics.Paint)fills, for a surface whose fill is a gradient, an image or several shapes: clip, paint them over the whole box, restore, then draw the border. Builds oneRoundRect, asclipRoundRect(limn.graphics.RoundRect)needs. A renderer that moves strokes onto its pixel grid moves this clip's edge with the border's centre line, asfillRoundRectInsideBorder(float, float, float, float, float, float, limn.graphics.Paint)explains. -
fillCircle
-
drawCircle
-
fillEllipse
-
drawEllipse
-
drawLine
Line segment with round caps. Axis-aligned lines are pixel-snapped. -
fillPath
Fills a path. Each closed subpath is filled independently (seePath2Dfor the v1 winding limitations). -
drawPath
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
Draws a single line of text with(x, y)at the baseline origin. Glyphs are rasterized atfont.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
TextRulerand draws theShapedTextit 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 bymeasureText(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 aShapedTextand 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,
\nincluded, 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
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 fromx, so the run covers[x, x + text.metrics().width()]and right-aligning a right-to-left paragraph is a matter of choosingx. Rasterization, atlas keying and pixel snapping are exactly asdrawText(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
Stringoverload 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 noFontparameter 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_GLYPHis 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.
Canvasis 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
Measures a single line of text in logical points (baseline-relative, unquantized and therefore independent of the monitor scale). Same code-point rules asdrawText(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
Drawsimageinto the rectangle(x, y, w, h)in logical points, scaled with linear filtering, modulated by the currentopacity(). The GPU texture is created lazily on first use and cached per window, withinImages.textureBudget()asImages.setTextureBudget(long)describes: a texture dropped at a frame boundary is made again when its image is next drawn, and none that the frame just ended drew is dropped. -
drawImage
Drawsimageat 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). WithSampling.SMOOTH, half a texel of bleed from neighboring atlas cells is possible at the edges; pack sheets with 1px gutters or useSampling.PIXELATEDfor 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, usePIXELATEDor 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 bytintexactly asdrawImage(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
Drawsimagetinted bytint(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, usedrawImageMask(limn.graphics.Image, float, float, float, float, limn.graphics.Color). -
drawImageMask
Drawsimageas a single-color mask: the image's alpha is used as coverage and painted intint'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, unlikedrawImage(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
Composites aGpuSurface(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.No default: it was a no-op, so a backend that did not implement it drew every 3D viewport and video blank without an error. A canvas that cannot composite a GPU surface (a recording or measuring canvas) implements it as a no-op, on purpose and in writing.
-