From 62ee63929f91a2cdd927460f4d2a2a99f45c7d65 Mon Sep 17 00:00:00 2001 From: Erik Corry Date: Wed, 22 Nov 2023 11:32:06 +0100 Subject: [PATCH] Move draw method from GradientElement to GradientRendering --- src/element.toit | 205 +++++++++++++++++++++++++---------------------- 1 file changed, 109 insertions(+), 96 deletions(-) diff --git a/src/element.toit b/src/element.toit index 3dc5abc..ee341ac 100644 --- a/src/element.toit +++ b/src/element.toit @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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