large amount of bitmap graphics rework

this is an unfortunately large commit. probably should've been broken
up into multiple smaller ones.

i decided to revert some earlier preparatory work i had done to organize
graphics functionality into two main streams: "indexed" and "rgb". this
was going to result in two completely different Bitmap structs which
were going to be largely similar and as i thought about it more, i
realized my earlier thinking that i wouldn't be able to generify the
Bitmap struct based on pixel-depth was actually not correct.

so, this commit re-works things significantly in a way which i think
is better but probably still not perfect. basically, we have one
Bitmap struct which provides a lot of generic functionality, but also
specialized implementations based on pixel-depth and this is now what is
separated out into graphics::bitmap::indexed and graphics::bitmap::rgb.

but the rest of the graphics functionality is not separated out by
module like this any longer.

note that i've still kept the GeneralBitmap trait and implementations.
i was originally thinking i could get rid of this since i'd now made
Bitmap generic over pixel-depth, but doing so would require me to
rename the common/general blit methods to avoid a name collision with
the specialized implementations (since they would both exist on the
same types now). and i did not like that. i dunno, maybe i will change
my mind later.
This commit is contained in:
Gered 2023-03-11 16:58:13 -05:00
parent 2b414072bc
commit 07f2b13f68
44 changed files with 2576 additions and 2305 deletions

View file

@ -2,7 +2,7 @@ use std::path::Path;
use anyhow::Result; use anyhow::Result;
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
struct AudioChannelStatus { struct AudioChannelStatus {

View file

@ -2,7 +2,7 @@ use std::path::Path;
use anyhow::Result; use anyhow::Result;
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
const NUM_BALLS: usize = 128; const NUM_BALLS: usize = 128;
const NUM_BALL_SPRITES: usize = 16; const NUM_BALL_SPRITES: usize = 16;
@ -29,13 +29,13 @@ fn main() -> Result<()> {
let (balls_bmp, balls_palette) = Bitmap::load_pcx_file(Path::new("./assets/balls.pcx"))?; let (balls_bmp, balls_palette) = Bitmap::load_pcx_file(Path::new("./assets/balls.pcx"))?;
system.res.palette = balls_palette.clone(); system.res.palette = balls_palette.clone();
let mut sprites = Vec::<Bitmap>::new(); let mut sprites = Vec::<IndexedBitmap>::new();
let mut balls = Vec::<Ball>::new(); let mut balls = Vec::<Ball>::new();
for i in 0..NUM_BALL_SPRITES { for i in 0..NUM_BALL_SPRITES {
let mut sprite = Bitmap::new(BALL_WIDTH, BALL_HEIGHT)?; let mut sprite = IndexedBitmap::new(BALL_WIDTH, BALL_HEIGHT)?;
sprite.blit_region( sprite.blit_region(
BlitMethod::Solid, IndexedBlitMethod::Solid,
&balls_bmp, &balls_bmp,
&Rect::new(i as i32 * BALL_WIDTH as i32, 0, BALL_WIDTH, BALL_HEIGHT), &Rect::new(i as i32 * BALL_WIDTH as i32, 0, BALL_WIDTH, BALL_HEIGHT),
0, 0,
@ -100,7 +100,7 @@ fn main() -> Result<()> {
for i in 0..NUM_BALLS { for i in 0..NUM_BALLS {
system.res.video.blit( system.res.video.blit(
BlitMethod::Transparent(0), IndexedBlitMethod::Transparent(0),
&sprites[balls[i].sprite], &sprites[balls[i].sprite],
balls[i].x, balls[i].x,
balls[i].y, balls[i].y,

View file

@ -1,4 +1,4 @@
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
use crate::states::*; use crate::states::*;
@ -161,7 +161,7 @@ fn render_system_sprites(context: &mut Context) {
for (entity, sprite_index) in sprite_indices.iter() { for (entity, sprite_index) in sprite_indices.iter() {
let position = positions.get(&entity).unwrap(); let position = positions.get(&entity).unwrap();
context.system.res.video.blit( context.system.res.video.blit(
BlitMethod::Transparent(0), IndexedBlitMethod::Transparent(0),
&context.sprites[sprite_index.0], &context.sprites[sprite_index.0],
position.0.x as i32, position.0.x as i32,
position.0.y as i32, position.0.y as i32,

View file

@ -2,7 +2,7 @@ use std::path::Path;
use anyhow::Result; use anyhow::Result;
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
use crate::entities::*; use crate::entities::*;
use crate::states::*; use crate::states::*;

View file

@ -1,4 +1,4 @@
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
use crate::*; use crate::*;
@ -10,7 +10,7 @@ pub struct Context {
pub delta: f32, pub delta: f32,
pub system: System<DosLike>, pub system: System<DosLike>,
pub font: BitmaskFont, pub font: BitmaskFont,
pub sprites: Vec<Bitmap>, pub sprites: Vec<IndexedBitmap>,
pub entities: Entities, pub entities: Entities,
pub event_publisher: EventPublisher<Event>, pub event_publisher: EventPublisher<Event>,
} }
@ -25,14 +25,14 @@ impl Game {
pub fn new(mut system: System<DosLike>) -> Result<Self> { pub fn new(mut system: System<DosLike>) -> Result<Self> {
let font = BitmaskFont::new_vga_font()?; let font = BitmaskFont::new_vga_font()?;
let (balls_bmp, balls_palette) = Bitmap::load_pcx_file(Path::new("./assets/balls.pcx"))?; let (balls_bmp, balls_palette) = IndexedBitmap::load_pcx_file(Path::new("./assets/balls.pcx"))?;
system.res.palette = balls_palette.clone(); system.res.palette = balls_palette.clone();
let mut sprites = Vec::new(); let mut sprites = Vec::new();
for i in 0..NUM_BALL_SPRITES { for i in 0..NUM_BALL_SPRITES {
let mut sprite = Bitmap::new(BALL_SIZE as u32, BALL_SIZE as u32)?; let mut sprite = IndexedBitmap::new(BALL_SIZE as u32, BALL_SIZE as u32)?;
sprite.blit_region( sprite.blit_region(
BlitMethod::Solid, IndexedBlitMethod::Solid,
&balls_bmp, &balls_bmp,
&Rect::new(i as i32 * BALL_SIZE as i32, 0, BALL_SIZE as u32, BALL_SIZE as u32), &Rect::new(i as i32 * BALL_SIZE as i32, 0, BALL_SIZE as u32, BALL_SIZE as u32),
0, 0,

View file

@ -1,4 +1,4 @@
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
use crate::Core; use crate::Core;
use crate::entities::*; use crate::entities::*;

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::rc::Rc; use std::rc::Rc;
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
use crate::{Core, Game, TILE_HEIGHT, TILE_WIDTH, TileMap}; use crate::{Core, Game, TILE_HEIGHT, TILE_WIDTH, TileMap};
@ -218,7 +218,7 @@ pub struct LifeTime(pub f32);
pub struct Pixel(pub u8); pub struct Pixel(pub u8);
pub struct Sprite { pub struct Sprite {
pub atlas: Rc<BitmapAtlas<Bitmap>>, pub atlas: Rc<BitmapAtlas<IndexedBitmap>>,
pub index: usize, pub index: usize,
} }
@ -314,7 +314,7 @@ pub struct SpriteIndexByDirection {
} }
pub struct Weapon { pub struct Weapon {
pub atlas: Rc<BitmapAtlas<Bitmap>>, pub atlas: Rc<BitmapAtlas<IndexedBitmap>>,
pub base_index: usize, pub base_index: usize,
pub offsets: [Vector2; 4], pub offsets: [Vector2; 4],
pub damage: i32, pub damage: i32,

View file

@ -1,4 +1,4 @@
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
use crate::{Core, TILE_HEIGHT, TILE_WIDTH}; use crate::{Core, TILE_HEIGHT, TILE_WIDTH};
use crate::entities::*; use crate::entities::*;
@ -662,7 +662,7 @@ fn render_system_sprites(context: &mut Core) {
// build up list of entities to be rendered with their positions so we can sort them // build up list of entities to be rendered with their positions so we can sort them
// and render these entities with a proper y-based sort order // and render these entities with a proper y-based sort order
for (entity, _) in sprites.iter() { for (entity, _) in sprites.iter() {
let mut blit_method = BlitMethod::Transparent(0); let mut blit_method = IndexedBlitMethod::Transparent(0);
// check for flicker effects // check for flicker effects
if let Some(flicker) = timed_flickers.get(entity) { if let Some(flicker) = timed_flickers.get(entity) {
@ -673,7 +673,7 @@ fn render_system_sprites(context: &mut Core) {
continue; continue;
} }
FlickerMethod::Color(draw_color) => { FlickerMethod::Color(draw_color) => {
blit_method = BlitMethod::TransparentSingle { blit_method = IndexedBlitMethod::TransparentSingle {
transparent_color: 0, transparent_color: 0,
draw_color, draw_color,
}; };

View file

@ -6,7 +6,7 @@ use std::rc::Rc;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
use crate::entities::*; use crate::entities::*;
use crate::states::*; use crate::states::*;
@ -29,24 +29,24 @@ pub struct Core {
pub event_publisher: EventPublisher<Event>, pub event_publisher: EventPublisher<Event>,
pub palette: Palette, pub palette: Palette,
pub fade_out_palette: Palette, pub fade_out_palette: Palette,
pub tiles: Rc<BitmapAtlas<Bitmap>>, pub tiles: Rc<BitmapAtlas<IndexedBitmap>>,
pub hero_male: Rc<BitmapAtlas<Bitmap>>, pub hero_male: Rc<BitmapAtlas<IndexedBitmap>>,
pub hero_female: Rc<BitmapAtlas<Bitmap>>, pub hero_female: Rc<BitmapAtlas<IndexedBitmap>>,
pub green_slime: Rc<BitmapAtlas<Bitmap>>, pub green_slime: Rc<BitmapAtlas<IndexedBitmap>>,
pub blue_slime: Rc<BitmapAtlas<Bitmap>>, pub blue_slime: Rc<BitmapAtlas<IndexedBitmap>>,
pub orange_slime: Rc<BitmapAtlas<Bitmap>>, pub orange_slime: Rc<BitmapAtlas<IndexedBitmap>>,
pub fist: Rc<BitmapAtlas<Bitmap>>, pub fist: Rc<BitmapAtlas<IndexedBitmap>>,
pub sword: Rc<BitmapAtlas<Bitmap>>, pub sword: Rc<BitmapAtlas<IndexedBitmap>>,
pub particles: Rc<BitmapAtlas<Bitmap>>, pub particles: Rc<BitmapAtlas<IndexedBitmap>>,
pub items: Rc<BitmapAtlas<Bitmap>>, pub items: Rc<BitmapAtlas<IndexedBitmap>>,
pub ui: Rc<BitmapAtlas<Bitmap>>, pub ui: Rc<BitmapAtlas<IndexedBitmap>>,
pub tilemap: TileMap, pub tilemap: TileMap,
pub slime_activity_states: Rc<HashMap<EntityActivity, Rc<AnimationDef>>>, pub slime_activity_states: Rc<HashMap<EntityActivity, Rc<AnimationDef>>>,
pub hero_activity_states: Rc<HashMap<EntityActivity, Rc<AnimationDef>>>, pub hero_activity_states: Rc<HashMap<EntityActivity, Rc<AnimationDef>>>,
pub poof1_animation_def: Rc<AnimationDef>, pub poof1_animation_def: Rc<AnimationDef>,
pub poof2_animation_def: Rc<AnimationDef>, pub poof2_animation_def: Rc<AnimationDef>,
pub sparkles_animation_def: Rc<AnimationDef>, pub sparkles_animation_def: Rc<AnimationDef>,
pub sprite_render_list: Vec<(EntityId, Vector2, BlitMethod)>, pub sprite_render_list: Vec<(EntityId, Vector2, IndexedBlitMethod)>,
} }
impl CoreState<DosLike> for Core { impl CoreState<DosLike> for Core {

View file

@ -1,6 +1,6 @@
use std::path::Path; use std::path::Path;
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
use crate::entities::*; use crate::entities::*;
use crate::Game; use crate::Game;

View file

@ -2,7 +2,7 @@ use std::path::Path;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
use crate::{Game, TILE_HEIGHT, TILE_WIDTH}; use crate::{Game, TILE_HEIGHT, TILE_WIDTH};
@ -14,40 +14,40 @@ pub fn load_font(path: &Path) -> Result<BitmaskFont> {
BitmaskFont::load_from_file(path).context(format!("Loading font: {:?}", path)) BitmaskFont::load_from_file(path).context(format!("Loading font: {:?}", path))
} }
pub fn load_bitmap_atlas_autogrid(path: &Path) -> Result<BitmapAtlas<Bitmap>> { pub fn load_bitmap_atlas_autogrid(path: &Path) -> Result<BitmapAtlas<IndexedBitmap>> {
let (bmp, _) = Bitmap::load_file(path).context(format!("Loading bitmap atlas: {:?}", path))?; let (bmp, _) = Bitmap::load_file(path).context(format!("Loading bitmap atlas: {:?}", path))?;
let mut atlas = BitmapAtlas::new(bmp); let mut atlas = BitmapAtlas::new(bmp);
atlas.add_grid(TILE_WIDTH, TILE_HEIGHT)?; atlas.add_grid(TILE_WIDTH, TILE_HEIGHT)?;
Ok(atlas) Ok(atlas)
} }
pub fn load_bitmap_atlas(path: &Path) -> Result<BitmapAtlas<Bitmap>> { pub fn load_bitmap_atlas(path: &Path) -> Result<BitmapAtlas<IndexedBitmap>> {
let (bmp, _) = Bitmap::load_file(path).context(format!("Loading bitmap atlas: {:?}", path))?; let (bmp, _) = Bitmap::load_file(path).context(format!("Loading bitmap atlas: {:?}", path))?;
let atlas = BitmapAtlas::new(bmp); let atlas = BitmapAtlas::new(bmp);
Ok(atlas) Ok(atlas)
} }
pub fn draw_window(dest: &mut Bitmap, ui: &BitmapAtlas<Bitmap>, left: i32, top: i32, right: i32, bottom: i32) { pub fn draw_window(dest: &mut IndexedBitmap, ui: &BitmapAtlas<IndexedBitmap>, left: i32, top: i32, right: i32, bottom: i32) {
dest.filled_rect(left + 8, top + 8, right - 8, bottom - 8, 1); dest.filled_rect(left + 8, top + 8, right - 8, bottom - 8, 1);
// corners // corners
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[2], left, top); dest.blit_region(IndexedBlitMethod::Transparent(0), &ui.bitmap(), &ui[2], left, top);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[3], right - 8, top); dest.blit_region(IndexedBlitMethod::Transparent(0), &ui.bitmap(), &ui[3], right - 8, top);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[4], left, bottom - 8); dest.blit_region(IndexedBlitMethod::Transparent(0), &ui.bitmap(), &ui[4], left, bottom - 8);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[5], right - 8, bottom - 8); dest.blit_region(IndexedBlitMethod::Transparent(0), &ui.bitmap(), &ui[5], right - 8, bottom - 8);
// top and bottom edges // top and bottom edges
for i in 0..((right - left) / 8) - 2 { for i in 0..((right - left) / 8) - 2 {
let x = left + 8 + (i * 8); let x = left + 8 + (i * 8);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[9], x, top); dest.blit_region(IndexedBlitMethod::Transparent(0), &ui.bitmap(), &ui[9], x, top);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[8], x, bottom - 8); dest.blit_region(IndexedBlitMethod::Transparent(0), &ui.bitmap(), &ui[8], x, bottom - 8);
} }
// left and right edges // left and right edges
for i in 0..((bottom - top) / 8) - 2 { for i in 0..((bottom - top) / 8) - 2 {
let y = top + 8 + (i * 8); let y = top + 8 + (i * 8);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[6], left, y); dest.blit_region(IndexedBlitMethod::Transparent(0), &ui.bitmap(), &ui[6], left, y);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[7], right - 8, y); dest.blit_region(IndexedBlitMethod::Transparent(0), &ui.bitmap(), &ui[7], right - 8, y);
} }
} }

View file

@ -5,7 +5,7 @@ use std::path::Path;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use serde::Deserialize; use serde::Deserialize;
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
use crate::{TILE_HEIGHT, TILE_WIDTH}; use crate::{TILE_HEIGHT, TILE_WIDTH};
@ -61,7 +61,7 @@ impl TileMap {
&self.layers[2] &self.layers[2]
} }
pub fn draw(&self, dest: &mut Bitmap, tiles: &BitmapAtlas<Bitmap>, camera_x: i32, camera_y: i32) { pub fn draw(&self, dest: &mut IndexedBitmap, tiles: &BitmapAtlas<IndexedBitmap>, camera_x: i32, camera_y: i32) {
let xt = camera_x / TILE_WIDTH as i32; let xt = camera_x / TILE_WIDTH as i32;
let yt = camera_y / TILE_HEIGHT as i32; let yt = camera_y / TILE_HEIGHT as i32;
let xp = camera_x % TILE_WIDTH as i32; let xp = camera_x % TILE_WIDTH as i32;
@ -75,11 +75,11 @@ impl TileMap {
let lower = self.layers[0][index]; let lower = self.layers[0][index];
if lower >= 0 { if lower >= 0 {
dest.blit_region(BlitMethod::Solid, tiles.bitmap(), &tiles[lower as usize], xd, yd); dest.blit_region(IndexedBlitMethod::Solid, tiles.bitmap(), &tiles[lower as usize], xd, yd);
} }
let upper = self.layers[1][index]; let upper = self.layers[1][index];
if upper >= 0 { if upper >= 0 {
dest.blit_region(BlitMethod::Transparent(0), tiles.bitmap(), &tiles[upper as usize], xd, yd); dest.blit_region(IndexedBlitMethod::Transparent(0), tiles.bitmap(), &tiles[upper as usize], xd, yd);
} }
} }
} }

View file

@ -1,6 +1,6 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////

View file

@ -1,6 +1,6 @@
use anyhow::Result; use anyhow::Result;
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
fn main() -> Result<()> { fn main() -> Result<()> {
let config = DosLikeConfig::new(); let config = DosLikeConfig::new();

View file

@ -1,9 +1,9 @@
use criterion::{black_box, Criterion, criterion_group, criterion_main}; use criterion::{black_box, Criterion, criterion_group, criterion_main};
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
pub fn criterion_benchmark(c: &mut Criterion) { pub fn criterion_benchmark(c: &mut Criterion) {
let mut source = Bitmap::new(SCREEN_WIDTH, SCREEN_HEIGHT).unwrap(); let mut source = IndexedBitmap::new(SCREEN_WIDTH, SCREEN_HEIGHT).unwrap();
let mut dest = vec![0u32; (SCREEN_WIDTH * SCREEN_HEIGHT * 4) as usize].into_boxed_slice(); let mut dest = vec![0u32; (SCREEN_WIDTH * SCREEN_HEIGHT * 4) as usize].into_boxed_slice();
let palette = Palette::new_vga_palette().unwrap(); let palette = Palette::new_vga_palette().unwrap();

View file

@ -2,23 +2,23 @@ use std::path::Path;
use criterion::{black_box, Criterion, criterion_group, criterion_main}; use criterion::{black_box, Criterion, criterion_group, criterion_main};
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
pub fn criterion_benchmark(c: &mut Criterion) { pub fn criterion_benchmark(c: &mut Criterion) {
let mut framebuffer = Bitmap::new(320, 240).unwrap(); let mut framebuffer = IndexedBitmap::new(320, 240).unwrap();
let (bmp, _) = Bitmap::load_iff_file(Path::new("./test-assets/test-tiles.lbm")).unwrap(); let (bmp, _) = IndexedBitmap::load_iff_file(Path::new("./test-assets/test-tiles.lbm")).unwrap();
let mut solid_bmp = Bitmap::new(16, 16).unwrap(); let mut solid_bmp = IndexedBitmap::new(16, 16).unwrap();
solid_bmp.blit_region(BlitMethod::Solid, &bmp, &Rect::new(16, 16, 16, 16), 0, 0); solid_bmp.blit_region(IndexedBlitMethod::Solid, &bmp, &Rect::new(16, 16, 16, 16), 0, 0);
let mut trans_bmp = Bitmap::new(16, 16).unwrap(); let mut trans_bmp = IndexedBitmap::new(16, 16).unwrap();
trans_bmp.blit_region(BlitMethod::Solid, &bmp, &Rect::new(160, 0, 16, 16), 0, 0); trans_bmp.blit_region(IndexedBlitMethod::Solid, &bmp, &Rect::new(160, 0, 16, 16), 0, 0);
////// //////
c.bench_function("blit_single_checked_solid", |b| { c.bench_function("blit_single_checked_solid", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::Solid), black_box(IndexedBlitMethod::Solid),
black_box(&solid_bmp), black_box(&solid_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
@ -29,7 +29,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_single_unchecked_solid", |b| { c.bench_function("blit_single_unchecked_solid", |b| {
b.iter(|| unsafe { b.iter(|| unsafe {
framebuffer.blit_unchecked( framebuffer.blit_unchecked(
black_box(BlitMethod::Solid), black_box(IndexedBlitMethod::Solid),
black_box(&solid_bmp), black_box(&solid_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
@ -42,7 +42,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_single_checked_transparent", |b| { c.bench_function("blit_single_checked_transparent", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::Transparent(0)), black_box(IndexedBlitMethod::Transparent(0)),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
@ -53,7 +53,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_single_unchecked_transparent", |b| { c.bench_function("blit_single_unchecked_transparent", |b| {
b.iter(|| unsafe { b.iter(|| unsafe {
framebuffer.blit_unchecked( framebuffer.blit_unchecked(
black_box(BlitMethod::Transparent(0)), black_box(IndexedBlitMethod::Transparent(0)),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
@ -66,7 +66,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_solid_flipped_not_flipped", |b| { c.bench_function("blit_solid_flipped_not_flipped", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidFlipped { black_box(IndexedBlitMethod::SolidFlipped {
horizontal_flip: false, horizontal_flip: false,
vertical_flip: false, vertical_flip: false,
}), }),
@ -80,7 +80,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_solid_flipped_horizontally", |b| { c.bench_function("blit_solid_flipped_horizontally", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidFlipped { black_box(IndexedBlitMethod::SolidFlipped {
horizontal_flip: true, horizontal_flip: true,
vertical_flip: false, vertical_flip: false,
}), }),
@ -94,7 +94,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_solid_flipped_vertically", |b| { c.bench_function("blit_solid_flipped_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidFlipped { black_box(IndexedBlitMethod::SolidFlipped {
horizontal_flip: false, horizontal_flip: false,
vertical_flip: true, vertical_flip: true,
}), }),
@ -108,7 +108,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_solid_flipped_horizontally_and_vertically", |b| { c.bench_function("blit_solid_flipped_horizontally_and_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidFlipped { black_box(IndexedBlitMethod::SolidFlipped {
horizontal_flip: true, horizontal_flip: true,
vertical_flip: true, vertical_flip: true,
}), }),
@ -124,7 +124,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_flipped_not_flipped", |b| { c.bench_function("blit_transparent_flipped_not_flipped", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlipped { black_box(IndexedBlitMethod::TransparentFlipped {
transparent_color: 0, transparent_color: 0,
horizontal_flip: false, horizontal_flip: false,
vertical_flip: false, vertical_flip: false,
@ -139,7 +139,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_flipped_horizontally", |b| { c.bench_function("blit_transparent_flipped_horizontally", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlipped { black_box(IndexedBlitMethod::TransparentFlipped {
transparent_color: 0, transparent_color: 0,
horizontal_flip: true, horizontal_flip: true,
vertical_flip: false, vertical_flip: false,
@ -154,7 +154,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_flipped_vertically", |b| { c.bench_function("blit_transparent_flipped_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlipped { black_box(IndexedBlitMethod::TransparentFlipped {
transparent_color: 0, transparent_color: 0,
horizontal_flip: false, horizontal_flip: false,
vertical_flip: true, vertical_flip: true,
@ -169,7 +169,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_flipped_horizontally_and_vertically", |b| { c.bench_function("blit_transparent_flipped_horizontally_and_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlipped { black_box(IndexedBlitMethod::TransparentFlipped {
transparent_color: 0, transparent_color: 0,
horizontal_flip: true, horizontal_flip: true,
vertical_flip: true, vertical_flip: true,
@ -186,7 +186,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_single_flipped_not_flipped", |b| { c.bench_function("blit_transparent_single_flipped_not_flipped", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlippedSingle { black_box(IndexedBlitMethod::TransparentFlippedSingle {
transparent_color: 0, transparent_color: 0,
horizontal_flip: false, horizontal_flip: false,
vertical_flip: false, vertical_flip: false,
@ -202,7 +202,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_single_flipped_horizontally", |b| { c.bench_function("blit_transparent_single_flipped_horizontally", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlippedSingle { black_box(IndexedBlitMethod::TransparentFlippedSingle {
transparent_color: 0, transparent_color: 0,
horizontal_flip: true, horizontal_flip: true,
vertical_flip: false, vertical_flip: false,
@ -218,7 +218,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_single_flipped_vertically", |b| { c.bench_function("blit_transparent_single_flipped_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlippedSingle { black_box(IndexedBlitMethod::TransparentFlippedSingle {
transparent_color: 0, transparent_color: 0,
horizontal_flip: false, horizontal_flip: false,
vertical_flip: true, vertical_flip: true,
@ -234,7 +234,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_single_flipped_horizontally_and_vertically", |b| { c.bench_function("blit_transparent_single_flipped_horizontally_and_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlippedSingle { black_box(IndexedBlitMethod::TransparentFlippedSingle {
transparent_color: 0, transparent_color: 0,
horizontal_flip: true, horizontal_flip: true,
vertical_flip: true, vertical_flip: true,
@ -252,7 +252,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_single", |b| { c.bench_function("blit_transparent_single", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentSingle { black_box(IndexedBlitMethod::TransparentSingle {
transparent_color: 0, transparent_color: 0,
draw_color: 17, draw_color: 17,
}), }),
@ -268,7 +268,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_offset", |b| { c.bench_function("blit_transparent_offset", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentOffset { black_box(IndexedBlitMethod::TransparentOffset {
transparent_color: 0, transparent_color: 0,
offset: 42, offset: 42,
}), }),
@ -284,7 +284,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_offset_flipped_not_flipped", |b| { c.bench_function("blit_transparent_offset_flipped_not_flipped", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlippedOffset { black_box(IndexedBlitMethod::TransparentFlippedOffset {
transparent_color: 0, transparent_color: 0,
horizontal_flip: false, horizontal_flip: false,
vertical_flip: false, vertical_flip: false,
@ -300,7 +300,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_offset_flipped_horizontally", |b| { c.bench_function("blit_transparent_offset_flipped_horizontally", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlippedOffset { black_box(IndexedBlitMethod::TransparentFlippedOffset {
transparent_color: 0, transparent_color: 0,
horizontal_flip: true, horizontal_flip: true,
vertical_flip: false, vertical_flip: false,
@ -316,7 +316,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_offset_flipped_vertically", |b| { c.bench_function("blit_transparent_offset_flipped_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlippedOffset { black_box(IndexedBlitMethod::TransparentFlippedOffset {
transparent_color: 0, transparent_color: 0,
horizontal_flip: false, horizontal_flip: false,
vertical_flip: true, vertical_flip: true,
@ -332,7 +332,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_offset_flipped_horizontally_and_vertically", |b| { c.bench_function("blit_transparent_offset_flipped_horizontally_and_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlippedOffset { black_box(IndexedBlitMethod::TransparentFlippedOffset {
transparent_color: 0, transparent_color: 0,
horizontal_flip: true, horizontal_flip: true,
vertical_flip: true, vertical_flip: true,
@ -350,7 +350,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_solid_offset", |b| { c.bench_function("blit_solid_offset", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidOffset(42)), black_box(IndexedBlitMethod::SolidOffset(42)),
black_box(&solid_bmp), black_box(&solid_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
@ -363,7 +363,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_solid_offset_flipped_not_flipped", |b| { c.bench_function("blit_solid_offset_flipped_not_flipped", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidFlippedOffset { black_box(IndexedBlitMethod::SolidFlippedOffset {
horizontal_flip: false, horizontal_flip: false,
vertical_flip: false, vertical_flip: false,
offset: 42, offset: 42,
@ -378,7 +378,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_solid_offset_flipped_horizontally", |b| { c.bench_function("blit_solid_offset_flipped_horizontally", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidFlippedOffset { black_box(IndexedBlitMethod::SolidFlippedOffset {
horizontal_flip: true, horizontal_flip: true,
vertical_flip: false, vertical_flip: false,
offset: 42, offset: 42,
@ -393,7 +393,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_solid_offset_flipped_vertically", |b| { c.bench_function("blit_solid_offset_flipped_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidFlippedOffset { black_box(IndexedBlitMethod::SolidFlippedOffset {
horizontal_flip: false, horizontal_flip: false,
vertical_flip: true, vertical_flip: true,
offset: 42, offset: 42,
@ -408,7 +408,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_solid_offset_flipped_horizontally_and_vertically", |b| { c.bench_function("blit_solid_offset_flipped_horizontally_and_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidFlippedOffset { black_box(IndexedBlitMethod::SolidFlippedOffset {
horizontal_flip: true, horizontal_flip: true,
vertical_flip: true, vertical_flip: true,
offset: 42, offset: 42,
@ -425,7 +425,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_rotozoom", |b| { c.bench_function("blit_rotozoom", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::RotoZoom { black_box(IndexedBlitMethod::RotoZoom {
angle: 73.0f32.to_radians(), angle: 73.0f32.to_radians(),
scale_x: 1.2, scale_x: 1.2,
scale_y: 0.8, scale_y: 0.8,
@ -442,7 +442,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_rotozoom_offset", |b| { c.bench_function("blit_rotozoom_offset", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::RotoZoomOffset { black_box(IndexedBlitMethod::RotoZoomOffset {
angle: 73.0f32.to_radians(), angle: 73.0f32.to_radians(),
scale_x: 1.2, scale_x: 1.2,
scale_y: 0.8, scale_y: 0.8,
@ -460,7 +460,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_rotozoom_transparent", |b| { c.bench_function("blit_rotozoom_transparent", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::RotoZoomTransparent { black_box(IndexedBlitMethod::RotoZoomTransparent {
angle: 73.0f32.to_radians(), angle: 73.0f32.to_radians(),
scale_x: 1.2, scale_x: 1.2,
scale_y: 0.8, scale_y: 0.8,
@ -478,7 +478,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_rotozoom_offset_transparent", |b| { c.bench_function("blit_rotozoom_offset_transparent", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::RotoZoomTransparentOffset { black_box(IndexedBlitMethod::RotoZoomTransparentOffset {
angle: 73.0f32.to_radians(), angle: 73.0f32.to_radians(),
scale_x: 1.2, scale_x: 1.2,
scale_y: 0.8, scale_y: 0.8,

View file

@ -2,7 +2,7 @@ use std::io::Cursor;
use criterion::{black_box, Criterion, criterion_group, criterion_main}; use criterion::{black_box, Criterion, criterion_group, criterion_main};
use ggdt::prelude::dos_like::*; use ggdt::prelude::*;
pub static SMALL_GIF_FILE_BYTES: &[u8] = include_bytes!("../test-assets/test.gif"); pub static SMALL_GIF_FILE_BYTES: &[u8] = include_bytes!("../test-assets/test.gif");
pub static LARGE_GIF_FILE_BYTES: &[u8] = include_bytes!("../test-assets/test_image.gif"); pub static LARGE_GIF_FILE_BYTES: &[u8] = include_bytes!("../test-assets/test_image.gif");
@ -11,14 +11,14 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("loading_small_gif", |b| { c.bench_function("loading_small_gif", |b| {
b.iter(|| { b.iter(|| {
let mut reader = Cursor::new(SMALL_GIF_FILE_BYTES); let mut reader = Cursor::new(SMALL_GIF_FILE_BYTES);
Bitmap::load_gif_bytes(black_box(&mut reader)).unwrap(); IndexedBitmap::load_gif_bytes(black_box(&mut reader)).unwrap();
}) })
}); });
c.bench_function("loading_large_gif", |b| { c.bench_function("loading_large_gif", |b| {
b.iter(|| { b.iter(|| {
let mut reader = Cursor::new(LARGE_GIF_FILE_BYTES); let mut reader = Cursor::new(LARGE_GIF_FILE_BYTES);
Bitmap::load_gif_bytes(black_box(&mut reader)).unwrap(); IndexedBitmap::load_gif_bytes(black_box(&mut reader)).unwrap();
}) })
}); });
} }

View file

@ -1,127 +1,7 @@
use std::rc::Rc; use crate::graphics::bitmap::Bitmap;
use crate::graphics::Pixel;
use crate::graphics::bitmapatlas::BitmapAtlas;
use crate::graphics::indexed::bitmap::Bitmap;
use crate::graphics::indexed::blendmap::BlendMap;
use crate::math::rect::Rect; use crate::math::rect::Rect;
#[derive(Clone, PartialEq)]
pub enum BlitMethod {
/// Solid blit, no transparency or other per-pixel adjustments.
Solid,
/// Same as [BlitMethod::Solid] but the drawn image can also be flipped horizontally
/// and/or vertically.
SolidFlipped {
horizontal_flip: bool,
vertical_flip: bool,
},
/// Transparent blit, the specified source color pixels are skipped.
Transparent(u8),
/// Same as [BlitMethod::Transparent] but the drawn image can also be flipped horizontally
/// and/or vertically.
TransparentFlipped {
transparent_color: u8,
horizontal_flip: bool,
vertical_flip: bool,
},
/// Same as [BlitMethod::Transparent] except that the visible pixels on the destination are all
/// drawn using the same color.
TransparentSingle {
transparent_color: u8,
draw_color: u8,
},
/// Combination of [BlitMethod::TransparentFlipped] and [BlitMethod::TransparentSingle].
TransparentFlippedSingle {
transparent_color: u8,
horizontal_flip: bool,
vertical_flip: bool,
draw_color: u8,
},
/// Same as [BlitMethod::Solid] except that the drawn pixels have their color indices offset
/// by the amount given.
SolidOffset(u8),
/// Combination of [BlitMethod::SolidFlipped] and [BlitMethod::SolidOffset].
SolidFlippedOffset {
horizontal_flip: bool,
vertical_flip: bool,
offset: u8,
},
/// Same as [BlitMethod::Transparent] except that the drawn pixels have their color indices
/// offset by the amount given. The transparent color check is not affected by the offset and
/// is always treated as an absolute palette color index.
TransparentOffset { transparent_color: u8, offset: u8 },
/// Combination of [BlitMethod::TransparentFlipped] and [BlitMethod::TransparentOffset].
TransparentFlippedOffset {
transparent_color: u8,
horizontal_flip: bool,
vertical_flip: bool,
offset: u8,
},
/// Rotozoom blit, works the same as [BlitMethod::Solid] except that rotation and scaling is
/// performed.
RotoZoom {
angle: f32,
scale_x: f32,
scale_y: f32,
},
/// Same as [BlitMethod::RotoZoom] except that the specified source color pixels are skipped.
RotoZoomTransparent {
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u8,
},
/// Same as [BlitMethod::RotoZoom] except that the drawn pixels have their color indices
/// offset by the amount given.
RotoZoomOffset {
angle: f32,
scale_x: f32,
scale_y: f32,
offset: u8,
},
/// Same as [BlitMethod::RotoZoomTransparent] except that the drawn pixels have their color
/// indices offset by the amount given. The transparent color check is not affected by the
/// offset and is always treated as an absolute palette color index.
RotoZoomTransparentOffset {
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u8,
offset: u8,
},
SolidBlended {
blend_map: Rc<BlendMap>,
},
SolidFlippedBlended {
horizontal_flip: bool,
vertical_flip: bool,
blend_map: Rc<BlendMap>,
},
TransparentBlended {
transparent_color: u8,
blend_map: Rc<BlendMap>,
},
TransparentFlippedBlended {
transparent_color: u8,
horizontal_flip: bool,
vertical_flip: bool,
blend_map: Rc<BlendMap>,
},
RotoZoomBlended {
angle: f32,
scale_x: f32,
scale_y: f32,
blend_map: Rc<BlendMap>,
},
RotoZoomTransparentBlended {
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u8,
blend_map: Rc<BlendMap>,
},
}
/// Clips the region for a source bitmap to be used in a subsequent blit operation. The source /// Clips the region for a source bitmap to be used in a subsequent blit operation. The source
/// region will be clipped against the clipping region given for the destination bitmap. The /// region will be clipped against the clipping region given for the destination bitmap. The
/// top-left coordinates of the location to blit to on the destination bitmap are also adjusted /// top-left coordinates of the location to blit to on the destination bitmap are also adjusted
@ -212,8 +92,8 @@ pub fn clip_blit(
} }
#[inline] #[inline]
fn get_flipped_blit_properties( fn get_flipped_blit_properties<PixelType: Pixel>(
src: &Bitmap, src: &Bitmap<PixelType>,
src_region: &Rect, src_region: &Rect,
horizontal_flip: bool, horizontal_flip: bool,
vertical_flip: bool, vertical_flip: bool,
@ -249,13 +129,13 @@ fn get_flipped_blit_properties(
} }
#[inline] #[inline]
unsafe fn per_pixel_blit( pub unsafe fn per_pixel_blit<PixelType: Pixel>(
dest: &mut Bitmap, dest: &mut Bitmap<PixelType>,
src: &Bitmap, src: &Bitmap<PixelType>,
src_region: &Rect, src_region: &Rect,
dest_x: i32, dest_x: i32,
dest_y: i32, dest_y: i32,
pixel_fn: impl Fn(*const u8, *mut u8), pixel_fn: impl Fn(*const PixelType, *mut PixelType),
) { ) {
let src_next_row_inc = (src.width - src_region.width) as usize; let src_next_row_inc = (src.width - src_region.width) as usize;
let dest_next_row_inc = (dest.width - src_region.width) as usize; let dest_next_row_inc = (dest.width - src_region.width) as usize;
@ -275,15 +155,15 @@ unsafe fn per_pixel_blit(
} }
#[inline] #[inline]
unsafe fn per_pixel_flipped_blit( pub unsafe fn per_pixel_flipped_blit<PixelType: Pixel>(
dest: &mut Bitmap, dest: &mut Bitmap<PixelType>,
src: &Bitmap, src: &Bitmap<PixelType>,
src_region: &Rect, src_region: &Rect,
dest_x: i32, dest_x: i32,
dest_y: i32, dest_y: i32,
horizontal_flip: bool, horizontal_flip: bool,
vertical_flip: bool, vertical_flip: bool,
pixel_fn: impl Fn(*const u8, *mut u8), pixel_fn: impl Fn(*const PixelType, *mut PixelType),
) { ) {
let dest_next_row_inc = (dest.width - src_region.width) as usize; let dest_next_row_inc = (dest.width - src_region.width) as usize;
let (x_inc, src_start_x, src_start_y, src_next_row_inc) = let (x_inc, src_start_x, src_start_y, src_next_row_inc) =
@ -305,16 +185,16 @@ unsafe fn per_pixel_flipped_blit(
} }
#[inline] #[inline]
unsafe fn per_pixel_rotozoom_blit( pub unsafe fn per_pixel_rotozoom_blit<PixelType: Pixel>(
dest: &mut Bitmap, dest: &mut Bitmap<PixelType>,
src: &Bitmap, src: &Bitmap<PixelType>,
src_region: &Rect, src_region: &Rect,
dest_x: i32, dest_x: i32,
dest_y: i32, dest_y: i32,
angle: f32, angle: f32,
scale_x: f32, scale_x: f32,
scale_y: f32, scale_y: f32,
pixel_fn: impl Fn(u8, &mut Bitmap, i32, i32), pixel_fn: impl Fn(PixelType, &mut Bitmap<PixelType>, i32, i32),
) { ) {
let dest_width = src_region.width as f32 * scale_x; let dest_width = src_region.width as f32 * scale_x;
let dest_height = src_region.height as f32 * scale_y; let dest_height = src_region.height as f32 * scale_y;
@ -393,11 +273,11 @@ unsafe fn per_pixel_rotozoom_blit(
} }
} }
impl Bitmap { impl<PixelType: Pixel> Bitmap<PixelType> {
pub unsafe fn solid_blit(&mut self, src: &Bitmap, src_region: &Rect, dest_x: i32, dest_y: i32) { pub unsafe fn solid_blit(&mut self, src: &Self, src_region: &Rect, dest_x: i32, dest_y: i32) {
let src_row_length = src_region.width as usize; let src_row_length = src_region.width as usize * Self::PIXEL_SIZE;
let src_pitch = src.width as usize; let src_pitch = src.width as usize * Self::PIXEL_SIZE;
let dest_pitch = self.width as usize; let dest_pitch = self.width as usize * Self::PIXEL_SIZE;
let mut src_pixels = src.pixels_at_ptr_unchecked(src_region.x, src_region.y); 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); let mut dest_pixels = self.pixels_at_mut_ptr_unchecked(dest_x, dest_y);
@ -408,29 +288,9 @@ impl Bitmap {
} }
} }
pub unsafe fn solid_blended_blit(
&mut self,
src: &Bitmap,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
blend_map: Rc<BlendMap>,
) {
per_pixel_blit(
self, src, src_region, dest_x, dest_y,
|src_pixels, dest_pixels| {
if let Some(blended_pixel) = blend_map.blend(*src_pixels, *dest_pixels) {
*dest_pixels = blended_pixel;
} else {
*dest_pixels = *src_pixels;
}
},
);
}
pub unsafe fn solid_flipped_blit( pub unsafe fn solid_flipped_blit(
&mut self, &mut self,
src: &Bitmap, src: &Self,
src_region: &Rect, src_region: &Rect,
dest_x: i32, dest_x: i32,
dest_y: i32, dest_y: i32,
@ -445,69 +305,13 @@ impl Bitmap {
); );
} }
pub unsafe fn solid_flipped_blended_blit(
&mut self,
src: &Bitmap,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
horizontal_flip: bool,
vertical_flip: bool,
blend_map: Rc<BlendMap>,
) {
per_pixel_flipped_blit(
self, src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip,
|src_pixels, dest_pixels| {
if let Some(blended_pixel) = blend_map.blend(*src_pixels, *dest_pixels) {
*dest_pixels = blended_pixel;
} else {
*dest_pixels = *src_pixels;
}
},
);
}
pub unsafe fn solid_palette_offset_blit(
&mut self,
src: &Bitmap,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
offset: u8,
) {
per_pixel_blit(
self, src, src_region, dest_x, dest_y,
|src_pixels, dest_pixels| {
*dest_pixels = (*src_pixels).wrapping_add(offset);
},
);
}
pub unsafe fn solid_flipped_palette_offset_blit(
&mut self,
src: &Bitmap,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
horizontal_flip: bool,
vertical_flip: bool,
offset: u8,
) {
per_pixel_flipped_blit(
self, src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip,
|src_pixels, dest_pixels| {
*dest_pixels = (*src_pixels).wrapping_add(offset);
},
);
}
pub unsafe fn transparent_blit( pub unsafe fn transparent_blit(
&mut self, &mut self,
src: &Bitmap, src: &Self,
src_region: &Rect, src_region: &Rect,
dest_x: i32, dest_x: i32,
dest_y: i32, dest_y: i32,
transparent_color: u8, transparent_color: PixelType,
) { ) {
per_pixel_blit( per_pixel_blit(
self, src, src_region, dest_x, dest_y, self, src, src_region, dest_x, dest_y,
@ -519,36 +323,13 @@ impl Bitmap {
); );
} }
pub unsafe fn transparent_blended_blit(
&mut self,
src: &Bitmap,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u8,
blend_map: Rc<BlendMap>,
) {
per_pixel_blit(
self, src, src_region, dest_x, dest_y,
|src_pixels, dest_pixels| {
if *src_pixels != transparent_color {
if let Some(blended_pixel) = blend_map.blend(*src_pixels, *dest_pixels) {
*dest_pixels = blended_pixel;
} else {
*dest_pixels = *src_pixels;
}
}
},
);
}
pub unsafe fn transparent_flipped_blit( pub unsafe fn transparent_flipped_blit(
&mut self, &mut self,
src: &Bitmap, src: &Self,
src_region: &Rect, src_region: &Rect,
dest_x: i32, dest_x: i32,
dest_y: i32, dest_y: i32,
transparent_color: u8, transparent_color: PixelType,
horizontal_flip: bool, horizontal_flip: bool,
vertical_flip: bool, vertical_flip: bool,
) { ) {
@ -562,79 +343,14 @@ impl Bitmap {
); );
} }
pub unsafe fn transparent_flipped_blended_blit(
&mut self,
src: &Bitmap,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u8,
horizontal_flip: bool,
vertical_flip: bool,
blend_map: Rc<BlendMap>,
) {
per_pixel_flipped_blit(
self, src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip,
|src_pixels, dest_pixels| {
if *src_pixels != transparent_color {
if let Some(blended_pixel) = blend_map.blend(*src_pixels, *dest_pixels) {
*dest_pixels = blended_pixel;
} else {
*dest_pixels = *src_pixels;
}
}
},
);
}
pub unsafe fn transparent_palette_offset_blit(
&mut self,
src: &Bitmap,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u8,
offset: u8,
) {
per_pixel_blit(
self, src, src_region, dest_x, dest_y,
|src_pixels, dest_pixels| {
if *src_pixels != transparent_color {
*dest_pixels = (*src_pixels).wrapping_add(offset);
}
},
);
}
pub unsafe fn transparent_flipped_palette_offset_blit(
&mut self,
src: &Bitmap,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u8,
horizontal_flip: bool,
vertical_flip: bool,
offset: u8,
) {
per_pixel_flipped_blit(
self, src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip,
|src_pixels, dest_pixels| {
if *src_pixels != transparent_color {
*dest_pixels = (*src_pixels).wrapping_add(offset);
}
},
);
}
pub unsafe fn transparent_single_color_blit( pub unsafe fn transparent_single_color_blit(
&mut self, &mut self,
src: &Bitmap, src: &Self,
src_region: &Rect, src_region: &Rect,
dest_x: i32, dest_x: i32,
dest_y: i32, dest_y: i32,
transparent_color: u8, transparent_color: PixelType,
draw_color: u8, draw_color: PixelType,
) { ) {
per_pixel_blit( per_pixel_blit(
self, src, src_region, dest_x, dest_y, self, src, src_region, dest_x, dest_y,
@ -648,14 +364,14 @@ impl Bitmap {
pub unsafe fn transparent_flipped_single_color_blit( pub unsafe fn transparent_flipped_single_color_blit(
&mut self, &mut self,
src: &Bitmap, src: &Self,
src_region: &Rect, src_region: &Rect,
dest_x: i32, dest_x: i32,
dest_y: i32, dest_y: i32,
transparent_color: u8, transparent_color: PixelType,
horizontal_flip: bool, horizontal_flip: bool,
vertical_flip: bool, vertical_flip: bool,
draw_color: u8, draw_color: PixelType,
) { ) {
per_pixel_flipped_blit( per_pixel_flipped_blit(
self, src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip, self, src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip,
@ -669,7 +385,7 @@ impl Bitmap {
pub unsafe fn rotozoom_blit( pub unsafe fn rotozoom_blit(
&mut self, &mut self,
src: &Bitmap, src: &Self,
src_region: &Rect, src_region: &Rect,
dest_x: i32, dest_x: i32,
dest_y: i32, dest_y: i32,
@ -681,47 +397,20 @@ impl Bitmap {
self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y, self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y,
|src_pixel, dest_bitmap, draw_x, draw_y| { |src_pixel, dest_bitmap, draw_x, draw_y| {
dest_bitmap.set_pixel(draw_x, draw_y, src_pixel); dest_bitmap.set_pixel(draw_x, draw_y, src_pixel);
//dest_bitmap.set_pixel(draw_x + 1, draw_y, src_pixel);
},
);
}
pub unsafe fn rotozoom_blended_blit(
&mut self,
src: &Bitmap,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
angle: f32,
scale_x: f32,
scale_y: f32,
blend_map: Rc<BlendMap>,
) {
per_pixel_rotozoom_blit(
self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y,
|src_pixel, dest_bitmap, draw_x, draw_y| {
if let Some(dest_pixel) = dest_bitmap.get_pixel(draw_x, draw_y) {
let draw_pixel = if let Some(blended_pixel) = blend_map.blend(src_pixel, dest_pixel) {
blended_pixel
} else {
src_pixel
};
dest_bitmap.set_pixel(draw_x, draw_y, draw_pixel);
}
}, },
); );
} }
pub unsafe fn rotozoom_transparent_blit( pub unsafe fn rotozoom_transparent_blit(
&mut self, &mut self,
src: &Bitmap, src: &Self,
src_region: &Rect, src_region: &Rect,
dest_x: i32, dest_x: i32,
dest_y: i32, dest_y: i32,
angle: f32, angle: f32,
scale_x: f32, scale_x: f32,
scale_y: f32, scale_y: f32,
transparent_color: u8, transparent_color: PixelType,
) { ) {
per_pixel_rotozoom_blit( per_pixel_rotozoom_blit(
self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y, self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y,
@ -732,241 +421,6 @@ impl Bitmap {
}, },
); );
} }
pub unsafe fn rotozoom_transparent_blended_blit(
&mut self,
src: &Bitmap,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u8,
blend_map: Rc<BlendMap>,
) {
per_pixel_rotozoom_blit(
self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y,
|src_pixel, dest_bitmap, draw_x, draw_y| {
if transparent_color != src_pixel {
if let Some(dest_pixel) = dest_bitmap.get_pixel(draw_x, draw_y) {
let draw_pixel = if let Some(blended_pixel) = blend_map.blend(src_pixel, dest_pixel) {
blended_pixel
} else {
src_pixel
};
dest_bitmap.set_pixel(draw_x, draw_y, draw_pixel);
}
}
},
);
}
pub unsafe fn rotozoom_palette_offset_blit(
&mut self,
src: &Bitmap,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
angle: f32,
scale_x: f32,
scale_y: f32,
offset: u8,
) {
per_pixel_rotozoom_blit(
self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y,
|src_pixel, dest_bitmap, draw_x, draw_y| {
let src_pixel = src_pixel.wrapping_add(offset);
dest_bitmap.set_pixel(draw_x, draw_y, src_pixel);
},
);
}
pub unsafe fn rotozoom_transparent_palette_offset_blit(
&mut self,
src: &Bitmap,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u8,
offset: u8,
) {
per_pixel_rotozoom_blit(
self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y,
|src_pixel, dest_bitmap, draw_x, draw_y| {
if transparent_color != src_pixel {
let src_pixel = src_pixel.wrapping_add(offset);
dest_bitmap.set_pixel(draw_x, draw_y, src_pixel);
}
},
);
}
pub fn blit_region(
&mut self,
method: BlitMethod,
src: &Bitmap,
src_region: &Rect,
mut dest_x: i32,
mut dest_y: i32,
) {
// make sure the source region is clipped or even valid at all for the source bitmap given
let mut src_region = *src_region;
if !src_region.clamp_to(&src.clip_region) {
return;
}
// some blit methods need to handle clipping a bit differently than others
use BlitMethod::*;
match method {
// rotozoom blits internally clip per-pixel right now ... and regardless, the normal
// clip_blit() function wouldn't handle a rotozoom blit destination region anyway ...
RotoZoom { .. } => {}
RotoZoomBlended { .. } => {}
RotoZoomOffset { .. } => {}
RotoZoomTransparent { .. } => {}
RotoZoomTransparentBlended { .. } => {}
RotoZoomTransparentOffset { .. } => {}
// set axis flip arguments
SolidFlipped { horizontal_flip, vertical_flip, .. } |
SolidFlippedBlended { horizontal_flip, vertical_flip, .. } |
SolidFlippedOffset { horizontal_flip, vertical_flip, .. } |
TransparentFlipped { horizontal_flip, vertical_flip, .. } |
TransparentFlippedBlended { horizontal_flip, vertical_flip, .. } |
TransparentFlippedSingle { horizontal_flip, vertical_flip, .. } |
TransparentFlippedOffset { horizontal_flip, vertical_flip, .. } => {
if !clip_blit(
self.clip_region(),
&mut src_region,
&mut dest_x,
&mut dest_y,
horizontal_flip,
vertical_flip,
) {
return;
}
}
// otherwise clip like normal!
_ => {
if !clip_blit(
self.clip_region(),
&mut src_region,
&mut dest_x,
&mut dest_y,
false,
false,
) {
return;
}
}
}
unsafe {
self.blit_region_unchecked(method, src, &src_region, dest_x, dest_y);
};
}
#[inline]
#[rustfmt::skip]
pub unsafe fn blit_region_unchecked(
&mut self,
method: BlitMethod,
src: &Bitmap,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
) {
use BlitMethod::*;
match method {
Solid => self.solid_blit(src, src_region, dest_x, dest_y),
SolidFlipped { horizontal_flip, vertical_flip } => {
self.solid_flipped_blit(src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip)
}
SolidOffset(offset) => self.solid_palette_offset_blit(src, src_region, dest_x, dest_y, offset),
SolidFlippedOffset { horizontal_flip, vertical_flip, offset } => {
self.solid_flipped_palette_offset_blit(src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip, offset)
}
Transparent(transparent_color) => {
self.transparent_blit(src, src_region, dest_x, dest_y, transparent_color)
}
TransparentFlipped { transparent_color, horizontal_flip, vertical_flip } => {
self.transparent_flipped_blit(src, src_region, dest_x, dest_y, transparent_color, horizontal_flip, vertical_flip)
}
TransparentOffset { transparent_color, offset } => {
self.transparent_palette_offset_blit(src, src_region, dest_x, dest_y, transparent_color, offset)
}
TransparentFlippedOffset { transparent_color, horizontal_flip, vertical_flip, offset } => {
self.transparent_flipped_palette_offset_blit(src, src_region, dest_x, dest_y, transparent_color, horizontal_flip, vertical_flip, offset)
}
TransparentSingle { transparent_color, draw_color } => {
self.transparent_single_color_blit(src, src_region, dest_x, dest_y, transparent_color, draw_color)
}
TransparentFlippedSingle { transparent_color, horizontal_flip, vertical_flip, draw_color } => {
self.transparent_flipped_single_color_blit(src, src_region, dest_x, dest_y, transparent_color, horizontal_flip, vertical_flip, draw_color)
}
RotoZoom { angle, scale_x, scale_y } => {
self.rotozoom_blit(src, src_region, dest_x, dest_y, angle, scale_x, scale_y)
}
RotoZoomOffset { angle, scale_x, scale_y, offset } => {
self.rotozoom_palette_offset_blit(src, src_region, dest_x, dest_y, angle, scale_x, scale_y, offset)
}
RotoZoomTransparent { angle, scale_x, scale_y, transparent_color } => {
self.rotozoom_transparent_blit(src, src_region, dest_x, dest_y, angle, scale_x, scale_y, transparent_color)
}
RotoZoomTransparentOffset { angle, scale_x, scale_y, transparent_color, offset } => {
self.rotozoom_transparent_palette_offset_blit(src, src_region, dest_x, dest_y, angle, scale_x, scale_y, transparent_color, offset)
}
SolidBlended { blend_map } => {
self.solid_blended_blit(src, src_region, dest_x, dest_y, blend_map)
}
SolidFlippedBlended { horizontal_flip, vertical_flip, blend_map } => {
self.solid_flipped_blended_blit(src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip, blend_map)
}
TransparentBlended { transparent_color, blend_map } => {
self.transparent_blended_blit(src, src_region, dest_x, dest_y, transparent_color, blend_map)
}
TransparentFlippedBlended { transparent_color, horizontal_flip, vertical_flip, blend_map } => {
self.transparent_flipped_blended_blit(src, src_region, dest_x, dest_y, transparent_color, horizontal_flip, vertical_flip, blend_map)
}
RotoZoomBlended { angle, scale_x, scale_y, blend_map } => {
self.rotozoom_blended_blit(src, src_region, dest_x, dest_y, angle, scale_x, scale_y, blend_map)
}
RotoZoomTransparentBlended { angle, scale_x, scale_y, transparent_color, blend_map } => {
self.rotozoom_transparent_blended_blit(src, src_region, dest_x, dest_y, angle, scale_x, scale_y, transparent_color, blend_map)
}
}
}
#[inline]
pub fn blit(&mut self, method: BlitMethod, src: &Bitmap, x: i32, y: i32) {
let src_region = Rect::new(0, 0, src.width, src.height);
self.blit_region(method, src, &src_region, x, y);
}
#[inline]
pub fn blit_atlas(&mut self, method: BlitMethod, src: &BitmapAtlas<Self>, index: usize, x: i32, y: i32) {
if let Some(src_region) = src.get(index) {
self.blit_region(method, src.bitmap(), src_region, x, y);
}
}
#[inline]
pub unsafe fn blit_unchecked(&mut self, method: BlitMethod, src: &Bitmap, x: i32, y: i32) {
let src_region = Rect::new(0, 0, src.width, src.height);
self.blit_region_unchecked(method, src, &src_region, x, y);
}
#[inline]
pub unsafe fn blit_atlas_unchecked(&mut self, method: BlitMethod, src: &BitmapAtlas<Self>, index: usize, x: i32, y: i32) {
if let Some(src_region) = src.get(index) {
self.blit_region_unchecked(method, src.bitmap(), &src_region, x, y);
}
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -6,9 +6,12 @@
//! //!
//! Only a subset of the most common Bitmap drawing operations will be provided here. //! Only a subset of the most common Bitmap drawing operations will be provided here.
use std::error::Error; use crate::graphics::bitmap::BitmapError;
use crate::graphics::bitmap::indexed::blit::IndexedBlitMethod;
use crate::graphics::{indexed, Pixel}; use crate::graphics::bitmap::indexed::IndexedBitmap;
use crate::graphics::bitmap::rgb::blit::RgbaBlitMethod;
use crate::graphics::bitmap::rgb::RgbaBitmap;
use crate::graphics::Pixel;
use crate::math::rect::Rect; use crate::math::rect::Rect;
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
@ -22,10 +25,9 @@ pub enum GeneralBlitMethod<PixelType: Pixel> {
/// any one pixel-depth. Note that this does not provide cross-bit-depth drawing support. /// any one pixel-depth. Note that this does not provide cross-bit-depth drawing support.
pub trait GeneralBitmap: Sized + Clone { pub trait GeneralBitmap: Sized + Clone {
type PixelType: Pixel; type PixelType: Pixel;
type ErrorType: Error;
/// Creates a new bitmap with the specified dimensions, in pixels. /// Creates a new bitmap with the specified dimensions, in pixels.
fn new(width: u32, height: u32) -> Result<Self, Self::ErrorType>; fn new(width: u32, height: u32) -> Result<Self, BitmapError>;
/// Returns the width of the bitmap in pixels. /// Returns the width of the bitmap in pixels.
fn width(&self) -> u32; fn width(&self) -> u32;
@ -94,20 +96,17 @@ pub trait GeneralBitmap: Sized + Clone {
} }
} }
impl GeneralBitmap for indexed::bitmap::Bitmap { impl GeneralBitmap for IndexedBitmap {
type PixelType = u8; type PixelType = u8;
type ErrorType = indexed::bitmap::BitmapError;
fn new(width: u32, height: u32) -> Result<Self, Self::ErrorType> { fn new(width: u32, height: u32) -> Result<Self, BitmapError> {
Self::new(width, height) Self::new(width, height)
} }
#[inline]
fn width(&self) -> u32 { fn width(&self) -> u32 {
self.width() self.width()
} }
#[inline]
fn height(&self) -> u32 { fn height(&self) -> u32 {
self.height() self.height()
} }
@ -116,59 +115,48 @@ impl GeneralBitmap for indexed::bitmap::Bitmap {
self.clip_region() self.clip_region()
} }
#[inline]
fn full_bounds(&self) -> Rect { fn full_bounds(&self) -> Rect {
self.full_bounds() self.full_bounds()
} }
#[inline] fn clear(&mut self, color: Self::PixelType) {
fn clear(&mut self, color: u8) { self.clear(color)
self.clear(color);
} }
#[inline] fn set_pixel(&mut self, x: i32, y: i32, color: Self::PixelType) {
fn set_pixel(&mut self, x: i32, y: i32, color: u8) { self.set_pixel(x, y, color)
self.set_pixel(x, y, color);
} }
#[inline] fn get_pixel(&self, x: i32, y: i32) -> Option<Self::PixelType> {
fn get_pixel(&self, x: i32, y: i32) -> Option<u8> {
self.get_pixel(x, y) self.get_pixel(x, y)
} }
#[inline] fn line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: Self::PixelType) {
fn line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: u8) { self.line(x1, y1, x2, y2, color)
self.line(x1, y1, x2, y2, color);
} }
#[inline] fn horiz_line(&mut self, x1: i32, x2: i32, y: i32, color: Self::PixelType) {
fn horiz_line(&mut self, x1: i32, x2: i32, y: i32, color: u8) { self.horiz_line(x1, x2, y, color)
self.horiz_line(x1, x2, y, color);
} }
#[inline] fn vert_line(&mut self, x: i32, y1: i32, y2: i32, color: Self::PixelType) {
fn vert_line(&mut self, x: i32, y1: i32, y2: i32, color: u8) { self.vert_line(x, y1, y2, color)
self.vert_line(x, y1, y2, color);
} }
#[inline] fn rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: Self::PixelType) {
fn rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: u8) { self.rect(x1, y1, x2, y2, color)
self.rect(x1, y1, x2, y2, color);
} }
#[inline] fn filled_rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: Self::PixelType) {
fn filled_rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: u8) { self.filled_rect(x1, y1, x2, y2, color)
self.filled_rect(x1, y1, x2, y2, color);
} }
#[inline] fn circle(&mut self, center_x: i32, center_y: i32, radius: u32, color: Self::PixelType) {
fn circle(&mut self, center_x: i32, center_y: i32, radius: u32, color: u8) { self.circle(center_x, center_y, radius, color)
self.circle(center_x, center_y, radius, color);
} }
#[inline] fn filled_circle(&mut self, center_x: i32, center_y: i32, radius: u32, color: Self::PixelType) {
fn filled_circle(&mut self, center_x: i32, center_y: i32, radius: u32, color: u8) { self.filled_circle(center_x, center_y, radius, color)
self.filled_circle(center_x, center_y, radius, color);
} }
fn blit_region( fn blit_region(
@ -179,11 +167,88 @@ impl GeneralBitmap for indexed::bitmap::Bitmap {
dest_x: i32, dest_x: i32,
dest_y: i32 dest_y: i32
) { ) {
use indexed::bitmap::blit::BlitMethod;
let blit_method = match method { let blit_method = match method {
GeneralBlitMethod::Solid => BlitMethod::Solid, GeneralBlitMethod::Solid => IndexedBlitMethod::Solid,
GeneralBlitMethod::Transparent(color) => BlitMethod::Transparent(color), GeneralBlitMethod::Transparent(color) => IndexedBlitMethod::Transparent(color),
};
self.blit_region(blit_method, src, src_region, dest_x, dest_y)
}
}
impl GeneralBitmap for RgbaBitmap {
type PixelType = u32;
fn new(width: u32, height: u32) -> Result<Self, BitmapError> {
Self::new(width, height)
}
fn width(&self) -> u32 {
self.width()
}
fn height(&self) -> u32 {
self.height()
}
fn clip_region(&self) -> &Rect {
self.clip_region()
}
fn full_bounds(&self) -> Rect {
self.full_bounds()
}
fn clear(&mut self, color: Self::PixelType) {
self.clear(color)
}
fn set_pixel(&mut self, x: i32, y: i32, color: Self::PixelType) {
self.set_pixel(x, y, color)
}
fn get_pixel(&self, x: i32, y: i32) -> Option<Self::PixelType> {
self.get_pixel(x, y)
}
fn line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: Self::PixelType) {
self.line(x1, y1, x2, y2, color)
}
fn horiz_line(&mut self, x1: i32, x2: i32, y: i32, color: Self::PixelType) {
self.horiz_line(x1, x2, y, color)
}
fn vert_line(&mut self, x: i32, y1: i32, y2: i32, color: Self::PixelType) {
self.vert_line(x, y1, y2, color)
}
fn rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: Self::PixelType) {
self.rect(x1, y1, x2, y2, color)
}
fn filled_rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: Self::PixelType) {
self.filled_rect(x1, y1, x2, y2, color)
}
fn circle(&mut self, center_x: i32, center_y: i32, radius: u32, color: Self::PixelType) {
self.circle(center_x, center_y, radius, color)
}
fn filled_circle(&mut self, center_x: i32, center_y: i32, radius: u32, color: Self::PixelType) {
self.filled_circle(center_x, center_y, radius, color)
}
fn blit_region(
&mut self,
method: GeneralBlitMethod<Self::PixelType>,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32
) {
let blit_method = match method {
GeneralBlitMethod::Solid => RgbaBlitMethod::Solid,
GeneralBlitMethod::Transparent(color) => RgbaBlitMethod::Transparent(color),
}; };
self.blit_region(blit_method, src, src_region, dest_x, dest_y) self.blit_region(blit_method, src, src_region, dest_x, dest_y)
} }

View file

@ -5,8 +5,8 @@ use std::path::Path;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use thiserror::Error; use thiserror::Error;
use crate::graphics::indexed::bitmap::Bitmap; use crate::graphics::bitmap::indexed::IndexedBitmap;
use crate::graphics::indexed::palette::{Palette, PaletteError, PaletteFormat}; use crate::graphics::palette::{Palette, PaletteError, PaletteFormat};
use crate::utils::lzwgif::{lzw_decode, lzw_encode, LzwError}; use crate::utils::lzwgif::{lzw_decode, lzw_encode, LzwError};
const BITS_FOR_256_COLORS: u32 = 7; // formula is `2 ^ (bits + 1) = num_colors` const BITS_FOR_256_COLORS: u32 = 7; // formula is `2 ^ (bits + 1) = num_colors`
@ -352,7 +352,7 @@ fn load_image_section<T: ReadBytesExt>(
reader: &mut T, reader: &mut T,
gif_header: &GifHeader, gif_header: &GifHeader,
_graphic_control: &Option<GraphicControlExtension>, _graphic_control: &Option<GraphicControlExtension>,
) -> Result<(Bitmap, Option<Palette>), GifError> { ) -> Result<(IndexedBitmap, Option<Palette>), GifError> {
let descriptor = LocalImageDescriptor::read(reader)?; let descriptor = LocalImageDescriptor::read(reader)?;
let palette: Option<Palette>; let palette: Option<Palette>;
@ -367,7 +367,7 @@ fn load_image_section<T: ReadBytesExt>(
palette = None; // we expect that there was a global color table previously palette = None; // we expect that there was a global color table previously
} }
let mut bitmap = Bitmap::new(gif_header.screen_width as u32, gif_header.screen_height as u32).unwrap(); let mut bitmap = IndexedBitmap::new(gif_header.screen_width as u32, gif_header.screen_height as u32).unwrap();
let mut writer = bitmap.pixels_mut(); let mut writer = bitmap.pixels_mut();
lzw_decode(reader, &mut writer)?; lzw_decode(reader, &mut writer)?;
@ -376,7 +376,7 @@ fn load_image_section<T: ReadBytesExt>(
fn save_image_section<T: WriteBytesExt>( fn save_image_section<T: WriteBytesExt>(
writer: &mut T, writer: &mut T,
bitmap: &Bitmap, bitmap: &IndexedBitmap,
) -> Result<(), GifError> { ) -> Result<(), GifError> {
writer.write_u8(IMAGE_DESCRIPTOR_SEPARATOR)?; writer.write_u8(IMAGE_DESCRIPTOR_SEPARATOR)?;
let image_descriptor = LocalImageDescriptor { let image_descriptor = LocalImageDescriptor {
@ -398,10 +398,10 @@ fn save_image_section<T: WriteBytesExt>(
Ok(()) Ok(())
} }
impl Bitmap { impl IndexedBitmap {
pub fn load_gif_bytes<T: ReadBytesExt>( pub fn load_gif_bytes<T: ReadBytesExt>(
reader: &mut T, reader: &mut T,
) -> Result<(Bitmap, Palette), GifError> { ) -> Result<(IndexedBitmap, Palette), GifError> {
let header = GifHeader::read(reader)?; let header = GifHeader::read(reader)?;
if header.signature != *b"GIF" || header.version != *b"89a" { if header.signature != *b"GIF" || header.version != *b"89a" {
return Err(GifError::BadFile(String::from("Expected GIF89a header signature"))); return Err(GifError::BadFile(String::from("Expected GIF89a header signature")));
@ -420,7 +420,7 @@ impl Bitmap {
palette = None; // we expect to find a local color table later palette = None; // we expect to find a local color table later
} }
let mut bitmap: Option<Bitmap> = None; let mut bitmap: Option<IndexedBitmap> = None;
let mut current_graphic_control: Option<GraphicControlExtension> = None; let mut current_graphic_control: Option<GraphicControlExtension> = None;
loop { loop {
@ -482,7 +482,7 @@ impl Bitmap {
Ok((bitmap.unwrap(), palette.unwrap())) Ok((bitmap.unwrap(), palette.unwrap()))
} }
pub fn load_gif_file(path: &Path) -> Result<(Bitmap, Palette), GifError> { pub fn load_gif_file(path: &Path) -> Result<(IndexedBitmap, Palette), GifError> {
let f = File::open(path)?; let f = File::open(path)?;
let mut reader = BufReader::new(f); let mut reader = BufReader::new(f);
Self::load_gif_bytes(&mut reader) Self::load_gif_bytes(&mut reader)
@ -557,9 +557,9 @@ pub mod tests {
use super::*; use super::*;
pub static TEST_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../../test-assets/test_bmp_pixels_raw.bin"); pub static TEST_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../test-assets/test_bmp_pixels_raw.bin");
pub static TEST_LARGE_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../../test-assets/test_large_bmp_pixels_raw.bin"); pub static TEST_LARGE_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../test-assets/test_large_bmp_pixels_raw.bin");
pub static TEST_LARGE_BMP_PIXELS_RAW_2: &[u8] = include_bytes!("../../../../test-assets/test_large_bmp_pixels_raw2.bin"); pub static TEST_LARGE_BMP_PIXELS_RAW_2: &[u8] = include_bytes!("../../../test-assets/test_large_bmp_pixels_raw2.bin");
#[test] #[test]
fn load_and_save() -> Result<(), GifError> { fn load_and_save() -> Result<(), GifError> {
@ -568,7 +568,7 @@ pub mod tests {
.unwrap(); .unwrap();
let tmp_dir = TempDir::new()?; let tmp_dir = TempDir::new()?;
let (bmp, palette) = Bitmap::load_gif_file(Path::new("./test-assets/test.gif"))?; let (bmp, palette) = IndexedBitmap::load_gif_file(Path::new("./test-assets/test.gif"))?;
assert_eq!(16, bmp.width()); assert_eq!(16, bmp.width());
assert_eq!(16, bmp.height()); assert_eq!(16, bmp.height());
assert_eq!(bmp.pixels(), TEST_BMP_PIXELS_RAW); assert_eq!(bmp.pixels(), TEST_BMP_PIXELS_RAW);
@ -576,7 +576,7 @@ pub mod tests {
let save_path = tmp_dir.path().join("test_save.gif"); let save_path = tmp_dir.path().join("test_save.gif");
bmp.to_gif_file(&save_path, &palette, GifSettings::Default)?; bmp.to_gif_file(&save_path, &palette, GifSettings::Default)?;
let (reloaded_bmp, reloaded_palette) = Bitmap::load_gif_file(&save_path)?; let (reloaded_bmp, reloaded_palette) = IndexedBitmap::load_gif_file(&save_path)?;
assert_eq!(16, reloaded_bmp.width()); assert_eq!(16, reloaded_bmp.width());
assert_eq!(16, reloaded_bmp.height()); assert_eq!(16, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_BMP_PIXELS_RAW); assert_eq!(reloaded_bmp.pixels(), TEST_BMP_PIXELS_RAW);
@ -594,28 +594,28 @@ pub mod tests {
// first image // first image
let (bmp, palette) = Bitmap::load_gif_file(Path::new("./test-assets/test_image.gif"))?; let (bmp, palette) = IndexedBitmap::load_gif_file(Path::new("./test-assets/test_image.gif"))?;
assert_eq!(320, bmp.width()); assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height()); assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW); assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
let save_path = tmp_dir.path().join("test_save.gif"); let save_path = tmp_dir.path().join("test_save.gif");
bmp.to_gif_file(&save_path, &palette, GifSettings::Default)?; bmp.to_gif_file(&save_path, &palette, GifSettings::Default)?;
let (reloaded_bmp, _) = Bitmap::load_gif_file(&save_path)?; let (reloaded_bmp, _) = IndexedBitmap::load_gif_file(&save_path)?;
assert_eq!(320, reloaded_bmp.width()); assert_eq!(320, reloaded_bmp.width());
assert_eq!(200, reloaded_bmp.height()); assert_eq!(200, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW); assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
// second image // second image
let (bmp, palette) = Bitmap::load_gif_file(Path::new("./test-assets/test_image2.gif"))?; let (bmp, palette) = IndexedBitmap::load_gif_file(Path::new("./test-assets/test_image2.gif"))?;
assert_eq!(320, bmp.width()); assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height()); assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2); assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2);
let save_path = tmp_dir.path().join("test_save_2.gif"); let save_path = tmp_dir.path().join("test_save_2.gif");
bmp.to_gif_file(&save_path, &palette, GifSettings::Default)?; bmp.to_gif_file(&save_path, &palette, GifSettings::Default)?;
let (reloaded_bmp, _) = Bitmap::load_gif_file(&save_path)?; let (reloaded_bmp, _) = IndexedBitmap::load_gif_file(&save_path)?;
assert_eq!(320, reloaded_bmp.width()); assert_eq!(320, reloaded_bmp.width());
assert_eq!(200, reloaded_bmp.height()); assert_eq!(200, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2); assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2);

View file

@ -6,8 +6,8 @@ use std::path::Path;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use thiserror::Error; use thiserror::Error;
use crate::graphics::indexed::bitmap::Bitmap; use crate::graphics::bitmap::indexed::IndexedBitmap;
use crate::graphics::indexed::palette::{Palette, PaletteError, PaletteFormat}; use crate::graphics::palette::{Palette, PaletteError, PaletteFormat};
use crate::utils::packbits::{pack_bits, PackBitsError, unpack_bits}; use crate::utils::packbits::{pack_bits, PackBitsError, unpack_bits};
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -250,8 +250,8 @@ fn extract_bitplane(plane: u32, src: &[u8], dest: &mut [u8], row_size: usize) {
} }
} }
fn load_planar_body<T: ReadBytesExt>(reader: &mut T, bmhd: &BMHDChunk) -> Result<Bitmap, IffError> { fn load_planar_body<T: ReadBytesExt>(reader: &mut T, bmhd: &BMHDChunk) -> Result<IndexedBitmap, IffError> {
let mut bitmap = Bitmap::new(bmhd.width as u32, bmhd.height as u32).unwrap(); let mut bitmap = IndexedBitmap::new(bmhd.width as u32, bmhd.height as u32).unwrap();
let row_bytes = (((bmhd.width + 15) >> 4) << 1) as usize; let row_bytes = (((bmhd.width + 15) >> 4) << 1) as usize;
let mut buffer = vec![0u8; row_bytes]; let mut buffer = vec![0u8; row_bytes];
@ -297,8 +297,8 @@ fn load_planar_body<T: ReadBytesExt>(reader: &mut T, bmhd: &BMHDChunk) -> Result
Ok(bitmap) Ok(bitmap)
} }
fn load_chunky_body<T: ReadBytesExt>(reader: &mut T, bmhd: &BMHDChunk) -> Result<Bitmap, IffError> { fn load_chunky_body<T: ReadBytesExt>(reader: &mut T, bmhd: &BMHDChunk) -> Result<IndexedBitmap, IffError> {
let mut bitmap = Bitmap::new(bmhd.width as u32, bmhd.height as u32).unwrap(); let mut bitmap = IndexedBitmap::new(bmhd.width as u32, bmhd.height as u32).unwrap();
for y in 0..bmhd.height { for y in 0..bmhd.height {
if bmhd.compress == 1 { if bmhd.compress == 1 {
@ -317,7 +317,7 @@ fn load_chunky_body<T: ReadBytesExt>(reader: &mut T, bmhd: &BMHDChunk) -> Result
fn write_planar_body<T: WriteBytesExt>( fn write_planar_body<T: WriteBytesExt>(
writer: &mut T, writer: &mut T,
bitmap: &Bitmap, bitmap: &IndexedBitmap,
bmhd: &BMHDChunk, bmhd: &BMHDChunk,
) -> Result<(), IffError> { ) -> Result<(), IffError> {
let row_bytes = (((bitmap.width() + 15) >> 4) << 1) as usize; let row_bytes = (((bitmap.width() + 15) >> 4) << 1) as usize;
@ -349,7 +349,7 @@ fn write_planar_body<T: WriteBytesExt>(
fn write_chunky_body<T: WriteBytesExt>( fn write_chunky_body<T: WriteBytesExt>(
writer: &mut T, writer: &mut T,
bitmap: &Bitmap, bitmap: &IndexedBitmap,
bmhd: &BMHDChunk, bmhd: &BMHDChunk,
) -> Result<(), IffError> { ) -> Result<(), IffError> {
for y in 0..bitmap.height() { for y in 0..bitmap.height() {
@ -367,10 +367,10 @@ fn write_chunky_body<T: WriteBytesExt>(
Ok(()) Ok(())
} }
impl Bitmap { impl IndexedBitmap {
pub fn load_iff_bytes<T: ReadBytesExt + Seek>( pub fn load_iff_bytes<T: ReadBytesExt + Seek>(
reader: &mut T, reader: &mut T,
) -> Result<(Bitmap, Palette), IffError> { ) -> Result<(IndexedBitmap, Palette), IffError> {
let form_chunk = FormChunkHeader::read(reader)?; let form_chunk = FormChunkHeader::read(reader)?;
if form_chunk.chunk_id.id != *b"FORM" { if form_chunk.chunk_id.id != *b"FORM" {
return Err(IffError::BadFile(String::from( return Err(IffError::BadFile(String::from(
@ -385,7 +385,7 @@ impl Bitmap {
let mut bmhd: Option<BMHDChunk> = None; let mut bmhd: Option<BMHDChunk> = None;
let mut palette: Option<Palette> = None; let mut palette: Option<Palette> = None;
let mut bitmap: Option<Bitmap> = None; let mut bitmap: Option<IndexedBitmap> = None;
loop { loop {
let header = match SubChunkHeader::read(reader) { let header = match SubChunkHeader::read(reader) {
@ -447,7 +447,7 @@ impl Bitmap {
Ok((bitmap.unwrap(), palette.unwrap())) Ok((bitmap.unwrap(), palette.unwrap()))
} }
pub fn load_iff_file(path: &Path) -> Result<(Bitmap, Palette), IffError> { pub fn load_iff_file(path: &Path) -> Result<(IndexedBitmap, Palette), IffError> {
let f = File::open(path)?; let f = File::open(path)?;
let mut reader = BufReader::new(f); let mut reader = BufReader::new(f);
Self::load_iff_bytes(&mut reader) Self::load_iff_bytes(&mut reader)
@ -565,9 +565,9 @@ mod tests {
use super::*; use super::*;
pub static TEST_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../../test-assets/test_bmp_pixels_raw.bin"); pub static TEST_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../test-assets/test_bmp_pixels_raw.bin");
pub static TEST_LARGE_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../../test-assets/test_large_bmp_pixels_raw.bin"); pub static TEST_LARGE_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../test-assets/test_large_bmp_pixels_raw.bin");
pub static TEST_LARGE_BMP_PIXELS_RAW_2: &[u8] = include_bytes!("../../../../test-assets/test_large_bmp_pixels_raw2.bin"); pub static TEST_LARGE_BMP_PIXELS_RAW_2: &[u8] = include_bytes!("../../../test-assets/test_large_bmp_pixels_raw2.bin");
#[test] #[test]
pub fn load_and_save() -> Result<(), IffError> { pub fn load_and_save() -> Result<(), IffError> {
@ -578,7 +578,7 @@ mod tests {
// ILBM format // ILBM format
let (bmp, palette) = Bitmap::load_iff_file(Path::new("./test-assets/test_ilbm.lbm"))?; let (bmp, palette) = IndexedBitmap::load_iff_file(Path::new("./test-assets/test_ilbm.lbm"))?;
assert_eq!(16, bmp.width()); assert_eq!(16, bmp.width());
assert_eq!(16, bmp.height()); assert_eq!(16, bmp.height());
assert_eq!(bmp.pixels(), TEST_BMP_PIXELS_RAW); assert_eq!(bmp.pixels(), TEST_BMP_PIXELS_RAW);
@ -586,7 +586,7 @@ mod tests {
let save_path = tmp_dir.path().join("test_save_ilbm.lbm"); let save_path = tmp_dir.path().join("test_save_ilbm.lbm");
bmp.to_iff_file(&save_path, &palette, IffFormat::Ilbm)?; bmp.to_iff_file(&save_path, &palette, IffFormat::Ilbm)?;
let (reloaded_bmp, reloaded_palette) = Bitmap::load_iff_file(&save_path)?; let (reloaded_bmp, reloaded_palette) = IndexedBitmap::load_iff_file(&save_path)?;
assert_eq!(16, reloaded_bmp.width()); assert_eq!(16, reloaded_bmp.width());
assert_eq!(16, reloaded_bmp.height()); assert_eq!(16, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_BMP_PIXELS_RAW); assert_eq!(reloaded_bmp.pixels(), TEST_BMP_PIXELS_RAW);
@ -594,7 +594,7 @@ mod tests {
// PBM format // PBM format
let (bmp, palette) = Bitmap::load_iff_file(Path::new("./test-assets/test_pbm.lbm"))?; let (bmp, palette) = IndexedBitmap::load_iff_file(Path::new("./test-assets/test_pbm.lbm"))?;
assert_eq!(16, bmp.width()); assert_eq!(16, bmp.width());
assert_eq!(16, bmp.height()); assert_eq!(16, bmp.height());
assert_eq!(bmp.pixels(), TEST_BMP_PIXELS_RAW); assert_eq!(bmp.pixels(), TEST_BMP_PIXELS_RAW);
@ -602,7 +602,7 @@ mod tests {
let save_path = tmp_dir.path().join("test_save_pbm.lbm"); let save_path = tmp_dir.path().join("test_save_pbm.lbm");
bmp.to_iff_file(&save_path, &palette, IffFormat::Pbm)?; bmp.to_iff_file(&save_path, &palette, IffFormat::Pbm)?;
let (reloaded_bmp, reloaded_palette) = Bitmap::load_iff_file(&save_path)?; let (reloaded_bmp, reloaded_palette) = IndexedBitmap::load_iff_file(&save_path)?;
assert_eq!(16, reloaded_bmp.width()); assert_eq!(16, reloaded_bmp.width());
assert_eq!(16, reloaded_bmp.height()); assert_eq!(16, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_BMP_PIXELS_RAW); assert_eq!(reloaded_bmp.pixels(), TEST_BMP_PIXELS_RAW);
@ -617,56 +617,56 @@ mod tests {
// first image, PBM format // first image, PBM format
let (bmp, palette) = Bitmap::load_iff_file(Path::new("./test-assets/test_pbm_image.lbm"))?; let (bmp, palette) = IndexedBitmap::load_iff_file(Path::new("./test-assets/test_pbm_image.lbm"))?;
assert_eq!(320, bmp.width()); assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height()); assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW); assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
let save_path = tmp_dir.path().join("test_save_pbm_image.lbm"); let save_path = tmp_dir.path().join("test_save_pbm_image.lbm");
bmp.to_iff_file(&save_path, &palette, IffFormat::Pbm)?; bmp.to_iff_file(&save_path, &palette, IffFormat::Pbm)?;
let (reloaded_bmp, _) = Bitmap::load_iff_file(&save_path)?; let (reloaded_bmp, _) = IndexedBitmap::load_iff_file(&save_path)?;
assert_eq!(320, reloaded_bmp.width()); assert_eq!(320, reloaded_bmp.width());
assert_eq!(200, reloaded_bmp.height()); assert_eq!(200, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW); assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
// second image, PBM format // second image, PBM format
let (bmp, palette) = Bitmap::load_iff_file(Path::new("./test-assets/test_pbm_image2.lbm"))?; let (bmp, palette) = IndexedBitmap::load_iff_file(Path::new("./test-assets/test_pbm_image2.lbm"))?;
assert_eq!(320, bmp.width()); assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height()); assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2); assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2);
let save_path = tmp_dir.path().join("test_save_pbm_image2.lbm"); let save_path = tmp_dir.path().join("test_save_pbm_image2.lbm");
bmp.to_iff_file(&save_path, &palette, IffFormat::Pbm)?; bmp.to_iff_file(&save_path, &palette, IffFormat::Pbm)?;
let (reloaded_bmp, _) = Bitmap::load_iff_file(&save_path)?; let (reloaded_bmp, _) = IndexedBitmap::load_iff_file(&save_path)?;
assert_eq!(320, reloaded_bmp.width()); assert_eq!(320, reloaded_bmp.width());
assert_eq!(200, reloaded_bmp.height()); assert_eq!(200, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2); assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2);
// first image, ILBM format // first image, ILBM format
let (bmp, palette) = Bitmap::load_iff_file(Path::new("./test-assets/test_ilbm_image.lbm"))?; let (bmp, palette) = IndexedBitmap::load_iff_file(Path::new("./test-assets/test_ilbm_image.lbm"))?;
assert_eq!(320, bmp.width()); assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height()); assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW); assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
let save_path = tmp_dir.path().join("test_save_ilbm_image.lbm"); let save_path = tmp_dir.path().join("test_save_ilbm_image.lbm");
bmp.to_iff_file(&save_path, &palette, IffFormat::Ilbm)?; bmp.to_iff_file(&save_path, &palette, IffFormat::Ilbm)?;
let (reloaded_bmp, _) = Bitmap::load_iff_file(&save_path)?; let (reloaded_bmp, _) = IndexedBitmap::load_iff_file(&save_path)?;
assert_eq!(320, reloaded_bmp.width()); assert_eq!(320, reloaded_bmp.width());
assert_eq!(200, reloaded_bmp.height()); assert_eq!(200, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW); assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
// second image, ILBM format // second image, ILBM format
let (bmp, palette) = Bitmap::load_iff_file(Path::new("./test-assets/test_ilbm_image2.lbm"))?; let (bmp, palette) = IndexedBitmap::load_iff_file(Path::new("./test-assets/test_ilbm_image2.lbm"))?;
assert_eq!(320, bmp.width()); assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height()); assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2); assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2);
let save_path = tmp_dir.path().join("test_save_ilbm_image2.lbm"); let save_path = tmp_dir.path().join("test_save_ilbm_image2.lbm");
bmp.to_iff_file(&save_path, &palette, IffFormat::Ilbm)?; bmp.to_iff_file(&save_path, &palette, IffFormat::Ilbm)?;
let (reloaded_bmp, _) = Bitmap::load_iff_file(&save_path)?; let (reloaded_bmp, _) = IndexedBitmap::load_iff_file(&save_path)?;
assert_eq!(320, reloaded_bmp.width()); assert_eq!(320, reloaded_bmp.width());
assert_eq!(200, reloaded_bmp.height()); assert_eq!(200, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2); assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2);

View file

@ -0,0 +1,551 @@
use std::rc::Rc;
use crate::graphics::bitmap::blit::{clip_blit, per_pixel_blit, per_pixel_flipped_blit, per_pixel_rotozoom_blit};
use crate::graphics::bitmap::indexed::IndexedBitmap;
use crate::graphics::bitmapatlas::BitmapAtlas;
use crate::graphics::blendmap::BlendMap;
use crate::math::rect::Rect;
#[derive(Clone, PartialEq)]
pub enum IndexedBlitMethod {
/// Solid blit, no transparency or other per-pixel adjustments.
Solid,
/// Same as [IndexedBlitMethod::Solid] but the drawn image can also be flipped horizontally
/// and/or vertically.
SolidFlipped {
horizontal_flip: bool,
vertical_flip: bool,
},
/// Transparent blit, the specified source color pixels are skipped.
Transparent(u8),
/// Same as [IndexedBlitMethod::Transparent] but the drawn image can also be flipped horizontally
/// and/or vertically.
TransparentFlipped {
transparent_color: u8,
horizontal_flip: bool,
vertical_flip: bool,
},
/// Same as [IndexedBlitMethod::Transparent] except that the visible pixels on the destination are all
/// drawn using the same color.
TransparentSingle {
transparent_color: u8,
draw_color: u8,
},
/// Combination of [IndexedBlitMethod::TransparentFlipped] and [IndexedBlitMethod::TransparentSingle].
TransparentFlippedSingle {
transparent_color: u8,
horizontal_flip: bool,
vertical_flip: bool,
draw_color: u8,
},
/// Same as [IndexedBlitMethod::Solid] except that the drawn pixels have their color indices offset
/// by the amount given.
SolidOffset(u8),
/// Combination of [IndexedBlitMethod::SolidFlipped] and [IndexedBlitMethod::SolidOffset].
SolidFlippedOffset {
horizontal_flip: bool,
vertical_flip: bool,
offset: u8,
},
/// Same as [IndexedBlitMethod::Transparent] except that the drawn pixels have their color indices
/// offset by the amount given. The transparent color check is not affected by the offset and
/// is always treated as an absolute palette color index.
TransparentOffset { transparent_color: u8, offset: u8 },
/// Combination of [IndexedBlitMethod::TransparentFlipped] and [IndexedBlitMethod::TransparentOffset].
TransparentFlippedOffset {
transparent_color: u8,
horizontal_flip: bool,
vertical_flip: bool,
offset: u8,
},
/// Rotozoom blit, works the same as [IndexedBlitMethod::Solid] except that rotation and scaling is
/// performed.
RotoZoom {
angle: f32,
scale_x: f32,
scale_y: f32,
},
/// Same as [IndexedBlitMethod::RotoZoom] except that the specified source color pixels are skipped.
RotoZoomTransparent {
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u8,
},
/// Same as [IndexedBlitMethod::RotoZoom] except that the drawn pixels have their color indices
/// offset by the amount given.
RotoZoomOffset {
angle: f32,
scale_x: f32,
scale_y: f32,
offset: u8,
},
/// Same as [IndexedBlitMethod::RotoZoomTransparent] except that the drawn pixels have their color
/// indices offset by the amount given. The transparent color check is not affected by the
/// offset and is always treated as an absolute palette color index.
RotoZoomTransparentOffset {
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u8,
offset: u8,
},
SolidBlended {
blend_map: Rc<BlendMap>,
},
SolidFlippedBlended {
horizontal_flip: bool,
vertical_flip: bool,
blend_map: Rc<BlendMap>,
},
TransparentBlended {
transparent_color: u8,
blend_map: Rc<BlendMap>,
},
TransparentFlippedBlended {
transparent_color: u8,
horizontal_flip: bool,
vertical_flip: bool,
blend_map: Rc<BlendMap>,
},
RotoZoomBlended {
angle: f32,
scale_x: f32,
scale_y: f32,
blend_map: Rc<BlendMap>,
},
RotoZoomTransparentBlended {
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u8,
blend_map: Rc<BlendMap>,
},
}
impl IndexedBitmap {
pub unsafe fn solid_blended_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
blend_map: Rc<BlendMap>,
) {
per_pixel_blit(
self, src, src_region, dest_x, dest_y,
|src_pixels, dest_pixels| {
if let Some(blended_pixel) = blend_map.blend(*src_pixels, *dest_pixels) {
*dest_pixels = blended_pixel;
} else {
*dest_pixels = *src_pixels;
}
},
);
}
pub unsafe fn solid_flipped_blended_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
horizontal_flip: bool,
vertical_flip: bool,
blend_map: Rc<BlendMap>,
) {
per_pixel_flipped_blit(
self, src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip,
|src_pixels, dest_pixels| {
if let Some(blended_pixel) = blend_map.blend(*src_pixels, *dest_pixels) {
*dest_pixels = blended_pixel;
} else {
*dest_pixels = *src_pixels;
}
},
);
}
pub unsafe fn solid_palette_offset_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
offset: u8,
) {
per_pixel_blit(
self, src, src_region, dest_x, dest_y,
|src_pixels, dest_pixels| {
*dest_pixels = (*src_pixels).wrapping_add(offset);
},
);
}
pub unsafe fn solid_flipped_palette_offset_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
horizontal_flip: bool,
vertical_flip: bool,
offset: u8,
) {
per_pixel_flipped_blit(
self, src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip,
|src_pixels, dest_pixels| {
*dest_pixels = (*src_pixels).wrapping_add(offset);
},
);
}
pub unsafe fn transparent_blended_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u8,
blend_map: Rc<BlendMap>,
) {
per_pixel_blit(
self, src, src_region, dest_x, dest_y,
|src_pixels, dest_pixels| {
if *src_pixels != transparent_color {
if let Some(blended_pixel) = blend_map.blend(*src_pixels, *dest_pixels) {
*dest_pixels = blended_pixel;
} else {
*dest_pixels = *src_pixels;
}
}
},
);
}
pub unsafe fn transparent_flipped_blended_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u8,
horizontal_flip: bool,
vertical_flip: bool,
blend_map: Rc<BlendMap>,
) {
per_pixel_flipped_blit(
self, src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip,
|src_pixels, dest_pixels| {
if *src_pixels != transparent_color {
if let Some(blended_pixel) = blend_map.blend(*src_pixels, *dest_pixels) {
*dest_pixels = blended_pixel;
} else {
*dest_pixels = *src_pixels;
}
}
},
);
}
pub unsafe fn transparent_palette_offset_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u8,
offset: u8,
) {
per_pixel_blit(
self, src, src_region, dest_x, dest_y,
|src_pixels, dest_pixels| {
if *src_pixels != transparent_color {
*dest_pixels = (*src_pixels).wrapping_add(offset);
}
},
);
}
pub unsafe fn transparent_flipped_palette_offset_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u8,
horizontal_flip: bool,
vertical_flip: bool,
offset: u8,
) {
per_pixel_flipped_blit(
self, src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip,
|src_pixels, dest_pixels| {
if *src_pixels != transparent_color {
*dest_pixels = (*src_pixels).wrapping_add(offset);
}
},
);
}
pub unsafe fn rotozoom_blended_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
angle: f32,
scale_x: f32,
scale_y: f32,
blend_map: Rc<BlendMap>,
) {
per_pixel_rotozoom_blit(
self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y,
|src_pixel, dest_bitmap, draw_x, draw_y| {
if let Some(dest_pixel) = dest_bitmap.get_pixel(draw_x, draw_y) {
let draw_pixel = if let Some(blended_pixel) = blend_map.blend(src_pixel, dest_pixel) {
blended_pixel
} else {
src_pixel
};
dest_bitmap.set_pixel(draw_x, draw_y, draw_pixel);
}
},
);
}
pub unsafe fn rotozoom_transparent_blended_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u8,
blend_map: Rc<BlendMap>,
) {
per_pixel_rotozoom_blit(
self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y,
|src_pixel, dest_bitmap, draw_x, draw_y| {
if transparent_color != src_pixel {
if let Some(dest_pixel) = dest_bitmap.get_pixel(draw_x, draw_y) {
let draw_pixel = if let Some(blended_pixel) = blend_map.blend(src_pixel, dest_pixel) {
blended_pixel
} else {
src_pixel
};
dest_bitmap.set_pixel(draw_x, draw_y, draw_pixel);
}
}
},
);
}
pub unsafe fn rotozoom_palette_offset_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
angle: f32,
scale_x: f32,
scale_y: f32,
offset: u8,
) {
per_pixel_rotozoom_blit(
self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y,
|src_pixel, dest_bitmap, draw_x, draw_y| {
let src_pixel = src_pixel.wrapping_add(offset);
dest_bitmap.set_pixel(draw_x, draw_y, src_pixel);
},
);
}
pub unsafe fn rotozoom_transparent_palette_offset_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u8,
offset: u8,
) {
per_pixel_rotozoom_blit(
self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y,
|src_pixel, dest_bitmap, draw_x, draw_y| {
if transparent_color != src_pixel {
let src_pixel = src_pixel.wrapping_add(offset);
dest_bitmap.set_pixel(draw_x, draw_y, src_pixel);
}
},
);
}
pub fn blit_region(
&mut self,
method: IndexedBlitMethod,
src: &Self,
src_region: &Rect,
mut dest_x: i32,
mut dest_y: i32,
) {
// make sure the source region is clipped or even valid at all for the source bitmap given
let mut src_region = *src_region;
if !src_region.clamp_to(&src.clip_region) {
return;
}
// some blit methods need to handle clipping a bit differently than others
use IndexedBlitMethod::*;
match method {
// rotozoom blits internally clip per-pixel right now ... and regardless, the normal
// clip_blit() function wouldn't handle a rotozoom blit destination region anyway ...
RotoZoom { .. } => {}
RotoZoomBlended { .. } => {}
RotoZoomOffset { .. } => {}
RotoZoomTransparent { .. } => {}
RotoZoomTransparentBlended { .. } => {}
RotoZoomTransparentOffset { .. } => {}
// set axis flip arguments
SolidFlipped { horizontal_flip, vertical_flip, .. } |
SolidFlippedBlended { horizontal_flip, vertical_flip, .. } |
SolidFlippedOffset { horizontal_flip, vertical_flip, .. } |
TransparentFlipped { horizontal_flip, vertical_flip, .. } |
TransparentFlippedBlended { horizontal_flip, vertical_flip, .. } |
TransparentFlippedSingle { horizontal_flip, vertical_flip, .. } |
TransparentFlippedOffset { horizontal_flip, vertical_flip, .. } => {
if !clip_blit(
self.clip_region(),
&mut src_region,
&mut dest_x,
&mut dest_y,
horizontal_flip,
vertical_flip,
) {
return;
}
}
// otherwise clip like normal!
_ => {
if !clip_blit(
self.clip_region(),
&mut src_region,
&mut dest_x,
&mut dest_y,
false,
false,
) {
return;
}
}
}
unsafe {
self.blit_region_unchecked(method, src, &src_region, dest_x, dest_y);
};
}
#[inline]
#[rustfmt::skip]
pub unsafe fn blit_region_unchecked(
&mut self,
method: IndexedBlitMethod,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
) {
use IndexedBlitMethod::*;
match method {
Solid => self.solid_blit(src, src_region, dest_x, dest_y),
SolidFlipped { horizontal_flip, vertical_flip } => {
self.solid_flipped_blit(src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip)
}
SolidOffset(offset) => self.solid_palette_offset_blit(src, src_region, dest_x, dest_y, offset),
SolidFlippedOffset { horizontal_flip, vertical_flip, offset } => {
self.solid_flipped_palette_offset_blit(src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip, offset)
}
Transparent(transparent_color) => {
self.transparent_blit(src, src_region, dest_x, dest_y, transparent_color)
}
TransparentFlipped { transparent_color, horizontal_flip, vertical_flip } => {
self.transparent_flipped_blit(src, src_region, dest_x, dest_y, transparent_color, horizontal_flip, vertical_flip)
}
TransparentOffset { transparent_color, offset } => {
self.transparent_palette_offset_blit(src, src_region, dest_x, dest_y, transparent_color, offset)
}
TransparentFlippedOffset { transparent_color, horizontal_flip, vertical_flip, offset } => {
self.transparent_flipped_palette_offset_blit(src, src_region, dest_x, dest_y, transparent_color, horizontal_flip, vertical_flip, offset)
}
TransparentSingle { transparent_color, draw_color } => {
self.transparent_single_color_blit(src, src_region, dest_x, dest_y, transparent_color, draw_color)
}
TransparentFlippedSingle { transparent_color, horizontal_flip, vertical_flip, draw_color } => {
self.transparent_flipped_single_color_blit(src, src_region, dest_x, dest_y, transparent_color, horizontal_flip, vertical_flip, draw_color)
}
RotoZoom { angle, scale_x, scale_y } => {
self.rotozoom_blit(src, src_region, dest_x, dest_y, angle, scale_x, scale_y)
}
RotoZoomOffset { angle, scale_x, scale_y, offset } => {
self.rotozoom_palette_offset_blit(src, src_region, dest_x, dest_y, angle, scale_x, scale_y, offset)
}
RotoZoomTransparent { angle, scale_x, scale_y, transparent_color } => {
self.rotozoom_transparent_blit(src, src_region, dest_x, dest_y, angle, scale_x, scale_y, transparent_color)
}
RotoZoomTransparentOffset { angle, scale_x, scale_y, transparent_color, offset } => {
self.rotozoom_transparent_palette_offset_blit(src, src_region, dest_x, dest_y, angle, scale_x, scale_y, transparent_color, offset)
}
SolidBlended { blend_map } => {
self.solid_blended_blit(src, src_region, dest_x, dest_y, blend_map)
}
SolidFlippedBlended { horizontal_flip, vertical_flip, blend_map } => {
self.solid_flipped_blended_blit(src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip, blend_map)
}
TransparentBlended { transparent_color, blend_map } => {
self.transparent_blended_blit(src, src_region, dest_x, dest_y, transparent_color, blend_map)
}
TransparentFlippedBlended { transparent_color, horizontal_flip, vertical_flip, blend_map } => {
self.transparent_flipped_blended_blit(src, src_region, dest_x, dest_y, transparent_color, horizontal_flip, vertical_flip, blend_map)
}
RotoZoomBlended { angle, scale_x, scale_y, blend_map } => {
self.rotozoom_blended_blit(src, src_region, dest_x, dest_y, angle, scale_x, scale_y, blend_map)
}
RotoZoomTransparentBlended { angle, scale_x, scale_y, transparent_color, blend_map } => {
self.rotozoom_transparent_blended_blit(src, src_region, dest_x, dest_y, angle, scale_x, scale_y, transparent_color, blend_map)
}
}
}
#[inline]
pub fn blit(&mut self, method: IndexedBlitMethod, src: &Self, x: i32, y: i32) {
let src_region = Rect::new(0, 0, src.width, src.height);
self.blit_region(method, src, &src_region, x, y);
}
#[inline]
pub fn blit_atlas(&mut self, method: IndexedBlitMethod, src: &BitmapAtlas<Self>, index: usize, x: i32, y: i32) {
if let Some(src_region) = src.get(index) {
self.blit_region(method, src.bitmap(), src_region, x, y);
}
}
#[inline]
pub unsafe fn blit_unchecked(&mut self, method: IndexedBlitMethod, src: &Self, x: i32, y: i32) {
let src_region = Rect::new(0, 0, src.width, src.height);
self.blit_region_unchecked(method, src, &src_region, x, y);
}
#[inline]
pub unsafe fn blit_atlas_unchecked(&mut self, method: IndexedBlitMethod, src: &BitmapAtlas<Self>, index: usize, x: i32, y: i32) {
if let Some(src_region) = src.get(index) {
self.blit_region_unchecked(method, src.bitmap(), &src_region, x, y);
}
}
}

View file

@ -0,0 +1,46 @@
use std::path::Path;
use crate::graphics::bitmap::{Bitmap, BitmapError};
use crate::graphics::palette::Palette;
pub mod blit;
pub mod primitives;
pub type IndexedBitmap = Bitmap<u8>;
impl IndexedBitmap {
pub fn load_file(path: &Path) -> Result<(Self, Palette), BitmapError> {
if let Some(extension) = path.extension() {
let extension = extension.to_ascii_lowercase();
match extension.to_str() {
Some("pcx") => Ok(Self::load_pcx_file(path)?),
Some("gif") => Ok(Self::load_gif_file(path)?),
Some("iff") | Some("lbm") | Some("pbm") | Some("bbm") => {
Ok(Self::load_iff_file(path)?)
}
_ => Err(BitmapError::UnknownFileType(String::from(
"Unrecognized file extension",
))),
}
} else {
Err(BitmapError::UnknownFileType(String::from(
"No file extension",
)))
}
}
/// Copies and converts the entire pixel data from this bitmap to a destination expecting
/// 32-bit ARGB-format pixel data. This can be used to display the contents of the bitmap
/// on-screen by using an SDL Surface, OpenGL texture, etc as the destination.
///
/// # Arguments
///
/// * `dest`: destination 32-bit ARGB pixel buffer to copy converted pixels to
/// * `palette`: the 256 colour palette to use during pixel conversion
pub fn copy_as_argb_to(&self, dest: &mut [u32], palette: &Palette) {
for (src, dest) in self.pixels().iter().zip(dest.iter_mut()) {
*dest = palette[*src];
}
}
}

View file

@ -0,0 +1,243 @@
use std::mem::swap;
use crate::graphics::bitmap::indexed::IndexedBitmap;
use crate::graphics::blendmap::BlendMap;
use crate::math::rect::Rect;
impl IndexedBitmap {
/// Sets the pixel at the given coordinates using a blended color via the specified blend map,
/// or using the color specified if the blend map does not include the given color. If the
/// coordinates lie outside of the bitmaps clipping region, no pixels will be changed.
#[inline]
pub fn set_blended_pixel(&mut self, x: i32, y: i32, color: u8, blend_map: &BlendMap) {
if let Some(pixels) = self.pixels_at_mut(x, y) {
let dest_color = pixels[0];
if let Some(blended_color) = blend_map.blend(color, dest_color) {
pixels[0] = blended_color;
} else {
pixels[0] = color;
}
}
}
/// Sets the pixel at the given coordinates using a blended color via the specified blend map,
/// or using the color specified if the blend map does not include the given color. 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_blended_pixel_unchecked(&mut self, x: i32, y: i32, color: u8, blend_map: &BlendMap) {
let p = self.pixels_at_mut_ptr_unchecked(x, y);
if let Some(blended_color) = blend_map.blend(color, *p) {
*p = blended_color;
} else {
*p = color;
}
}
/// Draws a line from x1,y1 to x2,y2 by blending the drawn pixels using the given blend map,
/// or the color specified if the blend map does not include this color.
pub fn blended_line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: u8, blend_map: &BlendMap) {
if let Some(blend_mapping) = blend_map.get_mapping(color) {
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 = blend_mapping[*dest as usize];
}
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 = blend_mapping[*dest as usize];
}
}
} 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 = blend_mapping[*dest as usize];
}
}
}
}
} else {
self.line(x1, y1, x2, y2, color);
}
}
/// Draws a horizontal line from x1,y to x2,y by blending the drawn pixels using the given
/// blend map, or the color specified if the blend map does not include this color.
pub fn blended_horiz_line(&mut self, x1: i32, x2: i32, y: i32, color: u8, blend_map: &BlendMap) {
if let Some(blend_mapping) = blend_map.get_mapping(color) {
let mut region = Rect::from_coords(x1, y, x2, y);
if region.clamp_to(&self.clip_region) {
unsafe {
let dest = self.pixels_at_mut_unchecked(region.x, region.y);
for x in 0..region.width as usize {
dest[x] = blend_mapping[dest[x] as usize];
}
}
}
} else {
self.horiz_line(x1, x2, y, color);
}
}
/// Draws a vertical line from x,y1 to x,y2 by blending the drawn pixels using the given blend
/// map, or the color specified if the blend map does not include this color.
pub fn blended_vert_line(&mut self, x: i32, y1: i32, y2: i32, color: u8, blend_map: &BlendMap) {
if let Some(blend_mapping) = blend_map.get_mapping(color) {
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 = blend_mapping[*dest as usize];
dest = dest.add(self.width as usize);
}
}
}
} else {
self.vert_line(x, y1, y2, color);
}
}
/// 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.
/// The box is drawn by blending the drawn pixels using the given blend map, or the color
/// specified if the blend map does not include this color.
pub fn blended_rect(&mut self, mut x1: i32, mut y1: i32, mut x2: i32, mut y2: i32, color: u8, blend_map: &BlendMap) {
if let Some(blend_mapping) = blend_map.get_mapping(color) {
// 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;
}
// note that since we're performing a blend based on the existing destination pixel,
// we need to make sure that we don't draw any overlapping corner pixels (where we
// would end up blending with the edge of a previously drawn line).
// to solve this issue, we just cut off the left-most and right-most pixels for the
// two horizontal lines drawn. those corner pixels will be drawn during the vertical
// line drawing instead.
// top line, only if y1 was originally within bounds
if y1 == region.y {
unsafe {
let dest = self.pixels_at_mut_unchecked(region.x, region.y);
for x in 1..(region.width - 1) as usize {
dest[x] = blend_mapping[dest[x] as usize];
}
}
}
// bottom line, only if y2 was originally within bounds
if y2 == region.bottom() {
unsafe {
let dest = self.pixels_at_mut_unchecked(region.x, region.bottom());
for x in 1..(region.width - 1) as usize {
dest[x] = blend_mapping[dest[x] as usize];
}
}
}
// 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 = blend_mapping[*dest as usize];
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 = blend_mapping[*dest as usize];
dest = dest.add(self.width as usize);
}
}
}
} else {
self.rect(x1, y1, x2, y2, color);
}
}
/// 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. The
/// filled box is draw by blending the drawn pixels using the given blend map, or the color
/// specified if the blend map does not include this color.
pub fn blended_filled_rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: u8, blend_map: &BlendMap) {
if let Some(blend_mapping) = blend_map.get_mapping(color) {
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 {
for x in 0..region.width as usize {
let dest_x = dest.offset(x as isize);
*dest_x = blend_mapping[*dest_x as usize];
}
dest = dest.add(self.width as usize);
}
}
}
} else {
self.filled_rect(x1, y1, x2, y2, color);
}
}
}

View file

@ -1,16 +1,16 @@
use std::path::Path;
use std::slice;
use thiserror::Error; use thiserror::Error;
use crate::graphics::indexed::palette::Palette; use crate::graphics::Pixel;
use crate::math::rect::Rect; use crate::math::rect::Rect;
pub mod blit; pub mod blit;
pub mod general;
pub mod gif; pub mod gif;
pub mod iff; pub mod iff;
pub mod indexed;
pub mod pcx; pub mod pcx;
pub mod primitives; pub mod primitives;
pub mod rgb;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum BitmapError { pub enum BitmapError {
@ -40,14 +40,14 @@ pub enum BitmapError {
/// here are done with respect to the bitmaps clipping region, where rendering outside of the /// here are done with respect to the bitmaps clipping region, where rendering outside of the
/// clipping region is simply not performed / stops at the clipping boundary. /// clipping region is simply not performed / stops at the clipping boundary.
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
pub struct Bitmap { pub struct Bitmap<PixelType: Pixel> {
width: u32, width: u32,
height: u32, height: u32,
pixels: Box<[u8]>, pixels: Box<[PixelType]>,
clip_region: Rect, clip_region: Rect,
} }
impl std::fmt::Debug for Bitmap { impl<PixelType: Pixel> std::fmt::Debug for Bitmap<PixelType> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Bitmap") f.debug_struct("Bitmap")
.field("width", &self.width) .field("width", &self.width)
@ -57,7 +57,9 @@ impl std::fmt::Debug for Bitmap {
} }
} }
impl Bitmap { impl<PixelType: Pixel> Bitmap<PixelType> {
pub const PIXEL_SIZE: usize = std::mem::size_of::<PixelType>();
/// Creates a new Bitmap with the specified dimensions. /// Creates a new Bitmap with the specified dimensions.
/// ///
/// # Arguments /// # Arguments
@ -66,7 +68,7 @@ impl Bitmap {
/// * `height`: the height of the bitmap in pixels /// * `height`: the height of the bitmap in pixels
/// ///
/// returns: `Result<Bitmap, BitmapError>` /// returns: `Result<Bitmap, BitmapError>`
pub fn new(width: u32, height: u32) -> Result<Bitmap, BitmapError> { pub fn new(width: u32, height: u32) -> Result<Self, BitmapError> {
if width == 0 || height == 0 { if width == 0 || height == 0 {
return Err(BitmapError::InvalidDimensions); return Err(BitmapError::InvalidDimensions);
} }
@ -74,7 +76,7 @@ impl Bitmap {
Ok(Bitmap { Ok(Bitmap {
width, width,
height, height,
pixels: vec![0u8; (width * height) as usize].into_boxed_slice(), pixels: vec![Default::default(); (width * height) as usize].into_boxed_slice(),
clip_region: Rect { clip_region: Rect {
x: 0, x: 0,
y: 0, y: 0,
@ -93,36 +95,16 @@ impl Bitmap {
/// * `region`: the region on the source bitmap to copy from /// * `region`: the region on the source bitmap to copy from
/// ///
/// returns: `Result<Bitmap, BitmapError>` /// returns: `Result<Bitmap, BitmapError>`
pub fn from(source: &Bitmap, region: &Rect) -> Result<Bitmap, BitmapError> { pub fn from(source: &Self, region: &Rect) -> Result<Self, BitmapError> {
if !source.full_bounds().contains_rect(region) { if !source.full_bounds().contains_rect(region) {
return Err(BitmapError::OutOfBounds); return Err(BitmapError::OutOfBounds);
} }
let mut bmp = Bitmap::new(region.width, region.height)?; let mut bmp = Self::new(region.width, region.height)?;
unsafe { bmp.solid_blit(source, region, 0, 0) }; unsafe { bmp.solid_blit(source, region, 0, 0) };
Ok(bmp) Ok(bmp)
} }
pub fn load_file(path: &Path) -> Result<(Bitmap, Palette), BitmapError> {
if let Some(extension) = path.extension() {
let extension = extension.to_ascii_lowercase();
match extension.to_str() {
Some("pcx") => Ok(Self::load_pcx_file(path)?),
Some("gif") => Ok(Self::load_gif_file(path)?),
Some("iff") | Some("lbm") | Some("pbm") | Some("bbm") => {
Ok(Self::load_iff_file(path)?)
}
_ => Err(BitmapError::UnknownFileType(String::from(
"Unrecognized file extension",
))),
}
} else {
Err(BitmapError::UnknownFileType(String::from(
"No file extension",
)))
}
}
/// Returns the width of the bitmap in pixels. /// Returns the width of the bitmap in pixels.
#[inline] #[inline]
pub fn width(&self) -> u32 { pub fn width(&self) -> u32 {
@ -183,13 +165,13 @@ impl Bitmap {
/// Returns a reference to the raw pixels in this bitmap. /// Returns a reference to the raw pixels in this bitmap.
#[inline] #[inline]
pub fn pixels(&self) -> &[u8] { pub fn pixels(&self) -> &[PixelType] {
&self.pixels &self.pixels
} }
/// Returns a mutable reference to the raw pixels in this bitmap. /// Returns a mutable reference to the raw pixels in this bitmap.
#[inline] #[inline]
pub fn pixels_mut(&mut self) -> &mut [u8] { pub fn pixels_mut(&mut self) -> &mut [PixelType] {
&mut self.pixels &mut self.pixels
} }
@ -197,7 +179,7 @@ impl Bitmap {
/// given coordinates and extending to the end of the bitmap. If the coordinates given are /// given coordinates and extending to the end of the bitmap. If the coordinates given are
/// outside the bitmap's current clipping region, None is returned. /// outside the bitmap's current clipping region, None is returned.
#[inline] #[inline]
pub fn pixels_at(&self, x: i32, y: i32) -> Option<&[u8]> { pub fn pixels_at(&self, x: i32, y: i32) -> Option<&[PixelType]> {
if self.is_xy_visible(x, y) { if self.is_xy_visible(x, y) {
let offset = self.get_offset_to_xy(x, y); let offset = self.get_offset_to_xy(x, y);
Some(&self.pixels[offset..]) Some(&self.pixels[offset..])
@ -210,7 +192,7 @@ impl Bitmap {
/// given coordinates and extending to the end of the bitmap. If the coordinates given are /// given coordinates and extending to the end of the bitmap. If the coordinates given are
/// outside the bitmap's current clipping region, None is returned. /// outside the bitmap's current clipping region, None is returned.
#[inline] #[inline]
pub fn pixels_at_mut(&mut self, x: i32, y: i32) -> Option<&mut [u8]> { pub fn pixels_at_mut(&mut self, x: i32, y: i32) -> Option<&mut [PixelType]> {
if self.is_xy_visible(x, y) { if self.is_xy_visible(x, y) {
let offset = self.get_offset_to_xy(x, y); let offset = self.get_offset_to_xy(x, y);
Some(&mut self.pixels[offset..]) Some(&mut self.pixels[offset..])
@ -223,18 +205,18 @@ impl Bitmap {
/// given coordinates and extending to the end of the bitmap. The coordinates are not checked /// given coordinates and extending to the end of the bitmap. The coordinates are not checked
/// for validity, so it is up to you to ensure they lie within the bounds of the bitmap. /// for validity, so it is up to you to ensure they lie within the bounds of the bitmap.
#[inline] #[inline]
pub unsafe fn pixels_at_unchecked(&self, x: i32, y: i32) -> &[u8] { pub unsafe fn pixels_at_unchecked(&self, x: i32, y: i32) -> &[PixelType] {
let offset = self.get_offset_to_xy(x, y); let offset = self.get_offset_to_xy(x, y);
slice::from_raw_parts(self.pixels.as_ptr().add(offset), self.pixels.len() - offset) std::slice::from_raw_parts(self.pixels.as_ptr().add(offset), self.pixels.len() - offset)
} }
/// Returns a mutable unsafe reference to the subset of the raw pixels in this bitmap beginning /// Returns a mutable unsafe reference to the subset of the raw pixels in this bitmap beginning
/// at the given coordinates and extending to the end of the bitmap. The coordinates are not /// at the given coordinates and extending to the end of the bitmap. The coordinates are not
/// checked for validity, so it is up to you to ensure they lie within the bounds of the bitmap. /// checked for validity, so it is up to you to ensure they lie within the bounds of the bitmap.
#[inline] #[inline]
pub unsafe fn pixels_at_mut_unchecked(&mut self, x: i32, y: i32) -> &mut [u8] { pub unsafe fn pixels_at_mut_unchecked(&mut self, x: i32, y: i32) -> &mut [PixelType] {
let offset = self.get_offset_to_xy(x, y); let offset = self.get_offset_to_xy(x, y);
slice::from_raw_parts_mut( std::slice::from_raw_parts_mut(
self.pixels.as_mut_ptr().add(offset), self.pixels.as_mut_ptr().add(offset),
self.pixels.len() - offset, self.pixels.len() - offset,
) )
@ -244,7 +226,7 @@ impl Bitmap {
/// coordinates. If the coordinates given are outside the bitmap's current clipping region, /// coordinates. If the coordinates given are outside the bitmap's current clipping region,
/// None is returned. /// None is returned.
#[inline] #[inline]
pub unsafe fn pixels_at_ptr(&self, x: i32, y: i32) -> Option<*const u8> { pub unsafe fn pixels_at_ptr(&self, x: i32, y: i32) -> Option<*const PixelType> {
if self.is_xy_visible(x, y) { if self.is_xy_visible(x, y) {
let offset = self.get_offset_to_xy(x, y); let offset = self.get_offset_to_xy(x, y);
Some(self.pixels.as_ptr().add(offset)) Some(self.pixels.as_ptr().add(offset))
@ -257,7 +239,7 @@ impl Bitmap {
/// given coordinates. If the coordinates given are outside the bitmap's current clipping /// given coordinates. If the coordinates given are outside the bitmap's current clipping
/// region, None is returned. /// region, None is returned.
#[inline] #[inline]
pub unsafe fn pixels_at_mut_ptr(&mut self, x: i32, y: i32) -> Option<*mut u8> { pub unsafe fn pixels_at_mut_ptr(&mut self, x: i32, y: i32) -> Option<*mut PixelType> {
if self.is_xy_visible(x, y) { if self.is_xy_visible(x, y) {
let offset = self.get_offset_to_xy(x, y); let offset = self.get_offset_to_xy(x, y);
Some(self.pixels.as_mut_ptr().add(offset)) Some(self.pixels.as_mut_ptr().add(offset))
@ -270,7 +252,7 @@ impl Bitmap {
/// given coordinates. The coordinates are not checked for validity, so it is up to you to /// 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. /// ensure they lie within the bounds of the bitmap.
#[inline] #[inline]
pub unsafe fn pixels_at_ptr_unchecked(&self, x: i32, y: i32) -> *const u8 { pub unsafe fn pixels_at_ptr_unchecked(&self, x: i32, y: i32) -> *const PixelType {
let offset = self.get_offset_to_xy(x, y); let offset = self.get_offset_to_xy(x, y);
self.pixels.as_ptr().add(offset) self.pixels.as_ptr().add(offset)
} }
@ -279,7 +261,7 @@ impl Bitmap {
/// at the given coordinates. The coordinates are not checked for validity, so it is up to you /// 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. /// to ensure they lie within the bounds of the bitmap.
#[inline] #[inline]
pub unsafe fn pixels_at_mut_ptr_unchecked(&mut self, x: i32, y: i32) -> *mut u8 { pub unsafe fn pixels_at_mut_ptr_unchecked(&mut self, x: i32, y: i32) -> *mut PixelType {
let offset = self.get_offset_to_xy(x, y); let offset = self.get_offset_to_xy(x, y);
self.pixels.as_mut_ptr().add(offset) self.pixels.as_mut_ptr().add(offset)
} }
@ -300,20 +282,6 @@ impl Bitmap {
&& (x <= self.clip_region.right()) && (x <= self.clip_region.right())
&& (y <= self.clip_region.bottom()) && (y <= self.clip_region.bottom())
} }
/// Copies and converts the entire pixel data from this bitmap to a destination expecting
/// 32-bit ARGB-format pixel data. This can be used to display the contents of the bitmap
/// on-screen by using an SDL Surface, OpenGL texture, etc as the destination.
///
/// # Arguments
///
/// * `dest`: destination 32-bit ARGB pixel buffer to copy converted pixels to
/// * `palette`: the 256 colour palette to use during pixel conversion
pub fn copy_as_argb_to(&self, dest: &mut [u32], palette: &Palette) {
for (src, dest) in self.pixels().iter().zip(dest.iter_mut()) {
*dest = palette[*src];
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -344,10 +312,10 @@ pub mod tests {
#[test] #[test]
pub fn creation_and_sizing() { pub fn creation_and_sizing() {
assert_matches!(Bitmap::new(0, 0), Err(BitmapError::InvalidDimensions)); assert_matches!(Bitmap::<u8>::new(0, 0), Err(BitmapError::InvalidDimensions));
assert_matches!(Bitmap::new(16, 0), Err(BitmapError::InvalidDimensions)); assert_matches!(Bitmap::<u8>::new(16, 0), Err(BitmapError::InvalidDimensions));
assert_matches!(Bitmap::new(0, 32), Err(BitmapError::InvalidDimensions)); assert_matches!(Bitmap::<u8>::new(0, 32), Err(BitmapError::InvalidDimensions));
let bmp = Bitmap::new(16, 32).unwrap(); let bmp = Bitmap::<u8>::new(16, 32).unwrap();
assert_eq!(16, bmp.width()); assert_eq!(16, bmp.width());
assert_eq!(32, bmp.height()); assert_eq!(32, bmp.height());
assert_eq!(15, bmp.right()); assert_eq!(15, bmp.right());
@ -374,24 +342,24 @@ pub mod tests {
#[test] #[test]
pub fn copy_from() { pub fn copy_from() {
let mut bmp = Bitmap::new(8, 8).unwrap(); let mut bmp = Bitmap::<u8>::new(8, 8).unwrap();
bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS); bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS);
assert_matches!( assert_matches!(
Bitmap::from(&bmp, &Rect::new(0, 0, 16, 16)), Bitmap::<u8>::from(&bmp, &Rect::new(0, 0, 16, 16)),
Err(BitmapError::OutOfBounds) Err(BitmapError::OutOfBounds)
); );
let copy = Bitmap::from(&bmp, &Rect::new(0, 0, 8, 8)).unwrap(); let copy = Bitmap::<u8>::from(&bmp, &Rect::new(0, 0, 8, 8)).unwrap();
assert_eq!(bmp.pixels(), copy.pixels()); assert_eq!(bmp.pixels(), copy.pixels());
let copy = Bitmap::from(&bmp, &Rect::new(4, 4, 4, 4)).unwrap(); let copy = Bitmap::<u8>::from(&bmp, &Rect::new(4, 4, 4, 4)).unwrap();
assert_eq!(RAW_BMP_PIXELS_SUBSET, copy.pixels()); assert_eq!(RAW_BMP_PIXELS_SUBSET, copy.pixels());
} }
#[test] #[test]
pub fn xy_offset_calculation() { pub fn xy_offset_calculation() {
let bmp = Bitmap::new(20, 15).unwrap(); let bmp = Bitmap::<u8>::new(20, 15).unwrap();
assert_eq!(0, bmp.get_offset_to_xy(0, 0)); assert_eq!(0, bmp.get_offset_to_xy(0, 0));
assert_eq!(19, bmp.get_offset_to_xy(19, 0)); assert_eq!(19, bmp.get_offset_to_xy(19, 0));
assert_eq!(20, bmp.get_offset_to_xy(0, 1)); assert_eq!(20, bmp.get_offset_to_xy(0, 1));
@ -402,7 +370,7 @@ pub mod tests {
#[test] #[test]
pub fn bounds_testing_and_clip_region() { pub fn bounds_testing_and_clip_region() {
let mut bmp = Bitmap::new(16, 8).unwrap(); let mut bmp = Bitmap::<u8>::new(16, 8).unwrap();
assert!(bmp.is_xy_visible(0, 0)); assert!(bmp.is_xy_visible(0, 0));
assert!(bmp.is_xy_visible(15, 0)); assert!(bmp.is_xy_visible(15, 0));
assert!(bmp.is_xy_visible(0, 7)); assert!(bmp.is_xy_visible(0, 7));
@ -452,7 +420,7 @@ pub mod tests {
#[test] #[test]
pub fn pixels_at() { pub fn pixels_at() {
let mut bmp = Bitmap::new(8, 8).unwrap(); let mut bmp = Bitmap::<u8>::new(8, 8).unwrap();
bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS); bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS);
assert_eq!(None, bmp.pixels_at(-1, -1)); assert_eq!(None, bmp.pixels_at(-1, -1));
@ -472,7 +440,7 @@ pub mod tests {
#[test] #[test]
pub fn pixels_at_mut() { pub fn pixels_at_mut() {
let mut bmp = Bitmap::new(8, 8).unwrap(); let mut bmp = Bitmap::<u8>::new(8, 8).unwrap();
bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS); bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS);
assert_eq!(None, bmp.pixels_at_mut(-1, -1)); assert_eq!(None, bmp.pixels_at_mut(-1, -1));
@ -492,7 +460,7 @@ pub mod tests {
#[test] #[test]
pub fn pixels_at_unchecked() { pub fn pixels_at_unchecked() {
let mut bmp = Bitmap::new(8, 8).unwrap(); let mut bmp = Bitmap::<u8>::new(8, 8).unwrap();
bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS); bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS);
let offset = bmp.get_offset_to_xy(1, 1); let offset = bmp.get_offset_to_xy(1, 1);
@ -510,7 +478,7 @@ pub mod tests {
#[test] #[test]
pub fn pixels_at_mut_unchecked() { pub fn pixels_at_mut_unchecked() {
let mut bmp = Bitmap::new(8, 8).unwrap(); let mut bmp = Bitmap::<u8>::new(8, 8).unwrap();
bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS); bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS);
let offset = bmp.get_offset_to_xy(1, 1); let offset = bmp.get_offset_to_xy(1, 1);
@ -528,7 +496,7 @@ pub mod tests {
#[test] #[test]
pub fn pixels_at_ptr() { pub fn pixels_at_ptr() {
let mut bmp = Bitmap::new(8, 8).unwrap(); let mut bmp = Bitmap::<u8>::new(8, 8).unwrap();
bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS); bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS);
assert_eq!(None, unsafe { bmp.pixels_at_ptr(-1, -1) }); assert_eq!(None, unsafe { bmp.pixels_at_ptr(-1, -1) });
@ -546,7 +514,7 @@ pub mod tests {
#[test] #[test]
pub fn pixels_at_mut_ptr() { pub fn pixels_at_mut_ptr() {
let mut bmp = Bitmap::new(8, 8).unwrap(); let mut bmp = Bitmap::<u8>::new(8, 8).unwrap();
bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS); bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS);
assert_eq!(None, unsafe { bmp.pixels_at_mut_ptr(-1, -1) }); assert_eq!(None, unsafe { bmp.pixels_at_mut_ptr(-1, -1) });
@ -564,7 +532,7 @@ pub mod tests {
#[test] #[test]
pub fn pixels_at_ptr_unchecked() { pub fn pixels_at_ptr_unchecked() {
let mut bmp = Bitmap::new(8, 8).unwrap(); let mut bmp = Bitmap::<u8>::new(8, 8).unwrap();
bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS); bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS);
let offset = bmp.get_offset_to_xy(1, 1); let offset = bmp.get_offset_to_xy(1, 1);
@ -580,7 +548,7 @@ pub mod tests {
#[test] #[test]
pub fn pixels_at_mut_ptr_unchecked() { pub fn pixels_at_mut_ptr_unchecked() {
let mut bmp = Bitmap::new(8, 8).unwrap(); let mut bmp = Bitmap::<u8>::new(8, 8).unwrap();
bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS); bmp.pixels_mut().copy_from_slice(RAW_BMP_PIXELS);
let offset = bmp.get_offset_to_xy(1, 1); let offset = bmp.get_offset_to_xy(1, 1);

View file

@ -5,9 +5,9 @@ use std::path::Path;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use thiserror::Error; use thiserror::Error;
use crate::graphics::bitmap::indexed::IndexedBitmap;
use crate::graphics::color::from_rgb32; use crate::graphics::color::from_rgb32;
use crate::graphics::indexed::bitmap::Bitmap; use crate::graphics::palette::{Palette, PaletteError, PaletteFormat};
use crate::graphics::indexed::palette::{Palette, PaletteError, PaletteFormat};
use crate::utils::bytes::ReadFixedLengthByteArray; use crate::utils::bytes::ReadFixedLengthByteArray;
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -104,10 +104,10 @@ fn write_pcx_data<T: WriteBytesExt>(
Ok(()) Ok(())
} }
impl Bitmap { impl IndexedBitmap {
pub fn load_pcx_bytes<T: ReadBytesExt + Seek>( pub fn load_pcx_bytes<T: ReadBytesExt + Seek>(
reader: &mut T, reader: &mut T,
) -> Result<(Bitmap, Palette), PcxError> { ) -> Result<(IndexedBitmap, Palette), PcxError> {
let header = PcxHeader::read(reader)?; let header = PcxHeader::read(reader)?;
if header.manufacturer != 10 { if header.manufacturer != 10 {
@ -140,7 +140,7 @@ impl Bitmap {
let width = (header.x2 + 1) as u32; let width = (header.x2 + 1) as u32;
let height = (header.y2 + 1) as u32; let height = (header.y2 + 1) as u32;
let mut bmp = Bitmap::new(width, height).unwrap(); let mut bmp = IndexedBitmap::new(width, height).unwrap();
let mut writer = Cursor::new(bmp.pixels_mut()); let mut writer = Cursor::new(bmp.pixels_mut());
for _y in 0..height { for _y in 0..height {
@ -194,7 +194,7 @@ impl Bitmap {
Ok((bmp, palette)) Ok((bmp, palette))
} }
pub fn load_pcx_file(path: &Path) -> Result<(Bitmap, Palette), PcxError> { pub fn load_pcx_file(path: &Path) -> Result<(IndexedBitmap, Palette), PcxError> {
let f = File::open(path)?; let f = File::open(path)?;
let mut reader = BufReader::new(f); let mut reader = BufReader::new(f);
Self::load_pcx_bytes(&mut reader) Self::load_pcx_bytes(&mut reader)
@ -286,9 +286,9 @@ pub mod tests {
use super::*; use super::*;
pub static TEST_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../../test-assets/test_bmp_pixels_raw.bin"); pub static TEST_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../test-assets/test_bmp_pixels_raw.bin");
pub static TEST_LARGE_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../../test-assets/test_large_bmp_pixels_raw.bin"); pub static TEST_LARGE_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../test-assets/test_large_bmp_pixels_raw.bin");
pub static TEST_LARGE_BMP_PIXELS_RAW_2: &[u8] = include_bytes!("../../../../test-assets/test_large_bmp_pixels_raw2.bin"); pub static TEST_LARGE_BMP_PIXELS_RAW_2: &[u8] = include_bytes!("../../../test-assets/test_large_bmp_pixels_raw2.bin");
#[test] #[test]
pub fn load_and_save() -> Result<(), PcxError> { pub fn load_and_save() -> Result<(), PcxError> {
@ -297,7 +297,7 @@ pub mod tests {
.unwrap(); .unwrap();
let tmp_dir = TempDir::new()?; let tmp_dir = TempDir::new()?;
let (bmp, palette) = Bitmap::load_pcx_file(Path::new("./test-assets/test.pcx"))?; let (bmp, palette) = IndexedBitmap::load_pcx_file(Path::new("./test-assets/test.pcx"))?;
assert_eq!(16, bmp.width()); assert_eq!(16, bmp.width());
assert_eq!(16, bmp.height()); assert_eq!(16, bmp.height());
assert_eq!(bmp.pixels(), TEST_BMP_PIXELS_RAW); assert_eq!(bmp.pixels(), TEST_BMP_PIXELS_RAW);
@ -305,7 +305,7 @@ pub mod tests {
let save_path = tmp_dir.path().join("test_save.pcx"); let save_path = tmp_dir.path().join("test_save.pcx");
bmp.to_pcx_file(&save_path, &palette)?; bmp.to_pcx_file(&save_path, &palette)?;
let (reloaded_bmp, reloaded_palette) = Bitmap::load_pcx_file(&save_path)?; let (reloaded_bmp, reloaded_palette) = IndexedBitmap::load_pcx_file(&save_path)?;
assert_eq!(16, reloaded_bmp.width()); assert_eq!(16, reloaded_bmp.width());
assert_eq!(16, reloaded_bmp.height()); assert_eq!(16, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_BMP_PIXELS_RAW); assert_eq!(reloaded_bmp.pixels(), TEST_BMP_PIXELS_RAW);
@ -320,28 +320,28 @@ pub mod tests {
// first image // first image
let (bmp, palette) = Bitmap::load_pcx_file(Path::new("./test-assets/test_image.pcx"))?; let (bmp, palette) = IndexedBitmap::load_pcx_file(Path::new("./test-assets/test_image.pcx"))?;
assert_eq!(320, bmp.width()); assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height()); assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW); assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
let save_path = tmp_dir.path().join("test_save.pcx"); let save_path = tmp_dir.path().join("test_save.pcx");
bmp.to_pcx_file(&save_path, &palette)?; bmp.to_pcx_file(&save_path, &palette)?;
let (reloaded_bmp, _) = Bitmap::load_pcx_file(&save_path)?; let (reloaded_bmp, _) = IndexedBitmap::load_pcx_file(&save_path)?;
assert_eq!(320, reloaded_bmp.width()); assert_eq!(320, reloaded_bmp.width());
assert_eq!(200, reloaded_bmp.height()); assert_eq!(200, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW); assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
// second image // second image
let (bmp, palette) = Bitmap::load_pcx_file(Path::new("./test-assets/test_image2.pcx"))?; let (bmp, palette) = IndexedBitmap::load_pcx_file(Path::new("./test-assets/test_image2.pcx"))?;
assert_eq!(320, bmp.width()); assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height()); assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2); assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2);
let save_path = tmp_dir.path().join("test_save_2.pcx"); let save_path = tmp_dir.path().join("test_save_2.pcx");
bmp.to_pcx_file(&save_path, &palette)?; bmp.to_pcx_file(&save_path, &palette)?;
let (reloaded_bmp, _) = Bitmap::load_pcx_file(&save_path)?; let (reloaded_bmp, _) = IndexedBitmap::load_pcx_file(&save_path)?;
assert_eq!(320, reloaded_bmp.width()); assert_eq!(320, reloaded_bmp.width());
assert_eq!(200, reloaded_bmp.height()); assert_eq!(200, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2); assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2);

View file

@ -0,0 +1,392 @@
use std::mem::swap;
use crate::graphics::bitmap::Bitmap;
use crate::graphics::font::{Character, Font, FontRenderOpts};
use crate::graphics::Pixel;
use crate::math::rect::Rect;
impl<PixelType: Pixel> Bitmap<PixelType> {
/// Fills the entire bitmap with the given color.
pub fn clear(&mut self, color: PixelType) {
self.pixels.fill(color);
}
/// Sets the pixel at the given coordinates to the color specified. If the coordinates lie
/// outside of the bitmaps clipping region, no pixels will be changed.
#[inline]
pub fn set_pixel(&mut self, x: i32, y: i32, color: PixelType) {
if let Some(pixels) = self.pixels_at_mut(x, y) {
pixels[0] = color;
}
}
/// 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.
#[inline]
pub unsafe fn set_pixel_unchecked(&mut self, x: i32, y: i32, color: PixelType) {
let p = self.pixels_at_mut_ptr_unchecked(x, y);
*p = color;
}
/// Gets the pixel at the given coordinates. If the coordinates lie outside of the bitmaps
/// clipping region, None is returned.
#[inline]
pub fn get_pixel(&self, x: i32, y: i32) -> Option<PixelType> {
if let Some(pixels) = self.pixels_at(x, y) {
Some(pixels[0])
} else {
None
}
}
/// Gets the 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 get_pixel_unchecked(&self, x: i32, y: i32) -> PixelType {
*(self.pixels_at_ptr_unchecked(x, y))
}
/// Renders a single character using the font given.
#[inline]
pub fn print_char<T: Font>(&mut self, ch: char, x: i32, y: i32, opts: FontRenderOpts<PixelType>, font: &T) {
font.character(ch)
.draw(self, x, y, opts);
}
/// Renders the string of text using the font given.
pub fn print_string<T: Font>(&mut self, text: &str, x: i32, y: i32, opts: FontRenderOpts<PixelType>, font: &T) {
let mut current_x = x;
let mut current_y = y;
for ch in text.chars() {
match ch {
' ' => current_x += font.space_width() as i32,
'\n' => {
current_x = x;
current_y += font.line_height() as i32
}
'\r' => (),
otherwise => {
self.print_char(otherwise, current_x, current_y, opts, font);
current_x += font.character(otherwise).bounds().width as i32;
}
}
}
}
/// Draws a line from x1,y1 to x2,y2.
pub fn line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: 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 = color;
}
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 = color;
}
}
} 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 = color;
}
}
}
}
}
/// 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);
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 = color;
}
}
}
}
/// 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);
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 = color;
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) {
// 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;
}
// top line, only if y1 was originally within bounds
if y1 == region.y {
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 = color;
}
}
}
// bottom line, only if y2 was originally within bounds
if y2 == region.bottom() {
unsafe {
let dest = &mut self.pixels_at_mut_unchecked(region.x, region.bottom())[0..region.width as usize];
for pixel in dest.iter_mut() {
*pixel = color;
}
}
}
// 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 = color;
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 = color;
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) {
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 = color;
}
// 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
let mut x = 0;
let mut y = radius as i32;
let mut m = 5 - 4 * radius as i32;
while x <= y {
self.set_pixel(center_x + x, center_y + y, color);
self.set_pixel(center_x + x, center_y - y, color);
self.set_pixel(center_x - x, center_y + y, color);
self.set_pixel(center_x - x, center_y - y, color);
self.set_pixel(center_x + y, center_y + x, color);
self.set_pixel(center_x + y, center_y - x, color);
self.set_pixel(center_x - y, center_y + x, color);
self.set_pixel(center_x - y, center_y - x, color);
if m > 0 {
y -= 1;
m -= 8 * y;
}
x += 1;
m += 8 * x + 4;
}
}
/// Draws a filled circle formed by the center point and radius given.
pub fn filled_circle(&mut self, center_x: i32, center_y: i32, radius: u32, color: PixelType) {
// TODO: optimize
let mut x = 0;
let mut y = radius as i32;
let mut m = 5 - 4 * radius as i32;
while x <= y {
self.horiz_line(center_x - x, center_x + x, center_y - y, color);
self.horiz_line(center_x - y, center_x + y, center_y - x, color);
self.horiz_line(center_x - y, center_x + y, center_y + x, color);
self.horiz_line(center_x - x, center_x + x, center_y + y, color);
if m > 0 {
y -= 1;
m -= 8 * y;
}
x += 1;
m += 8 * x + 4;
}
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[rustfmt::skip]
#[test]
pub fn set_and_get_pixel() {
let mut bmp = Bitmap::<u8>::new(8, 8).unwrap();
assert_eq!(None, bmp.get_pixel(-1, -1));
assert_eq!(0, bmp.get_pixel(0, 0).unwrap());
bmp.set_pixel(0, 0, 7);
assert_eq!(7, bmp.get_pixel(0, 0).unwrap());
assert_eq!(
bmp.pixels(),
&[
7, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
]
);
assert_eq!(0, bmp.get_pixel(2, 4).unwrap());
bmp.set_pixel(2, 4, 5);
assert_eq!(5, bmp.get_pixel(2, 4).unwrap());
assert_eq!(
bmp.pixels(),
&[
7, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 5, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
]
);
}
#[rustfmt::skip]
#[test]
pub fn set_and_get_pixel_unchecked() {
let mut bmp = Bitmap::<u8>::new(8, 8).unwrap();
assert_eq!(0, unsafe { bmp.get_pixel_unchecked(0, 0) });
unsafe { bmp.set_pixel_unchecked(0, 0, 7) };
assert_eq!(7, unsafe { bmp.get_pixel_unchecked(0, 0) });
assert_eq!(
bmp.pixels(),
&[
7, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
]
);
assert_eq!(0, unsafe { bmp.get_pixel_unchecked(2, 4) });
unsafe { bmp.set_pixel_unchecked(2, 4, 5) };
assert_eq!(5, unsafe { bmp.get_pixel_unchecked(2, 4) });
assert_eq!(
bmp.pixels(),
&[
7, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 5, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
]
);
}
}

View file

@ -0,0 +1,175 @@
use crate::graphics::bitmap::blit::clip_blit;
use crate::graphics::bitmap::rgb::RgbaBitmap;
use crate::graphics::bitmapatlas::BitmapAtlas;
use crate::math::rect::Rect;
#[derive(Clone, PartialEq)]
pub enum RgbaBlitMethod {
/// Solid blit, no transparency or other per-pixel adjustments.
Solid,
/// Same as [RgbaBlitMethod::Solid] but the drawn image can also be flipped horizontally
/// and/or vertically.
SolidFlipped {
horizontal_flip: bool,
vertical_flip: bool,
},
/// Transparent blit, the specified source color pixels are skipped.
Transparent(u32),
/// Same as [RgbaBlitMethod::Transparent] but the drawn image can also be flipped horizontally
/// and/or vertically.
TransparentFlipped {
transparent_color: u32,
horizontal_flip: bool,
vertical_flip: bool,
},
/// Same as [RgbaBlitMethod::Transparent] except that the visible pixels on the destination are all
/// drawn using the same color.
TransparentSingle {
transparent_color: u32,
draw_color: u32,
},
/// Combination of [RgbaBlitMethod::TransparentFlipped] and [RgbaBlitMethod::TransparentSingle].
TransparentFlippedSingle {
transparent_color: u32,
horizontal_flip: bool,
vertical_flip: bool,
draw_color: u32,
},
/// Rotozoom blit, works the same as [RgbaBlitMethod::Solid] except that rotation and scaling is
/// performed.
RotoZoom {
angle: f32,
scale_x: f32,
scale_y: f32,
},
/// Same as [RgbaBlitMethod::RotoZoom] except that the specified source color pixels are skipped.
RotoZoomTransparent {
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u32,
},
}
impl RgbaBitmap {
pub fn blit_region(
&mut self,
method: RgbaBlitMethod,
src: &Self,
src_region: &Rect,
mut dest_x: i32,
mut dest_y: i32,
) {
// make sure the source region is clipped or even valid at all for the source bitmap given
let mut src_region = *src_region;
if !src_region.clamp_to(&src.clip_region) {
return;
}
// some blit methods need to handle clipping a bit differently than others
use RgbaBlitMethod::*;
match method {
// rotozoom blits internally clip per-pixel right now ... and regardless, the normal
// clip_blit() function wouldn't handle a rotozoom blit destination region anyway ...
RotoZoom { .. } => {}
RotoZoomTransparent { .. } => {}
// set axis flip arguments
SolidFlipped { horizontal_flip, vertical_flip, .. } |
TransparentFlipped { horizontal_flip, vertical_flip, .. } |
TransparentFlippedSingle { horizontal_flip, vertical_flip, .. } => {
if !clip_blit(
self.clip_region(),
&mut src_region,
&mut dest_x,
&mut dest_y,
horizontal_flip,
vertical_flip,
) {
return;
}
}
// otherwise clip like normal!
_ => {
if !clip_blit(
self.clip_region(),
&mut src_region,
&mut dest_x,
&mut dest_y,
false,
false,
) {
return;
}
}
}
unsafe {
self.blit_region_unchecked(method, src, &src_region, dest_x, dest_y);
};
}
#[inline]
#[rustfmt::skip]
pub unsafe fn blit_region_unchecked(
&mut self,
method: RgbaBlitMethod,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
) {
use RgbaBlitMethod::*;
match method {
Solid => self.solid_blit(src, src_region, dest_x, dest_y),
SolidFlipped { horizontal_flip, vertical_flip } => {
self.solid_flipped_blit(src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip)
}
Transparent(transparent_color) => {
self.transparent_blit(src, src_region, dest_x, dest_y, transparent_color)
}
TransparentFlipped { transparent_color, horizontal_flip, vertical_flip } => {
self.transparent_flipped_blit(src, src_region, dest_x, dest_y, transparent_color, horizontal_flip, vertical_flip)
}
TransparentSingle { transparent_color, draw_color } => {
self.transparent_single_color_blit(src, src_region, dest_x, dest_y, transparent_color, draw_color)
}
TransparentFlippedSingle { transparent_color, horizontal_flip, vertical_flip, draw_color } => {
self.transparent_flipped_single_color_blit(src, src_region, dest_x, dest_y, transparent_color, horizontal_flip, vertical_flip, draw_color)
}
RotoZoom { angle, scale_x, scale_y } => {
self.rotozoom_blit(src, src_region, dest_x, dest_y, angle, scale_x, scale_y)
}
RotoZoomTransparent { angle, scale_x, scale_y, transparent_color } => {
self.rotozoom_transparent_blit(src, src_region, dest_x, dest_y, angle, scale_x, scale_y, transparent_color)
}
}
}
#[inline]
pub fn blit(&mut self, method: RgbaBlitMethod, src: &Self, x: i32, y: i32) {
let src_region = Rect::new(0, 0, src.width, src.height);
self.blit_region(method, src, &src_region, x, y);
}
#[inline]
pub fn blit_atlas(&mut self, method: RgbaBlitMethod, src: &BitmapAtlas<Self>, index: usize, x: i32, y: i32) {
if let Some(src_region) = src.get(index) {
self.blit_region(method, src.bitmap(), src_region, x, y);
}
}
#[inline]
pub unsafe fn blit_unchecked(&mut self, method: RgbaBlitMethod, src: &Self, x: i32, y: i32) {
let src_region = Rect::new(0, 0, src.width, src.height);
self.blit_region_unchecked(method, src, &src_region, x, y);
}
#[inline]
pub unsafe fn blit_atlas_unchecked(&mut self, method: RgbaBlitMethod, src: &BitmapAtlas<Self>, index: usize, x: i32, y: i32) {
if let Some(src_region) = src.get(index) {
self.blit_region_unchecked(method, src.bitmap(), &src_region, x, y);
}
}
}

View file

@ -0,0 +1,9 @@
use crate::graphics::bitmap::Bitmap;
pub mod blit;
pub type RgbaBitmap = Bitmap<u32>;
impl RgbaBitmap {
}

View file

@ -2,7 +2,7 @@ use std::ops::Index;
use thiserror::Error; use thiserror::Error;
use crate::graphics::bitmap::GeneralBitmap; use crate::graphics::bitmap::general::GeneralBitmap;
use crate::math::rect::Rect; use crate::math::rect::Rect;
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -134,13 +134,13 @@ where
pub mod tests { pub mod tests {
use claim::*; use claim::*;
use crate::graphics::indexed::bitmap::Bitmap; use crate::graphics::bitmap::indexed::IndexedBitmap;
use super::*; use super::*;
#[test] #[test]
pub fn adding_rects() { pub fn adding_rects() {
let bmp = Bitmap::new(64, 64).unwrap(); let bmp = IndexedBitmap::new(64, 64).unwrap();
let mut atlas = BitmapAtlas::new(bmp); let mut atlas = BitmapAtlas::new(bmp);
let rect = Rect::new(0, 0, 16, 16); let rect = Rect::new(0, 0, 16, 16);
@ -174,7 +174,7 @@ pub mod tests {
#[test] #[test]
pub fn adding_grid() { pub fn adding_grid() {
let bmp = Bitmap::new(64, 64).unwrap(); let bmp = IndexedBitmap::new(64, 64).unwrap();
let mut atlas = BitmapAtlas::new(bmp); let mut atlas = BitmapAtlas::new(bmp);
assert_eq!(3, atlas.add_custom_grid(0, 0, 8, 8, 2, 2, 0).unwrap()); assert_eq!(3, atlas.add_custom_grid(0, 0, 8, 8, 2, 2, 0).unwrap());

View file

@ -6,7 +6,7 @@ use byteorder::{ReadBytesExt, WriteBytesExt};
use thiserror::Error; use thiserror::Error;
use crate::graphics::color::{from_rgb32, luminance}; use crate::graphics::color::{from_rgb32, luminance};
use crate::graphics::indexed::palette::Palette; use crate::graphics::palette::Palette;
use crate::math::lerp; use crate::math::lerp;
use crate::utils::bytes::ReadFixedLengthByteArray; use crate::utils::bytes::ReadFixedLengthByteArray;

View file

@ -5,7 +5,7 @@ use std::path::Path;
use byteorder::{ReadBytesExt, WriteBytesExt}; use byteorder::{ReadBytesExt, WriteBytesExt};
use thiserror::Error; use thiserror::Error;
use crate::graphics::bitmap::GeneralBitmap; use crate::graphics::bitmap::Bitmap;
use crate::graphics::Pixel; use crate::graphics::Pixel;
use crate::math::rect::Rect; use crate::math::rect::Rect;
@ -32,9 +32,9 @@ pub enum FontRenderOpts<PixelType: Pixel> {
pub trait Character { pub trait Character {
fn bounds(&self) -> &Rect; fn bounds(&self) -> &Rect;
fn draw<BitmapType>(&self, dest: &mut BitmapType, x: i32, y: i32, opts: FontRenderOpts<BitmapType::PixelType>) fn draw<PixelType>(&self, dest: &mut Bitmap<PixelType>, x: i32, y: i32, opts: FontRenderOpts<PixelType>)
where where
BitmapType: GeneralBitmap; PixelType: Pixel;
} }
pub trait Font { pub trait Font {
@ -60,9 +60,9 @@ impl Character for BitmaskCharacter {
&self.bounds &self.bounds
} }
fn draw<BitmapType>(&self, dest: &mut BitmapType, x: i32, y: i32, opts: FontRenderOpts<BitmapType::PixelType>) fn draw<PixelType>(&self, dest: &mut Bitmap<PixelType>, x: i32, y: i32, opts: FontRenderOpts<PixelType>)
where where
BitmapType: GeneralBitmap PixelType: Pixel
{ {
// out of bounds check // out of bounds check
if ((x + self.bounds.width as i32) < dest.clip_region().x) if ((x + self.bounds.width as i32) < dest.clip_region().x)

View file

@ -1,617 +0,0 @@
use std::mem::swap;
use crate::graphics::font::{Character, Font, FontRenderOpts};
use crate::graphics::indexed::bitmap::Bitmap;
use crate::graphics::indexed::blendmap::BlendMap;
use crate::math::rect::Rect;
impl Bitmap {
/// Fills the entire bitmap with the given color.
pub fn clear(&mut self, color: u8) {
self.pixels.fill(color);
}
/// Sets the pixel at the given coordinates to the color specified. If the coordinates lie
/// outside of the bitmaps clipping region, no pixels will be changed.
#[inline]
pub fn set_pixel(&mut self, x: i32, y: i32, color: u8) {
if let Some(pixels) = self.pixels_at_mut(x, y) {
pixels[0] = color;
}
}
/// Sets the pixel at the given coordinates using a blended color via the specified blend map,
/// or using the color specified if the blend map does not include the given color. If the
/// coordinates lie outside of the bitmaps clipping region, no pixels will be changed.
#[inline]
pub fn set_blended_pixel(&mut self, x: i32, y: i32, color: u8, blend_map: &BlendMap) {
if let Some(pixels) = self.pixels_at_mut(x, y) {
let dest_color = pixels[0];
if let Some(blended_color) = blend_map.blend(color, dest_color) {
pixels[0] = blended_color;
} else {
pixels[0] = color;
}
}
}
/// 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.
#[inline]
pub unsafe fn set_pixel_unchecked(&mut self, x: i32, y: i32, color: u8) {
let p = self.pixels_at_mut_ptr_unchecked(x, y);
*p = color;
}
/// Sets the pixel at the given coordinates using a blended color via the specified blend map,
/// or using the color specified if the blend map does not include the given color. 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_blended_pixel_unchecked(&mut self, x: i32, y: i32, color: u8, blend_map: &BlendMap) {
let p = self.pixels_at_mut_ptr_unchecked(x, y);
if let Some(blended_color) = blend_map.blend(color, *p) {
*p = blended_color;
} else {
*p = color;
}
}
/// Gets the pixel at the given coordinates. If the coordinates lie outside of the bitmaps
/// clipping region, None is returned.
#[inline]
pub fn get_pixel(&self, x: i32, y: i32) -> Option<u8> {
if let Some(pixels) = self.pixels_at(x, y) {
Some(pixels[0])
} else {
None
}
}
/// Gets the 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 get_pixel_unchecked(&self, x: i32, y: i32) -> u8 {
*(self.pixels_at_ptr_unchecked(x, y))
}
/// Renders a single character using the font given.
#[inline]
pub fn print_char<T: Font>(&mut self, ch: char, x: i32, y: i32, opts: FontRenderOpts<u8>, font: &T) {
font.character(ch)
.draw(self, x, y, opts);
}
/// Renders the string of text using the font given.
pub fn print_string<T: Font>(&mut self, text: &str, x: i32, y: i32, opts: FontRenderOpts<u8>, font: &T) {
let mut current_x = x;
let mut current_y = y;
for ch in text.chars() {
match ch {
' ' => current_x += font.space_width() as i32,
'\n' => {
current_x = x;
current_y += font.line_height() as i32
}
'\r' => (),
otherwise => {
self.print_char(otherwise, current_x, current_y, opts, font);
current_x += font.character(otherwise).bounds().width as i32;
}
}
}
}
/// Draws a line from x1,y1 to x2,y2.
pub fn line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: u8) {
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 = color;
}
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 = color;
}
}
} 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 = color;
}
}
}
}
}
/// Draws a line from x1,y1 to x2,y2 by blending the drawn pixels using the given blend map,
/// or the color specified if the blend map does not include this color.
pub fn blended_line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: u8, blend_map: &BlendMap) {
if let Some(blend_mapping) = blend_map.get_mapping(color) {
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 = blend_mapping[*dest as usize];
}
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 = blend_mapping[*dest as usize];
}
}
} 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 = blend_mapping[*dest as usize];
}
}
}
}
} else {
self.line(x1, y1, x2, y2, color);
}
}
/// Draws a horizontal line from x1,y to x2,y.
pub fn horiz_line(&mut self, x1: i32, x2: i32, y: i32, color: u8) {
let mut region = Rect::from_coords(x1, y, x2, y);
if region.clamp_to(&self.clip_region) {
unsafe {
let dest = self.pixels_at_mut_ptr_unchecked(region.x, region.y);
dest.write_bytes(color, region.width as usize);
}
}
}
/// Draws a horizontal line from x1,y to x2,y by blending the drawn pixels using the given
/// blend map, or the color specified if the blend map does not include this color.
pub fn blended_horiz_line(&mut self, x1: i32, x2: i32, y: i32, color: u8, blend_map: &BlendMap) {
if let Some(blend_mapping) = blend_map.get_mapping(color) {
let mut region = Rect::from_coords(x1, y, x2, y);
if region.clamp_to(&self.clip_region) {
unsafe {
let dest = self.pixels_at_mut_unchecked(region.x, region.y);
for x in 0..region.width as usize {
dest[x] = blend_mapping[dest[x] as usize];
}
}
}
} else {
self.horiz_line(x1, x2, y, color);
}
}
/// Draws a vertical line from x,y1 to x,y2.
pub fn vert_line(&mut self, x: i32, y1: i32, y2: i32, color: u8) {
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 = color;
dest = dest.add(self.width as usize);
}
}
}
}
/// Draws a vertical line from x,y1 to x,y2 by blending the drawn pixels using the given blend
/// map, or the color specified if the blend map does not include this color.
pub fn blended_vert_line(&mut self, x: i32, y1: i32, y2: i32, color: u8, blend_map: &BlendMap) {
if let Some(blend_mapping) = blend_map.get_mapping(color) {
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 = blend_mapping[*dest as usize];
dest = dest.add(self.width as usize);
}
}
}
} else {
self.vert_line(x, y1, y2, color);
}
}
/// 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: u8) {
// 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;
}
// top line, only if y1 was originally within bounds
if y1 == region.y {
unsafe {
let dest = self.pixels_at_mut_ptr_unchecked(region.x, region.y);
dest.write_bytes(color, region.width as usize);
}
}
// bottom line, only if y2 was originally within bounds
if y2 == region.bottom() {
unsafe {
let dest = self.pixels_at_mut_ptr_unchecked(region.x, region.bottom());
dest.write_bytes(color, region.width as usize);
}
}
// 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 = color;
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 = color;
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.
/// The box is drawn by blending the drawn pixels using the given blend map, or the color
/// specified if the blend map does not include this color.
pub fn blended_rect(&mut self, mut x1: i32, mut y1: i32, mut x2: i32, mut y2: i32, color: u8, blend_map: &BlendMap) {
if let Some(blend_mapping) = blend_map.get_mapping(color) {
// 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;
}
// note that since we're performing a blend based on the existing destination pixel,
// we need to make sure that we don't draw any overlapping corner pixels (where we
// would end up blending with the edge of a previously drawn line).
// to solve this issue, we just cut off the left-most and right-most pixels for the
// two horizontal lines drawn. those corner pixels will be drawn during the vertical
// line drawing instead.
// top line, only if y1 was originally within bounds
if y1 == region.y {
unsafe {
let dest = self.pixels_at_mut_unchecked(region.x, region.y);
for x in 1..(region.width - 1) as usize {
dest[x] = blend_mapping[dest[x] as usize];
}
}
}
// bottom line, only if y2 was originally within bounds
if y2 == region.bottom() {
unsafe {
let dest = self.pixels_at_mut_unchecked(region.x, region.bottom());
for x in 1..(region.width - 1) as usize {
dest[x] = blend_mapping[dest[x] as usize];
}
}
}
// 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 = blend_mapping[*dest as usize];
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 = blend_mapping[*dest as usize];
dest = dest.add(self.width as usize);
}
}
}
} else {
self.rect(x1, y1, x2, y2, color);
}
}
/// 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: u8) {
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 {
dest.write_bytes(color, region.width as usize);
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. The
/// filled box is draw by blending the drawn pixels using the given blend map, or the color
/// specified if the blend map does not include this color.
pub fn blended_filled_rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: u8, blend_map: &BlendMap) {
if let Some(blend_mapping) = blend_map.get_mapping(color) {
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 {
for x in 0..region.width as usize {
let dest_x = dest.offset(x as isize);
*dest_x = blend_mapping[*dest_x as usize];
}
dest = dest.add(self.width as usize);
}
}
}
} else {
self.filled_rect(x1, y1, x2, y2, color);
}
}
/// 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: u8) {
// TODO: optimize
let mut x = 0;
let mut y = radius as i32;
let mut m = 5 - 4 * radius as i32;
while x <= y {
self.set_pixel(center_x + x, center_y + y, color);
self.set_pixel(center_x + x, center_y - y, color);
self.set_pixel(center_x - x, center_y + y, color);
self.set_pixel(center_x - x, center_y - y, color);
self.set_pixel(center_x + y, center_y + x, color);
self.set_pixel(center_x + y, center_y - x, color);
self.set_pixel(center_x - y, center_y + x, color);
self.set_pixel(center_x - y, center_y - x, color);
if m > 0 {
y -= 1;
m -= 8 * y;
}
x += 1;
m += 8 * x + 4;
}
}
/// Draws a filled circle formed by the center point and radius given.
pub fn filled_circle(&mut self, center_x: i32, center_y: i32, radius: u32, color: u8) {
// TODO: optimize
let mut x = 0;
let mut y = radius as i32;
let mut m = 5 - 4 * radius as i32;
while x <= y {
self.horiz_line(center_x - x, center_x + x, center_y - y, color);
self.horiz_line(center_x - y, center_x + y, center_y - x, color);
self.horiz_line(center_x - y, center_x + y, center_y + x, color);
self.horiz_line(center_x - x, center_x + x, center_y + y, color);
if m > 0 {
y -= 1;
m -= 8 * y;
}
x += 1;
m += 8 * x + 4;
}
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[rustfmt::skip]
#[test]
pub fn set_and_get_pixel() {
let mut bmp = Bitmap::new(8, 8).unwrap();
assert_eq!(None, bmp.get_pixel(-1, -1));
assert_eq!(0, bmp.get_pixel(0, 0).unwrap());
bmp.set_pixel(0, 0, 7);
assert_eq!(7, bmp.get_pixel(0, 0).unwrap());
assert_eq!(
bmp.pixels(),
&[
7, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
]
);
assert_eq!(0, bmp.get_pixel(2, 4).unwrap());
bmp.set_pixel(2, 4, 5);
assert_eq!(5, bmp.get_pixel(2, 4).unwrap());
assert_eq!(
bmp.pixels(),
&[
7, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 5, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
]
);
}
#[rustfmt::skip]
#[test]
pub fn set_and_get_pixel_unchecked() {
let mut bmp = Bitmap::new(8, 8).unwrap();
assert_eq!(0, unsafe { bmp.get_pixel_unchecked(0, 0) });
unsafe { bmp.set_pixel_unchecked(0, 0, 7) };
assert_eq!(7, unsafe { bmp.get_pixel_unchecked(0, 0) });
assert_eq!(
bmp.pixels(),
&[
7, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
]
);
assert_eq!(0, unsafe { bmp.get_pixel_unchecked(2, 4) });
unsafe { bmp.set_pixel_unchecked(2, 4, 5) };
assert_eq!(5, unsafe { bmp.get_pixel_unchecked(2, 4) });
assert_eq!(
bmp.pixels(),
&[
7, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 5, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
]
);
}
}

View file

@ -1,8 +0,0 @@
//! This module and all sub-modules contain graphics functionality that uses indexed colours. That is, each pixel
//! is a `u8` and treated as index into a [`Palette`], so 256 maximum colours are possible.
pub mod bitmap;
pub mod blendmap;
pub mod palette;
pub mod prelude;

View file

@ -1,22 +0,0 @@
// include all things useable for indexed colour graphics
pub use crate::graphics::{
bitmap::*,
bitmapatlas::*,
color::*,
font::*,
indexed::{
*,
bitmap::{
*,
blit::*,
gif::*,
iff::*,
pcx::*,
primitives::*,
},
blendmap::*,
palette::*,
},
Pixel,
};

View file

@ -4,11 +4,11 @@ pub mod bitmap;
pub mod bitmapatlas; pub mod bitmapatlas;
pub mod color; pub mod color;
pub mod font; pub mod font;
pub mod indexed; pub mod blendmap;
pub mod rgb; pub mod palette;
pub mod prelude; pub mod prelude;
/// Common trait to represent single pixel/colour values. /// Common trait to represent single pixel/colour values.
pub trait Pixel: PrimInt + Unsigned {} pub trait Pixel: PrimInt + Unsigned + Default {}
impl<T> Pixel for T where T: PrimInt + Unsigned {} impl<T> Pixel for T where T: PrimInt + Unsigned + Default {}

View file

@ -7,8 +7,8 @@ use std::path::Path;
use byteorder::{ReadBytesExt, WriteBytesExt}; use byteorder::{ReadBytesExt, WriteBytesExt};
use thiserror::Error; use thiserror::Error;
use crate::graphics::bitmap::indexed::IndexedBitmap;
use crate::graphics::color::{from_rgb32, lerp_rgb32, to_rgb32}; use crate::graphics::color::{from_rgb32, lerp_rgb32, to_rgb32};
use crate::graphics::indexed::bitmap::Bitmap;
use crate::NUM_COLORS; use crate::NUM_COLORS;
use crate::utils::abs_diff; use crate::utils::abs_diff;
@ -16,7 +16,7 @@ use crate::utils::abs_diff;
pub trait ColorRange: RangeBounds<u8> + Iterator<Item=u8> {} pub trait ColorRange: RangeBounds<u8> + Iterator<Item=u8> {}
impl<T> ColorRange for T where T: RangeBounds<u8> + Iterator<Item=u8> {} impl<T> ColorRange for T where T: RangeBounds<u8> + Iterator<Item=u8> {}
pub static VGA_PALETTE_BYTES: &[u8] = include_bytes!("../../../assets/vga.pal"); pub static VGA_PALETTE_BYTES: &[u8] = include_bytes!("../../assets/vga.pal");
// vga bios (0-63) format // vga bios (0-63) format
fn read_palette_6bit<T: ReadBytesExt>( fn read_palette_6bit<T: ReadBytesExt>(
@ -484,7 +484,7 @@ impl Palette {
/// pixel is one of the colors from this palette, in ascending order, left-to-right, /// pixel is one of the colors from this palette, in ascending order, left-to-right,
/// top-to-bottom. The coordinates given specify the top-left coordinate on the destination /// top-to-bottom. The coordinates given specify the top-left coordinate on the destination
/// bitmap to begin drawing the palette at. /// bitmap to begin drawing the palette at.
pub fn draw(&self, dest: &mut Bitmap, x: i32, y: i32) { pub fn draw(&self, dest: &mut IndexedBitmap, x: i32, y: i32) {
let mut color = 0; let mut color = 0;
for yd in 0..16 { for yd in 0..16 {
for xd in 0..16 { for xd in 0..16 {

View file

@ -1,9 +1,27 @@
pub use crate::graphics::{ pub use crate::graphics::{
*, *,
bitmap::*, bitmap::{
*,
blit::*,
general::*,
gif::*,
iff::*,
indexed::{
*,
blit::*,
primitives::*,
},
pcx::*,
primitives::*,
rgb::{
*,
blit::*,
},
},
bitmapatlas::*, bitmapatlas::*,
blendmap::*,
color::*, color::*,
font::*, font::*,
indexed::prelude::*, palette::*,
rgb::prelude::*,
}; };

View file

@ -1,4 +0,0 @@
//! This module and all sub-modules contain graphics functionality that uses 32-bit colour data. That is, each pixel
//! is a `u32` and contains full RGBA information.
pub mod prelude;

View file

@ -1,14 +0,0 @@
// include all things useable for 32-bit colour graphics
pub use crate::graphics::{
bitmap::*,
bitmapatlas::*,
color::*,
font::*,
Pixel,
rgb::{
*,
// todo
},
};

View file

@ -1,5 +1,3 @@
// to get everything this library has to offer, including all `SystemResources` implementations
pub use crate::{ pub use crate::{
*, *,
audio::prelude::*, audio::prelude::*,
@ -17,26 +15,3 @@ pub use crate::{
}, },
utils::prelude::*, utils::prelude::*,
}; };
// specific module preludes that can be used instead that grab everything relevant to a specific `SystemResources`
// implementation only, since most applications will only use one and not care about the rest
pub mod dos_like {
pub use crate::{
*,
audio::prelude::*,
base::*,
entities::*,
events::*,
graphics::indexed::prelude::*,
math::prelude::*,
states::*,
system::{
prelude::*,
res::{
dos_like::*,
},
},
utils::prelude::*,
};
}

View file

@ -1,5 +1,5 @@
use crate::graphics::bitmap::{GeneralBitmap, GeneralBlitMethod}; use crate::graphics::bitmap::general::{GeneralBitmap, GeneralBlitMethod};
use crate::graphics::indexed; use crate::graphics::bitmap::indexed::IndexedBitmap;
use crate::math::rect::Rect; use crate::math::rect::Rect;
use crate::system::input_devices::mouse::Mouse; use crate::system::input_devices::mouse::Mouse;
@ -205,8 +205,8 @@ where
} }
} }
impl DefaultMouseCursorBitmaps<indexed::bitmap::Bitmap> for CustomMouseCursor<indexed::bitmap::Bitmap> { impl DefaultMouseCursorBitmaps<IndexedBitmap> for CustomMouseCursor<IndexedBitmap> {
fn get_default() -> MouseCursorBitmap<indexed::bitmap::Bitmap> { fn get_default() -> MouseCursorBitmap<IndexedBitmap> {
#[rustfmt::skip] #[rustfmt::skip]
const CURSOR_PIXELS: [u8; DEFAULT_MOUSE_CURSOR_WIDTH * DEFAULT_MOUSE_CURSOR_HEIGHT] = [ const CURSOR_PIXELS: [u8; DEFAULT_MOUSE_CURSOR_WIDTH * DEFAULT_MOUSE_CURSOR_HEIGHT] = [
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
@ -227,7 +227,7 @@ impl DefaultMouseCursorBitmaps<indexed::bitmap::Bitmap> for CustomMouseCursor<in
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
]; ];
let mut cursor = indexed::bitmap::Bitmap::new( let mut cursor = IndexedBitmap::new(
DEFAULT_MOUSE_CURSOR_WIDTH as u32, DEFAULT_MOUSE_CURSOR_WIDTH as u32,
DEFAULT_MOUSE_CURSOR_HEIGHT as u32, DEFAULT_MOUSE_CURSOR_HEIGHT as u32,
).unwrap(); ).unwrap();

View file

@ -32,8 +32,8 @@ use crate::{DEFAULT_SCALE_FACTOR, SCREEN_HEIGHT, SCREEN_WIDTH};
use crate::audio::{Audio, TARGET_AUDIO_CHANNELS, TARGET_AUDIO_FREQUENCY}; use crate::audio::{Audio, TARGET_AUDIO_CHANNELS, TARGET_AUDIO_FREQUENCY};
use crate::audio::queue::AudioQueue; use crate::audio::queue::AudioQueue;
use crate::graphics::font::BitmaskFont; use crate::graphics::font::BitmaskFont;
use crate::graphics::indexed::bitmap::Bitmap; use crate::graphics::bitmap::indexed::IndexedBitmap;
use crate::graphics::indexed::palette::Palette; use crate::graphics::palette::Palette;
use crate::system::event::{SystemEvent, SystemEventHandler}; use crate::system::event::{SystemEvent, SystemEventHandler};
use crate::system::input_devices::InputDevice; use crate::system::input_devices::InputDevice;
use crate::system::input_devices::keyboard::Keyboard; use crate::system::input_devices::keyboard::Keyboard;
@ -140,7 +140,7 @@ impl SystemResourcesConfig for DosLikeConfig {
// create the Bitmap object that will be exposed to the application acting as the system // create the Bitmap object that will be exposed to the application acting as the system
// backbuffer // backbuffer
let framebuffer = match Bitmap::new(SCREEN_WIDTH, SCREEN_HEIGHT) { let framebuffer = match IndexedBitmap::new(SCREEN_WIDTH, SCREEN_HEIGHT) {
Ok(bmp) => bmp, Ok(bmp) => bmp,
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())), Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
}; };
@ -218,7 +218,7 @@ pub struct DosLike {
/// The primary backbuffer [`Bitmap`] that will be rendered to the screen whenever /// The primary backbuffer [`Bitmap`] that will be rendered to the screen whenever
/// [`System::display`] is called. Regardless of the actual window size, this bitmap is always /// [`System::display`] is called. Regardless of the actual window size, this bitmap is always
/// [`SCREEN_WIDTH`]x[`SCREEN_HEIGHT`] pixels in size. /// [`SCREEN_WIDTH`]x[`SCREEN_HEIGHT`] pixels in size.
pub video: Bitmap, pub video: IndexedBitmap,
/// A pre-loaded [`Font`] that can be used for text rendering. /// A pre-loaded [`Font`] that can be used for text rendering.
pub font: BitmaskFont, pub font: BitmaskFont,
@ -233,7 +233,7 @@ pub struct DosLike {
/// Manages custom mouse cursor graphics and state. Use this to set/unset a custom mouse cursor bitmap. /// Manages custom mouse cursor graphics and state. Use this to set/unset a custom mouse cursor bitmap.
/// When set, rendering should occur automatically during calls to [`SystemResources::display`]. /// When set, rendering should occur automatically during calls to [`SystemResources::display`].
pub cursor: CustomMouseCursor<Bitmap>, pub cursor: CustomMouseCursor<IndexedBitmap>,
} }
impl std::fmt::Debug for DosLike { impl std::fmt::Debug for DosLike {

File diff suppressed because it is too large Load diff