You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
892 lines
31 KiB
892 lines
31 KiB
use crate::ui::{
|
|
display::Color,
|
|
geometry::{Offset, Point, Rect},
|
|
};
|
|
|
|
use super::super::{
|
|
algo::{circle_points, line_points, sin_i16, PI4},
|
|
BitmapView, Viewport,
|
|
};
|
|
|
|
#[cfg(feature = "ui_blurring")]
|
|
use crate::ui::shape::DrawingCache;
|
|
|
|
pub trait BasicCanvas {
|
|
/// Returns dimensions of the canvas in pixels.
|
|
fn size(&self) -> Offset;
|
|
|
|
/// Returns the dimensions of the canvas as a rectangle with
|
|
/// the top-left at (0,0).
|
|
fn bounds(&self) -> Rect {
|
|
Rect::from_size(self.size())
|
|
}
|
|
|
|
/// Returns the width of the canvas in pixels.
|
|
fn width(&self) -> i16 {
|
|
self.size().x
|
|
}
|
|
|
|
/// Returns the height of the canvas in pixels.
|
|
fn height(&self) -> i16 {
|
|
self.size().y
|
|
}
|
|
|
|
/// Gets the current drawing viewport previously set by `set_viewport()`
|
|
/// function.
|
|
fn viewport(&self) -> Viewport;
|
|
|
|
/// Sets the active viewport valid for all subsequent drawing operations.
|
|
fn set_viewport(&mut self, vp: Viewport);
|
|
|
|
/// Sets the new viewport that's intersection of the
|
|
/// current viewport and the `window` rectangle relative
|
|
/// to the current viewport. The viewport's origin is
|
|
/// set to the top-left corener of the `window`.
|
|
fn set_window(&mut self, window: Rect) -> Viewport {
|
|
let viewport = self.viewport();
|
|
self.set_viewport(viewport.relative_window(window));
|
|
viewport
|
|
}
|
|
|
|
/// Sets the new viewport that's intersection of the
|
|
/// current viewport and the `clip` rectangle relative
|
|
/// to the current viewport. The viewport's origin is
|
|
/// not changed.
|
|
fn set_clip(&mut self, clip: Rect) -> Viewport {
|
|
let viewport = self.viewport();
|
|
self.set_viewport(viewport.relative_clip(clip));
|
|
viewport
|
|
}
|
|
|
|
/// Draws a filled rectangle with the specified color.
|
|
fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8);
|
|
|
|
/// Fills the canvas background with the specified color.
|
|
fn fill_background(&mut self, color: Color) {
|
|
self.fill_rect(self.viewport().clip, color, 255);
|
|
}
|
|
|
|
/// Draws a bitmap of bitmap into to the rectangle.
|
|
fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView);
|
|
}
|
|
|
|
pub trait Canvas: BasicCanvas {
|
|
/// Returns a non-mutable view of the underlying bitmap.
|
|
fn view(&self) -> BitmapView;
|
|
|
|
/// Draw a pixel at specified coordinates.
|
|
fn draw_pixel(&mut self, pt: Point, color: Color);
|
|
|
|
/// Draws a single pixel and blends its color with the background.
|
|
///
|
|
/// - If alpha == 255, the (foreground) pixel color is used.
|
|
/// - If 0 < alpha << 255, pixel and backround colors are blended.
|
|
/// - If alpha == 0, the background color is used.
|
|
fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8);
|
|
|
|
/// Blends a bitmap with the canvas background
|
|
fn blend_bitmap(&mut self, r: Rect, src: BitmapView);
|
|
|
|
/// Applies a blur effect to the specified rectangle.
|
|
///
|
|
/// The blur effect works properly only when the rectangle is not clipped,
|
|
/// which is a strong constraint that's hard to be met. The function uses a
|
|
/// simple box filter, where the 'radius' argument represents the length
|
|
/// of the sides of this filter.
|
|
///
|
|
/// It's important to be aware that strong artifacts may appear on images
|
|
/// with horizontal/vertical lines.
|
|
#[cfg(feature = "ui_blurring")]
|
|
fn blur_rect(&mut self, r: Rect, radius: usize, cache: &DrawingCache);
|
|
|
|
/// Draws an outline of a rectangle with rounded corners.
|
|
fn draw_round_rect(&mut self, r: Rect, radius: i16, color: Color) {
|
|
let split = unwrap!(circle_points(radius).last()).v;
|
|
|
|
let b = Rect {
|
|
y1: r.y0 + radius - split + 1,
|
|
..r
|
|
};
|
|
|
|
if self.viewport().contains(b) {
|
|
for p in circle_points(radius) {
|
|
let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v);
|
|
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v);
|
|
if p.v == radius && p.last {
|
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, 255);
|
|
} else {
|
|
self.draw_pixel(pt_l, color);
|
|
self.draw_pixel(pt_r, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
let b = Rect {
|
|
y0: r.y0 + radius - split + 1,
|
|
y1: r.y0 + radius + 1,
|
|
..r
|
|
};
|
|
|
|
if self.viewport().contains(b) {
|
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
|
let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u);
|
|
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u);
|
|
self.draw_pixel(pt_l, color);
|
|
self.draw_pixel(pt_r, color);
|
|
}
|
|
}
|
|
|
|
self.fill_rect(
|
|
Rect {
|
|
x0: r.x0,
|
|
y0: r.y0 + radius + 1,
|
|
x1: r.x0 + 1,
|
|
y1: r.y1 - radius - 1,
|
|
},
|
|
color,
|
|
255,
|
|
);
|
|
|
|
self.fill_rect(
|
|
Rect {
|
|
x0: r.x1 - 1,
|
|
y0: r.y0 + radius + 1,
|
|
x1: r.x1,
|
|
y1: r.y1 - radius - 1,
|
|
},
|
|
color,
|
|
255,
|
|
);
|
|
|
|
let b = Rect {
|
|
y0: r.y1 - radius - 1,
|
|
y1: r.y1 - radius - 1 + split,
|
|
..r
|
|
};
|
|
|
|
if self.viewport().contains(b) {
|
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
|
let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u);
|
|
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u);
|
|
self.draw_pixel(pt_l, color);
|
|
self.draw_pixel(pt_r, color);
|
|
}
|
|
}
|
|
|
|
let b = Rect {
|
|
y0: r.y1 - radius - 1 + split,
|
|
..r
|
|
};
|
|
|
|
if self.viewport().contains(b) {
|
|
for p in circle_points(radius) {
|
|
let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v);
|
|
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v);
|
|
|
|
if p.v == radius && p.last {
|
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, 255);
|
|
} else {
|
|
self.draw_pixel(pt_l, color);
|
|
self.draw_pixel(pt_r, color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Draws filled rectangle with rounded corners.
|
|
#[cfg(not(feature = "ui_antialiasing"))]
|
|
fn fill_round_rect(&mut self, r: Rect, radius: i16, color: Color, alpha: u8) {
|
|
let split = unwrap!(circle_points(radius).last()).v;
|
|
|
|
let b = Rect {
|
|
y1: r.y0 + radius - split + 1,
|
|
..r
|
|
};
|
|
|
|
if self.viewport().contains(b) {
|
|
for p in circle_points(radius) {
|
|
if p.last {
|
|
let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v);
|
|
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v);
|
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
|
}
|
|
}
|
|
}
|
|
|
|
let b = Rect {
|
|
y0: r.y0 + radius - split + 1,
|
|
y1: r.y0 + radius + 1,
|
|
..r
|
|
};
|
|
|
|
if self.viewport().contains(b) {
|
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
|
let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u);
|
|
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u);
|
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
|
}
|
|
}
|
|
|
|
self.fill_rect(
|
|
Rect {
|
|
x0: r.x0,
|
|
y0: r.y0 + radius + 1,
|
|
x1: r.x1,
|
|
y1: r.y1 - radius - 1,
|
|
},
|
|
color,
|
|
alpha,
|
|
);
|
|
|
|
let b = Rect {
|
|
y0: r.y1 - radius - 1,
|
|
y1: r.y1 - radius - 1 + split,
|
|
..r
|
|
};
|
|
|
|
if self.viewport().contains(b) {
|
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
|
let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u);
|
|
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u);
|
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
|
}
|
|
}
|
|
|
|
let b = Rect {
|
|
y0: r.y1 - radius - 1 + split,
|
|
..r
|
|
};
|
|
|
|
if self.viewport().contains(b) {
|
|
for p in circle_points(radius) {
|
|
if p.last {
|
|
let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v);
|
|
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v);
|
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Draws filled rectangle with antialiased rounded corners.
|
|
#[cfg(feature = "ui_antialiasing")]
|
|
fn fill_round_rect(&mut self, r: Rect, radius: i16, color: Color, alpha: u8) {
|
|
let split = unwrap!(circle_points(radius).last()).v;
|
|
|
|
let b = Rect {
|
|
y1: r.y0 + radius - split + 1,
|
|
..r
|
|
};
|
|
|
|
let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 };
|
|
|
|
if self.viewport().contains(b) {
|
|
for p in circle_points(radius) {
|
|
let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v);
|
|
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v);
|
|
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
|
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
|
|
|
if p.first {
|
|
let inner = Rect::new(pt_l.onright(), pt_r.under());
|
|
self.fill_rect(inner, color, alpha);
|
|
}
|
|
}
|
|
}
|
|
|
|
let b = Rect {
|
|
y0: r.y0 + radius - split + 1,
|
|
y1: r.y0 + radius + 1,
|
|
..r
|
|
};
|
|
|
|
if self.viewport().contains(b) {
|
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
|
let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u);
|
|
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u);
|
|
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
|
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
|
|
|
let inner = Rect::new(pt_l.onright(), pt_r.under());
|
|
self.fill_rect(inner, color, alpha);
|
|
}
|
|
}
|
|
|
|
self.fill_rect(
|
|
Rect {
|
|
x0: r.x0,
|
|
y0: r.y0 + radius + 1,
|
|
x1: r.x1,
|
|
y1: r.y1 - radius - 1,
|
|
},
|
|
color,
|
|
alpha,
|
|
);
|
|
|
|
let b = Rect {
|
|
y0: r.y1 - radius - 1,
|
|
y1: r.y1 - radius - 1 + split,
|
|
..r
|
|
};
|
|
|
|
if self.viewport().contains(b) {
|
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
|
let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u);
|
|
let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u);
|
|
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
|
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
|
|
|
let b = Rect::new(pt_l.onright(), pt_r.under());
|
|
self.fill_rect(b, color, alpha);
|
|
}
|
|
}
|
|
|
|
let b = Rect {
|
|
y0: r.y1 - radius - 1 + split,
|
|
..r
|
|
};
|
|
|
|
if self.viewport().contains(b) {
|
|
for p in circle_points(radius) {
|
|
let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v);
|
|
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
|
let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v);
|
|
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
|
|
|
if p.first {
|
|
let b = Rect::new(pt_l.onright(), pt_r.under());
|
|
self.fill_rect(b, color, alpha);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draws circle with the specified center and the radius.
|
|
#[cfg(not(feature = "ui_antialiasing"))]
|
|
fn draw_circle(&mut self, center: Point, radius: i16, color: Color) {
|
|
let split = unwrap!(circle_points(radius).last()).v;
|
|
|
|
let r = Rect::new(
|
|
Point::new(center.x - radius, center.y - radius),
|
|
Point::new(center.x + radius + 1, center.y - split + 1),
|
|
);
|
|
|
|
if self.viewport().contains(r) {
|
|
for p in circle_points(radius) {
|
|
let pt_l = Point::new(center.x - p.u, center.y - p.v);
|
|
let pt_r = Point::new(center.x + p.u, center.y - p.v);
|
|
self.draw_pixel(pt_l, color);
|
|
self.draw_pixel(pt_r, color);
|
|
}
|
|
}
|
|
|
|
let r = Rect::new(
|
|
Point::new(center.x - radius, center.y - split),
|
|
Point::new(center.x + radius + 1, center.y + 1),
|
|
);
|
|
|
|
if self.viewport().contains(r) {
|
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
|
let pt_l = Point::new(center.x - p.v, center.y - p.u);
|
|
let pt_r = Point::new(center.x + p.v, center.y - p.u);
|
|
self.draw_pixel(pt_l, color);
|
|
self.draw_pixel(pt_r, color);
|
|
}
|
|
}
|
|
|
|
let r = Rect::new(
|
|
Point::new(center.x - radius, center.y + 1),
|
|
Point::new(center.x + radius + 1, center.y + split + 1),
|
|
);
|
|
|
|
if self.viewport().contains(r) {
|
|
for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
|
|
let pt_l = Point::new(center.x - p.v, center.y + p.u);
|
|
let pt_r = Point::new(center.x + p.v, center.y + p.u);
|
|
self.draw_pixel(pt_l, color);
|
|
self.draw_pixel(pt_r, color);
|
|
}
|
|
}
|
|
|
|
let r = Rect::new(
|
|
Point::new(center.x - radius, center.y + split),
|
|
Point::new(center.x + radius + 1, center.y + radius + 1),
|
|
);
|
|
|
|
if self.viewport().contains(r) {
|
|
for p in circle_points(radius) {
|
|
let pt_l = Point::new(center.x - p.u, center.y + p.v);
|
|
let pt_r = Point::new(center.x + p.u, center.y + p.v);
|
|
self.draw_pixel(pt_l, color);
|
|
self.draw_pixel(pt_r, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Draws antialiased circle with the specified center and the radius.
|
|
/*#[cfg(feature = "ui_antialiasing")]
|
|
fn draw_circle(&mut self, center: Point, radius: i16, color: Color) {
|
|
let split = unwrap!(circle_points(radius).last()).v;
|
|
|
|
let r = Rect::new(
|
|
Point::new(center.x - radius, center.y - radius),
|
|
Point::new(center.x + radius + 1, center.y - split + 1),
|
|
);
|
|
|
|
if self.viewport().contains(r) {
|
|
for p in circle_points(radius) {
|
|
let pt_l = Point::new(center.x - p.u, center.y - p.v);
|
|
self.blend_pixel(pt_l, color, p.frac);
|
|
self.blend_pixel(pt_l.under(), color, 255 - p.frac);
|
|
let pt_r = Point::new(center.x + p.u, center.y - p.v);
|
|
self.blend_pixel(pt_r, color, p.frac);
|
|
self.blend_pixel(pt_r.under(), color, 255 - p.frac);
|
|
}
|
|
}
|
|
|
|
let r = Rect::new(
|
|
Point::new(center.x - radius, center.y - split),
|
|
Point::new(center.x + radius + 1, center.y + 1),
|
|
);
|
|
|
|
if self.viewport().contains(r) {
|
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
|
let pt_l = Point::new(center.x - p.v, center.y - p.u);
|
|
self.blend_pixel(pt_l, color, p.frac);
|
|
self.blend_pixel(pt_l.onright(), color, 255 - p.frac);
|
|
let pt_r = Point::new(center.x + p.v, center.y - p.u);
|
|
self.blend_pixel(pt_r, color, p.frac);
|
|
self.blend_pixel(pt_r.onleft(), color, 255 - p.frac);
|
|
}
|
|
}
|
|
|
|
let r = Rect::new(
|
|
Point::new(center.x - radius, center.y + 1),
|
|
Point::new(center.x + radius + 1, center.y + split + 1),
|
|
);
|
|
|
|
if self.viewport().contains(r) {
|
|
for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
|
|
let pt_l = Point::new(center.x - p.v, center.y + p.u);
|
|
self.blend_pixel(pt_l, color, p.frac);
|
|
self.blend_pixel(pt_l.onright(), color, 255 - p.frac);
|
|
let pt_r = Point::new(center.x + p.v, center.y + p.u);
|
|
self.blend_pixel(pt_r, color, p.frac);
|
|
self.blend_pixel(pt_r.onleft(), color, 255 - p.frac);
|
|
}
|
|
}
|
|
|
|
let r = Rect::new(
|
|
Point::new(center.x - radius, center.y + split),
|
|
Point::new(center.x + radius + 1, center.y + radius + 1),
|
|
);
|
|
|
|
if self.viewport().contains(r) {
|
|
for p in circle_points(radius) {
|
|
let pt_l = Point::new(center.x - p.u, center.y + p.v);
|
|
self.blend_pixel(pt_l, color, p.frac);
|
|
self.blend_pixel(pt_l.above(), color, 255 - p.frac);
|
|
let pt_r = Point::new(center.x + p.u, center.y + p.v);
|
|
self.blend_pixel(pt_r, color, p.frac);
|
|
self.blend_pixel(pt_r.above(), color, 255 - p.frac);
|
|
}
|
|
}
|
|
}*/
|
|
|
|
/// Draws filled circle with the specified center and the radius.
|
|
#[cfg(not(feature = "ui_antialiasing"))]
|
|
fn fill_circle(&mut self, center: Point, radius: i16, color: Color) {
|
|
let split = unwrap!(circle_points(radius).last()).v;
|
|
let alpha = 255;
|
|
|
|
let r = Rect::new(
|
|
Point::new(center.x - radius, center.y - radius),
|
|
Point::new(center.x + radius + 1, center.y - split + 1),
|
|
);
|
|
|
|
if self.viewport().contains(r) {
|
|
for p in circle_points(radius) {
|
|
if p.last {
|
|
let pt_l = Point::new(center.x - p.u, center.y - p.v);
|
|
let pt_r = Point::new(center.x + p.u, center.y - p.v);
|
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
|
}
|
|
}
|
|
}
|
|
|
|
let r = Rect::new(
|
|
Point::new(center.x - radius, center.y - split),
|
|
Point::new(center.x + radius + 1, center.y + 1),
|
|
);
|
|
|
|
if self.viewport().contains(r) {
|
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
|
let pt_l = Point::new(center.x - p.v, center.y - p.u);
|
|
let pt_r = Point::new(center.x + p.v, center.y - p.u);
|
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
|
}
|
|
}
|
|
|
|
let r = Rect::new(
|
|
Point::new(center.x - radius, center.y + 1),
|
|
Point::new(center.x + radius + 1, center.y + split + 1),
|
|
);
|
|
|
|
if self.viewport().contains(r) {
|
|
for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
|
|
let pt_l = Point::new(center.x - p.v, center.y + p.u);
|
|
let pt_r = Point::new(center.x + p.v, center.y + p.u);
|
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
|
}
|
|
}
|
|
|
|
let r = Rect::new(
|
|
Point::new(center.x - radius, center.y + split),
|
|
Point::new(center.x + radius + 1, center.y + radius + 1),
|
|
);
|
|
|
|
if self.viewport().contains(r) {
|
|
for p in circle_points(radius) {
|
|
if p.last {
|
|
let pt_l = Point::new(center.x - p.u, center.y + p.v);
|
|
let pt_r = Point::new(center.x + p.u, center.y + p.v);
|
|
self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Draws antialiased filled circle with the specified center and the
|
|
/// radius.
|
|
#[cfg(feature = "ui_antialiasing")]
|
|
fn fill_circle(&mut self, center: Point, radius: i16, color: Color) {
|
|
let split = unwrap!(circle_points(radius).last()).v;
|
|
|
|
let alpha = 255;
|
|
let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 };
|
|
|
|
let r = Rect::new(
|
|
Point::new(center.x - radius, center.y - radius),
|
|
Point::new(center.x + radius + 1, center.y - split + 1),
|
|
);
|
|
|
|
if self.viewport().contains(r) {
|
|
for p in circle_points(radius) {
|
|
let pt_l = Point::new(center.x - p.u, center.y - p.v);
|
|
let pt_r = Point::new(center.x + p.u, center.y - p.v);
|
|
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
|
if pt_l != pt_r {
|
|
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
|
}
|
|
|
|
if p.first {
|
|
let r = Rect::new(pt_l.onright(), pt_r.under());
|
|
self.fill_rect(r, color, alpha);
|
|
}
|
|
}
|
|
}
|
|
|
|
let r = Rect::new(
|
|
Point::new(center.x - radius, center.y - split),
|
|
Point::new(center.x + radius + 1, center.y + 1),
|
|
);
|
|
|
|
if self.viewport().contains(r) {
|
|
for p in circle_points(radius).take_while(|p| p.u < p.v) {
|
|
let pt_l = Point::new(center.x - p.v, center.y - p.u);
|
|
let pt_r = Point::new(center.x + p.v, center.y - p.u);
|
|
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
|
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
|
|
|
let r = Rect::new(pt_l.onright(), pt_r.under());
|
|
self.fill_rect(r, color, alpha);
|
|
}
|
|
}
|
|
|
|
let r = Rect::new(
|
|
Point::new(center.x - radius, center.y + 1),
|
|
Point::new(center.x + radius + 1, center.y + split + 1),
|
|
);
|
|
|
|
if self.viewport().contains(r) {
|
|
for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) {
|
|
let pt_l = Point::new(center.x - p.v, center.y + p.u);
|
|
let pt_r = Point::new(center.x + p.v, center.y + p.u);
|
|
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
|
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
|
|
|
let r = Rect::new(pt_l.onright(), pt_r.under());
|
|
self.fill_rect(r, color, alpha);
|
|
}
|
|
}
|
|
|
|
let r = Rect::new(
|
|
Point::new(center.x - radius, center.y + split),
|
|
Point::new(center.x + radius + 1, center.y + radius + 1),
|
|
);
|
|
|
|
if self.viewport().contains(r) {
|
|
for p in circle_points(radius) {
|
|
let pt_l = Point::new(center.x - p.u, center.y + p.v);
|
|
let pt_r = Point::new(center.x + p.u, center.y + p.v);
|
|
if pt_l != pt_r {
|
|
self.blend_pixel(pt_l, color, alpha_mul(p.frac));
|
|
}
|
|
self.blend_pixel(pt_r, color, alpha_mul(p.frac));
|
|
|
|
if p.first {
|
|
let r = Rect::new(pt_l.onright(), pt_r.under());
|
|
self.fill_rect(r, color, alpha);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Fills circle sector with a specified color.
|
|
fn fill_sector(
|
|
&mut self,
|
|
center: Point,
|
|
radius: i16,
|
|
mut start: i16,
|
|
mut end: i16,
|
|
color: Color,
|
|
) {
|
|
start = (PI4 * 8 + start % (PI4 * 8)) % (PI4 * 8);
|
|
end = (PI4 * 8 + end % (PI4 * 8)) % (PI4 * 8);
|
|
|
|
let alpha = 255;
|
|
let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 };
|
|
|
|
if start != end {
|
|
// The algorithm fills everything except the middle point ;-)
|
|
self.draw_pixel(center, color);
|
|
}
|
|
|
|
for octant in 0..8 {
|
|
let angle = octant * PI4;
|
|
|
|
// Function for calculation of 'u' coordinate inside the circle octant
|
|
// radius * sin(angle)
|
|
let sin = |angle: i16| -> i16 { sin_i16(angle, radius) };
|
|
|
|
// Calculate the octant's bounding rectangle
|
|
let p = Point::new(sin(PI4) + 1, -radius - 1).rot(octant);
|
|
let r = Rect::new(center, p + center.into()).normalize();
|
|
|
|
// Skip octant if not visible
|
|
if !self.viewport().contains(r) {
|
|
continue;
|
|
}
|
|
|
|
// Function for filling a line between two endpoints with antialiasing.
|
|
// The function is special for each octant using 4 different axes of symmetry
|
|
let filler = &mut |p1: Option<Point>, p1_frac, p2: Point, p2_frac| {
|
|
let p2: Point = center + p2.rot(octant).into();
|
|
self.blend_pixel(p2, color, alpha_mul(p2_frac));
|
|
if let Some(p1) = p1 {
|
|
let p1: Point = center + p1.rot(octant).into();
|
|
let ofs = Point::new(-1, 0).rot(octant);
|
|
self.blend_pixel(p1 + ofs.into(), color, alpha_mul(p1_frac));
|
|
if ofs.x + ofs.y < 0 {
|
|
if ofs.x != 0 {
|
|
self.fill_rect(Rect::new(p1, p2.under()), color, alpha);
|
|
} else {
|
|
self.fill_rect(Rect::new(p1, p2.onright()), color, alpha);
|
|
}
|
|
} else {
|
|
let p1 = p1 + ofs.into();
|
|
let p2 = p2 + ofs.into();
|
|
if ofs.x != 0 {
|
|
self.fill_rect(Rect::new(p2, p1.under()), color, alpha);
|
|
} else {
|
|
self.fill_rect(Rect::new(p2, p1.onright()), color, alpha);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
let corr = if octant & 1 == 0 {
|
|
// The clockwise octant
|
|
|angle| angle
|
|
} else {
|
|
// The anticlockwise octant
|
|
|angle| PI4 - angle
|
|
};
|
|
|
|
if start <= end {
|
|
// Octant may contain 0 or 1 sector
|
|
if start < angle + PI4 && end > angle {
|
|
if start <= angle && end >= angle + PI4 {
|
|
// Fill all pixels in the octant
|
|
fill_octant(radius, 0, sin(PI4), filler);
|
|
} else {
|
|
// Partial fill
|
|
let u1 = if start <= angle {
|
|
sin(corr(0))
|
|
} else {
|
|
sin(corr(start - angle))
|
|
};
|
|
let u2 = if end <= angle + PI4 {
|
|
sin(corr(end - angle))
|
|
} else {
|
|
sin(corr(PI4))
|
|
};
|
|
|
|
fill_octant(radius, u1, u2, filler);
|
|
}
|
|
}
|
|
} else {
|
|
// Octant may contain 0, 1 or 2 sectors
|
|
if end >= angle + PI4 || start <= angle {
|
|
// Fill all pixels in the octant
|
|
fill_octant(radius, 0, sin(PI4), filler);
|
|
} else {
|
|
// Partial fill
|
|
if (end > angle) && (end < angle + PI4) {
|
|
// Fill up to `end`
|
|
fill_octant(radius, sin(corr(0)), sin(corr(end - angle)), filler);
|
|
}
|
|
if start < angle + PI4 {
|
|
// Fill all from `start`
|
|
fill_octant(radius, sin(corr(start - angle)), sin(corr(PI4)), filler);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Calculates endpoints of a single octant of a circle
|
|
///
|
|
/// Used internally by `Canvas::fill_sector()`.
|
|
fn fill_octant(
|
|
radius: i16,
|
|
mut u1: i16,
|
|
mut u2: i16,
|
|
fill: &mut impl FnMut(Option<Point>, u8, Point, u8),
|
|
) {
|
|
// Starting end ending points on
|
|
if u1 > u2 {
|
|
(u1, u2) = (u2, u1);
|
|
}
|
|
|
|
let mut iter = circle_points(radius).skip(u1 as usize);
|
|
|
|
// Intersection of the p1 line and the circle
|
|
let p1_start = unwrap!(iter.next());
|
|
|
|
// Intersection of the p1 line and the circle
|
|
let mut p2_start = p1_start;
|
|
|
|
loop {
|
|
if let Some(p) = iter.next() {
|
|
if p.u > u2 {
|
|
break;
|
|
}
|
|
p2_start = p;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Flag if we draw section up to 45degs
|
|
let join_flag = iter.next().is_none();
|
|
|
|
// Process area between a p1 line and the circle
|
|
let mut p1_iter = line_points(p1_start.v, p1_start.u, 0);
|
|
let mut first = true;
|
|
let mut skip = 0;
|
|
|
|
for c in circle_points(radius)
|
|
.skip(p1_start.u as usize)
|
|
.take((p2_start.u - p1_start.u) as usize)
|
|
{
|
|
let p2_coord = Point::new(c.u, -c.v);
|
|
|
|
if c.first || first {
|
|
let p1 = unwrap!(p1_iter.next());
|
|
let p1_coord = Point::new(p1_start.u - p1.v, -p1_start.v + p1.u);
|
|
first = false;
|
|
|
|
fill(Some(p1_coord), p1.frac, p2_coord, c.frac);
|
|
} else {
|
|
fill(None, 0, p2_coord, c.frac);
|
|
}
|
|
|
|
skip = if c.last { 0 } else { 1 };
|
|
}
|
|
|
|
// Process area between a p1 and p2 lines
|
|
let p2_iter = line_points(p2_start.v, p2_start.u, 0).skip(skip);
|
|
for (p1, p2) in p1_iter.zip(p2_iter) {
|
|
let p1_coord = Point::new(p1_start.u - p1.v, -p1_start.v + p1.u);
|
|
let p2_coord = Point::new(p2_start.u - p2.v, -p2_start.v + p2.u);
|
|
let p2_frac = if join_flag { 255 } else { 255 - p2.frac };
|
|
fill(Some(p1_coord), p1.frac, p2_coord, p2_frac);
|
|
}
|
|
}
|
|
|
|
impl Rect {
|
|
/// Normalizes the rectangle coordinates.
|
|
///
|
|
/// Returns a new `Rect` with potentially swapped left/right,
|
|
/// top/bottom coordinates, ensuring that `x0`, `y0` represents
|
|
/// the top-left corner and `x1`, `y1` represents the bottom-right corner.
|
|
pub fn normalize(&self) -> Self {
|
|
Rect {
|
|
x0: core::cmp::min(self.x0, self.x1),
|
|
y0: core::cmp::min(self.y0, self.y1),
|
|
x1: core::cmp::max(self.x0, self.x1),
|
|
y1: core::cmp::max(self.y0, self.y1),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Point {
|
|
fn onleft(self) -> Self {
|
|
Self {
|
|
x: self.x - 1,
|
|
..self
|
|
}
|
|
}
|
|
|
|
fn onright(self) -> Self {
|
|
Self {
|
|
x: self.x + 1,
|
|
..self
|
|
}
|
|
}
|
|
|
|
fn above(self) -> Self {
|
|
Self {
|
|
y: self.y - 1,
|
|
..self
|
|
}
|
|
}
|
|
|
|
fn under(self) -> Self {
|
|
Self {
|
|
y: self.y + 1,
|
|
..self
|
|
}
|
|
}
|
|
|
|
fn rot(self, octant: i16) -> Self {
|
|
let mut result = self;
|
|
|
|
if (octant + 1) & 2 != 0 {
|
|
result = Point::new(-result.y, -result.x);
|
|
}
|
|
|
|
if octant & 4 != 0 {
|
|
result = Point::new(-result.x, result.y);
|
|
}
|
|
|
|
if (octant + 2) & 4 != 0 {
|
|
result = Point::new(result.x, -result.y);
|
|
}
|
|
|
|
result
|
|
}
|
|
}
|