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:
parent
2b414072bc
commit
07f2b13f68
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use ggdt::prelude::dos_like::*;
|
||||
use ggdt::prelude::*;
|
||||
|
||||
use crate::Core;
|
||||
use crate::entities::*;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::path::Path;
|
||||
|
||||
use ggdt::prelude::dos_like::*;
|
||||
use ggdt::prelude::*;
|
||||
|
||||
use crate::entities::*;
|
||||
use crate::Game;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::{Context, Result};
|
||||
|
||||
use ggdt::prelude::dos_like::*;
|
||||
use ggdt::prelude::*;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
|
||||
use ggdt::prelude::dos_like::*;
|
||||
use ggdt::prelude::*;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let config = DosLikeConfig::new();
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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)]
|
|
@ -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)
|
||||
}
|
|
@ -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);
|
|
@ -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);
|
551
ggdt/src/graphics/bitmap/indexed/blit.rs
Normal file
551
ggdt/src/graphics/bitmap/indexed/blit.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
46
ggdt/src/graphics/bitmap/indexed/mod.rs
Normal file
46
ggdt/src/graphics/bitmap/indexed/mod.rs
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
243
ggdt/src/graphics/bitmap/indexed/primitives.rs
Normal file
243
ggdt/src/graphics/bitmap/indexed/primitives.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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);
|
392
ggdt/src/graphics/bitmap/primitives.rs
Normal file
392
ggdt/src/graphics/bitmap/primitives.rs
Normal 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,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
175
ggdt/src/graphics/bitmap/rgb/blit.rs
Normal file
175
ggdt/src/graphics/bitmap/rgb/blit.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
9
ggdt/src/graphics/bitmap/rgb/mod.rs
Normal file
9
ggdt/src/graphics/bitmap/rgb/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
use crate::graphics::bitmap::Bitmap;
|
||||
|
||||
pub mod blit;
|
||||
|
||||
pub type RgbaBitmap = Bitmap<u32>;
|
||||
|
||||
impl RgbaBitmap {
|
||||
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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,
|
||||
};
|
|
@ -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 {}
|
||||
|
|
|
@ -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 {
|
|
@ -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::*,
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -1,14 +0,0 @@
|
|||
// include all things useable for 32-bit colour graphics
|
||||
|
||||
pub use crate::graphics::{
|
||||
bitmap::*,
|
||||
bitmapatlas::*,
|
||||
color::*,
|
||||
font::*,
|
||||
Pixel,
|
||||
rgb::{
|
||||
*,
|
||||
// todo
|
||||
},
|
||||
};
|
||||
|
|
@ -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::*,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue