add generic primitive drawing methods accepting custom pixel functions

This commit is contained in:
Gered 2023-03-26 16:26:15 -04:00
parent 847149607d
commit a667a657d2

View file

@ -20,6 +20,19 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
} }
} }
/// Sets the pixel at the given coordinates to the color returned by the given function. The
/// given function is one that accepts a color value that corresponds to the current pixel
/// at the given coordinates. If the coordinates lie outside of the bitmaps clipping region,
/// no pixels will be changed and the given pixel function will not be called.
#[inline]
pub fn set_custom_pixel(&mut self, x: i32, y: i32, pixel_fn: impl Fn(PixelType) -> PixelType) {
unsafe {
if let Some(pixels) = self.pixels_at_mut_ptr(x, y) {
*pixels = pixel_fn(*pixels);
}
}
}
/// Sets the pixel at the given coordinates to the color specified. The coordinates are not /// Sets the pixel at the given coordinates to the color specified. The coordinates are not
/// checked for validity, so it is up to you to ensure they lie within the bounds of the /// checked for validity, so it is up to you to ensure they lie within the bounds of the
/// bitmap. /// bitmap.
@ -29,6 +42,16 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
*p = color; *p = color;
} }
/// Sets the pixel at the given coordinates to the color returned by the given function. The
/// given function is one that accepts a color value that corresponds to the current pixel at
/// the given coordinates. The coordinates are not checked for validity, so it is up to you to
/// ensure they lie within the bounds of the bitmap.
#[inline]
pub unsafe fn set_custom_pixel_unchecked(&mut self, x: i32, y: i32, pixel_fn: impl Fn(PixelType) -> PixelType) {
let p = self.pixels_at_mut_ptr_unchecked(x, y);
*p = pixel_fn(*p);
}
/// Gets the pixel at the given coordinates. If the coordinates lie outside of the bitmaps /// Gets the pixel at the given coordinates. If the coordinates lie outside of the bitmaps
/// clipping region, None is returned. /// clipping region, None is returned.
#[inline] #[inline]
@ -137,6 +160,68 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
} }
} }
pub fn line_custom(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, pixel_fn: impl Fn(PixelType) -> PixelType) {
let mut dx = x1;
let mut dy = y1;
let delta_x = x2 - x1;
let delta_y = y2 - y1;
let delta_x_abs = delta_x.abs();
let delta_y_abs = delta_y.abs();
let delta_x_sign = delta_x.signum();
let delta_y_sign = delta_y.signum();
let mut x = delta_x_abs / 2;
let mut y = delta_y_abs / 2;
let offset_x_inc = delta_x_sign;
let offset_y_inc = delta_y_sign * self.width as i32;
unsafe {
// safety: while we are blindly getting a pointer to this x/y coordinate, we don't
// write to it unless we know the coordinates are in bounds.
// TODO: should be ok ... ? or am i making too many assumptions about memory layout?
let mut dest = self.pixels_at_mut_ptr_unchecked(x1, y1);
if self.is_xy_visible(dx, dy) {
*dest = pixel_fn(*dest);
}
if delta_x_abs >= delta_y_abs {
for _ in 0..delta_x_abs {
y += delta_y_abs;
if y >= delta_x_abs {
y -= delta_x_abs;
dy += delta_y_sign;
dest = dest.offset(offset_y_inc as isize);
}
dx += delta_x_sign;
dest = dest.offset(offset_x_inc as isize);
if self.is_xy_visible(dx, dy) {
*dest = pixel_fn(*dest);
}
}
} else {
for _ in 0..delta_y_abs {
x += delta_x_abs;
if x >= delta_y_abs {
x -= delta_y_abs;
dx += delta_x_sign;
dest = dest.offset(offset_x_inc as isize);
}
dy += delta_y_sign;
dest = dest.offset(offset_y_inc as isize);
if self.is_xy_visible(dx, dy) {
*dest = pixel_fn(*dest);
}
}
}
}
}
/// Draws a horizontal line from x1,y to x2,y. /// Draws a horizontal line from x1,y to x2,y.
pub fn horiz_line(&mut self, x1: i32, x2: i32, y: i32, color: PixelType) { pub fn horiz_line(&mut self, x1: i32, x2: i32, y: i32, color: PixelType) {
let mut region = Rect::from_coords(x1, y, x2, y); let mut region = Rect::from_coords(x1, y, x2, y);
@ -150,6 +235,18 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
} }
} }
pub fn horiz_line_custom(&mut self, x1: i32, x2: i32, y: i32, pixel_fn: impl Fn(PixelType) -> PixelType) {
let mut region = Rect::from_coords(x1, y, x2, y);
if region.clamp_to(&self.clip_region) {
unsafe {
let dest = &mut self.pixels_at_mut_unchecked(region.x, region.y)[0..region.width as usize];
for pixel in dest.iter_mut() {
*pixel = pixel_fn(*pixel);
}
}
}
}
/// Draws a vertical line from x,y1 to x,y2. /// Draws a vertical line from x,y1 to x,y2.
pub fn vert_line(&mut self, x: i32, y1: i32, y2: i32, color: PixelType) { pub fn vert_line(&mut self, x: i32, y1: i32, y2: i32, color: PixelType) {
let mut region = Rect::from_coords(x, y1, x, y2); let mut region = Rect::from_coords(x, y1, x, y2);
@ -164,6 +261,19 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
} }
} }
pub fn vert_line_custom(&mut self, x: i32, y1: i32, y2: i32, pixel_fn: impl Fn(PixelType) -> PixelType) {
let mut region = Rect::from_coords(x, y1, x, y2);
if region.clamp_to(&self.clip_region) {
unsafe {
let mut dest = self.pixels_at_mut_ptr_unchecked(region.x, region.y);
for _ in 0..region.height {
*dest = pixel_fn(*dest);
dest = dest.add(self.width as usize);
}
}
}
}
/// Draws an empty box (rectangle) using the points x1,y1 and x2,y2 to form the box to be /// Draws an empty box (rectangle) using the points x1,y1 and x2,y2 to form the box to be
/// drawn, assuming they are specifying the top-left and bottom-right corners respectively. /// drawn, assuming they are specifying the top-left and bottom-right corners respectively.
pub fn rect(&mut self, mut x1: i32, mut y1: i32, mut x2: i32, mut y2: i32, color: PixelType) { pub fn rect(&mut self, mut x1: i32, mut y1: i32, mut x2: i32, mut y2: i32, color: PixelType) {
@ -229,6 +339,91 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
} }
} }
pub fn rect_custom(
&mut self,
mut x1: i32,
mut y1: i32,
mut x2: i32,
mut y2: i32,
pixel_fn: impl Fn(PixelType) -> PixelType
) {
// note: need to manually do all this instead of just relying on Rect::from_coords (which
// could otherwise figure all this out for us) mainly just because we need the post-swap
// x1,y1,x2,y2 values for post-region-clamping comparison purposes ...
if x2 < x1 {
swap(&mut x1, &mut x2);
}
if y2 < y1 {
swap(&mut y1, &mut y2);
}
let mut region = Rect {
x: x1,
y: y1,
width: (x2 - x1 + 1) as u32,
height: (y2 - y1 + 1) as u32,
};
if !region.clamp_to(&self.clip_region) {
return;
}
// we want to draw the top and bottom lines 1 pixel shorter at both ends to avoid overdrawing the
// corners of the rect. this is mostly important for blended drawing operations.
// if the region's left and/or right x coordinate was NOT clipped, we chop off 1 pixel from either/both
// to avoid corner overdraw. if either/both of these x coordinates WAS clipped, we don't need to adjust that
// side, as no corner overdraw is possible in that case.
let mut horiz_draw_x = region.x;
let mut horiz_draw_width = region.width;
if x1 == region.x {
horiz_draw_x += 1;
horiz_draw_width -= 1;
}
if x2 == region.right() {
horiz_draw_width -= 1;
}
// top line, only if y1 was originally within bounds
if y1 == region.y {
unsafe {
let dest = &mut self.pixels_at_mut_unchecked(horiz_draw_x, region.y)[0..horiz_draw_width as usize];
for pixel in dest.iter_mut() {
*pixel = pixel_fn(*pixel);
}
}
}
// bottom line, only if y2 was originally within bounds
if y2 == region.bottom() {
unsafe {
let dest = &mut self.pixels_at_mut_unchecked(horiz_draw_x, region.bottom())[0..horiz_draw_width as usize];
for pixel in dest.iter_mut() {
*pixel = pixel_fn(*pixel);
}
}
}
// left line, only if x1 was originally within bounds
if x1 == region.x {
unsafe {
let mut dest = self.pixels_at_mut_ptr_unchecked(region.x, region.y);
for _ in 0..region.height {
*dest = pixel_fn(*dest);
dest = dest.add(self.width as usize);
}
}
}
// right line, only if x2 was originally within bounds
if x2 == region.right() {
unsafe {
let mut dest = self.pixels_at_mut_ptr_unchecked(region.right(), region.y);
for _ in 0..region.height {
*dest = pixel_fn(*dest);
dest = dest.add(self.width as usize);
}
}
}
}
/// Draws a filled box (rectangle) using the points x1,y1 and x2,y2 to form the box to be /// Draws a filled box (rectangle) using the points x1,y1 and x2,y2 to form the box to be
/// drawn, assuming they are specifying the top-left and bottom-right corners respectively. /// drawn, assuming they are specifying the top-left and bottom-right corners respectively.
pub fn filled_rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: PixelType) { pub fn filled_rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: PixelType) {
@ -249,6 +444,31 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
} }
} }
pub fn filled_rect_custom(
&mut self,
x1: i32,
y1: i32,
x2: i32,
y2: i32,
pixel_fn: impl Fn(PixelType) -> PixelType
) {
let mut region = Rect::from_coords(x1, y1, x2, y2);
if region.clamp_to(&self.clip_region) {
unsafe {
let mut dest = self.pixels_at_mut_ptr_unchecked(region.x, region.y);
for _ in 0..region.height {
// write the pixels horizontally for the entire width
for x in 0..region.width as isize {
let dest_x = dest.offset(x);
*dest_x = pixel_fn(*dest_x);
}
// move original pointer to the next row
dest = dest.add(self.width as usize);
}
}
}
}
/// Draws the outline of a circle formed by the center point and radius given. /// Draws the outline of a circle formed by the center point and radius given.
pub fn circle(&mut self, center_x: i32, center_y: i32, radius: u32, color: PixelType) { pub fn circle(&mut self, center_x: i32, center_y: i32, radius: u32, color: PixelType) {
// TODO: optimize // TODO: optimize