add color offset blit method variants

these allow a per-pixel color index offset to be applied during blits
This commit is contained in:
Gered 2022-06-04 16:13:50 -04:00
parent 6bb52e57e1
commit 71556c6d65

View file

@ -6,8 +6,12 @@ pub enum BlitMethod {
Solid,
Transparent(u8),
TransparentSingle(u8, u8), // transparent color, single visible color
SolidOffset(u8),
TransparentOffset(u8, u8),
RotoZoom { angle: f32, scale_x: f32, scale_y: f32 },
RotoZoomTransparent { angle: f32, scale_x: f32, scale_y: f32, transparent_color: u8 },
RotoZoomOffset { angle: f32, scale_x: f32, scale_y: f32, offset: u8 },
RotoZoomTransparentOffset { angle: f32, scale_x: f32, scale_y: f32, transparent_color: u8, offset: u8 },
}
/// Clips the region for a source bitmap to be used in a subsequent blit operation. The source
@ -100,6 +104,24 @@ impl Bitmap {
}
}
pub unsafe fn solid_blit_palette_offset(&mut self, src: &Bitmap, src_region: &Rect, dest_x: i32, dest_y: i32, offset: u8) {
let src_next_row_inc = (src.width - src_region.width) as usize;
let dest_next_row_inc = (self.width - src_region.width) as usize;
let mut src_pixels = src.pixels_at_ptr_unchecked(src_region.x, src_region.y);
let mut dest_pixels = self.pixels_at_mut_ptr_unchecked(dest_x, dest_y);
for _ in 0..src_region.height {
for _ in 0..src_region.width {
*dest_pixels = (*src_pixels).wrapping_add(offset);
src_pixels = src_pixels.add(1);
dest_pixels = dest_pixels.add(1);
}
src_pixels = src_pixels.add(src_next_row_inc);
dest_pixels = dest_pixels.add(dest_next_row_inc);
}
}
pub unsafe fn transparent_blit(
&mut self,
src: &Bitmap,
@ -129,6 +151,36 @@ impl Bitmap {
}
}
pub unsafe fn transparent_blit_palette_offset(
&mut self,
src: &Bitmap,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u8,
offset: u8,
) {
let src_next_row_inc = (src.width - src_region.width) as usize;
let dest_next_row_inc = (self.width - src_region.width) as usize;
let mut src_pixels = src.pixels_at_ptr_unchecked(src_region.x, src_region.y);
let mut dest_pixels = self.pixels_at_mut_ptr_unchecked(dest_x, dest_y);
for _ in 0..src_region.height {
for _ in 0..src_region.width {
let pixel = *src_pixels;
if pixel != transparent_color {
*dest_pixels = pixel.wrapping_add(offset);
}
src_pixels = src_pixels.add(1);
dest_pixels = dest_pixels.add(1);
}
src_pixels = src_pixels.add(src_next_row_inc);
dest_pixels = dest_pixels.add(dest_next_row_inc);
}
}
pub unsafe fn transparent_single_color_blit(
&mut self,
src: &Bitmap,
@ -224,6 +276,74 @@ impl Bitmap {
}
}
pub unsafe fn rotozoom_blit_palette_offset(
&mut self,
src: &Bitmap,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: Option<u8>,
offset: u8,
) {
// TODO: this isn't the best rotozoom algorithm i guess. it has some floating point issues
// that result in missing pixels/rows still in a few places. also the double pixel
// write exists to mask that issue (even worse without it).
// need to re-do this with a better rotozoom algorithm!
let new_width = src_region.width as f32 * scale_x;
let new_height = src_region.height as f32 * scale_y;
if new_width as i32 <= 0 || new_height as i32 <= 0 {
return;
}
let angle_cos = angle.cos();
let angle_sin = angle.sin();
let src_delta_x = src_region.width as f32 / new_width;
let src_delta_y = src_region.height as f32 / new_height;
let mut src_x = 0.0;
let mut src_y = 0.0;
let dest_center_x = dest_x as f32 + (new_width / 2.0);
let dest_center_y = dest_y as f32 + (new_height / 2.0);
for point_y in 0..new_height as i32 {
let src_pixels = src.pixels_at_unchecked(src_region.x, src_region.y + src_y as i32);
for point_x in 0..new_width as i32 {
let pixel = src_pixels[src_x as usize];
if transparent_color.is_none() || transparent_color != Some(pixel) {
let draw_x = (
(angle_cos * (point_x as f32 - (new_width / 2.0)))
- (angle_sin * (point_y as f32 - (new_height / 2.0)))
+ dest_center_x
) as i32;
let draw_y = (
(angle_cos * (point_y as f32 - (new_height / 2.0)))
+ (angle_sin * (point_x as f32 - (new_width / 2.0)))
+ dest_center_y
) as i32;
let pixel = pixel.wrapping_add(offset);
// write the same pixel twice to mask some floating point issues (?) which would
// manifest as "gap" pixels on the destination. ugh!
self.set_pixel(draw_x, draw_y, pixel);
self.set_pixel(draw_x + 1, draw_y, pixel);
}
src_x += src_delta_x;
}
src_x = 0.0;
src_y += src_delta_y;
}
}
pub fn blit_region(
&mut self,
method: BlitMethod,
@ -276,18 +396,28 @@ impl Bitmap {
use BlitMethod::*;
match method {
Solid => self.solid_blit(src, src_region, dest_x, dest_y),
SolidOffset(offset) => self.solid_blit_palette_offset(src, src_region, dest_x, dest_y, offset),
Transparent(transparent_color) => {
self.transparent_blit(src, src_region, dest_x, dest_y, transparent_color)
},
TransparentOffset(transparent_color, offset) => {
self.transparent_blit_palette_offset(src, src_region, dest_x, dest_y, transparent_color, offset)
},
TransparentSingle(transparent_color, single_color) => {
self.transparent_single_color_blit(src, src_region, dest_x, dest_y, transparent_color, single_color)
},
RotoZoom { angle, scale_x, scale_y } => {
self.rotozoom_blit(src, src_region, dest_x, dest_y, angle, scale_x, scale_y, None)
},
RotoZoomOffset { angle, scale_x, scale_y, offset } => {
self.rotozoom_blit_palette_offset(src, src_region, dest_x, dest_y, angle, scale_x, scale_y, None, offset)
},
RotoZoomTransparent { angle, scale_x, scale_y, transparent_color } => {
self.rotozoom_blit(src, src_region, dest_x, dest_y, angle, scale_x, scale_y, Some(transparent_color))
}
},
RotoZoomTransparentOffset { angle, scale_x, scale_y, transparent_color, offset } => {
self.rotozoom_blit_palette_offset(src, src_region, dest_x, dest_y, angle, scale_x, scale_y, Some(transparent_color), offset)
},
}
}