diff --git a/ggdt/src/graphics/bitmap/primitives.rs b/ggdt/src/graphics/bitmap/primitives.rs index 880cbb1..adc78c9 100644 --- a/ggdt/src/graphics/bitmap/primitives.rs +++ b/ggdt/src/graphics/bitmap/primitives.rs @@ -20,6 +20,19 @@ impl Bitmap { } } + /// 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 Bitmap { *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 Bitmap { } } + 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 Bitmap { } } + 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 Bitmap { } } + 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 Bitmap { } } + 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 Bitmap { } } + 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