add generic primitive drawing methods accepting custom pixel functions
This commit is contained in:
parent
847149607d
commit
a667a657d2
|
@ -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
|
||||
/// checked for validity, so it is up to you to ensure they lie within the bounds of the
|
||||
/// bitmap.
|
||||
|
@ -29,6 +42,16 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
|
|||
*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
|
||||
/// clipping region, None is returned.
|
||||
#[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.
|
||||
pub fn horiz_line(&mut self, x1: i32, x2: i32, y: i32, color: PixelType) {
|
||||
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.
|
||||
pub fn vert_line(&mut self, x: i32, y1: i32, y2: i32, color: PixelType) {
|
||||
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
|
||||
/// 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) {
|
||||
|
@ -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
|
||||
/// 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) {
|
||||
|
@ -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.
|
||||
pub fn circle(&mut self, center_x: i32, center_y: i32, radius: u32, color: PixelType) {
|
||||
// TODO: optimize
|
||||
|
|
Loading…
Reference in a new issue