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
|
/// 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
|
||||||
|
|
Loading…
Reference in a new issue