Skip to content

Commit

Permalink
Move draw method from GradientElement to GradientRendering
Browse files Browse the repository at this point in the history
  • Loading branch information
Erik Corry committed Nov 22, 2023
1 parent 02e07ad commit 62ee639
Showing 1 changed file with 109 additions and 96 deletions.
205 changes: 109 additions & 96 deletions src/element.toit
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ class GradientRendering_:
green_pixels_/ByteArray? := null
blue_pixels_/ByteArray? := null
draw_vertical_/bool? := null
angle_ /int
h_ /int := 0
w_ /int := 0

static map_ := Map.weak

Expand All @@ -190,7 +193,11 @@ class GradientRendering_:

constructor w/int? h/int? gradient/Gradient:
angle := gradient.angle
angle_ = angle
if h != 0 and h != null and w != 0 and w != null:
h_ = h
w_ = w

// CSS gradient angles are:
// 0 bottom to top.
// 90 left to right
Expand Down Expand Up @@ -227,93 +234,10 @@ class GradientRendering_:
green_pixels_[index] = green
blue_pixels_[index] = blue

/// Returns a list of quadruples of the form starting-percent ending-percent start-color end-color.
static extract_ranges_ specifiers/List -> List:
result := []
for i := -1; i < specifiers.size; i++:
from := i < 0 ? 0 : specifiers[i].percent
to := i >= specifiers.size - 1 ? 100 : specifiers[i + 1].percent
if to != from:
from_color := specifiers[max i 0].color
to_color := specifiers[min (i + 1) (specifiers.size - 1)].color
result.add [from, to, from_color, to_color]
return result

static get_colors range/List h/int [block] -> none:
from_y := range[0] * h / 100
to_y := range[1] * h / 100
if to_y == from_y: return
divisor := to_y - from_y
from_color := range[2]
// Use 8.16 fixed point arithmetic to avoid floating point.
r := from_color & 0xff0000
g := (from_color & 0xff00) << 8
b := (from_color & 0xff) << 16
to_color := range[3]
to_r := to_color & 0xff0000
to_g := (to_color & 0xff00) << 8
to_b := (to_color & 0xff) << 16
step_r := (to_r - r) / divisor
step_g := (to_g - g) / divisor
step_b := (to_b - b) / divisor
for y := from_y; y < to_y; y++:
block.call y (r >> 16) (g >> 16) (b >> 16)
r += step_r
g += step_g
b += step_b

/**
GradientElements are similar to CSS linear gradients and SVG gradients.
They are given a list of $GradientSpecifier, each of which has a color and
a percentage, indicating where in the gradient the color should appear.
The specifiers should be ordered in increasing order of percentage.
Angles are as in CSS, with 0 degrees being up and 90 degrees being to the right
(this is different from text orientations, which go anti-clockwise).
See https://cssgradient.io/ for a visual explanation and playground for CSS
gradients.
Example:
```
gradient = GradientElement --w=200 --h=100 --angle=45
--specifiers=[
GradientSpecifier --color=0xff0000 10, // Red from 0-10%, red-to-green from 10-50%.
GradientSpecifier --color=0x00ff00 50, // Green-to-blue from 50-90%.
GradientSpecifier --color=0x0000ff 90, // Blue from 90-100%.
]
display.add gradient
```
*/
class GradientElement extends ResizableElement:
gradient_/Gradient := ?
specification_/GradientSpecification_? := null
rendering_/GradientRendering_? := null

constructor --x/int?=null --y/int?=null --w/int?=null --h/int?=null --gradient/Gradient:
gradient_ = gradient
super --x=x --y=y --w=w --h=h
rendering_ = null

gradient= gradient/Gradient -> none:
if gradient != gradient_:
invalidate
gradient_ = gradient
rendering_ = null

gradient -> Gradient:
return gradient_

w= value/int -> none:
super = value
rendering_ = null

h= value/int -> none:
super = value
rendering_ = null

draw canvas/Canvas -> none:
if not (x and y and w and h): return
if not canvas.supports_8_bit: throw "UNSUPPORTED"
if not rendering_: rendering_ = GradientRendering_.get w h gradient_
angle := gradient_.angle
draw canvas/Canvas x/int y/int -> none:
angle := angle_
w := w_
h := h_
analysis := canvas.bounds_analysis x y w h
if analysis == Canvas.ALL_OUTSIDE: return
// Determine whether the draw operations will be automatically cropped for
Expand All @@ -328,7 +252,7 @@ class GradientElement extends ResizableElement:
// 180 top to bottom
// 270 right to left
if rendering_.draw_vertical_:
if draw_vertical_:
// The gradient goes broadly vertically, and we draw in vertical strips.
up/bool := angle < 90
orientation/int := ORIENTATION_90
Expand All @@ -347,13 +271,13 @@ class GradientElement extends ResizableElement:
stop = w
i_step = 1
offset := 0
step := ((rendering_.red_pixels_.size - h) << 16) / w // n.16 fixed point.
step := ((red_pixels_.size - h) << 16) / w // n.16 fixed point.
for i := start; i != stop; i += i_step:
o := offset >> 16
y3 := ?
r := rendering_.red_pixels_
g := rendering_.green_pixels_
b := rendering_.blue_pixels_
r := red_pixels_
g := green_pixels_
b := blue_pixels_
if auto_crop:
if orientation == ORIENTATION_90:
y3 = y2 + o
Expand Down Expand Up @@ -388,13 +312,13 @@ class GradientElement extends ResizableElement:
stop = h
i_step = 1
offset := 0
step := ((rendering_.red_pixels_.size - w) << 16) / h // n.16 fixed point.
step := ((red_pixels_.size - w) << 16) / h // n.16 fixed point.
for i := start; i != stop; i += i_step:
o := offset >> 16
x3 := ?
r := rendering_.red_pixels_
g := rendering_.green_pixels_
b := rendering_.blue_pixels_
r := red_pixels_
g := green_pixels_
b := blue_pixels_
if auto_crop:
if orientation == ORIENTATION_0:
x3 = x2 - o
Expand All @@ -411,6 +335,95 @@ class GradientElement extends ResizableElement:
canvas.rgb_pixmap x3 (i + y2) --r=r --g=g --b=b --source_width=w --orientation=orientation
offset += step

/// Returns a list of quadruples of the form starting-percent ending-percent start-color end-color.
static extract_ranges_ specifiers/List -> List:
result := []
for i := -1; i < specifiers.size; i++:
from := i < 0 ? 0 : specifiers[i].percent
to := i >= specifiers.size - 1 ? 100 : specifiers[i + 1].percent
if to != from:
from_color := specifiers[max i 0].color
to_color := specifiers[min (i + 1) (specifiers.size - 1)].color
result.add [from, to, from_color, to_color]
return result

static get_colors range/List h/int [block] -> none:
from_y := range[0] * h / 100
to_y := range[1] * h / 100
if to_y == from_y: return
divisor := to_y - from_y
from_color := range[2]
// Use 8.16 fixed point arithmetic to avoid floating point.
r := from_color & 0xff0000
g := (from_color & 0xff00) << 8
b := (from_color & 0xff) << 16
to_color := range[3]
to_r := to_color & 0xff0000
to_g := (to_color & 0xff00) << 8
to_b := (to_color & 0xff) << 16
step_r := (to_r - r) / divisor
step_g := (to_g - g) / divisor
step_b := (to_b - b) / divisor
for y := from_y; y < to_y; y++:
block.call y (r >> 16) (g >> 16) (b >> 16)
r += step_r
g += step_g
b += step_b

/**
GradientElements are similar to CSS linear gradients and SVG gradients.
They are given a list of $GradientSpecifier, each of which has a color and
a percentage, indicating where in the gradient the color should appear.
The specifiers should be ordered in increasing order of percentage.
Angles are as in CSS, with 0 degrees being up and 90 degrees being to the right
(this is different from text orientations, which go anti-clockwise).
See https://cssgradient.io/ for a visual explanation and playground for CSS
gradients.
Example:
```
gradient = GradientElement --w=200 --h=100 --angle=45
--specifiers=[
GradientSpecifier --color=0xff0000 10, // Red from 0-10%, red-to-green from 10-50%.
GradientSpecifier --color=0x00ff00 50, // Green-to-blue from 50-90%.
GradientSpecifier --color=0x0000ff 90, // Blue from 90-100%.
]
display.add gradient
```
*/
class GradientElement extends ResizableElement:
gradient_/Gradient := ?
specification_/GradientSpecification_? := null
rendering_/GradientRendering_? := null

constructor --x/int?=null --y/int?=null --w/int?=null --h/int?=null --gradient/Gradient:
gradient_ = gradient
super --x=x --y=y --w=w --h=h
rendering_ = null

gradient= gradient/Gradient -> none:
if gradient != gradient_:
invalidate
gradient_ = gradient
rendering_ = null

gradient -> Gradient:
return gradient_

w= value/int -> none:
super = value
rendering_ = null

h= value/int -> none:
super = value
rendering_ = null

draw canvas/Canvas -> none:
if not (x and y and w and h): return
if not canvas.supports_8_bit: throw "UNSUPPORTED"
if not rendering_: rendering_ = GradientRendering_.get w h gradient_
rendering_.draw canvas x y


class FilledRectangleElement extends RectangleElement:
constructor --x/int?=null --y/int?=null --w/int?=null --h/int?=null --color/int?=null:
super --x=x --y=y --w=w --h=h --color=color
Expand Down

0 comments on commit 62ee639

Please sign in to comment.