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 ggdt::prelude::dos_like::*;
use ggdt::prelude::*;
#[derive(Debug, Copy, Clone)]
struct AudioChannelStatus {

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
use ggdt::prelude::dos_like::*;
use ggdt::prelude::*;
use crate::*;
@ -10,7 +10,7 @@ pub struct Context {
pub delta: f32,
pub system: System<DosLike>,
pub font: BitmaskFont,
pub sprites: Vec<Bitmap>,
pub sprites: Vec<IndexedBitmap>,
pub entities: Entities,
pub event_publisher: EventPublisher<Event>,
}
@ -25,14 +25,14 @@ impl Game {
pub fn new(mut system: System<DosLike>) -> Result<Self> {
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();
let mut sprites = Vec::new();
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(
BlitMethod::Solid,
IndexedBlitMethod::Solid,
&balls_bmp,
&Rect::new(i as i32 * BALL_SIZE as i32, 0, BALL_SIZE as u32, BALL_SIZE as u32),
0,

View file

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

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::path::Path;
use std::rc::Rc;
use ggdt::prelude::dos_like::*;
use ggdt::prelude::*;
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 Sprite {
pub atlas: Rc<BitmapAtlas<Bitmap>>,
pub atlas: Rc<BitmapAtlas<IndexedBitmap>>,
pub index: usize,
}
@ -314,7 +314,7 @@ pub struct SpriteIndexByDirection {
}
pub struct Weapon {
pub atlas: Rc<BitmapAtlas<Bitmap>>,
pub atlas: Rc<BitmapAtlas<IndexedBitmap>>,
pub base_index: usize,
pub offsets: [Vector2; 4],
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::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
// and render these entities with a proper y-based sort order
for (entity, _) in sprites.iter() {
let mut blit_method = BlitMethod::Transparent(0);
let mut blit_method = IndexedBlitMethod::Transparent(0);
// check for flicker effects
if let Some(flicker) = timed_flickers.get(entity) {
@ -673,7 +673,7 @@ fn render_system_sprites(context: &mut Core) {
continue;
}
FlickerMethod::Color(draw_color) => {
blit_method = BlitMethod::TransparentSingle {
blit_method = IndexedBlitMethod::TransparentSingle {
transparent_color: 0,
draw_color,
};

View file

@ -6,7 +6,7 @@ use std::rc::Rc;
use anyhow::{Context, Result};
use ggdt::prelude::dos_like::*;
use ggdt::prelude::*;
use crate::entities::*;
use crate::states::*;
@ -29,24 +29,24 @@ pub struct Core {
pub event_publisher: EventPublisher<Event>,
pub palette: Palette,
pub fade_out_palette: Palette,
pub tiles: Rc<BitmapAtlas<Bitmap>>,
pub hero_male: Rc<BitmapAtlas<Bitmap>>,
pub hero_female: Rc<BitmapAtlas<Bitmap>>,
pub green_slime: Rc<BitmapAtlas<Bitmap>>,
pub blue_slime: Rc<BitmapAtlas<Bitmap>>,
pub orange_slime: Rc<BitmapAtlas<Bitmap>>,
pub fist: Rc<BitmapAtlas<Bitmap>>,
pub sword: Rc<BitmapAtlas<Bitmap>>,
pub particles: Rc<BitmapAtlas<Bitmap>>,
pub items: Rc<BitmapAtlas<Bitmap>>,
pub ui: Rc<BitmapAtlas<Bitmap>>,
pub tiles: Rc<BitmapAtlas<IndexedBitmap>>,
pub hero_male: Rc<BitmapAtlas<IndexedBitmap>>,
pub hero_female: Rc<BitmapAtlas<IndexedBitmap>>,
pub green_slime: Rc<BitmapAtlas<IndexedBitmap>>,
pub blue_slime: Rc<BitmapAtlas<IndexedBitmap>>,
pub orange_slime: Rc<BitmapAtlas<IndexedBitmap>>,
pub fist: Rc<BitmapAtlas<IndexedBitmap>>,
pub sword: Rc<BitmapAtlas<IndexedBitmap>>,
pub particles: Rc<BitmapAtlas<IndexedBitmap>>,
pub items: Rc<BitmapAtlas<IndexedBitmap>>,
pub ui: Rc<BitmapAtlas<IndexedBitmap>>,
pub tilemap: TileMap,
pub slime_activity_states: Rc<HashMap<EntityActivity, Rc<AnimationDef>>>,
pub hero_activity_states: Rc<HashMap<EntityActivity, Rc<AnimationDef>>>,
pub poof1_animation_def: Rc<AnimationDef>,
pub poof2_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 {

View file

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

View file

@ -2,7 +2,7 @@ use std::path::Path;
use anyhow::{Context, Result};
use ggdt::prelude::dos_like::*;
use ggdt::prelude::*;
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))
}
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 mut atlas = BitmapAtlas::new(bmp);
atlas.add_grid(TILE_WIDTH, TILE_HEIGHT)?;
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 atlas = BitmapAtlas::new(bmp);
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);
// corners
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[2], left, top);
dest.blit_region(BlitMethod::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(BlitMethod::Transparent(0), &ui.bitmap(), &ui[5], right - 8, bottom - 8);
dest.blit_region(IndexedBlitMethod::Transparent(0), &ui.bitmap(), &ui[2], left, top);
dest.blit_region(IndexedBlitMethod::Transparent(0), &ui.bitmap(), &ui[3], right - 8, top);
dest.blit_region(IndexedBlitMethod::Transparent(0), &ui.bitmap(), &ui[4], left, bottom - 8);
dest.blit_region(IndexedBlitMethod::Transparent(0), &ui.bitmap(), &ui[5], right - 8, bottom - 8);
// top and bottom edges
for i in 0..((right - left) / 8) - 2 {
let x = left + 8 + (i * 8);
dest.blit_region(BlitMethod::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[9], x, top);
dest.blit_region(IndexedBlitMethod::Transparent(0), &ui.bitmap(), &ui[8], x, bottom - 8);
}
// left and right edges
for i in 0..((bottom - top) / 8) - 2 {
let y = top + 8 + (i * 8);
dest.blit_region(BlitMethod::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[6], left, 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 serde::Deserialize;
use ggdt::prelude::dos_like::*;
use ggdt::prelude::*;
use crate::{TILE_HEIGHT, TILE_WIDTH};
@ -61,7 +61,7 @@ impl TileMap {
&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 yt = camera_y / TILE_HEIGHT as i32;
let xp = camera_x % TILE_WIDTH as i32;
@ -75,11 +75,11 @@ impl TileMap {
let lower = self.layers[0][index];
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];
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 ggdt::prelude::dos_like::*;
use ggdt::prelude::*;
//////////////////////////////////////////////////////////////////////////////////////////////////

View file

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

View file

@ -1,9 +1,9 @@
use criterion::{black_box, Criterion, criterion_group, criterion_main};
use ggdt::prelude::dos_like::*;
use ggdt::prelude::*;
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 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 ggdt::prelude::dos_like::*;
use ggdt::prelude::*;
pub fn criterion_benchmark(c: &mut Criterion) {
let mut framebuffer = Bitmap::new(320, 240).unwrap();
let (bmp, _) = Bitmap::load_iff_file(Path::new("./test-assets/test-tiles.lbm")).unwrap();
let mut framebuffer = IndexedBitmap::new(320, 240).unwrap();
let (bmp, _) = IndexedBitmap::load_iff_file(Path::new("./test-assets/test-tiles.lbm")).unwrap();
let mut solid_bmp = Bitmap::new(16, 16).unwrap();
solid_bmp.blit_region(BlitMethod::Solid, &bmp, &Rect::new(16, 16, 16, 16), 0, 0);
let mut trans_bmp = Bitmap::new(16, 16).unwrap();
trans_bmp.blit_region(BlitMethod::Solid, &bmp, &Rect::new(160, 0, 16, 16), 0, 0);
let mut solid_bmp = IndexedBitmap::new(16, 16).unwrap();
solid_bmp.blit_region(IndexedBlitMethod::Solid, &bmp, &Rect::new(16, 16, 16, 16), 0, 0);
let mut trans_bmp = IndexedBitmap::new(16, 16).unwrap();
trans_bmp.blit_region(IndexedBlitMethod::Solid, &bmp, &Rect::new(160, 0, 16, 16), 0, 0);
//////
c.bench_function("blit_single_checked_solid", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::Solid),
black_box(IndexedBlitMethod::Solid),
black_box(&solid_bmp),
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| {
b.iter(|| unsafe {
framebuffer.blit_unchecked(
black_box(BlitMethod::Solid),
black_box(IndexedBlitMethod::Solid),
black_box(&solid_bmp),
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| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::Transparent(0)),
black_box(IndexedBlitMethod::Transparent(0)),
black_box(&trans_bmp),
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| {
b.iter(|| unsafe {
framebuffer.blit_unchecked(
black_box(BlitMethod::Transparent(0)),
black_box(IndexedBlitMethod::Transparent(0)),
black_box(&trans_bmp),
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| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::SolidFlipped {
black_box(IndexedBlitMethod::SolidFlipped {
horizontal_flip: false,
vertical_flip: false,
}),
@ -80,7 +80,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_solid_flipped_horizontally", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::SolidFlipped {
black_box(IndexedBlitMethod::SolidFlipped {
horizontal_flip: true,
vertical_flip: false,
}),
@ -94,7 +94,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_solid_flipped_vertically", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::SolidFlipped {
black_box(IndexedBlitMethod::SolidFlipped {
horizontal_flip: false,
vertical_flip: true,
}),
@ -108,7 +108,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_solid_flipped_horizontally_and_vertically", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::SolidFlipped {
black_box(IndexedBlitMethod::SolidFlipped {
horizontal_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| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::TransparentFlipped {
black_box(IndexedBlitMethod::TransparentFlipped {
transparent_color: 0,
horizontal_flip: false,
vertical_flip: false,
@ -139,7 +139,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_flipped_horizontally", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::TransparentFlipped {
black_box(IndexedBlitMethod::TransparentFlipped {
transparent_color: 0,
horizontal_flip: true,
vertical_flip: false,
@ -154,7 +154,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_flipped_vertically", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::TransparentFlipped {
black_box(IndexedBlitMethod::TransparentFlipped {
transparent_color: 0,
horizontal_flip: false,
vertical_flip: true,
@ -169,7 +169,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_flipped_horizontally_and_vertically", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::TransparentFlipped {
black_box(IndexedBlitMethod::TransparentFlipped {
transparent_color: 0,
horizontal_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| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::TransparentFlippedSingle {
black_box(IndexedBlitMethod::TransparentFlippedSingle {
transparent_color: 0,
horizontal_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| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::TransparentFlippedSingle {
black_box(IndexedBlitMethod::TransparentFlippedSingle {
transparent_color: 0,
horizontal_flip: true,
vertical_flip: false,
@ -218,7 +218,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_single_flipped_vertically", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::TransparentFlippedSingle {
black_box(IndexedBlitMethod::TransparentFlippedSingle {
transparent_color: 0,
horizontal_flip: false,
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| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::TransparentFlippedSingle {
black_box(IndexedBlitMethod::TransparentFlippedSingle {
transparent_color: 0,
horizontal_flip: true,
vertical_flip: true,
@ -252,7 +252,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_single", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::TransparentSingle {
black_box(IndexedBlitMethod::TransparentSingle {
transparent_color: 0,
draw_color: 17,
}),
@ -268,7 +268,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_offset", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::TransparentOffset {
black_box(IndexedBlitMethod::TransparentOffset {
transparent_color: 0,
offset: 42,
}),
@ -284,7 +284,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_offset_flipped_not_flipped", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::TransparentFlippedOffset {
black_box(IndexedBlitMethod::TransparentFlippedOffset {
transparent_color: 0,
horizontal_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| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::TransparentFlippedOffset {
black_box(IndexedBlitMethod::TransparentFlippedOffset {
transparent_color: 0,
horizontal_flip: true,
vertical_flip: false,
@ -316,7 +316,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_transparent_offset_flipped_vertically", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::TransparentFlippedOffset {
black_box(IndexedBlitMethod::TransparentFlippedOffset {
transparent_color: 0,
horizontal_flip: false,
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| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::TransparentFlippedOffset {
black_box(IndexedBlitMethod::TransparentFlippedOffset {
transparent_color: 0,
horizontal_flip: true,
vertical_flip: true,
@ -350,7 +350,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_solid_offset", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::SolidOffset(42)),
black_box(IndexedBlitMethod::SolidOffset(42)),
black_box(&solid_bmp),
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| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::SolidFlippedOffset {
black_box(IndexedBlitMethod::SolidFlippedOffset {
horizontal_flip: false,
vertical_flip: false,
offset: 42,
@ -378,7 +378,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_solid_offset_flipped_horizontally", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::SolidFlippedOffset {
black_box(IndexedBlitMethod::SolidFlippedOffset {
horizontal_flip: true,
vertical_flip: false,
offset: 42,
@ -393,7 +393,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_solid_offset_flipped_vertically", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::SolidFlippedOffset {
black_box(IndexedBlitMethod::SolidFlippedOffset {
horizontal_flip: false,
vertical_flip: true,
offset: 42,
@ -408,7 +408,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_solid_offset_flipped_horizontally_and_vertically", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::SolidFlippedOffset {
black_box(IndexedBlitMethod::SolidFlippedOffset {
horizontal_flip: true,
vertical_flip: true,
offset: 42,
@ -425,7 +425,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_rotozoom", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::RotoZoom {
black_box(IndexedBlitMethod::RotoZoom {
angle: 73.0f32.to_radians(),
scale_x: 1.2,
scale_y: 0.8,
@ -442,7 +442,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_rotozoom_offset", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::RotoZoomOffset {
black_box(IndexedBlitMethod::RotoZoomOffset {
angle: 73.0f32.to_radians(),
scale_x: 1.2,
scale_y: 0.8,
@ -460,7 +460,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_rotozoom_transparent", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::RotoZoomTransparent {
black_box(IndexedBlitMethod::RotoZoomTransparent {
angle: 73.0f32.to_radians(),
scale_x: 1.2,
scale_y: 0.8,
@ -478,7 +478,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("blit_rotozoom_offset_transparent", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::RotoZoomTransparentOffset {
black_box(IndexedBlitMethod::RotoZoomTransparentOffset {
angle: 73.0f32.to_radians(),
scale_x: 1.2,
scale_y: 0.8,

View file

@ -2,7 +2,7 @@ use std::io::Cursor;
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 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| {
b.iter(|| {
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| {
b.iter(|| {
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::bitmapatlas::BitmapAtlas;
use crate::graphics::indexed::bitmap::Bitmap;
use crate::graphics::indexed::blendmap::BlendMap;
use crate::graphics::bitmap::Bitmap;
use crate::graphics::Pixel;
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
/// 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
@ -212,8 +92,8 @@ pub fn clip_blit(
}
#[inline]
fn get_flipped_blit_properties(
src: &Bitmap,
fn get_flipped_blit_properties<PixelType: Pixel>(
src: &Bitmap<PixelType>,
src_region: &Rect,
horizontal_flip: bool,
vertical_flip: bool,
@ -249,13 +129,13 @@ fn get_flipped_blit_properties(
}
#[inline]
unsafe fn per_pixel_blit(
dest: &mut Bitmap,
src: &Bitmap,
pub unsafe fn per_pixel_blit<PixelType: Pixel>(
dest: &mut Bitmap<PixelType>,
src: &Bitmap<PixelType>,
src_region: &Rect,
dest_x: 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 dest_next_row_inc = (dest.width - src_region.width) as usize;
@ -275,15 +155,15 @@ unsafe fn per_pixel_blit(
}
#[inline]
unsafe fn per_pixel_flipped_blit(
dest: &mut Bitmap,
src: &Bitmap,
pub unsafe fn per_pixel_flipped_blit<PixelType: Pixel>(
dest: &mut Bitmap<PixelType>,
src: &Bitmap<PixelType>,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
horizontal_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 (x_inc, src_start_x, src_start_y, src_next_row_inc) =
@ -305,16 +185,16 @@ unsafe fn per_pixel_flipped_blit(
}
#[inline]
unsafe fn per_pixel_rotozoom_blit(
dest: &mut Bitmap,
src: &Bitmap,
pub unsafe fn per_pixel_rotozoom_blit<PixelType: Pixel>(
dest: &mut Bitmap<PixelType>,
src: &Bitmap<PixelType>,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
angle: f32,
scale_x: 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_height = src_region.height as f32 * scale_y;
@ -393,11 +273,11 @@ unsafe fn per_pixel_rotozoom_blit(
}
}
impl Bitmap {
pub unsafe fn solid_blit(&mut self, src: &Bitmap, src_region: &Rect, dest_x: i32, dest_y: i32) {
let src_row_length = src_region.width as usize;
let src_pitch = src.width as usize;
let dest_pitch = self.width as usize;
impl<PixelType: Pixel> Bitmap<PixelType> {
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 * Self::PIXEL_SIZE;
let src_pitch = src.width as usize * Self::PIXEL_SIZE;
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 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(
&mut self,
src: &Bitmap,
src: &Self,
src_region: &Rect,
dest_x: 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(
&mut self,
src: &Bitmap,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u8,
transparent_color: PixelType,
) {
per_pixel_blit(
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(
&mut self,
src: &Bitmap,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u8,
transparent_color: PixelType,
horizontal_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(
&mut self,
src: &Bitmap,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u8,
draw_color: u8,
transparent_color: PixelType,
draw_color: PixelType,
) {
per_pixel_blit(
self, src, src_region, dest_x, dest_y,
@ -648,14 +364,14 @@ impl Bitmap {
pub unsafe fn transparent_flipped_single_color_blit(
&mut self,
src: &Bitmap,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u8,
transparent_color: PixelType,
horizontal_flip: bool,
vertical_flip: bool,
draw_color: u8,
draw_color: PixelType,
) {
per_pixel_flipped_blit(
self, src, src_region, dest_x, dest_y, horizontal_flip, vertical_flip,
@ -669,7 +385,7 @@ impl Bitmap {
pub unsafe fn rotozoom_blit(
&mut self,
src: &Bitmap,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
@ -681,47 +397,20 @@ impl Bitmap {
self, src, src_region, dest_x, dest_y, angle, scale_x, scale_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 + 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(
&mut self,
src: &Bitmap,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u8,
transparent_color: PixelType,
) {
per_pixel_rotozoom_blit(
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)]

View file

@ -6,9 +6,12 @@
//!
//! Only a subset of the most common Bitmap drawing operations will be provided here.
use std::error::Error;
use crate::graphics::{indexed, Pixel};
use crate::graphics::bitmap::BitmapError;
use crate::graphics::bitmap::indexed::blit::IndexedBlitMethod;
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;
#[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.
pub trait GeneralBitmap: Sized + Clone {
type PixelType: Pixel;
type ErrorType: Error;
/// 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.
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 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)
}
#[inline]
fn width(&self) -> u32 {
self.width()
}
#[inline]
fn height(&self) -> u32 {
self.height()
}
@ -116,59 +115,48 @@ impl GeneralBitmap for indexed::bitmap::Bitmap {
self.clip_region()
}
#[inline]
fn full_bounds(&self) -> Rect {
self.full_bounds()
}
#[inline]
fn clear(&mut self, color: u8) {
self.clear(color);
fn clear(&mut self, color: Self::PixelType) {
self.clear(color)
}
#[inline]
fn set_pixel(&mut self, x: i32, y: i32, color: u8) {
self.set_pixel(x, y, color);
fn set_pixel(&mut self, x: i32, y: i32, color: Self::PixelType) {
self.set_pixel(x, y, color)
}
#[inline]
fn get_pixel(&self, x: i32, y: i32) -> Option<u8> {
fn get_pixel(&self, x: i32, y: i32) -> Option<Self::PixelType> {
self.get_pixel(x, y)
}
#[inline]
fn line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: u8) {
self.line(x1, y1, x2, y2, color);
fn line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: Self::PixelType) {
self.line(x1, y1, x2, y2, color)
}
#[inline]
fn horiz_line(&mut self, x1: i32, x2: i32, y: i32, color: u8) {
self.horiz_line(x1, x2, y, color);
fn horiz_line(&mut self, x1: i32, x2: i32, y: i32, color: Self::PixelType) {
self.horiz_line(x1, x2, y, color)
}
#[inline]
fn vert_line(&mut self, x: i32, y1: i32, y2: i32, color: u8) {
self.vert_line(x, y1, y2, color);
fn vert_line(&mut self, x: i32, y1: i32, y2: i32, color: Self::PixelType) {
self.vert_line(x, y1, y2, color)
}
#[inline]
fn rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: u8) {
self.rect(x1, y1, x2, y2, color);
fn rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: Self::PixelType) {
self.rect(x1, y1, x2, y2, color)
}
#[inline]
fn filled_rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: u8) {
self.filled_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)
}
#[inline]
fn circle(&mut self, center_x: i32, center_y: i32, radius: u32, color: u8) {
self.circle(center_x, center_y, radius, color);
fn circle(&mut self, center_x: i32, center_y: i32, radius: u32, color: Self::PixelType) {
self.circle(center_x, center_y, radius, color)
}
#[inline]
fn filled_circle(&mut self, center_x: i32, center_y: i32, radius: u32, color: u8) {
self.filled_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(
@ -179,11 +167,88 @@ impl GeneralBitmap for indexed::bitmap::Bitmap {
dest_x: i32,
dest_y: i32
) {
use indexed::bitmap::blit::BlitMethod;
let blit_method = match method {
GeneralBlitMethod::Solid => BlitMethod::Solid,
GeneralBlitMethod::Transparent(color) => BlitMethod::Transparent(color),
GeneralBlitMethod::Solid => IndexedBlitMethod::Solid,
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)
}

View file

@ -5,8 +5,8 @@ use std::path::Path;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use thiserror::Error;
use crate::graphics::indexed::bitmap::Bitmap;
use crate::graphics::indexed::palette::{Palette, PaletteError, PaletteFormat};
use crate::graphics::bitmap::indexed::IndexedBitmap;
use crate::graphics::palette::{Palette, PaletteError, PaletteFormat};
use crate::utils::lzwgif::{lzw_decode, lzw_encode, LzwError};
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,
gif_header: &GifHeader,
_graphic_control: &Option<GraphicControlExtension>,
) -> Result<(Bitmap, Option<Palette>), GifError> {
) -> Result<(IndexedBitmap, Option<Palette>), GifError> {
let descriptor = LocalImageDescriptor::read(reader)?;
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
}
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();
lzw_decode(reader, &mut writer)?;
@ -376,7 +376,7 @@ fn load_image_section<T: ReadBytesExt>(
fn save_image_section<T: WriteBytesExt>(
writer: &mut T,
bitmap: &Bitmap,
bitmap: &IndexedBitmap,
) -> Result<(), GifError> {
writer.write_u8(IMAGE_DESCRIPTOR_SEPARATOR)?;
let image_descriptor = LocalImageDescriptor {
@ -398,10 +398,10 @@ fn save_image_section<T: WriteBytesExt>(
Ok(())
}
impl Bitmap {
impl IndexedBitmap {
pub fn load_gif_bytes<T: ReadBytesExt>(
reader: &mut T,
) -> Result<(Bitmap, Palette), GifError> {
) -> Result<(IndexedBitmap, Palette), GifError> {
let header = GifHeader::read(reader)?;
if header.signature != *b"GIF" || header.version != *b"89a" {
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
}
let mut bitmap: Option<Bitmap> = None;
let mut bitmap: Option<IndexedBitmap> = None;
let mut current_graphic_control: Option<GraphicControlExtension> = None;
loop {
@ -482,7 +482,7 @@ impl Bitmap {
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 mut reader = BufReader::new(f);
Self::load_gif_bytes(&mut reader)
@ -557,9 +557,9 @@ pub mod tests {
use super::*;
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_2: &[u8] = include_bytes!("../../../../test-assets/test_large_bmp_pixels_raw2.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_2: &[u8] = include_bytes!("../../../test-assets/test_large_bmp_pixels_raw2.bin");
#[test]
fn load_and_save() -> Result<(), GifError> {
@ -568,7 +568,7 @@ pub mod tests {
.unwrap();
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.height());
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");
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.height());
assert_eq!(reloaded_bmp.pixels(), TEST_BMP_PIXELS_RAW);
@ -594,28 +594,28 @@ pub mod tests {
// 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!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
let save_path = tmp_dir.path().join("test_save.gif");
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!(200, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
// 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!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2);
let save_path = tmp_dir.path().join("test_save_2.gif");
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!(200, reloaded_bmp.height());
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 thiserror::Error;
use crate::graphics::indexed::bitmap::Bitmap;
use crate::graphics::indexed::palette::{Palette, PaletteError, PaletteFormat};
use crate::graphics::bitmap::indexed::IndexedBitmap;
use crate::graphics::palette::{Palette, PaletteError, PaletteFormat};
use crate::utils::packbits::{pack_bits, PackBitsError, unpack_bits};
#[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> {
let mut bitmap = Bitmap::new(bmhd.width as u32, bmhd.height as u32).unwrap();
fn load_planar_body<T: ReadBytesExt>(reader: &mut T, bmhd: &BMHDChunk) -> Result<IndexedBitmap, IffError> {
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 mut buffer = vec![0u8; row_bytes];
@ -297,8 +297,8 @@ fn load_planar_body<T: ReadBytesExt>(reader: &mut T, bmhd: &BMHDChunk) -> Result
Ok(bitmap)
}
fn load_chunky_body<T: ReadBytesExt>(reader: &mut T, bmhd: &BMHDChunk) -> Result<Bitmap, IffError> {
let mut bitmap = Bitmap::new(bmhd.width as u32, bmhd.height as u32).unwrap();
fn load_chunky_body<T: ReadBytesExt>(reader: &mut T, bmhd: &BMHDChunk) -> Result<IndexedBitmap, IffError> {
let mut bitmap = IndexedBitmap::new(bmhd.width as u32, bmhd.height as u32).unwrap();
for y in 0..bmhd.height {
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>(
writer: &mut T,
bitmap: &Bitmap,
bitmap: &IndexedBitmap,
bmhd: &BMHDChunk,
) -> Result<(), IffError> {
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>(
writer: &mut T,
bitmap: &Bitmap,
bitmap: &IndexedBitmap,
bmhd: &BMHDChunk,
) -> Result<(), IffError> {
for y in 0..bitmap.height() {
@ -367,10 +367,10 @@ fn write_chunky_body<T: WriteBytesExt>(
Ok(())
}
impl Bitmap {
impl IndexedBitmap {
pub fn load_iff_bytes<T: ReadBytesExt + Seek>(
reader: &mut T,
) -> Result<(Bitmap, Palette), IffError> {
) -> Result<(IndexedBitmap, Palette), IffError> {
let form_chunk = FormChunkHeader::read(reader)?;
if form_chunk.chunk_id.id != *b"FORM" {
return Err(IffError::BadFile(String::from(
@ -385,7 +385,7 @@ impl Bitmap {
let mut bmhd: Option<BMHDChunk> = None;
let mut palette: Option<Palette> = None;
let mut bitmap: Option<Bitmap> = None;
let mut bitmap: Option<IndexedBitmap> = None;
loop {
let header = match SubChunkHeader::read(reader) {
@ -447,7 +447,7 @@ impl Bitmap {
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 mut reader = BufReader::new(f);
Self::load_iff_bytes(&mut reader)
@ -565,9 +565,9 @@ mod tests {
use super::*;
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_2: &[u8] = include_bytes!("../../../../test-assets/test_large_bmp_pixels_raw2.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_2: &[u8] = include_bytes!("../../../test-assets/test_large_bmp_pixels_raw2.bin");
#[test]
pub fn load_and_save() -> Result<(), IffError> {
@ -578,7 +578,7 @@ mod tests {
// 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.height());
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");
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.height());
assert_eq!(reloaded_bmp.pixels(), TEST_BMP_PIXELS_RAW);
@ -594,7 +594,7 @@ mod tests {
// 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.height());
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");
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.height());
assert_eq!(reloaded_bmp.pixels(), TEST_BMP_PIXELS_RAW);
@ -617,56 +617,56 @@ mod tests {
// 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!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
let save_path = tmp_dir.path().join("test_save_pbm_image.lbm");
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!(200, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
// 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!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2);
let save_path = tmp_dir.path().join("test_save_pbm_image2.lbm");
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!(200, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2);
// 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!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
let save_path = tmp_dir.path().join("test_save_ilbm_image.lbm");
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!(200, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
// 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!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2);
let save_path = tmp_dir.path().join("test_save_ilbm_image2.lbm");
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!(200, reloaded_bmp.height());
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 crate::graphics::indexed::palette::Palette;
use crate::graphics::Pixel;
use crate::math::rect::Rect;
pub mod blit;
pub mod general;
pub mod gif;
pub mod iff;
pub mod indexed;
pub mod pcx;
pub mod primitives;
pub mod rgb;
#[derive(Error, Debug)]
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
/// clipping region is simply not performed / stops at the clipping boundary.
#[derive(Clone, Eq, PartialEq)]
pub struct Bitmap {
pub struct Bitmap<PixelType: Pixel> {
width: u32,
height: u32,
pixels: Box<[u8]>,
pixels: Box<[PixelType]>,
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 {
f.debug_struct("Bitmap")
.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.
///
/// # Arguments
@ -66,7 +68,7 @@ impl Bitmap {
/// * `height`: the height of the bitmap in pixels
///
/// 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 {
return Err(BitmapError::InvalidDimensions);
}
@ -74,7 +76,7 @@ impl Bitmap {
Ok(Bitmap {
width,
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 {
x: 0,
y: 0,
@ -93,36 +95,16 @@ impl Bitmap {
/// * `region`: the region on the source bitmap to copy from
///
/// 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) {
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) };
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.
#[inline]
pub fn width(&self) -> u32 {
@ -183,13 +165,13 @@ impl Bitmap {
/// Returns a reference to the raw pixels in this bitmap.
#[inline]
pub fn pixels(&self) -> &[u8] {
pub fn pixels(&self) -> &[PixelType] {
&self.pixels
}
/// Returns a mutable reference to the raw pixels in this bitmap.
#[inline]
pub fn pixels_mut(&mut self) -> &mut [u8] {
pub fn pixels_mut(&mut self) -> &mut [PixelType] {
&mut self.pixels
}
@ -197,7 +179,7 @@ impl Bitmap {
/// 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.
#[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) {
let offset = self.get_offset_to_xy(x, y);
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
/// outside the bitmap's current clipping region, None is returned.
#[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) {
let offset = self.get_offset_to_xy(x, y);
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
/// for validity, so it is up to you to ensure they lie within the bounds of the bitmap.
#[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);
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
/// 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.
#[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);
slice::from_raw_parts_mut(
std::slice::from_raw_parts_mut(
self.pixels.as_mut_ptr().add(offset),
self.pixels.len() - offset,
)
@ -244,7 +226,7 @@ impl Bitmap {
/// coordinates. If the coordinates given are outside the bitmap's current clipping region,
/// None is returned.
#[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) {
let offset = self.get_offset_to_xy(x, y);
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
/// region, None is returned.
#[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) {
let offset = self.get_offset_to_xy(x, y);
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
/// ensure they lie within the bounds of the bitmap.
#[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);
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
/// to ensure they lie within the bounds of the bitmap.
#[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);
self.pixels.as_mut_ptr().add(offset)
}
@ -300,20 +282,6 @@ impl Bitmap {
&& (x <= self.clip_region.right())
&& (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)]
@ -344,10 +312,10 @@ pub mod tests {
#[test]
pub fn creation_and_sizing() {
assert_matches!(Bitmap::new(0, 0), Err(BitmapError::InvalidDimensions));
assert_matches!(Bitmap::new(16, 0), Err(BitmapError::InvalidDimensions));
assert_matches!(Bitmap::new(0, 32), Err(BitmapError::InvalidDimensions));
let bmp = Bitmap::new(16, 32).unwrap();
assert_matches!(Bitmap::<u8>::new(0, 0), Err(BitmapError::InvalidDimensions));
assert_matches!(Bitmap::<u8>::new(16, 0), Err(BitmapError::InvalidDimensions));
assert_matches!(Bitmap::<u8>::new(0, 32), Err(BitmapError::InvalidDimensions));
let bmp = Bitmap::<u8>::new(16, 32).unwrap();
assert_eq!(16, bmp.width());
assert_eq!(32, bmp.height());
assert_eq!(15, bmp.right());
@ -374,24 +342,24 @@ pub mod tests {
#[test]
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);
assert_matches!(
Bitmap::from(&bmp, &Rect::new(0, 0, 16, 16)),
Bitmap::<u8>::from(&bmp, &Rect::new(0, 0, 16, 16)),
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());
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());
}
#[test]
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!(19, bmp.get_offset_to_xy(19, 0));
assert_eq!(20, bmp.get_offset_to_xy(0, 1));
@ -402,7 +370,7 @@ pub mod tests {
#[test]
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(15, 0));
assert!(bmp.is_xy_visible(0, 7));
@ -452,7 +420,7 @@ pub mod tests {
#[test]
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);
assert_eq!(None, bmp.pixels_at(-1, -1));
@ -472,7 +440,7 @@ pub mod tests {
#[test]
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);
assert_eq!(None, bmp.pixels_at_mut(-1, -1));
@ -492,7 +460,7 @@ pub mod tests {
#[test]
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);
let offset = bmp.get_offset_to_xy(1, 1);
@ -510,7 +478,7 @@ pub mod tests {
#[test]
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);
let offset = bmp.get_offset_to_xy(1, 1);
@ -528,7 +496,7 @@ pub mod tests {
#[test]
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);
assert_eq!(None, unsafe { bmp.pixels_at_ptr(-1, -1) });
@ -546,7 +514,7 @@ pub mod tests {
#[test]
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);
assert_eq!(None, unsafe { bmp.pixels_at_mut_ptr(-1, -1) });
@ -564,7 +532,7 @@ pub mod tests {
#[test]
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);
let offset = bmp.get_offset_to_xy(1, 1);
@ -580,7 +548,7 @@ pub mod tests {
#[test]
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);
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 thiserror::Error;
use crate::graphics::bitmap::indexed::IndexedBitmap;
use crate::graphics::color::from_rgb32;
use crate::graphics::indexed::bitmap::Bitmap;
use crate::graphics::indexed::palette::{Palette, PaletteError, PaletteFormat};
use crate::graphics::palette::{Palette, PaletteError, PaletteFormat};
use crate::utils::bytes::ReadFixedLengthByteArray;
#[derive(Error, Debug)]
@ -104,10 +104,10 @@ fn write_pcx_data<T: WriteBytesExt>(
Ok(())
}
impl Bitmap {
impl IndexedBitmap {
pub fn load_pcx_bytes<T: ReadBytesExt + Seek>(
reader: &mut T,
) -> Result<(Bitmap, Palette), PcxError> {
) -> Result<(IndexedBitmap, Palette), PcxError> {
let header = PcxHeader::read(reader)?;
if header.manufacturer != 10 {
@ -140,7 +140,7 @@ impl Bitmap {
let width = (header.x2 + 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());
for _y in 0..height {
@ -194,7 +194,7 @@ impl Bitmap {
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 mut reader = BufReader::new(f);
Self::load_pcx_bytes(&mut reader)
@ -286,9 +286,9 @@ pub mod tests {
use super::*;
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_2: &[u8] = include_bytes!("../../../../test-assets/test_large_bmp_pixels_raw2.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_2: &[u8] = include_bytes!("../../../test-assets/test_large_bmp_pixels_raw2.bin");
#[test]
pub fn load_and_save() -> Result<(), PcxError> {
@ -297,7 +297,7 @@ pub mod tests {
.unwrap();
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.height());
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");
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.height());
assert_eq!(reloaded_bmp.pixels(), TEST_BMP_PIXELS_RAW);
@ -320,28 +320,28 @@ pub mod tests {
// 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!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
let save_path = tmp_dir.path().join("test_save.pcx");
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!(200, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
// 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!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2);
let save_path = tmp_dir.path().join("test_save_2.pcx");
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!(200, reloaded_bmp.height());
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 crate::graphics::bitmap::GeneralBitmap;
use crate::graphics::bitmap::general::GeneralBitmap;
use crate::math::rect::Rect;
#[derive(Error, Debug)]
@ -134,13 +134,13 @@ where
pub mod tests {
use claim::*;
use crate::graphics::indexed::bitmap::Bitmap;
use crate::graphics::bitmap::indexed::IndexedBitmap;
use super::*;
#[test]
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 rect = Rect::new(0, 0, 16, 16);
@ -174,7 +174,7 @@ pub mod tests {
#[test]
pub fn adding_grid() {
let bmp = Bitmap::new(64, 64).unwrap();
let bmp = IndexedBitmap::new(64, 64).unwrap();
let mut atlas = BitmapAtlas::new(bmp);
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 crate::graphics::color::{from_rgb32, luminance};
use crate::graphics::indexed::palette::Palette;
use crate::graphics::palette::Palette;
use crate::math::lerp;
use crate::utils::bytes::ReadFixedLengthByteArray;

View file

@ -5,7 +5,7 @@ use std::path::Path;
use byteorder::{ReadBytesExt, WriteBytesExt};
use thiserror::Error;
use crate::graphics::bitmap::GeneralBitmap;
use crate::graphics::bitmap::Bitmap;
use crate::graphics::Pixel;
use crate::math::rect::Rect;
@ -32,9 +32,9 @@ pub enum FontRenderOpts<PixelType: Pixel> {
pub trait Character {
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
BitmapType: GeneralBitmap;
PixelType: Pixel;
}
pub trait Font {
@ -60,9 +60,9 @@ impl Character for BitmaskCharacter {
&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
BitmapType: GeneralBitmap
PixelType: Pixel
{
// out of bounds check
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 color;
pub mod font;
pub mod indexed;
pub mod rgb;
pub mod blendmap;
pub mod palette;
pub mod prelude;
/// Common trait to represent single pixel/colour values.
pub trait Pixel: PrimInt + Unsigned {}
impl<T> Pixel for T where T: PrimInt + Unsigned {}
pub trait Pixel: PrimInt + Unsigned + Default {}
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 thiserror::Error;
use crate::graphics::bitmap::indexed::IndexedBitmap;
use crate::graphics::color::{from_rgb32, lerp_rgb32, to_rgb32};
use crate::graphics::indexed::bitmap::Bitmap;
use crate::NUM_COLORS;
use crate::utils::abs_diff;
@ -16,7 +16,7 @@ use crate::utils::abs_diff;
pub trait ColorRange: 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
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,
/// top-to-bottom. The coordinates given specify the top-left coordinate on the destination
/// 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;
for yd in 0..16 {
for xd in 0..16 {

View file

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

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::{
*,
audio::prelude::*,
@ -17,26 +15,3 @@ pub use crate::{
},
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::indexed;
use crate::graphics::bitmap::general::{GeneralBitmap, GeneralBlitMethod};
use crate::graphics::bitmap::indexed::IndexedBitmap;
use crate::math::rect::Rect;
use crate::system::input_devices::mouse::Mouse;
@ -205,8 +205,8 @@ where
}
}
impl DefaultMouseCursorBitmaps<indexed::bitmap::Bitmap> for CustomMouseCursor<indexed::bitmap::Bitmap> {
fn get_default() -> MouseCursorBitmap<indexed::bitmap::Bitmap> {
impl DefaultMouseCursorBitmaps<IndexedBitmap> for CustomMouseCursor<IndexedBitmap> {
fn get_default() -> MouseCursorBitmap<IndexedBitmap> {
#[rustfmt::skip]
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,
@ -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
];
let mut cursor = indexed::bitmap::Bitmap::new(
let mut cursor = IndexedBitmap::new(
DEFAULT_MOUSE_CURSOR_WIDTH as u32,
DEFAULT_MOUSE_CURSOR_HEIGHT as u32,
).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::queue::AudioQueue;
use crate::graphics::font::BitmaskFont;
use crate::graphics::indexed::bitmap::Bitmap;
use crate::graphics::indexed::palette::Palette;
use crate::graphics::bitmap::indexed::IndexedBitmap;
use crate::graphics::palette::Palette;
use crate::system::event::{SystemEvent, SystemEventHandler};
use crate::system::input_devices::InputDevice;
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
// backbuffer
let framebuffer = match Bitmap::new(SCREEN_WIDTH, SCREEN_HEIGHT) {
let framebuffer = match IndexedBitmap::new(SCREEN_WIDTH, SCREEN_HEIGHT) {
Ok(bmp) => bmp,
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
/// [`System::display`] is called. Regardless of the actual window size, this bitmap is always
/// [`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.
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.
/// 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 {

File diff suppressed because it is too large Load diff