Skip to content

Commit

Permalink
Optimize gradient drawing when only part of the gradient is visible. (#…
Browse files Browse the repository at this point in the history
…83)

* Improve gradient speed by skipping off-canvas lines.
This can be a huge win when using a large gradient as a background.
  • Loading branch information
Erik Corry authored Dec 14, 2023
1 parent 8cce8d9 commit 21d743b
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/element.toit
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ abstract class CustomElement extends ClippingDiv_:
change-tracker.child-invalidated x y w h

draw canvas/Canvas -> none:
if not (x and y): return
if not (x and y and w and h): return
analysis := canvas.bounds-analysis x y w h
if analysis == Canvas.DISJOINT: return
autoclipped := analysis == Canvas.CANVAS-IN-AREA or analysis == Canvas.COINCIDENT
Expand Down
31 changes: 19 additions & 12 deletions src/gradient.toit
Original file line number Diff line number Diff line change
Expand Up @@ -243,14 +243,16 @@ class GradientRendering_:
// Use fixed point with 16 bits after the decimal point.
step := ((texture-length_ - h) << 16) / w
offset := 0
for i := 0; i < w; i += repeats:
lines := min repeats (w - i)
skew-adjusted-y := update-r-g-b-block.call y2 h lines orientation offset
if canvas.gray-scale:
canvas.pixmap (i + x2) skew-adjusted-y --pixels=b --source-width=source-width --orientation=orientation
else:
canvas.rgb-pixmap (i + x2) skew-adjusted-y --r=r --g=g --b=b --source-width=source-width --orientation=orientation
offset += step
canvas.visible-x-range x2 w: | x3 r3 |
offset += step * (x3 - x2)
for i := x3; i < r3; i += repeats:
lines := min repeats (r3 - i)
skew-adjusted-y := update-r-g-b-block.call y2 h lines orientation offset
if canvas.gray-scale:
canvas.pixmap i skew-adjusted-y --pixels=b --source-width=source-width --orientation=orientation
else:
canvas.rgb-pixmap i skew-adjusted-y --r=r --g=g --b=b --source-width=source-width --orientation=orientation
offset += step
else:
// The gradient goes broadly horizontally, and we draw in horizontal
// strips.
Expand All @@ -260,15 +262,20 @@ class GradientRendering_:
loop-body := : | i lines |
skew-adjusted-x := update-r-g-b-block.call x w lines ORIENTATION-0 offset
if canvas.gray-scale:
canvas.pixmap skew-adjusted-x (i + y) --pixels=b --source-width=source-width
canvas.pixmap skew-adjusted-x i --pixels=b --source-width=source-width
else:
canvas.rgb-pixmap skew-adjusted-x (i + y) --r=r --g=g --b=b --source-width=source-width
canvas.rgb-pixmap skew-adjusted-x i --r=r --g=g --b=b --source-width=source-width
offset += step
if up:
for i := 0; i < h; i += repeats: loop-body.call i (min repeats (h - i))
canvas.visible-y-range y (y + h): | y3 b3 |
offset += step * (y3 - y)
for i := y3; i < b3; i += repeats: loop-body.call i (min repeats (b3 - i))
else:
assert: repeats == 1
for i := h - 1; i >= 0; i--: loop-body.call i 1
b2 := y + h - 1
canvas.visible-y-range y b2: | y3 b3 |
offset += step * (b2 - b3)
for i := b3; i >= y3; i--: loop-body.call i 1

/// Returns a list of quadruples of the form starting-percent ending-percent start-color end-color.
static extract-ranges_ specifiers/List -> List:
Expand Down
26 changes: 26 additions & 0 deletions src/pixel-display.toit
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,32 @@ abstract class Canvas:
if x2 <= 0 and y2 <= 0 and right >= width_ and bottom >= height_: return CANVAS-IN-AREA
return OVERLAP

/**
Given an x position and a width in the coordinate system of the
canvas, calculates the leftmost and rightmost pixels that are visible, so
that superflous drawing operations can be optimized away
The block is called with the left and right pixel positions.
The function may conservatively add a pixel on one side.
*/
visible-x-range x/int w/int [block] -> none:
transform.invert.xywh 0 0 (width_ + 1) (height_ + 1): | x2 _ w2 _ |
block.call
max x x2
min (x + w) (x2 + w2)

/**
Given a y position and a height in the coordinate system of the
canvas, calculates the top and bottom pixels that are visible, so
that superflous drawing operations can be optimized away
The block is called with the top and bottom pixel positions.
The function may conservatively add a pixel on one side.
*/
visible-y-range y/int h/int [block] -> none:
transform.invert.xywh 0 0 (width_ + 1) (height_ + 1): | _ y2 _ h2 |
block.call
max y y2
min (y + h) (y2 + h2)

/**
Constant to indicate that all pixels are transparent.
For use with $composit.
Expand Down

0 comments on commit 21d743b

Please sign in to comment.