Compare commits

..

59 commits

Author SHA1 Message Date
Gered 16eb66b219 address a number of cargo warnings and clippy lints
while also ignored some bullshit clippy lints
2024-09-07 21:21:49 -04:00
Gered 040e480dd0 fix events::adding_and_removing_listeners test failure in release mode
best guess is that release mode optimizations collapsed these two
functions into one, because in release mode the assertion that would
fail was:

    assert!(listeners.add(other_dummy_listener));

which would mean that `listeners.add()` thought it was trying to add
a duplicate listener function.

adding dummy println's is a simple way to ensure that the compiler
won't try to collapse these thinking that they are the same function.
2024-09-07 17:12:34 -04:00
Gered d9e26556e6 fix simd imports 2024-09-07 16:51:36 -04:00
Gered ed95332699 update toolchain 2024-09-07 16:51:24 -04:00
Gered c7d6bd8aef revert app_root_dir usages in examples. re-org example asset files
ultimately, cargo workspaces are kind of weird. you can do things like
`cargo run` from the top-level cargo.toml file and run an individual
sub-module via `--bin`, or you can go to that sub-module's directory
where it's own cargo.toml is, and `cargo run` directly from there.

the current working directory will be different in each case, and this
difference can be very annoying when you want to do seemingly-logical
things like organize sub-module "asset" files (e.g. images, and other
types of data files that aren't code) within that sub-module's
directory. in this case, how do you write code in that sub-module that
can load those asset files in a way that will work in BOTH of the
aforementioned scenarios for `cargo run`?

you can't really! some people use the `CARGO_MANIFEST_DIR` environment
variable that cargo sets when running any given cargo module, but this
is only a valid method for obtaining the module's root directory when
running from a cargo command ... which you won't be doing when running
within a debugger! likely you or your IDE invoked the debugger process
against the already built executable directly, so `CARGO_MANIFEST_DIR`
will not be set and you're back to square one!

super annoying!

as such, i am now giving up and am just doing what it seems like most
other cargo projects that utilize workspaces do ... place all the
assets for all sub-modules together in the same directory, relative
to the workspace root.

why go with this approach?

because it works under the most common scenarios (but NOT all!):
- running via `cargo run --bin` from the workspace root
- debugging via gdb/lldb etc using the workspace root as the cwd

these seem to be the most common ways to do each type of task from
any rust-equipped editor/IDE that i've seen so far.
2024-09-05 22:35:20 -04:00
Gered 4d359f3e3c update all explicit Path instance allocation and use
previously, there were a ton of places that were allocating new Path
instances via `Path::new` for the purpose of creating a path to be
passed into a function as a Path argument

this has now been simplified everywhere by updating all functions
and methods that previously took Path arguments to instead take
AsRef<Path> arguments, allowing much more flexibility in the types
of path arguments passed in
2024-09-04 00:31:30 -04:00
Gered a18aee300d ignore some clippy warnings 2024-09-03 00:06:52 -04:00
Gered 0cbd5366fd add app_root_dir helper function. integrate with all examples 2024-09-03 00:00:07 -04:00
Gered 74d7f2b22c use workspace.dependencies for common dependency versions 2024-07-21 16:07:33 -04:00
Gered 389e27afe0 add Copy trait to BitmaskCharacter 2023-11-11 19:34:41 -05:00
Gered 053f95a929 add more ability to create/modify BitmaskCharacter instances 2023-11-11 19:29:04 -05:00
Gered f864592361 add methods to bitmask font/char structs to allow creation of new fonts 2023-11-11 19:04:08 -05:00
Gered 39c88567b5 downgrade to flate2 v1.0.27 due to performance issues
dev/test builds that heavily use deflate (e.g. anything with png files)
perform *incredibly* slow with 1.0.28. also tried switching the backend
to zlib which didn't really speed things up too much.

i hate updating dependencies. *sigh*
2023-11-11 17:59:41 -05:00
Gered 9d02e23e9a explicitly set workspace resolver to address cargo warning
since we're using 2021 edition, resolver = 2 is the default, but this
apparently needs to be explicitly set at the workspace level
2023-11-11 13:54:28 -05:00
Gered 0f8377455c update dependencies 2023-11-11 13:52:58 -05:00
Gered 3e88e41265 helper method for clearing imgui window focus
so that no window is focused at all
2023-06-03 17:10:46 -04:00
Gered 6aabe45fbd minor cleanup 2023-06-01 10:34:10 -04:00
Gered 81de65c8d0 bump to more recent nightly channel
the previous "illegal instruction" issues that i was getting on release
builds seems to have been resolved
2023-05-30 19:22:41 -04:00
Gered 3beb0f3449 Merge remote-tracking branch 'origin/master' 2023-05-30 18:52:39 -04:00
Gered 8557c24d48 add image_region ui widget which allows specifying a texture sub-region 2023-05-30 18:51:29 -04:00
Gered d80720f772 update image ui widget to return a bool with its clicked state 2023-05-30 18:50:20 -04:00
Gered d903ca59b3 add methods to get current ImGui display size
i suppose there is not much benefit to using this versus equivalent
functionality on SystemResources (for example). however the dimensions
returned by these new methods do respect whatever display scaling is
being used by ImGui. and these are technically more convenient to use
in whatever place you're building up a UI in. meh.
2023-05-30 18:49:14 -04:00
Gered df62205e41 add convenience method for getting uv coords for BitmapAtlas tiles 2023-05-30 18:46:40 -04:00
Gered ad8d1f9ae3 i have no idea why this decided to throw a compile error now? wtf?
my wild-ass guess is this is somehow related to the serde dependency
being included now, but who the fuck knows. what a joke.
2023-05-29 14:30:00 -04:00
Gered f5291a4d60 add support for loading and saving BitmapAtlas "descriptor" json files
as well as support for directly instantiating a BitmapAtlas from
such a descriptor.

saving a BitmapAtlas to a descriptor file directly is not added (yet?)
as my gut feeling is that these files would probably be hand-written?
saving from a BitmapAtlas directly would (in a simple/naive impl)
always result in a long-ish list of "tiles" anyway, since grids are
turned into tiles. this further reinforces me feeling that you'd either
write these files by hand, or at the very least, just construct a
descriptor instance in code somehow and save that
2023-05-29 14:29:12 -04:00
Gered 4f3a629f5a simplify
how did i not see this. lol
2023-05-26 21:09:37 -04:00
Gered afa7a92709 add checks for valid rect dimensions in BitmapAtlas 2023-05-26 13:22:26 -04:00
Gered b03b8f4915 add tests for BitmapAtlas.add_grid which were apparently forgotten 2023-05-26 13:21:25 -04:00
Gered ec6d0f1b73 convenience method for creating a basic image widget 2023-05-26 12:12:45 -04:00
Gered 3bb12fdcee update imgui example with image widgets showing slime colors/bitmaps 2023-05-26 12:04:02 -04:00
Gered 73e64946f7 convenience method to clone single BitmapAtlas tiles as new bitmaps 2023-05-26 11:58:30 -04:00
Gered 4257985962 access to the imgui texture map from our own imgui wrapper struct 2023-05-26 11:20:58 -04:00
Gered f6ef1ee22e add support for resetting the imgui renderer's texture map 2023-05-26 11:11:27 -04:00
Gered 457aa757cf simplify 2023-05-05 18:15:55 -04:00
Gered 3a56926345 the big switch from ARGB to RGBA format for 32-bit color pixels 2023-05-05 18:14:37 -04:00
Gered 78d8102a23 rename color types to somewhat less "noisy" names
plus these are easier to type.

i do not anticipate needing structured color types for any other
type of components (e.g. i don't see a need for an ARGBu16x4, etc).
so i am fine with just having `ARGB` being associated with four u8's
with no real indication of this in the type name itself.
2023-05-05 15:56:29 -04:00
Gered ae178e1fa0 remove old argb color functions 2023-05-05 15:32:57 -04:00
Gered 02447867a9 add doc comments for the new color types 2023-05-05 14:41:27 -04:00
Gered 09dbb913a5 convert RgbaBitmap to use ARGBu8x4 as its pixel type 2023-05-04 18:54:37 -04:00
Gered cf92d42b84 update Pixel trait to just the bare minimum requirements
our target is IndexedBitmap's will use PixelType=u8 and
RgbaBitmap's will use PixelType=ARGBu8x4

thus, restricting the Pixel trait to primitive unsigned numerics won't
work anymore, but we do still need to support basic stuff like
equality testing and copy/clone support for our generic bitmap
operations
2023-05-03 16:57:13 -04:00
Gered 579398ea5e use manual impls of Debug and Display for ARGBu8x4 and ARGBf32x4
simply because i wanted a specific format, mostly for ARGBu8x4 and
the barebones hex format string
2023-05-03 16:42:26 -04:00
Gered 0eac3ad7a3 make ARGBu8x4 and ARGBf32x4 derive Default 2023-05-03 16:09:30 -04:00
Gered d12d7035ca add ARGBu8x4 to u32 conversion 2023-05-03 16:06:13 -04:00
Gered ae07b9cd37 add ARGBu8x4 -> byte casting helper traits/methods 2023-05-02 18:52:10 -04:00
Gered a7567b8c0f Merge branch 'master' into color_types 2023-05-01 16:25:02 -04:00
Gered 3b159a61b8 add inline annotations to some methods where i probably forgot before
these don't really affect performance in any meaningful way, according
to the benchmarks i just added. lol
2023-05-01 16:24:51 -04:00
Gered a0ec2f4826 add very simple benchmarks for drawing primitives with RgbaBitmaps
will need these for simple comparisons in the very near future
2023-05-01 16:23:44 -04:00
Gered 836e577bac add missing inline annotation 2023-04-30 17:15:22 -04:00
Gered 2843f87bdc add luminance and greyscale methods to ARGBu8x4 2023-04-30 17:14:36 -04:00
Gered 351a6c7e03 add u32 to ARGBf32x4 conversion 2023-04-30 17:08:01 -04:00
Gered 34c64cd1f9 use #[repr(transparent)] for both color types 2023-04-30 17:03:36 -04:00
Gered 1667ae2c43 add color tinting for ARGBu8x4 2023-04-30 16:59:24 -04:00
Gered 033081a49e add color blending for ARGBu8x4 2023-04-30 16:57:19 -04:00
Gered 448ce11dbc add color interpolation for ARGBu8x4 2023-04-30 16:41:38 -04:00
Gered 0112e99fc7 add color multiplication via operator overload for ARGBu8x4
of course, here we immediately must question whether this is abuse
of operator overloading or not. since you cannot otherwise multiply
these types together, i think this is fine. plus we are not providing
any other math operations via operator overloading, so this feels like
a very explicit thing to do
2023-04-30 16:38:04 -04:00
Gered 9ba65a9e37 remove ARGBu32 struct and color type traits 2023-04-30 16:26:20 -04:00
Gered 53ba5cdb65 doesn't seem like we actually need to use wrapping_shl here
i think i used it here due to a misunderstanding
2023-04-28 15:21:50 -04:00
Gered b5072bcb0f add traits for color type argb component access 2023-04-28 15:15:01 -04:00
Gered 4bef45dc6d add initial color types
this time via the "new type" idiom. the type aliasing i previously did
was, in retrospect, not a great idea
2023-04-27 18:31:41 -04:00
124 changed files with 2689 additions and 1946 deletions

View file

@ -4,6 +4,14 @@ members = [
"ggdt_imgui",
"examples/*",
]
resolver = "2"
[workspace.dependencies]
anyhow = "=1.0.75"
imgui = { version = "0.11.0", features = ["docking"] }
serde = { version = "1.0.192", features = ["derive"] }
serde_json = "1.0.108"
thiserror = "=1.0.50"
# some stuff is becoming noticably slow with default dev profile settings (opt-level = 0). especially any of the
# fancier triangle_2d drawing stuff. while there are many optimizations still left to be made, it seems like a good

View file

@ -4,5 +4,5 @@ version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "=1.0.55"
anyhow.workspace = true
ggdt = { path = "../../ggdt" }

View file

@ -11,15 +11,15 @@ struct AudioChannelStatus {
playing: bool,
}
fn load_and_convert_wav(path: &Path, target_spec: &AudioSpec) -> Result<AudioBuffer> {
let sound = AudioBuffer::load_wav_file(path)?;
fn load_and_convert_wav(path: impl AsRef<Path>, target_spec: &AudioSpec) -> Result<AudioBuffer> {
let sound = AudioBuffer::load_wav_file(&path)?;
let original_spec = *sound.spec();
let sound = sound.convert(target_spec)?;
let final_spec = *sound.spec();
if original_spec != final_spec {
println!("{:?} was converted from {:?} to {:?}", path, original_spec, final_spec);
println!("{:?} was converted from {:?} to {:?}", path.as_ref(), original_spec, final_spec);
} else {
println!("{:?} did not need to be converted from {:?}", path, original_spec);
println!("{:?} did not need to be converted from {:?}", path.as_ref(), original_spec);
}
Ok(sound)
}
@ -28,6 +28,7 @@ pub struct SineWaveGenerator {
t: usize,
}
#[allow(clippy::new_without_default)]
impl SineWaveGenerator {
pub fn new() -> Self {
SineWaveGenerator { t: 0 }
@ -58,11 +59,11 @@ fn main() -> Result<()> {
let mut volume = 1.0;
let sounds = [
load_and_convert_wav(Path::new("./assets/pickup-coin.wav"), system.res.audio.spec())?,
load_and_convert_wav(Path::new("./assets/powerup.wav"), system.res.audio.spec())?,
load_and_convert_wav(Path::new("./assets/explosion.wav"), system.res.audio.spec())?,
load_and_convert_wav(Path::new("./assets/jump.wav"), system.res.audio.spec())?,
load_and_convert_wav(Path::new("./assets/laser-shoot.wav"), system.res.audio.spec())?,
load_and_convert_wav("./assets/pickup-coin.wav", system.res.audio.spec())?,
load_and_convert_wav("./assets/powerup.wav", system.res.audio.spec())?,
load_and_convert_wav("./assets/explosion.wav", system.res.audio.spec())?,
load_and_convert_wav("./assets/jump.wav", system.res.audio.spec())?,
load_and_convert_wav("./assets/laser-shoot.wav", system.res.audio.spec())?,
];
let mut statuses = [AudioChannelStatus { size: 0, position: 0, playing: false }; NUM_CHANNELS];
@ -154,7 +155,7 @@ fn main() -> Result<()> {
for index in 0..NUM_CHANNELS {
let channel = &audio_device[index];
let mut status = &mut statuses[index];
let status = &mut statuses[index];
status.playing = channel.playing;
status.position = channel.position;
status.size = channel.data.len();
@ -183,8 +184,7 @@ fn main() -> Result<()> {
system.res.video.print_string("Audio Channels", 16, 32, FontRenderOpts::Color(14), &system.res.font);
let mut y = 48;
for index in 0..NUM_CHANNELS {
let status = &statuses[index];
for (index, status) in statuses.iter().enumerate() {
system.res.video.print_string(
&format!(
"channel {} - {} {}",

View file

@ -4,5 +4,5 @@ version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "=1.0.55"
anyhow.workspace = true
ggdt = { path = "../../ggdt" }

View file

@ -1,5 +1,3 @@
use std::path::Path;
use anyhow::Result;
use ggdt::prelude::*;
@ -26,7 +24,7 @@ fn main() -> Result<()> {
let font = BitmaskFont::new_vga_font()?;
let (balls_bmp, balls_palette) = IndexedBitmap::load_pcx_file(Path::new("./assets/balls.pcx"))?;
let (balls_bmp, balls_palette) = IndexedBitmap::load_pcx_file("./assets/balls.pcx")?;
system.res.palette = balls_palette.clone();
let mut sprites = Vec::<IndexedBitmap>::new();
@ -62,8 +60,7 @@ fn main() -> Result<()> {
}
if system.res.keyboard.is_key_up(Scancode::S) {
for i in 0..NUM_BALLS {
let ball = &mut balls[i];
for ball in balls.iter_mut() {
ball.x += ball.dir_x;
ball.y += ball.dir_y;
@ -72,11 +69,9 @@ fn main() -> Result<()> {
ball.dir_x = -ball.dir_x;
ball.x = 0;
}
} else {
if ball.x >= (system.res.video.width() - BALL_WIDTH) as i32 {
ball.dir_x = -ball.dir_x;
ball.x = (system.res.video.width() - BALL_WIDTH) as i32;
}
} else if ball.x >= (system.res.video.width() - BALL_WIDTH) as i32 {
ball.dir_x = -ball.dir_x;
ball.x = (system.res.video.width() - BALL_WIDTH) as i32;
}
if ball.dir_y < 0 {
@ -84,11 +79,9 @@ fn main() -> Result<()> {
ball.dir_y = -ball.dir_y;
ball.y = 0;
}
} else {
if ball.y >= (system.res.video.height() - BALL_HEIGHT) as i32 {
ball.dir_y = -ball.dir_y;
ball.y = (system.res.video.height() - BALL_HEIGHT) as i32;
}
} else if ball.y >= (system.res.video.height() - BALL_HEIGHT) as i32 {
ball.dir_y = -ball.dir_y;
ball.y = (system.res.video.height() - BALL_HEIGHT) as i32;
}
}
}

View file

@ -4,5 +4,5 @@ version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "=1.0.55"
anyhow.workspace = true
ggdt = { path = "../../ggdt", features = [] }

Binary file not shown.

View file

@ -114,8 +114,8 @@ fn update_system_collision(context: &mut Context) {
let mut velocities = context.entities.components_mut::<Velocity>();
for (entity, _) in bounceables.iter() {
let mut position = positions.get_mut(entity).unwrap();
let mut velocity = velocities.get_mut(entity).unwrap();
let position = positions.get_mut(entity).unwrap();
let velocity = velocities.get_mut(entity).unwrap();
let mut bounced = false;
if position.0.x as i32 <= 0 || position.0.x as i32 + BALL_SIZE >= context.system.res.video.right() as i32 {

View file

@ -1,5 +1,3 @@
use std::path::Path;
use anyhow::Result;
use ggdt::prelude::*;

View file

@ -25,7 +25,7 @@ impl Game {
pub fn new(mut system: System<DosLike>) -> Result<Self> {
let font = BitmaskFont::new_vga_font()?;
let (balls_bmp, balls_palette) = IndexedBitmap::load_pcx_file(Path::new("./assets/balls.pcx"))?;
let (balls_bmp, balls_palette) = IndexedBitmap::load_pcx_file("./assets/balls.pcx")?;
system.res.palette = balls_palette.clone();
let mut sprites = Vec::new();

View file

@ -4,9 +4,9 @@ version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "=1.0.55"
anyhow.workspace = true
ggdt = { path = "../../ggdt" }
ggdt_imgui = { path = "../../ggdt_imgui" }
imgui = "0.11.0"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
imgui.workspace = true
serde.workspace = true
serde_json.workspace = true

View file

@ -1,17 +1,19 @@
use crate::entities::{AnimationDef, EntityActivity, Event};
use std::collections::HashMap;
use std::rc::Rc;
use anyhow::Result;
use ggdt::prelude::*;
use crate::entities::{AnimationDef, EntityActivity, Event, SlimeColor};
use crate::support::{load_bitmap_atlas_autogrid, load_font, load_palette};
use crate::tilemap::TileMap;
use anyhow::Result;
use ggdt::prelude::*;
use std::collections::HashMap;
use std::path::Path;
use std::rc::Rc;
pub struct CoreContext {
pub delta: f32,
pub camera_x: i32,
pub camera_y: i32,
pub transparent_color: u32,
pub transparent_color: RGBA,
pub system: System<Standard>,
pub palette: Palette,
pub font: BitmaskFont,
@ -25,6 +27,7 @@ pub struct CoreContext {
pub tilemap: TileMap,
pub slime_activity_states: Rc<HashMap<EntityActivity, Rc<AnimationDef>>>,
pub sprite_render_list: Vec<(EntityId, Vector2, RgbaBlitMethod)>,
pub slime_texture_id_map: HashMap<SlimeColor, imgui::TextureId>,
}
impl CoreState<Standard> for CoreContext {
@ -87,23 +90,31 @@ impl AppContext<Standard> for GameContext {
impl GameContext {
pub fn new(system: System<Standard>) -> Result<Self> {
let palette = load_palette(Path::new("./assets/db16.pal"))?;
let palette = load_palette("./assets/db16.pal")?;
let font = load_font(Path::new("./assets/dp.fnt"))?;
let small_font = load_font(Path::new("./assets/small.fnt"))?;
let font = load_font("./assets/dp.fnt")?;
let small_font = load_font("./assets/small.fnt")?;
let tiles = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/tiles.pcx"))?);
let green_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/green_slime.pcx"))?);
let blue_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/blue_slime.pcx"))?);
let orange_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/orange_slime.pcx"))?);
let tiles = Rc::new(load_bitmap_atlas_autogrid("./assets/tiles.pcx")?);
let green_slime = Rc::new(load_bitmap_atlas_autogrid("./assets/green_slime.pcx")?);
let blue_slime = Rc::new(load_bitmap_atlas_autogrid("./assets/blue_slime.pcx")?);
let orange_slime = Rc::new(load_bitmap_atlas_autogrid("./assets/orange_slime.pcx")?);
let tilemap = TileMap::load_from(Path::new("./assets/arena.map.json"))?;
let tilemap = TileMap::load_from("./assets/arena.map.json")?;
let entities = Entities::new();
let component_systems = ComponentSystems::new();
let event_publisher = EventPublisher::new();
let event_listeners = EventListeners::new();
let imgui = ggdt_imgui::ImGui::new();
let mut imgui = ggdt_imgui::ImGui::new();
let mut slime_texture_id_map = HashMap::new();
slime_texture_id_map
.insert(SlimeColor::Green, imgui.texture_map_mut().insert(green_slime.clone_tile(0).unwrap()));
slime_texture_id_map
.insert(SlimeColor::Blue, imgui.texture_map_mut().insert(blue_slime.clone_tile(0).unwrap()));
slime_texture_id_map
.insert(SlimeColor::Orange, imgui.texture_map_mut().insert(orange_slime.clone_tile(0).unwrap()));
let slime_activity_states = HashMap::from([
(EntityActivity::Idle, Rc::new(AnimationDef::new(&[1, 2], true, 1.0, Some(3)))),
@ -129,6 +140,7 @@ impl GameContext {
tilemap,
slime_activity_states: Rc::new(slime_activity_states),
sprite_render_list: Vec::new(),
slime_texture_id_map,
},
support: SupportContext { component_systems, event_listeners, imgui },
})

View file

@ -1,9 +1,11 @@
use crate::context::{CoreContext, GameContext};
use crate::tilemap::TileMap;
use ggdt::prelude::*;
use std::collections::HashMap;
use std::rc::Rc;
use ggdt::prelude::*;
use crate::context::{CoreContext, GameContext};
use crate::tilemap::TileMap;
pub const FRICTION: f32 = 0.5;
pub const DEFAULT_PUSH_STRENGTH: f32 = 0.5;
pub const DEFAULT_PUSH_DISSIPATION: f32 = 0.5;
@ -211,7 +213,7 @@ impl RandomlyWalksAround {
}
}
pub struct Slime;
pub struct Slime(pub SlimeColor);
pub struct Sprite {
pub atlas: Rc<BitmapAtlas<RgbaBitmap>>,
@ -367,7 +369,7 @@ pub fn remove_entity(entities: &mut Entities, entity: EntityId) {
pub fn set_entity_activity(entities: &mut Entities, entity: EntityId, new_activity: EntityActivity) {
let mut activities = entities.components_mut::<Activity>();
let mut activity = activities.get_mut(&entity).unwrap();
let activity = activities.get_mut(&entity).unwrap();
// only change the activity, and more importantly, the animation if we are actually applying
// an actual activity change from what it was before
@ -738,7 +740,7 @@ pub fn new_slime_entity(
let animate_by_activity = AnimateByActivity(context.slime_activity_states.clone());
let animation = AnimationInstance::from(animate_by_activity.0.get(&activity).unwrap().clone());
context.entities.add_component(id, Slime);
context.entities.add_component(id, Slime(color));
context.entities.add_component(id, Position(Vector2::new(x as f32, y as f32)));
context.entities.add_component(id, Velocity(Vector2::ZERO));
context.entities.add_component(id, Forces::new());

View file

@ -1,15 +1,16 @@
mod context;
mod entities;
mod support;
mod tilemap;
use anyhow::Result;
use ggdt::prelude::*;
use ggdt_imgui::UiSupport;
use crate::context::GameContext;
use crate::entities::{Forces, Position, Slime};
use crate::tilemap::{TILE_HEIGHT, TILE_WIDTH};
use ggdt::prelude::*;
use ggdt_imgui::UiSupport;
mod context;
mod entities;
mod support;
mod tilemap;
#[derive(Default)]
pub struct DemoState {
@ -27,17 +28,22 @@ impl AppState<GameContext> for DemoState {
let ui = context.support.imgui.new_frame(&context.core.system.res.video);
ui.window("Entities")
.position([10.0, 10.0], imgui::Condition::FirstUseEver)
.size([200.0, 200.0], imgui::Condition::FirstUseEver)
.size([240.0, 200.0], imgui::Condition::FirstUseEver)
.build(|| {
ui.text(format!("Camera: {}, {}", context.core.camera_x, context.core.camera_y));
ui.separator();
ui.text_colored([1.0, 1.0, 0.0, 1.0], "Slimes");
let mut positions = context.core.entities.components_mut::<Position>().unwrap();
for (slime, _) in context.core.entities.components::<Slime>().unwrap().iter() {
for (slime, slime_type) in context.core.entities.components::<Slime>().unwrap().iter() {
let position = positions.get(slime).unwrap();
ui.text(format!("{:2} @ {:3.0},{:3.0}", *slime, position.0.x, position.0.y));
if let Some(slime_type_texture_id) = context.core.slime_texture_id_map.get(&slime_type.0) {
ui.same_line();
ui.image("Slime Type", *slime_type_texture_id, [16.0, 16.0]);
}
ui.same_line();
let clicked = {
let _id = ui.push_id_ptr(slime);
@ -79,11 +85,12 @@ impl AppState<GameContext> for DemoState {
}
});
if !ui.is_any_hovered() && !ui.is_any_focused() {
if context.core.system.res.mouse.is_button_down(MouseButton::Right) {
context.core.camera_x -= context.core.system.res.mouse.x_delta() * 2;
context.core.camera_y -= context.core.system.res.mouse.y_delta() * 2;
}
if !ui.is_any_hovered()
&& !ui.is_any_focused()
&& context.core.system.res.mouse.is_button_down(MouseButton::Right)
{
context.core.camera_x -= context.core.system.res.mouse.x_delta() * 2;
context.core.camera_y -= context.core.system.res.mouse.y_delta() * 2;
}
context.support.do_events(&mut context.core);

View file

@ -1,17 +1,19 @@
use std::path::Path;
use crate::tilemap::{TILE_HEIGHT, TILE_WIDTH};
use anyhow::{Context, Result};
use ggdt::prelude::*;
pub fn load_palette(path: &std::path::Path) -> Result<Palette> {
Palette::load_from_file(path, PaletteFormat::Vga).context(format!("Loading palette: {:?}", path))
pub fn load_palette(path: impl AsRef<Path>) -> Result<Palette> {
Palette::load_from_file(&path, PaletteFormat::Vga).context(format!("Loading palette: {:?}", path.as_ref()))
}
pub fn load_font(path: &std::path::Path) -> Result<BitmaskFont> {
BitmaskFont::load_from_file(path).context(format!("Loading font: {:?}", path))
pub fn load_font(path: impl AsRef<Path>) -> Result<BitmaskFont> {
BitmaskFont::load_from_file(&path).context(format!("Loading font: {:?}", path.as_ref()))
}
pub fn load_bitmap_atlas_autogrid(path: &std::path::Path) -> Result<BitmapAtlas<RgbaBitmap>> {
let (bmp, _) = RgbaBitmap::load_file(path).context(format!("Loading bitmap atlas: {:?}", path))?;
pub fn load_bitmap_atlas_autogrid(path: impl AsRef<Path>) -> Result<BitmapAtlas<RgbaBitmap>> {
let (bmp, _) = RgbaBitmap::load_file(&path).context(format!("Loading bitmap atlas: {:?}", path.as_ref()))?;
let mut atlas = BitmapAtlas::new(bmp);
atlas.add_grid(TILE_WIDTH, TILE_HEIGHT)?;
Ok(atlas)

View file

@ -1,3 +1,5 @@
use std::path::Path;
use anyhow::{Context, Result};
use ggdt::prelude::*;
@ -16,10 +18,10 @@ pub struct TileMap {
}
impl TileMap {
pub fn load_from(path: &std::path::Path) -> Result<Self> {
let f = std::fs::File::open(path)?;
pub fn load_from(path: impl AsRef<Path>) -> Result<Self> {
let f = std::fs::File::open(&path)?;
let reader = std::io::BufReader::new(f);
serde_json::from_reader(reader).context(format!("Loading json tilemap: {:?}", path))
serde_json::from_reader(reader).context(format!("Loading json tilemap: {:?}", path.as_ref()))
}
#[inline]
@ -63,7 +65,7 @@ impl TileMap {
tiles: &BitmapAtlas<RgbaBitmap>,
camera_x: i32,
camera_y: i32,
transparent_color: u32,
transparent_color: RGBA,
) {
let xt = camera_x / TILE_WIDTH as i32;
let yt = camera_y / TILE_HEIGHT as i32;

View file

@ -4,8 +4,8 @@ version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "=1.0.55"
anyhow.workspace = true
ggdt = { path = "../../ggdt", features = [] }
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
serde.workspace = true
serde_json.workspace = true

View file

@ -1,9 +0,0 @@
{
"width":40,
"height":30,
"layers":[
[96,96,96,96,96,96,96,96,96,96,96,96,16,17,16,16,16,16,16,17,17,32,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,33,16,16,96,96,96,96,96,96,96,96,96,96,96,16,17,17,17,32,17,32,16,16,32,16,16,32,16,16,16,16,16,16,32,16,33,16,16,16,16,16,16,16,96,96,96,96,96,96,96,96,96,96,181,178,178,178,178,183,32,16,17,181,178,178,178,178,178,178,178,178,178,178,178,183,16,16,16,16,16,16,16,32,96,96,96,96,96,96,96,96,96,181,195,16,32,17,17,193,178,178,178,195,16,16,16,16,16,16,32,16,16,16,16,193,178,183,16,32,16,16,16,16,96,96,96,96,96,96,96,181,178,195,16,16,16,32,17,17,17,17,32,16,16,16,33,16,16,16,16,16,16,16,16,16,16,193,183,16,16,16,33,16,96,96,96,96,96,96,181,195,32,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,193,183,16,16,16,16,16,17,16,16,96,181,195,16,16,33,16,16,16,16,16,32,16,16,16,16,16,32,16,16,16,16,48,48,48,48,16,16,16,16,16,196,16,16,16,16,32,16,17,16,181,195,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,48,48,48,48,48,48,48,48,16,16,32,193,183,16,32,16,8,8,8,181,195,16,16,16,16,17,32,16,16,16,16,16,16,16,16,16,16,16,16,48,48,48,48,48,48,48,48,48,48,16,16,16,196,16,16,16,7,7,7,196,16,32,16,32,17,17,17,16,16,16,16,33,16,16,16,16,16,16,48,48,48,48,48,48,48,48,48,48,48,48,16,16,193,183,16,32,7,7,181,195,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,32,16,16,16,48,48,48,48,48,48,48,48,48,48,48,48,32,16,196,32,16,7,7,196,16,16,32,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,48,48,48,48,48,48,48,48,48,48,16,16,16,196,16,16,7,7,196,8,8,16,16,16,16,16,16,32,16,16,16,16,32,16,16,16,16,16,16,16,16,48,48,48,48,48,48,48,48,16,16,16,16,196,17,16,7,7,196,7,7,16,16,16,16,16,16,16,16,16,33,16,16,16,16,16,16,16,16,16,32,16,16,48,48,48,48,48,16,16,16,16,16,196,17,16,7,7,196,7,7,16,16,16,16,16,33,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,17,16,16,16,196,16,33,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,32,16,16,16,196,16,16,16,16,16,32,16,16,16,16,32,16,16,16,32,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,196,16,16,16,16,196,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,33,16,16,16,16,16,32,16,196,16,33,32,16,196,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,33,16,16,16,16,16,16,16,16,16,16,16,16,16,196,32,16,16,16,196,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,196,16,16,16,16,193,183,16,16,48,48,48,48,48,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,32,16,32,16,33,196,48,48,48,48,48,48,48,48,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,16,16,16,16,16,193,183,48,48,48,48,48,48,48,48,16,16,32,16,16,16,16,16,16,16,16,33,16,16,16,16,16,16,16,16,16,16,16,16,196,16,16,16,32,16,16,196,48,48,48,48,48,48,48,48,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,196,16,16,16,16,16,16,193,183,48,48,48,48,48,48,48,48,48,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,181,195,16,16,16,16,16,16,16,193,183,48,48,48,48,48,48,48,48,48,48,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,16,16,16,16,16,16,16,16,16,193,178,178,183,48,48,48,48,48,48,48,48,16,16,16,16,16,32,16,16,16,16,32,16,16,16,16,16,16,181,195,16,16,16,32,16,16,33,16,32,16,16,16,193,178,178,178,178,178,178,178,178,183,16,32,16,16,16,16,16,33,16,16,16,16,16,16,181,178,195,32,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,33,32,17,193,178,178,178,178,178,178,178,178,178,178,178,178,178,178,195,16,16,16,16,16,48,16,16,32,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,32,16,16,32,16,16,16,32,16,16,16,33,16,16,32,16,16,16,16,16,48,48],
[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,87,-1,104,108,108,108,105,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,98,105,-1,-1,-1,26,-1,26,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,80,108,108,108,97,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,-1,-1,-1,-1,52,50,-1,-1,-1,-1,-1,-1,-1,-1,46,46,46,102,-1,-1,27,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,-1,-1,-1,-1,-1,-1,27,-1,52,50,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,51,-1,-1,26,-1,-1,-1,-1,-1,-1,27,52,50,-1,-1,-1,-1,-1,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,278,-1,-1,-1,-1,52,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,67,-1,-1,27,279,-1,-1,294,-1,-1,-1,-1,-1,-1,-1,-1,-1,83,-1,-1,-1,46,46,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,65,67,-1,-1,-1,-1,-1,-1,26,-1,-1,266,68,-1,-1,-1,-1,86,-1,-1,-1,-1,-1,29,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,65,67,-1,-1,-1,-1,-1,-1,-1,-1,68,66,-1,-1,-1,-1,86,-1,-1,-1,-1,-1,29,-1,27,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,65,67,-1,-1,-1,-1,-1,68,66,-1,-1,26,-1,-1,86,-1,-1,-1,-1,-1,29,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,86,14,14,-1,14,14,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,86,-1,282,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,104,-1,282,-1,-1,-1,259,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,281,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,-1,-1,-1,-1,-1,52,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,52,-1,-1,-1,-1,-1,-1,-1,-1,-1,278,-1,-1,278,-1,-1,266,27,-1,278,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,27,-1,50,-1,-1,-1,-1,-1,-1,-1,-1,294,-1,-1,294,-1,27,279,-1,-1,294,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,52,50,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,278,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,52,50,-1,-1,-1,-1,-1,-1,-1,-1,278,-1,-1,278,-1,-1,278,-1,-1,80,-1,-1,-1,-1,26,-1,294,-1,-1,-1,-1,-1,27,-1,-1,-1,-1,-1,-1,-1,-1,266,52,-1,-1,-1,27,279,-1,-1,294,-1,-1,294,-1,-1,294,-1,266,-1,-1,-1,26,-1,-1,-1,-1,-1,260,-1,-1,-1,-1,-1,-1,27,-1,-1,-1,26,-1,-1,-1,50,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,27,-1,49,-1,278,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,-1,-1,-1,49,51,-1,294,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,49,51,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,27,-1],
[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,-1,0,1,1,0,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,0,0,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,-1,-1,-1,-1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,-1,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,1,0,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,1,0,1,0,0,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,1,0,1,1,1,1,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -381,7 +381,7 @@ pub struct Pickuper;
pub fn init_everything(
context: &mut Game,
map_file: &Path,
map_file: impl AsRef<Path>,
min_spawn_time: f32,
max_spawn_time: f32,
max_slimes: usize,

View file

@ -105,7 +105,7 @@ fn move_entity_with_collision(
pub fn set_entity_activity(entities: &mut Entities, entity: EntityId, new_activity: EntityActivity) {
let mut activities = entities.components_mut::<Activity>();
let mut activity = activities.get_mut(&entity).unwrap();
let activity = activities.get_mut(&entity).unwrap();
// only change the activity, and more importantly, the animation if we are actually applying
// an actual activity change from what it was before
@ -215,7 +215,7 @@ pub fn attack(context: &mut Core, entity: EntityId) {
}
}
pub fn hit_entity(context: &mut Core, target: EntityId, source: EntityId, damage: i32, damage_position: Vector2) {
pub fn hit_entity(context: &mut Core, target: EntityId, _source: EntityId, damage: i32, damage_position: Vector2) {
let position;
{
let positions = context.entities.components::<Position>();
@ -248,15 +248,15 @@ pub fn stop_attack(context: &mut Core, entity: EntityId) {
remove_entity_attachment(&mut context.entities, entity);
}
pub fn pickup(context: &mut Core, picked_up_by: EntityId, picked_up: EntityId) {
let kind;
pub fn pickup(context: &mut Core, _picked_up_by: EntityId, picked_up: EntityId) {
let _kind;
let position;
{
let positions = context.entities.components::<Position>();
position = positions.get(&picked_up).unwrap().0;
let pickupables = context.entities.components::<Pickupable>();
kind = pickupables.get(&picked_up).unwrap().kind;
_kind = pickupables.get(&picked_up).unwrap().kind;
}
// TODO: tally up the kinds
@ -332,7 +332,7 @@ fn update_system_pushing(context: &mut Core) {
let pusher_bounds = bounds.get(pusher_entity).unwrap();
let pusher_circle = Circle::new(pusher_position.0.x as i32, pusher_position.0.y as i32, pusher_bounds.radius);
for (pushable_entity, pushable) in pushable.iter() {
for (pushable_entity, _pushable) in pushable.iter() {
// don't push ourself ...
if *pushable_entity == *pusher_entity {
continue;
@ -535,7 +535,7 @@ fn update_system_randomly_spawn_slimes(context: &mut Core) {
fn update_system_camera_follows_player(context: &mut Core) {
if let Some((player_entity, _)) = context.entities.components::<Player>().single() {
if let Some((_, mut camera)) = context.entities.components_mut::<Camera>().single_mut() {
if let Some((_, camera)) = context.entities.components_mut::<Camera>().single_mut() {
let positions = context.entities.components::<Position>().unwrap();
let position = positions.get(player_entity).unwrap();
@ -569,7 +569,7 @@ fn update_system_turn_attached_entities(context: &mut Core) {
// change the direction of the attachment (child) to match the parent ... if the
// attachment even has a direction itself ...
if let Some(mut facing_direction) = facing_directions.get_mut(&attachment.0) {
if let Some(facing_direction) = facing_directions.get_mut(&attachment.0) {
facing_direction.0 = parent_facing_direction;
}
}
@ -593,7 +593,7 @@ fn update_system_position_attached_entities(context: &mut Core) {
}
let attached_entity = attachment.0;
if let Some(mut attachment_position) = positions.get_mut(&attached_entity) {
if let Some(attachment_position) = positions.get_mut(&attached_entity) {
// start off the attachment by placing it directly at the parent
attachment_position.0 = parent_position;

View file

@ -1,7 +1,6 @@
extern crate core;
use std::collections::HashMap;
use std::path::Path;
use std::rc::Rc;
use anyhow::{Context, Result};
@ -108,30 +107,30 @@ impl AppContext<DosLike> for Game {
impl Game {
pub fn new(mut system: System<DosLike>) -> Result<Self> {
let palette = load_palette(Path::new("./assets/db16.pal"))?;
let palette = load_palette("./assets/db16.pal")?;
system.res.palette = palette.clone();
let font = load_font(Path::new("./assets/dp.fnt"))?;
let font = load_font("./assets/dp.fnt")?;
let tiles = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/tiles.pcx"))?);
let hero_male = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/hero_male.pcx"))?);
let hero_female = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/hero_female.pcx"))?);
let green_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/green_slime.pcx"))?);
let blue_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/blue_slime.pcx"))?);
let orange_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/orange_slime.pcx"))?);
let fist = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/fist.pcx"))?);
let sword = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/sword.pcx"))?);
let particles = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/particles.pcx"))?);
let items = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/items.pcx"))?);
let tiles = Rc::new(load_bitmap_atlas_autogrid("./assets/tiles.pcx")?);
let hero_male = Rc::new(load_bitmap_atlas_autogrid("./assets/hero_male.pcx")?);
let hero_female = Rc::new(load_bitmap_atlas_autogrid("./assets/hero_female.pcx")?);
let green_slime = Rc::new(load_bitmap_atlas_autogrid("./assets/green_slime.pcx")?);
let blue_slime = Rc::new(load_bitmap_atlas_autogrid("./assets/blue_slime.pcx")?);
let orange_slime = Rc::new(load_bitmap_atlas_autogrid("./assets/orange_slime.pcx")?);
let fist = Rc::new(load_bitmap_atlas_autogrid("./assets/fist.pcx")?);
let sword = Rc::new(load_bitmap_atlas_autogrid("./assets/sword.pcx")?);
let particles = Rc::new(load_bitmap_atlas_autogrid("./assets/particles.pcx")?);
let items = Rc::new(load_bitmap_atlas_autogrid("./assets/items.pcx")?);
let mut ui = load_bitmap_atlas(Path::new("./assets/ui.pcx"))?;
let mut ui = load_bitmap_atlas("./assets/ui.pcx")?;
ui.add(Rect::new(0, 0, 16, 16))?;
ui.add(Rect::new(16, 0, 16, 16))?;
for i in 0..8 {
ui.add(Rect::new(i * 8, 16, 8, 8))?;
}
let tilemap = TileMap::load_from(Path::new("./assets/title_screen.map.json"))?;
let tilemap = TileMap::load_from("./assets/title_screen.map.json")?;
let entities = Entities::new();
let component_systems = ComponentSystems::new();

View file

@ -1,5 +1,3 @@
use std::path::Path;
use ggdt::prelude::*;
use crate::entities::*;
@ -45,7 +43,7 @@ impl AppState<Game> for MainMenuState {
None
}
fn render(&mut self, state: State, context: &mut Game) {
fn render(&mut self, _state: State, context: &mut Game) {
context.core.tilemap.draw(&mut context.core.system.res.video, &context.core.tiles, 0, 0);
context.support.component_systems.render(&mut context.core);
@ -85,10 +83,10 @@ impl AppState<Game> for MainMenuState {
update_fade_transition(state, &mut self.fade, context.core.delta * 3.0, context)
}
fn state_change(&mut self, new_state: State, old_state: State, context: &mut Game) {
fn state_change(&mut self, new_state: State, _old_state: State, context: &mut Game) {
match new_state {
State::Pending | State::Resume => {
init_everything(context, Path::new("./assets/title_screen.map.json"), 0.2, 1.0, 32);
init_everything(context, "./assets/title_screen.map.json", 0.2, 1.0, 32);
}
State::TransitionIn => {
self.fade = 0.0;
@ -172,7 +170,7 @@ impl AppState<Game> for GamePlayState {
None
}
fn render(&mut self, state: State, context: &mut Game) {
fn render(&mut self, _state: State, context: &mut Game) {
if let Some((_, camera)) = context.core.entities.components::<Camera>().single() {
context.core.tilemap.draw(&mut context.core.system.res.video, &context.core.tiles, camera.x, camera.y);
}
@ -216,10 +214,10 @@ impl AppState<Game> for GamePlayState {
update_fade_transition(state, &mut self.fade, context.core.delta * 3.0, context)
}
fn state_change(&mut self, new_state: State, old_state: State, context: &mut Game) {
fn state_change(&mut self, new_state: State, _old_state: State, context: &mut Game) {
match new_state {
State::Pending => {
init_everything(context, Path::new("./assets/arena.map.json"), 0.5, 2.0, 100);
init_everything(context, "./assets/arena.map.json", 0.5, 2.0, 100);
spawn_player_randomly(&mut context.core);
}
State::TransitionIn => {

View file

@ -6,23 +6,23 @@ use ggdt::prelude::*;
use crate::{Game, TILE_HEIGHT, TILE_WIDTH};
pub fn load_palette(path: &Path) -> Result<Palette> {
Palette::load_from_file(path, PaletteFormat::Vga).context(format!("Loading palette: {:?}", path))
pub fn load_palette(path: impl AsRef<Path>) -> Result<Palette> {
Palette::load_from_file(&path, PaletteFormat::Vga).context(format!("Loading palette: {:?}", path.as_ref()))
}
pub fn load_font(path: &Path) -> Result<BitmaskFont> {
BitmaskFont::load_from_file(path).context(format!("Loading font: {:?}", path))
pub fn load_font(path: impl AsRef<Path>) -> Result<BitmaskFont> {
BitmaskFont::load_from_file(&path).context(format!("Loading font: {:?}", path.as_ref()))
}
pub fn load_bitmap_atlas_autogrid(path: &Path) -> Result<BitmapAtlas<IndexedBitmap>> {
let (bmp, _) = IndexedBitmap::load_file(path).context(format!("Loading bitmap atlas: {:?}", path))?;
pub fn load_bitmap_atlas_autogrid(path: impl AsRef<Path>) -> Result<BitmapAtlas<IndexedBitmap>> {
let (bmp, _) = IndexedBitmap::load_file(&path).context(format!("Loading bitmap atlas: {:?}", path.as_ref()))?;
let mut atlas = BitmapAtlas::new(bmp);
atlas.add_grid(TILE_WIDTH, TILE_HEIGHT)?;
Ok(atlas)
}
pub fn load_bitmap_atlas(path: &Path) -> Result<BitmapAtlas<IndexedBitmap>> {
let (bmp, _) = IndexedBitmap::load_file(path).context(format!("Loading bitmap atlas: {:?}", path))?;
pub fn load_bitmap_atlas(path: impl AsRef<Path>) -> Result<BitmapAtlas<IndexedBitmap>> {
let (bmp, _) = IndexedBitmap::load_file(&path).context(format!("Loading bitmap atlas: {:?}", path.as_ref()))?;
let atlas = BitmapAtlas::new(bmp);
Ok(atlas)
}

View file

@ -21,10 +21,10 @@ pub struct TileMap {
}
impl TileMap {
pub fn load_from(path: &Path) -> Result<Self> {
let f = File::open(path)?;
pub fn load_from(path: impl AsRef<Path>) -> Result<Self> {
let f = File::open(&path)?;
let reader = BufReader::new(f);
serde_json::from_reader(reader).context(format!("Loading json tilemap: {:?}", path))
serde_json::from_reader(reader).context(format!("Loading json tilemap: {:?}", path.as_ref()))
}
#[inline]

View file

@ -4,5 +4,5 @@ version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "=1.0.55"
anyhow.workspace = true
ggdt = { path = "../../ggdt" }

View file

@ -108,16 +108,16 @@ impl AppState<App> for DemoState {
None
}
fn render(&mut self, state: State, context: &mut App) {
fn render(&mut self, _state: State, context: &mut App) {
context.core.system.res.video.clear(0);
context.support.component_systems.render(&mut context.core);
}
fn transition(&mut self, state: State, context: &mut App) -> bool {
fn transition(&mut self, _state: State, _context: &mut App) -> bool {
true
}
fn state_change(&mut self, new_state: State, old_state: State, context: &mut App) {
fn state_change(&mut self, new_state: State, _old_state: State, context: &mut App) {
match new_state {
State::Pending => {
self.init(context);

View file

@ -4,5 +4,5 @@ version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "=1.0.55"
anyhow.workspace = true
ggdt = { path = "../../ggdt" }

View file

@ -6,16 +6,18 @@ authors = ["Gered King <gered@blarg.ca>"]
edition = "2021"
[dependencies]
byte-slice-cast = "1.2.1"
byteorder = "1.4.3"
thiserror = "=1.0.30"
byte-slice-cast = "1.2.2"
byteorder = "1.5.0"
thiserror.workspace = true
rand = "0.8.5"
num = "0.4.0"
num-derive = "0.3.3"
num-traits = "0.2.15"
bitflags = "2.1.0"
flate2 = "1.0.25"
num = "0.4.1"
num-derive = "0.4.1"
num-traits = "0.2.17"
bitflags = "2.4.1"
flate2 = "=1.0.27"
crc32fast = "1.3.2"
serde.workspace = true
serde_json.workspace = true
[target.'cfg(not(windows))'.dependencies]
sdl2 = { git = "https://github.com/Rust-SDL2/rust-sdl2/", rev = "819ab438ac971a922d6ee1da558822002d343b4e", features = ["static-link", "bundled", "use-pkgconfig", "unsafe_textures"] }
@ -25,9 +27,9 @@ sdl2 = { git = "https://github.com/Rust-SDL2/rust-sdl2/", rev = "819ab438ac971a9
[dev-dependencies]
claim = "0.5.0"
criterion = "0.4.0"
anyhow = "=1.0.55"
tempfile = "3.4.0"
criterion = "0.5.1"
anyhow.workspace = true
tempfile = "3.8.1"
[[bench]]
name = "bitmap"
@ -41,6 +43,19 @@ harness = false
name = "loading"
harness = false
[[bench]]
name = "primitives"
harness = false
[[bench]]
name = "triangles"
harness = false
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(recreate_ref_test_images)'] }
[lints.clippy]
too_long_first_doc_paragraph = "allow"
tabs_in_doc_comments = "allow" # fuck off
too_many_arguments = "allow"

View file

@ -7,10 +7,10 @@ pub fn criterion_benchmark(c: &mut Criterion) {
let height = 240;
let mut source = IndexedBitmap::new(width, height).unwrap();
let mut dest = vec![0u32; (width * height * 4) as usize].into_boxed_slice();
let mut dest = vec![RGBA::default(); (width * height) as usize].into_boxed_slice();
let palette = Palette::new_vga_palette().unwrap();
c.bench_function("deindex_bitmap_pixels", |b| b.iter(|| source.copy_as_argb_to(&mut dest, &palette)));
c.bench_function("deindex_bitmap_pixels", |b| b.iter(|| source.copy_as_rgba_to(&mut dest, &palette)));
c.bench_function("set_pixel", |b| b.iter(|| source.set_pixel(black_box(100), black_box(100), black_box(42))));

View file

@ -1,12 +1,10 @@
use std::path::Path;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use ggdt::prelude::*;
pub fn criterion_benchmark(c: &mut Criterion) {
let mut framebuffer = IndexedBitmap::new(320, 240).unwrap();
let (bmp, _) = IndexedBitmap::load_iff_file(Path::new("./test-assets/test-tiles.lbm")).unwrap();
let (bmp, _) = IndexedBitmap::load_iff_file("./test-assets/test-tiles.lbm").unwrap();
let mut solid_bmp = IndexedBitmap::new(16, 16).unwrap();
solid_bmp.blit_region(IndexedBlitMethod::Solid, &bmp, &Rect::new(16, 16, 16, 16), 0, 0);

163
ggdt/benches/primitives.rs Normal file
View file

@ -0,0 +1,163 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use ggdt::prelude::*;
pub fn criterion_benchmark(c: &mut Criterion) {
let width = 320;
let height = 240;
const BG_COLOR: RGBA = RGBA::from_rgb([0, 0, 0]);
const SOLID_COLOR: RGBA = RGBA::from_rgb([255, 0, 255]);
const BLEND_COLOR: RGBA = RGBA::from_rgba([255, 0, 255, 127]);
let mut dest = RgbaBitmap::new(width, height).unwrap();
// note that none of these are all-inclusive benchmarks that cover all kinds of different scenarios and such
// where the rendering logic might change based on the actual arguments given (e.g. i am not benchmarking
// anything which does clipping as of yet).
// maybe i will eventually add that kind of detailed benchmarking, but this for now is to just get a very,
// very, VERY, basic idea about general performance ... moreso so that i can easily compare before/after
// as i change other things in the future
c.bench_function("rgbabitmap_primitives_set_pixel", |b| {
dest.clear(BG_COLOR);
b.iter(|| dest.set_pixel(black_box(100), black_box(100), black_box(SOLID_COLOR)))
});
c.bench_function("rgbabitmap_primitives_set_blended_pixel", |b| {
dest.clear(BG_COLOR);
b.iter(|| {
dest.set_blended_pixel(
black_box(100),
black_box(100),
black_box(BLEND_COLOR),
black_box(BlendFunction::Blend),
)
})
});
c.bench_function("rgbabitmap_primitives_set_pixel_unchecked", |b| {
dest.clear(BG_COLOR);
b.iter(|| unsafe { dest.set_pixel_unchecked(black_box(100), black_box(100), black_box(SOLID_COLOR)) })
});
c.bench_function("rgbabitmap_primitives_set_blended_pixel_unchecked", |b| {
dest.clear(BG_COLOR);
b.iter(|| unsafe {
dest.set_blended_pixel_unchecked(
black_box(100),
black_box(100),
black_box(BLEND_COLOR),
black_box(BlendFunction::Blend),
)
})
});
c.bench_function("rgbabitmap_primitives_line", |b| {
dest.clear(BG_COLOR);
b.iter(|| dest.line(black_box(10), black_box(50), black_box(310), black_box(120), black_box(SOLID_COLOR)))
});
c.bench_function("rgbabitmap_primitives_blended_line", |b| {
dest.clear(BG_COLOR);
b.iter(|| {
dest.blended_line(
black_box(10),
black_box(50),
black_box(310),
black_box(120),
black_box(BLEND_COLOR),
black_box(BlendFunction::Blend),
)
})
});
c.bench_function("rgbabitmap_primitives_horiz_line", |b| {
dest.clear(BG_COLOR);
b.iter(|| dest.horiz_line(black_box(10), black_box(310), black_box(70), black_box(SOLID_COLOR)))
});
c.bench_function("rgbabitmap_primitives_blended_horiz_line", |b| {
dest.clear(BG_COLOR);
b.iter(|| {
dest.blended_horiz_line(
black_box(10),
black_box(310),
black_box(70),
black_box(BLEND_COLOR),
black_box(BlendFunction::Blend),
)
})
});
c.bench_function("rgbabitmap_primitives_vert_line", |b| {
dest.clear(BG_COLOR);
b.iter(|| dest.vert_line(black_box(90), black_box(10), black_box(230), black_box(SOLID_COLOR)))
});
c.bench_function("rgbabitmap_primitives_blended_vert_line", |b| {
dest.clear(BG_COLOR);
b.iter(|| {
dest.blended_vert_line(
black_box(90),
black_box(10),
black_box(230),
black_box(BLEND_COLOR),
black_box(BlendFunction::Blend),
)
})
});
c.bench_function("rgbabitmap_primitives_rect", |b| {
dest.clear(BG_COLOR);
b.iter(|| dest.rect(black_box(10), black_box(10), black_box(310), black_box(230), black_box(SOLID_COLOR)))
});
c.bench_function("rgbabitmap_primitives_blended_rect", |b| {
dest.clear(BG_COLOR);
b.iter(|| {
dest.blended_rect(
black_box(10),
black_box(10),
black_box(310),
black_box(230),
black_box(BLEND_COLOR),
black_box(BlendFunction::Blend),
)
})
});
c.bench_function("rgbabitmap_primitives_filled_rect", |b| {
dest.clear(BG_COLOR);
b.iter(|| {
dest.filled_rect(black_box(10), black_box(10), black_box(310), black_box(230), black_box(SOLID_COLOR))
})
});
c.bench_function("rgbabitmap_primitives_blended_filled_rect", |b| {
dest.clear(BG_COLOR);
b.iter(|| {
dest.blended_filled_rect(
black_box(10),
black_box(10),
black_box(310),
black_box(230),
black_box(BLEND_COLOR),
black_box(BlendFunction::Blend),
)
})
});
c.bench_function("rgbabitmap_primitives_circle", |b| {
dest.clear(BG_COLOR);
b.iter(|| dest.circle(black_box(160), black_box(120), black_box(80), black_box(SOLID_COLOR)))
});
c.bench_function("rgbabitmap_primitives_filled_circle", |b| {
dest.clear(BG_COLOR);
b.iter(|| dest.filled_circle(black_box(160), black_box(120), black_box(80), black_box(SOLID_COLOR)))
});
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View file

@ -7,7 +7,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
let height = 240;
let mut dest = RgbaBitmap::new(width, height).unwrap();
let (texture, _) = RgbaBitmap::load_gif_file(std::path::Path::new("./test-assets/gif/small.gif")).unwrap();
let (texture, _) = RgbaBitmap::load_gif_file("./test-assets/gif/small.gif").unwrap();
let big_v1 = Vector2::new(47.0, 23.0);
let big_v2 = Vector2::new(60.0, 192.0);
@ -22,19 +22,20 @@ pub fn criterion_benchmark(c: &mut Criterion) {
let texcoord_0_1 = Vector2::new(0.0, 1.0);
let texcoord_1_1 = Vector2::new(1.0, 1.0);
let color_1 = to_argb32([255, 255, 0, 0]);
let color_2 = to_argb32([255, 0, 255, 0]);
let color_3 = to_argb32([255, 0, 0, 255]);
let color = RGBA::from_rgb([255, 255, 255]);
let color_1 = RGBA::from_rgba([255, 0, 0, 255]);
let color_2 = RGBA::from_rgba([0, 255, 0, 255]);
let color_3 = RGBA::from_rgba([0, 0, 255, 255]);
c.bench_function("rgbabitmap_triangle_2d_solid_color", |b| {
let triangle = RgbaTriangle2d::Solid { position: [big_v1, big_v2, big_v3], color: 5 };
let triangle = RgbaTriangle2d::Solid { position: [big_v1, big_v2, big_v3], color };
b.iter(|| {
dest.triangle_2d(black_box(&triangle));
})
});
c.bench_function("rgbabitmap_triangle_2d_solid_color_small", |b| {
let triangle = RgbaTriangle2d::Solid { position: [small_v1, small_v2, small_v3], color: 5 };
let triangle = RgbaTriangle2d::Solid { position: [small_v1, small_v2, small_v3], color };
b.iter(|| {
dest.triangle_2d(black_box(&triangle));
})

View file

@ -270,7 +270,7 @@ impl AudioBuffer {
/// Loads a WAV file into an [`AudioBuffer`]. The returned buffer will be in its original
/// format and may need to be converted before it can be played.
pub fn load_wav_file(path: &Path) -> Result<AudioBuffer, WavError> {
pub fn load_wav_file(path: impl AsRef<Path>) -> Result<AudioBuffer, WavError> {
let f = File::open(path)?;
let mut reader = BufReader::new(f);
Self::load_wav_bytes(&mut reader)
@ -287,31 +287,31 @@ mod tests {
const BASE_PATH: &str = "./test-assets/wav/";
fn test_file(file: &Path) -> PathBuf {
fn test_file(file: impl AsRef<Path>) -> PathBuf {
PathBuf::from(BASE_PATH).join(file)
}
#[test]
pub fn load_wav_file() -> Result<(), WavError> {
let wav_buffer = AudioBuffer::load_wav_file(test_file(Path::new("22khz_8bit_1ch.wav")).as_path())?;
let wav_buffer = AudioBuffer::load_wav_file(test_file("22khz_8bit_1ch.wav").as_path())?;
assert_eq!(AUDIO_FREQUENCY_22KHZ, wav_buffer.spec().frequency());
assert_eq!(1, wav_buffer.spec().channels());
assert_eq!(AudioFormat::U8, wav_buffer.spec.format);
assert_eq!(11248, wav_buffer.data.len());
let wav_buffer = AudioBuffer::load_wav_file(test_file(Path::new("44khz_8bit_1ch.wav")).as_path())?;
let wav_buffer = AudioBuffer::load_wav_file(test_file("44khz_8bit_1ch.wav").as_path())?;
assert_eq!(AUDIO_FREQUENCY_44KHZ, wav_buffer.spec().frequency());
assert_eq!(1, wav_buffer.spec().channels());
assert_eq!(AudioFormat::U8, wav_buffer.spec.format);
assert_eq!(22496, wav_buffer.data.len());
let wav_buffer = AudioBuffer::load_wav_file(test_file(Path::new("22khz_16bit_1ch.wav")).as_path())?;
let wav_buffer = AudioBuffer::load_wav_file(test_file("22khz_16bit_1ch.wav").as_path())?;
assert_eq!(AUDIO_FREQUENCY_22KHZ, wav_buffer.spec().frequency());
assert_eq!(1, wav_buffer.spec().channels());
assert_eq!(AudioFormat::S16LSB, wav_buffer.spec.format);
assert_eq!(22496, wav_buffer.data.len());
let wav_buffer = AudioBuffer::load_wav_file(test_file(Path::new("44khz_16bit_1ch.wav")).as_path())?;
let wav_buffer = AudioBuffer::load_wav_file(test_file("44khz_16bit_1ch.wav").as_path())?;
assert_eq!(AUDIO_FREQUENCY_44KHZ, wav_buffer.spec().frequency());
assert_eq!(1, wav_buffer.spec().channels());
assert_eq!(AudioFormat::S16LSB, wav_buffer.spec.format);

View file

@ -174,6 +174,12 @@ impl AudioChannel {
}
}
impl Default for AudioChannel {
fn default() -> Self {
Self::new()
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Error)]

View file

@ -2,8 +2,6 @@ use sdl2::audio::{AudioFormat, AudioFormatNum, AudioSpecDesired};
use sdl2::AudioSubsystem;
use thiserror::Error;
use crate::audio::AudioDevice;
mod buffer;
mod device;
mod queue;

View file

@ -1,133 +1,135 @@
//! Optional, extra types and helpers that can be used to get game's main loop boilerplate up and
//! running quicker.
//!
//! This is all of somewhat dubious quality and value at the moment. And it may continue to be this
//! way for a long while yet. And, truthfully, I suspect I may rip this out eventually. Maybe.
//!
//! The very-long-winded rationale here is that as I've started building more and more things with
//! ggdt, I started implementing games/apps using a particular pattern which I was largely
//! pushed towards due to the Rust borrow-checker (as is often the case with Rust). My games/apps
//! needed to keep their state (for clarity, the word 'state' here is being used very broadly to
//! refer to all game/app state, and not just referring to the stuff inside `ggdt::states`)
//! somewhere and my needs were a bit complicated since my game/app state often included things
//! which needed to get passed other things from inside that same "bag" of state.
//!
//! I originally wanted to do something like this, where this `App` struct is our overall "game/app
//! context" grab bag:
//!
//! ```
//! use ggdt::prelude::*;
//!
//! pub enum Event { /* .. various events here .. */ }
//!
//! struct App {
//! pub delta: f32,
//! pub system: System<DosLike>,
//! pub entities: Entities,
//! pub component_systems: ComponentSystems<App, App>, // oh no! :'(
//! pub event_publisher: EventPublisher<Event>,
//! pub event_listeners: EventListeners<Event, App>, // oh no again! :'(
//! }
//! ```
//!
//! Of course, we cannot do this, because then we end up trying to get additional mutable borrows
//! of `App` when we eventually try to call certain methods on either the `component_systems` or
//! `event_listeners` instances. Boooo! :-(
//!
//! That of course lead me to split this structure up. I didn't and still don't like this because
//! I really don't know what to call these two things. They're both "context" and they're literally
//! only split up because of borrow-checker issues. But splitting them up did work for me. I
//! initially went with a parent-child split, which seemed logical to me at the time:
//!
//! ```
//! use ggdt::prelude::*;
//!
//! pub enum Event { /* .. various events here .. */ }
//!
//! // "core" because what the heck else do i call this? "InnerContext"? "InnerApp"? ...
//! struct Core {
//! pub delta: f32,
//! pub system: System<DosLike>,
//! pub entities: Entities,
//! pub event_publisher: EventPublisher<Event>,
//! }
//!
//! // i guess this is a bit more obvious what to call it, but still ... doesn't sit right with me
//! struct App {
//! pub core: Core,
//! pub component_systems: ComponentSystems<Core, Core>,
//! pub event_listeners: EventListeners<Event, Core>,
//! }
//! ```
//!
//! This structure seemed to work generally well and I've gotten pretty far with it. Keeping the
//! main `ggdt::states::States` instance _separate_ was also key, and never really a problem
//! since that can (and should) just live at the top in your main loop. Easy.
//!
//! I ended up with some common bits of code that I'd always add to projects using this structure,
//! such as a very simple copy+pasted main loop, as well as a very simple function that calculates
//! the new frame `delta` each iteration of the main loop. As well as event processing via the
//! `event_publisher` and `event_listener` instances. I also expect this set of common bits of code
//! to grow over time. And I, ideally, want a single place to put it all.
//!
//! So, this module here is my attempt at trying to formalize this a bit more and do a bit of
//! refactoring where I can keep this common copy+pasted bits somewhere. As well, I decided to
//! move away from my "context" struct having a parent-child relation for the split of the data
//! kept in these, and instead just "flatten" it out a bit (sort of) as this seems much more
//! future-proof if/when I encounter more borrow-checker issues down the road with other additions
//! to these structures.
//!
//! But again, better naming still eludes me here!
//!
//! ```
//! use ggdt::prelude::*;
//!
//! pub enum Event { /* .. various events here .. */ }
//!
//! // "Core" because it contains the things that probably 90% of game/app code will need to work
//! // with. you'd probably want to put your game/app resources/assets on this struct too.
//! struct Core {
//! pub delta: f32,
//! pub system: System<DosLike>,
//! pub entities: Entities,
//! pub event_publisher: EventPublisher<Event>,
//! }
//!
//! // "Support" because it contains things that support the main/core game state?
//! // kinda grasping at straws here maybe ...
//! struct Support {
//! pub component_systems: ComponentSystems<Core, Core>,
//! pub event_listeners: EventListeners<Event, Core>,
//! }
//!
//! // better, maybe?
//! struct App {
//! pub core: Core,
//! pub support: Support,
//! }
//! ```
//!
//! Even though it's another struct being added, I do like this more, despite the naming
//! uncertainty.
//!
//! So, with this being my current preferred way to architect a ggdt-using project, I created
//! some traits here in this module to formalize this all a bit more. `CoreState` and (optionally)
//! `CoreStateWithEvents` are what you'd make your project's `Core` struct (as shown in the above
//! example code) implement, while `SupportSystems` and (optionally) `SupportSystemsWithEvents`
//! are what you'd make your project's `Support` struct (again, as shown in the above example code)
//! implement. Finally, `AppContext` is for your `App` struct that contains the two.
//!
//! Once you have all this (which ironically ends up being _more_ code than if you'd not used these
//! traits ... heh), you can now optionally use the `main_loop` function to get a ready-to-use
//! main loop which is set up to use a `ggdt::states::State` state manager.
//!
//! Having said all of this ... again, I will reiterate that I don't believe any of this has reached
//! anything resembling a "good design" ... yet. There may be a good design hidden somewhere in
//! here that I've yet to fully discover, but I definitely don't think I've arrived at quite it yet.
//!
//! So, basically, I expect this to evolve over time (probably a _long_ time). And this is all
//! totally optional anyway.
//!
/*!
Optional, extra types and helpers that can be used to get game's main loop boilerplate up and
running quicker.
This is all of somewhat dubious quality and value at the moment. And it may continue to be this
way for a long while yet. And, truthfully, I suspect I may rip this out eventually. Maybe.
The very-long-winded rationale here is that as I've started building more and more things with
ggdt, I started implementing games/apps using a particular pattern which I was largely
pushed towards due to the Rust borrow-checker (as is often the case with Rust). My games/apps
needed to keep their state (for clarity, the word 'state' here is being used very broadly to
refer to all game/app state, and not just referring to the stuff inside `ggdt::states`)
somewhere and my needs were a bit complicated since my game/app state often included things
which needed to get passed other things from inside that same "bag" of state.
I originally wanted to do something like this, where this `App` struct is our overall "game/app
context" grab bag:
```
use ggdt::prelude::*;
pub enum Event { /* .. various events here .. */ }
struct App {
pub delta: f32,
pub system: System<DosLike>,
pub entities: Entities,
pub component_systems: ComponentSystems<App, App>, // oh no! :'(
pub event_publisher: EventPublisher<Event>,
pub event_listeners: EventListeners<Event, App>, // oh no again! :'(
}
```
Of course, we cannot do this, because then we end up trying to get additional mutable borrows
of `App` when we eventually try to call certain methods on either the `component_systems` or
`event_listeners` instances. Boooo! :-(
That of course lead me to split this structure up. I didn't and still don't like this because
I really don't know what to call these two things. They're both "context" and they're literally
only split up because of borrow-checker issues. But splitting them up did work for me. I
initially went with a parent-child split, which seemed logical to me at the time:
```
use ggdt::prelude::*;
pub enum Event { /* .. various events here .. */ }
// "core" because what the heck else do i call this? "InnerContext"? "InnerApp"? ...
struct Core {
pub delta: f32,
pub system: System<DosLike>,
pub entities: Entities,
pub event_publisher: EventPublisher<Event>,
}
// i guess this is a bit more obvious what to call it, but still ... doesn't sit right with me
struct App {
pub core: Core,
pub component_systems: ComponentSystems<Core, Core>,
pub event_listeners: EventListeners<Event, Core>,
}
```
This structure seemed to work generally well and I've gotten pretty far with it. Keeping the
main `ggdt::states::States` instance _separate_ was also key, and never really a problem
since that can (and should) just live at the top in your main loop. Easy.
I ended up with some common bits of code that I'd always add to projects using this structure,
such as a very simple copy+pasted main loop, as well as a very simple function that calculates
the new frame `delta` each iteration of the main loop. As well as event processing via the
`event_publisher` and `event_listener` instances. I also expect this set of common bits of code
to grow over time. And I, ideally, want a single place to put it all.
So, this module here is my attempt at trying to formalize this a bit more and do a bit of
refactoring where I can keep this common copy+pasted bits somewhere. As well, I decided to
move away from my "context" struct having a parent-child relation for the split of the data
kept in these, and instead just "flatten" it out a bit (sort of) as this seems much more
future-proof if/when I encounter more borrow-checker issues down the road with other additions
to these structures.
But again, better naming still eludes me here!
```
use ggdt::prelude::*;
pub enum Event { /* .. various events here .. */ }
// "Core" because it contains the things that probably 90% of game/app code will need to work
// with. you'd probably want to put your game/app resources/assets on this struct too.
struct Core {
pub delta: f32,
pub system: System<DosLike>,
pub entities: Entities,
pub event_publisher: EventPublisher<Event>,
}
// "Support" because it contains things that support the main/core game state?
// kinda grasping at straws here maybe ...
struct Support {
pub component_systems: ComponentSystems<Core, Core>,
pub event_listeners: EventListeners<Event, Core>,
}
// better, maybe?
struct App {
pub core: Core,
pub support: Support,
}
```
Even though it's another struct being added, I do like this more, despite the naming
uncertainty.
So, with this being my current preferred way to architect a ggdt-using project, I created
some traits here in this module to formalize this all a bit more. `CoreState` and (optionally)
`CoreStateWithEvents` are what you'd make your project's `Core` struct (as shown in the above
example code) implement, while `SupportSystems` and (optionally) `SupportSystemsWithEvents`
are what you'd make your project's `Support` struct (again, as shown in the above example code)
implement. Finally, `AppContext` is for your `App` struct that contains the two.
Once you have all this (which ironically ends up being _more_ code than if you'd not used these
traits ... heh), you can now optionally use the `main_loop` function to get a ready-to-use
main loop which is set up to use a `ggdt::states::State` state manager.
Having said all of this ... again, I will reiterate that I don't believe any of this has reached
anything resembling a "good design" ... yet. There may be a good design hidden somewhere in
here that I've yet to fully discover, but I definitely don't think I've arrived at quite it yet.
So, basically, I expect this to evolve over time (probably a _long_ time). And this is all
totally optional anyway.
*/
use thiserror::Error;

View file

@ -230,6 +230,12 @@ impl Entities {
}
}
impl Default for Entities {
fn default() -> Self {
Self::new()
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// TODO: is there some fancy way to get rid of the impl duplication here ... ?
@ -470,6 +476,12 @@ impl<U, R> ComponentSystems<U, R> {
}
}
impl<U, R> Default for ComponentSystems<U, R> {
fn default() -> Self {
Self::new()
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#[cfg(test)]
@ -667,7 +679,7 @@ mod tests {
// modify position components
{
let mut positions = em.components_mut::<Position>().unwrap();
for mut component in positions.values_mut() {
for component in positions.values_mut() {
component.0 += 5;
}
@ -678,7 +690,7 @@ mod tests {
// modify health components
{
let mut healths = em.components_mut::<Health>().unwrap();
for mut component in healths.values_mut() {
for component in healths.values_mut() {
component.0 += 5;
}
assert_eq!(Health(25), *healths.get(&a).unwrap());
@ -722,10 +734,10 @@ mod tests {
println!("entity {}, health: {:?}, position: {:?}", name.0, health, position);
if let Some(mut health) = health {
if let Some(health) = health {
health.0 += 5;
}
if let Some(mut position) = position {
if let Some(position) = position {
position.0 += 5;
}
}

View file

@ -55,6 +55,12 @@ impl<EventType> EventPublisher<EventType> {
}
}
impl<EventType> Default for EventPublisher<EventType> {
fn default() -> Self {
Self::new()
}
}
/// A manager for application event listeners/handlers that can dispatch events queued up by a
/// [`EventPublisher`] to each of the event listeners/handlers registered with this manager.
///
@ -150,6 +156,12 @@ impl<EventType, ContextType> EventListeners<EventType, ContextType> {
}
}
impl<EventType, ContextType> Default for EventListeners<EventType, ContextType> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -175,10 +187,12 @@ mod tests {
}
fn dummy_listener(_event: &TestEvent, _context: &mut DummyContext) -> bool {
println!("dummy_listener event fired");
false
}
fn other_dummy_listener(_event: &TestEvent, _context: &mut DummyContext) -> bool {
println!("other_dummy_listener event fired");
false
}

View file

@ -127,6 +127,10 @@ fn get_flipped_blit_properties<PixelType: Pixel>(
(x_inc, src_start_x, src_start_y, src_next_row_inc)
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
#[inline]
pub unsafe fn per_pixel_blit<PixelType: Pixel>(
dest: &mut Bitmap<PixelType>,
@ -153,6 +157,10 @@ pub unsafe fn per_pixel_blit<PixelType: Pixel>(
}
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
#[inline]
pub unsafe fn per_pixel_flipped_blit<PixelType: Pixel>(
dest: &mut Bitmap<PixelType>,
@ -183,6 +191,10 @@ pub unsafe fn per_pixel_flipped_blit<PixelType: Pixel>(
}
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
#[inline]
pub unsafe fn per_pixel_rotozoom_blit<PixelType: Pixel>(
dest: &mut Bitmap<PixelType>,
@ -280,6 +292,10 @@ pub unsafe fn per_pixel_rotozoom_blit<PixelType: Pixel>(
}
impl<PixelType: Pixel> Bitmap<PixelType> {
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn solid_blit(&mut self, src: &Self, src_region: &Rect, dest_x: i32, dest_y: i32) {
let src_row_length = src_region.width as usize;
let src_pitch = src.width as usize;
@ -294,6 +310,10 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
}
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn solid_flipped_blit(
&mut self,
src: &Self,
@ -317,6 +337,10 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn transparent_blit(
&mut self,
src: &Self,
@ -339,6 +363,10 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn transparent_flipped_blit(
&mut self,
src: &Self,
@ -365,6 +393,10 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn transparent_single_color_blit(
&mut self,
src: &Self,
@ -388,6 +420,10 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn transparent_flipped_single_color_blit(
&mut self,
src: &Self,
@ -415,6 +451,10 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn rotozoom_blit(
&mut self,
src: &Self,
@ -440,6 +480,10 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn rotozoom_transparent_blit(
&mut self,
src: &Self,

View file

@ -7,7 +7,7 @@
//! Only a subset of the most common Bitmap drawing operations will be provided here.
use crate::graphics::{
BitmapError, Font, FontRenderOpts, IndexedBitmap, IndexedBlitMethod, Pixel, RgbaBitmap, RgbaBlitMethod,
BitmapError, Font, FontRenderOpts, IndexedBitmap, IndexedBlitMethod, Pixel, RgbaBitmap, RgbaBlitMethod, RGBA,
};
use crate::math::Rect;
@ -220,7 +220,7 @@ impl GeneralBitmap for IndexedBitmap {
}
impl GeneralBitmap for RgbaBitmap {
type PixelType = u32;
type PixelType = RGBA;
#[inline]
fn new(width: u32, height: u32) -> Result<Self, BitmapError> {

View file

@ -468,7 +468,7 @@ impl IndexedBitmap {
Ok((bitmap.unwrap(), palette.unwrap()))
}
pub fn load_gif_file(path: &Path) -> Result<(IndexedBitmap, Palette), GifError> {
pub fn load_gif_file(path: impl AsRef<Path>) -> Result<(IndexedBitmap, Palette), GifError> {
let f = File::open(path)?;
let mut reader = BufReader::new(f);
Self::load_gif_bytes(&mut reader)
@ -520,7 +520,12 @@ impl IndexedBitmap {
Ok(())
}
pub fn to_gif_file(&self, path: &Path, palette: &Palette, settings: GifSettings) -> Result<(), GifError> {
pub fn to_gif_file(
&self,
path: impl AsRef<Path>,
palette: &Palette,
settings: GifSettings,
) -> Result<(), GifError> {
let f = File::create(path)?;
let mut writer = BufWriter::new(f);
self.to_gif_bytes(&mut writer, palette, settings)
@ -537,7 +542,7 @@ impl RgbaBitmap {
Ok((output, palette))
}
pub fn load_gif_file(path: &Path) -> Result<(RgbaBitmap, Palette), GifError> {
pub fn load_gif_file(path: impl AsRef<Path>) -> Result<(RgbaBitmap, Palette), GifError> {
let (temp_bitmap, palette) = IndexedBitmap::load_gif_file(path)?;
let output = temp_bitmap.to_rgba(&palette);
Ok((output, palette))
@ -556,7 +561,7 @@ mod tests {
const BASE_PATH: &str = "./test-assets/gif/";
fn test_file(file: &Path) -> PathBuf {
fn test_file(file: impl AsRef<Path>) -> PathBuf {
PathBuf::from(BASE_PATH).join(file)
}
@ -564,14 +569,14 @@ mod tests {
fn load_and_save() -> Result<(), GifError> {
let tmp_dir = TempDir::new()?;
let ref_pixels = load_raw_indexed(test_file(Path::new("small.bin")).as_path())?;
let ref_pixels = load_raw_indexed(test_file("small.bin"))?;
let dp2_palette = Palette::load_from_file(
test_assets_file(Path::new("dp2.pal")).as_path(), //
test_assets_file("dp2.pal"), //
PaletteFormat::Normal,
)
.unwrap();
let (bmp, palette) = IndexedBitmap::load_gif_file(test_file(Path::new("small.gif")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_gif_file(test_file("small.gif"))?;
assert_eq!(16, bmp.width());
assert_eq!(16, bmp.height());
assert_eq!(bmp.pixels(), ref_pixels.as_ref());
@ -597,9 +602,9 @@ mod tests {
// first image
let ref_pixels = load_raw_indexed(test_file(Path::new("large_1.bin")).as_path())?;
let ref_pixels = load_raw_indexed(test_file("large_1.bin"))?;
let (bmp, palette) = IndexedBitmap::load_gif_file(test_file(Path::new("large_1.gif")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_gif_file(test_file("large_1.gif"))?;
assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), ref_pixels.as_ref());
@ -613,9 +618,9 @@ mod tests {
// second image
let ref_pixels = load_raw_indexed(test_file(Path::new("large_2.bin")).as_path())?;
let ref_pixels = load_raw_indexed(test_file("large_2.bin"))?;
let (bmp, palette) = IndexedBitmap::load_gif_file(test_file(Path::new("large_2.gif")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_gif_file(test_file("large_2.gif"))?;
assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), ref_pixels.as_ref());

View file

@ -213,7 +213,7 @@ fn merge_bitplane(plane: u32, src: &[u8], dest: &mut [u8], row_size: usize) {
fn extract_bitplane(plane: u32, src: &[u8], dest: &mut [u8], row_size: usize) {
let bitmask = 1 << plane;
let mut src_base_index = 0;
for x in 0..row_size {
for dest_pixel in dest.iter_mut().take(row_size) {
let mut data = 0;
if src[src_base_index] & bitmask != 0 {
data |= 128;
@ -241,7 +241,7 @@ fn extract_bitplane(plane: u32, src: &[u8], dest: &mut [u8], row_size: usize) {
}
src_base_index += 8;
dest[x] = data;
*dest_pixel = data;
}
}
@ -430,7 +430,7 @@ impl IndexedBitmap {
Ok((bitmap.unwrap(), palette.unwrap()))
}
pub fn load_iff_file(path: &Path) -> Result<(IndexedBitmap, Palette), IffError> {
pub fn load_iff_file(path: impl AsRef<Path>) -> Result<(IndexedBitmap, Palette), IffError> {
let f = File::open(path)?;
let mut reader = BufReader::new(f);
Self::load_iff_bytes(&mut reader)
@ -522,7 +522,7 @@ impl IndexedBitmap {
Ok(())
}
pub fn to_iff_file(&self, path: &Path, palette: &Palette, format: IffFormat) -> Result<(), IffError> {
pub fn to_iff_file(&self, path: impl AsRef<Path>, palette: &Palette, format: IffFormat) -> Result<(), IffError> {
let f = File::create(path)?;
let mut writer = BufWriter::new(f);
self.to_iff_bytes(&mut writer, palette, format)
@ -539,7 +539,7 @@ impl RgbaBitmap {
Ok((output, palette))
}
pub fn load_iff_file(path: &Path) -> Result<(RgbaBitmap, Palette), IffError> {
pub fn load_iff_file(path: impl AsRef<Path>) -> Result<(RgbaBitmap, Palette), IffError> {
let (temp_bitmap, palette) = IndexedBitmap::load_iff_file(path)?;
let output = temp_bitmap.to_rgba(&palette);
Ok((output, palette))
@ -558,7 +558,7 @@ mod tests {
const BASE_PATH: &str = "./test-assets/iff/";
fn test_file(file: &Path) -> PathBuf {
fn test_file(file: impl AsRef<Path>) -> PathBuf {
PathBuf::from(BASE_PATH).join(file)
}
@ -566,16 +566,16 @@ mod tests {
pub fn load_and_save() -> Result<(), IffError> {
let tmp_dir = TempDir::new()?;
let ref_pixels = load_raw_indexed(test_file(Path::new("small.bin")).as_path())?;
let ref_pixels = load_raw_indexed(test_file("small.bin"))?;
let dp2_palette = Palette::load_from_file(
test_assets_file(Path::new("dp2.pal")).as_path(), //
test_assets_file("dp2.pal"), //
PaletteFormat::Normal,
)
.unwrap();
// ILBM format
let (bmp, palette) = IndexedBitmap::load_iff_file(test_file(Path::new("small.lbm")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_iff_file(test_file("small.lbm"))?;
assert_eq!(16, bmp.width());
assert_eq!(16, bmp.height());
assert_eq!(bmp.pixels(), ref_pixels.as_ref());
@ -591,7 +591,7 @@ mod tests {
// PBM format
let (bmp, palette) = IndexedBitmap::load_iff_file(test_file(Path::new("small.pbm")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_iff_file(test_file("small.pbm"))?;
assert_eq!(16, bmp.width());
assert_eq!(16, bmp.height());
assert_eq!(bmp.pixels(), ref_pixels.as_ref());
@ -614,9 +614,9 @@ mod tests {
// first image, PBM format
let ref_pixels = load_raw_indexed(test_file(Path::new("large_1.bin")).as_path())?;
let ref_pixels = load_raw_indexed(test_file("large_1.bin"))?;
let (bmp, palette) = IndexedBitmap::load_iff_file(test_file(Path::new("large_1.pbm")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_iff_file(test_file("large_1.pbm"))?;
assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), ref_pixels.as_ref());
@ -630,7 +630,7 @@ mod tests {
// first image, ILBM format
let (bmp, palette) = IndexedBitmap::load_iff_file(test_file(Path::new("large_1.lbm")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_iff_file(test_file("large_1.lbm"))?;
assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), ref_pixels.as_ref());
@ -644,9 +644,9 @@ mod tests {
// second image, PBM format
let ref_pixels = load_raw_indexed(test_file(Path::new("large_2.bin")).as_path())?;
let ref_pixels = load_raw_indexed(test_file("large_2.bin"))?;
let (bmp, palette) = IndexedBitmap::load_iff_file(test_file(Path::new("large_2.lbm")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_iff_file(test_file("large_2.lbm"))?;
assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), ref_pixels.as_ref());
@ -660,7 +660,7 @@ mod tests {
// second image, ILBM format
let (bmp, palette) = IndexedBitmap::load_iff_file(test_file(Path::new("large_2.lbm")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_iff_file(test_file("large_2.lbm"))?;
assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), ref_pixels.as_ref());

View file

@ -126,6 +126,10 @@ pub enum IndexedBlitMethod {
}
impl IndexedBitmap {
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn solid_blended_blit(
&mut self,
src: &Self,
@ -150,6 +154,10 @@ impl IndexedBitmap {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn solid_flipped_blended_blit(
&mut self,
src: &Self,
@ -178,6 +186,10 @@ impl IndexedBitmap {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn solid_palette_offset_blit(
&mut self,
src: &Self,
@ -198,6 +210,10 @@ impl IndexedBitmap {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn solid_flipped_palette_offset_blit(
&mut self,
src: &Self,
@ -222,6 +238,10 @@ impl IndexedBitmap {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn transparent_blended_blit(
&mut self,
src: &Self,
@ -249,6 +269,10 @@ impl IndexedBitmap {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn transparent_flipped_blended_blit(
&mut self,
src: &Self,
@ -280,6 +304,10 @@ impl IndexedBitmap {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn transparent_palette_offset_blit(
&mut self,
src: &Self,
@ -303,6 +331,10 @@ impl IndexedBitmap {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn transparent_flipped_palette_offset_blit(
&mut self,
src: &Self,
@ -330,6 +362,10 @@ impl IndexedBitmap {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn rotozoom_blended_blit(
&mut self,
src: &Self,
@ -363,6 +399,10 @@ impl IndexedBitmap {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn rotozoom_transparent_blended_blit(
&mut self,
src: &Self,
@ -399,6 +439,10 @@ impl IndexedBitmap {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn rotozoom_palette_offset_blit(
&mut self,
src: &Self,
@ -426,6 +470,10 @@ impl IndexedBitmap {
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn rotozoom_transparent_palette_offset_blit(
&mut self,
src: &Self,
@ -522,6 +570,10 @@ impl IndexedBitmap {
};
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
#[inline]
#[rustfmt::skip]
pub unsafe fn blit_region_unchecked(
@ -606,12 +658,20 @@ impl IndexedBitmap {
}
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
#[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);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
#[inline]
pub unsafe fn blit_atlas_unchecked(
&mut self,

View file

@ -1,6 +1,6 @@
use std::path::Path;
use crate::graphics::{Bitmap, BitmapError, Palette, RgbaBitmap};
use crate::graphics::{Bitmap, BitmapError, Palette, RgbaBitmap, RGBA};
mod blit;
mod primitives;
@ -25,8 +25,8 @@ impl IndexedBitmap {
Self::internal_new(width, height, 0)
}
pub fn load_file(path: &Path) -> Result<(Self, Palette), BitmapError> {
if let Some(extension) = path.extension() {
pub fn load_file(path: impl AsRef<Path>) -> Result<(Self, Palette), BitmapError> {
if let Some(extension) = path.as_ref().extension() {
let extension = extension.to_ascii_lowercase();
match extension.to_str() {
Some("png") => {
@ -44,14 +44,14 @@ impl IndexedBitmap {
}
/// 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
/// 32-bit RGBA-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
/// * `dest`: destination 32-bit RGBA 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) {
pub fn copy_as_rgba_to(&self, dest: &mut [RGBA], palette: &Palette) {
for (src, dest) in self.pixels().iter().zip(dest.iter_mut()) {
*dest = palette[*src];
}
@ -67,7 +67,7 @@ impl IndexedBitmap {
/// returns: `RgbaBitmap`
pub fn to_rgba(&self, palette: &Palette) -> RgbaBitmap {
let mut output = RgbaBitmap::new(self.width, self.height).unwrap();
self.copy_as_argb_to(output.pixels_mut(), palette);
self.copy_as_rgba_to(output.pixels_mut(), palette);
output
}
}

View file

@ -20,8 +20,11 @@ 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. The
/// coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// or using the color specified if the blend map does not include the given color.
///
/// # Safety
///
/// 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) {
@ -40,6 +43,7 @@ impl IndexedBitmap {
/// 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.
#[inline]
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) {
self.line_custom(
@ -56,6 +60,7 @@ impl IndexedBitmap {
/// 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.
#[inline]
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) {
self.horiz_line_custom(
@ -71,6 +76,7 @@ impl IndexedBitmap {
/// 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.
#[inline]
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) {
self.vert_line_custom(
@ -88,6 +94,7 @@ impl IndexedBitmap {
/// 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.
#[inline]
pub fn blended_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) {
self.rect_custom(
@ -106,6 +113,7 @@ impl IndexedBitmap {
/// 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.
#[inline]
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) {
self.filled_rect_custom(

View file

@ -89,7 +89,7 @@ impl IndexedBitmap {
use IndexedTriangle2d::*;
match triangle {
Solid { position, color } => self.solid_triangle_2d(position, *color),
SolidBlended { position, color, blendmap } => self.solid_blended_triangle_2d(position, *color, *blendmap),
SolidBlended { position, color, blendmap } => self.solid_blended_triangle_2d(position, *color, blendmap),
SolidTextured { position, texcoord, bitmap } => self.solid_textured_triangle_2d(position, texcoord, bitmap),
SolidTexturedBlended { position, texcoord, bitmap, blendmap } => {
self.solid_textured_blended_triangle_2d(position, texcoord, bitmap, blendmap)

View file

@ -212,8 +212,12 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
}
/// Returns an 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.
/// given coordinates and extending to the end of the bitmap.
///
/// # Safety
///
/// 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) -> &[PixelType] {
let offset = self.get_offset_to_xy(x, y);
@ -221,8 +225,12 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
}
/// 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.
/// at the given coordinates and extending to the end of the bitmap.
///
/// # Safety
///
/// 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 [PixelType] {
let offset = self.get_offset_to_xy(x, y);
@ -236,10 +244,10 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
/// 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 PixelType> {
pub 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))
Some(unsafe { self.pixels.as_ptr().add(offset) })
} else {
None
}
@ -249,18 +257,22 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
/// 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 PixelType> {
pub 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))
Some(unsafe { self.pixels.as_mut_ptr().add(offset) })
} else {
None
}
}
/// Returns an unsafe pointer to the subset of the raw pixels in this bitmap beginning 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.
/// given coordinates.
///
/// # Safety
///
/// 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 PixelType {
let offset = self.get_offset_to_xy(x, y);
@ -268,8 +280,12 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
}
/// Returns a mutable unsafe pointer to the subset of the raw pixels in this bitmap beginning
/// 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.
/// at the given coordinates.
///
/// # Safety
///
/// 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 PixelType {
let offset = self.get_offset_to_xy(x, y);
@ -482,15 +498,15 @@ mod tests {
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) });
assert_eq!(None, bmp.pixels_at_ptr(-1, -1));
let offset = bmp.get_offset_to_xy(1, 1);
let pixels = unsafe { bmp.pixels_at_ptr(0, 0).unwrap() };
let pixels = bmp.pixels_at_ptr(0, 0).unwrap();
assert_eq!(0, unsafe { *pixels });
assert_eq!(1, unsafe { *(pixels.add(offset)) });
assert_eq!(2, unsafe { *(pixels.add(63)) });
let pixels = unsafe { bmp.pixels_at_ptr(1, 1).unwrap() };
let pixels = bmp.pixels_at_ptr(1, 1).unwrap();
assert_eq!(1, unsafe { *pixels });
assert_eq!(2, unsafe { *(pixels.add(54)) });
}
@ -500,15 +516,15 @@ mod tests {
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) });
assert_eq!(None, bmp.pixels_at_mut_ptr(-1, -1));
let offset = bmp.get_offset_to_xy(1, 1);
let pixels = unsafe { bmp.pixels_at_mut_ptr(0, 0).unwrap() };
let pixels = bmp.pixels_at_mut_ptr(0, 0).unwrap();
assert_eq!(0, unsafe { *pixels });
assert_eq!(1, unsafe { *(pixels.add(offset)) });
assert_eq!(2, unsafe { *(pixels.add(63)) });
let pixels = unsafe { bmp.pixels_at_mut_ptr(1, 1).unwrap() };
let pixels = bmp.pixels_at_mut_ptr(1, 1).unwrap();
assert_eq!(1, unsafe { *pixels });
assert_eq!(2, unsafe { *(pixels.add(54)) });
}

View file

@ -5,7 +5,7 @@ use std::path::Path;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use thiserror::Error;
use crate::graphics::{from_rgb32, IndexedBitmap, Palette, PaletteError, PaletteFormat, RgbaBitmap};
use crate::graphics::{IndexedBitmap, Palette, PaletteError, PaletteFormat, RgbaBitmap};
use crate::utils::ReadFixedLengthByteArray;
#[derive(Error, Debug)]
@ -178,7 +178,7 @@ impl IndexedBitmap {
Ok((bmp, palette))
}
pub fn load_pcx_file(path: &Path) -> Result<(IndexedBitmap, Palette), PcxError> {
pub fn load_pcx_file(path: impl AsRef<Path>) -> Result<(IndexedBitmap, Palette), PcxError> {
let f = File::open(path)?;
let mut reader = BufReader::new(f);
Self::load_pcx_bytes(&mut reader)
@ -241,17 +241,16 @@ impl IndexedBitmap {
writer.write_u8(0xc)?;
for i in 0..=255 {
let argb = palette[i];
let [r, g, b] = from_rgb32(argb);
writer.write_u8(r)?;
writer.write_u8(g)?;
writer.write_u8(b)?;
let color = palette[i];
writer.write_u8(color.r())?;
writer.write_u8(color.g())?;
writer.write_u8(color.b())?;
}
Ok(())
}
pub fn to_pcx_file(&self, path: &Path, palette: &Palette) -> Result<(), PcxError> {
pub fn to_pcx_file(&self, path: impl AsRef<Path>, palette: &Palette) -> Result<(), PcxError> {
let f = File::create(path)?;
let mut writer = BufWriter::new(f);
self.to_pcx_bytes(&mut writer, palette)
@ -268,7 +267,7 @@ impl RgbaBitmap {
Ok((output, palette))
}
pub fn load_pcx_file(path: &Path) -> Result<(RgbaBitmap, Palette), PcxError> {
pub fn load_pcx_file(path: impl AsRef<Path>) -> Result<(RgbaBitmap, Palette), PcxError> {
let (temp_bitmap, palette) = IndexedBitmap::load_pcx_file(path)?;
let output = temp_bitmap.to_rgba(&palette);
Ok((output, palette))
@ -287,7 +286,7 @@ mod tests {
const BASE_PATH: &str = "./test-assets/pcx/";
fn test_file(file: &Path) -> PathBuf {
fn test_file(file: impl AsRef<Path>) -> PathBuf {
PathBuf::from(BASE_PATH).join(file)
}
@ -295,14 +294,14 @@ mod tests {
pub fn load_and_save() -> Result<(), PcxError> {
let tmp_dir = TempDir::new()?;
let ref_pixels = load_raw_indexed(test_file(Path::new("small.bin")).as_path())?;
let ref_pixels = load_raw_indexed(test_file("small.bin"))?;
let dp2_palette = Palette::load_from_file(
test_assets_file(Path::new("dp2.pal")).as_path(), //
test_assets_file("dp2.pal"), //
PaletteFormat::Normal,
)
.unwrap();
let (bmp, palette) = IndexedBitmap::load_pcx_file(test_file(Path::new("small.pcx")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_pcx_file(test_file("small.pcx"))?;
assert_eq!(16, bmp.width());
assert_eq!(16, bmp.height());
assert_eq!(bmp.pixels(), ref_pixels.as_ref());
@ -325,9 +324,9 @@ mod tests {
// first image
let ref_pixels = load_raw_indexed(test_file(Path::new("large_1.bin")).as_path())?;
let ref_pixels = load_raw_indexed(test_file("large_1.bin"))?;
let (bmp, palette) = IndexedBitmap::load_pcx_file(test_file(Path::new("large_1.pcx")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_pcx_file(test_file("large_1.pcx"))?;
assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), ref_pixels.as_ref());
@ -341,9 +340,9 @@ mod tests {
// second image
let ref_pixels = load_raw_indexed(test_file(Path::new("large_2.bin")).as_path())?;
let ref_pixels = load_raw_indexed(test_file("large_2.bin"))?;
let (bmp, palette) = IndexedBitmap::load_pcx_file(test_file(Path::new("large_2.pcx")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_pcx_file(test_file("large_2.pcx"))?;
assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), ref_pixels.as_ref());

View file

@ -7,10 +7,7 @@ use std::path::Path;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use thiserror::Error;
use crate::graphics::{
from_argb32, from_rgb32, to_argb32, to_rgb32, Bitmap, IndexedBitmap, Palette, PaletteError, PaletteFormat, Pixel,
RgbaBitmap,
};
use crate::graphics::{Bitmap, IndexedBitmap, Palette, PaletteError, PaletteFormat, Pixel, RgbaBitmap, RGBA};
use crate::utils::ReadFixedLengthByteArray;
const PNG_HEADER: [u8; 8] = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
@ -45,10 +42,10 @@ pub enum PngFormat {
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum ColorFormat {
Grayscale = 0,
RGB = 2,
Rgb = 2,
IndexedColor = 3,
GrayscaleAlpha = 4,
RGBA = 6,
Rgba = 6,
}
impl ColorFormat {
@ -56,10 +53,10 @@ impl ColorFormat {
use ColorFormat::*;
match value {
0 => Ok(Grayscale),
2 => Ok(RGB),
2 => Ok(Rgb),
3 => Ok(IndexedColor),
4 => Ok(GrayscaleAlpha),
6 => Ok(RGBA),
6 => Ok(Rgba),
_ => Err(PngError::UnsupportedColorType(value)),
}
}
@ -205,8 +202,8 @@ impl ScanlineBuffer {
pub fn new(ihdr: &ImageHeaderChunk) -> Result<Self, PngError> {
let bpp = match ihdr.format {
ColorFormat::IndexedColor => 1,
ColorFormat::RGB => 3,
ColorFormat::RGBA => 4,
ColorFormat::Rgb => 3,
ColorFormat::Rgba => 4,
_ => return Err(PngError::BadFile(format!("Unsupported color format: {:?}", ihdr.format))),
};
let stride = ihdr.width as usize * bpp;
@ -322,8 +319,8 @@ impl ScanlinePixelConverter<u8> for ScanlineBuffer {
}
}
impl ScanlinePixelConverter<u32> for ScanlineBuffer {
fn read_pixel(&mut self, x: usize, palette: &Option<Palette>) -> Result<u32, PngError> {
impl ScanlinePixelConverter<RGBA> for ScanlineBuffer {
fn read_pixel(&mut self, x: usize, palette: &Option<Palette>) -> Result<RGBA, PngError> {
let offset = x * self.bpp;
match self.format {
ColorFormat::IndexedColor => {
@ -336,39 +333,37 @@ impl ScanlinePixelConverter<u32> for ScanlineBuffer {
)))
}
}
ColorFormat::RGB => {
ColorFormat::Rgb => {
let r = self.current[offset];
let g = self.current[offset + 1];
let b = self.current[offset + 2];
Ok(to_rgb32([r, g, b]))
Ok(RGBA::from_rgb([r, g, b]))
}
ColorFormat::RGBA => {
ColorFormat::Rgba => {
let r = self.current[offset];
let g = self.current[offset + 1];
let b = self.current[offset + 2];
let a = self.current[offset + 3];
Ok(to_argb32([a, r, g, b]))
Ok(RGBA::from_rgba([r, g, b, a]))
}
_ => Err(PngError::BadFile(format!("Unsupported color format for this PixelReader: {:?}", self.format))),
}
}
fn write_pixel(&mut self, x: usize, pixel: u32) -> Result<(), PngError> {
fn write_pixel(&mut self, x: usize, pixel: RGBA) -> Result<(), PngError> {
let offset = x * self.bpp;
match self.format {
ColorFormat::RGB => {
let [r, g, b] = from_rgb32(pixel);
self.current[offset] = r;
self.current[offset + 1] = g;
self.current[offset + 2] = b;
ColorFormat::Rgb => {
self.current[offset] = pixel.r();
self.current[offset + 1] = pixel.g();
self.current[offset + 2] = pixel.b();
Ok(())
}
ColorFormat::RGBA => {
let [a, r, g, b] = from_argb32(pixel);
self.current[offset] = r;
self.current[offset + 1] = g;
self.current[offset + 2] = b;
self.current[offset + 3] = a;
ColorFormat::Rgba => {
self.current[offset] = pixel.r();
self.current[offset + 1] = pixel.g();
self.current[offset + 2] = pixel.b();
self.current[offset + 3] = pixel.a();
Ok(())
}
_ => Err(PngError::BadFile(format!("Unsupported color format for this PixelReader: {:?}", self.format))),
@ -405,8 +400,8 @@ where
return Err(PngError::BadFile(String::from("Unsupported color bit depth.")));
}
if ihdr.format != ColorFormat::IndexedColor // .
&& ihdr.format != ColorFormat::RGB
&& ihdr.format != ColorFormat::RGBA
&& ihdr.format != ColorFormat::Rgb
&& ihdr.format != ColorFormat::Rgba
{
return Err(PngError::BadFile(String::from("Unsupported pixel color format.")));
}
@ -565,7 +560,7 @@ impl IndexedBitmap {
load_png_bytes(reader)
}
pub fn load_png_file(path: &Path) -> Result<(IndexedBitmap, Option<Palette>), PngError> {
pub fn load_png_file(path: impl AsRef<Path>) -> Result<(IndexedBitmap, Option<Palette>), PngError> {
let f = File::open(path)?;
let mut reader = BufReader::new(f);
Self::load_png_bytes(&mut reader)
@ -575,7 +570,7 @@ impl IndexedBitmap {
write_png_bytes(writer, self, ColorFormat::IndexedColor, Some(palette))
}
pub fn to_png_file(&self, path: &Path, palette: &Palette) -> Result<(), PngError> {
pub fn to_png_file(&self, path: impl AsRef<Path>, palette: &Palette) -> Result<(), PngError> {
let f = File::create(path)?;
let mut writer = BufWriter::new(f);
self.to_png_bytes(&mut writer, palette)
@ -587,7 +582,7 @@ impl RgbaBitmap {
load_png_bytes(reader)
}
pub fn load_png_file(path: &Path) -> Result<(RgbaBitmap, Option<Palette>), PngError> {
pub fn load_png_file(path: impl AsRef<Path>) -> Result<(RgbaBitmap, Option<Palette>), PngError> {
let f = File::open(path)?;
let mut reader = BufReader::new(f);
Self::load_png_bytes(&mut reader)
@ -598,14 +593,14 @@ impl RgbaBitmap {
writer,
self,
match format {
PngFormat::RGB => ColorFormat::RGB,
PngFormat::RGBA => ColorFormat::RGBA,
PngFormat::RGB => ColorFormat::Rgb,
PngFormat::RGBA => ColorFormat::Rgba,
},
None,
)
}
pub fn to_png_file(&self, path: &Path, format: PngFormat) -> Result<(), PngError> {
pub fn to_png_file(&self, path: impl AsRef<Path>, format: PngFormat) -> Result<(), PngError> {
let f = File::create(path)?;
let mut writer = BufWriter::new(f);
self.to_png_bytes(&mut writer, format)
@ -619,20 +614,20 @@ mod tests {
use claim::*;
use tempfile::TempDir;
use crate::tests::{load_raw_argb, load_raw_indexed};
use crate::tests::{load_raw_indexed, load_raw_rgba};
use super::*;
const BASE_PATH: &str = "./test-assets/png/";
fn test_file(file: &Path) -> PathBuf {
fn test_file(file: impl AsRef<Path>) -> PathBuf {
PathBuf::from(BASE_PATH).join(file)
}
#[test]
pub fn loads_indexed_256_color() -> Result<(), PngError> {
let ref_bytes = load_raw_indexed(test_file(Path::new("indexed_8.bin")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_png_file(test_file(Path::new("indexed_8.png")).as_path())?;
let ref_bytes = load_raw_indexed(test_file("indexed_8.bin"))?;
let (bmp, palette) = IndexedBitmap::load_png_file(test_file("indexed_8.png"))?;
assert!(palette.is_some());
assert_eq!(ref_bytes, bmp.pixels);
Ok(())
@ -640,8 +635,8 @@ mod tests {
#[test]
pub fn loads_indexed_256_color_to_rgba_destination() -> Result<(), PngError> {
let ref_bytes = load_raw_argb(test_file(Path::new("indexed_8_rgba.bin")).as_path())?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file(Path::new("indexed_8.png")).as_path())?;
let ref_bytes = load_raw_rgba(test_file("indexed_8_rgba.bin"))?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file("indexed_8.png"))?;
assert!(palette.is_some());
assert_eq!(ref_bytes, bmp.pixels);
Ok(())
@ -649,8 +644,8 @@ mod tests {
#[test]
pub fn loads_rgb_color() -> Result<(), PngError> {
let ref_bytes = load_raw_argb(test_file(Path::new("rgb.bin")).as_path())?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file(Path::new("rgb.png")).as_path())?;
let ref_bytes = load_raw_rgba(test_file("rgb.bin"))?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file("rgb.png"))?;
assert!(palette.is_none());
assert_eq!(ref_bytes, bmp.pixels);
Ok(())
@ -658,8 +653,8 @@ mod tests {
#[test]
pub fn loads_rgba_color() -> Result<(), PngError> {
let ref_bytes = load_raw_argb(test_file(Path::new("rgba.bin")).as_path())?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file(Path::new("rgba.png")).as_path())?;
let ref_bytes = load_raw_rgba(test_file("rgba.bin"))?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file("rgba.png"))?;
assert!(palette.is_none());
assert_eq!(ref_bytes, bmp.pixels);
Ok(())
@ -667,8 +662,8 @@ mod tests {
#[test]
pub fn loads_filter_0() -> Result<(), PngError> {
let ref_bytes = load_raw_argb(test_file(Path::new("filter_0_rgb.bin")).as_path())?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file(Path::new("filter_0_rgb.png")).as_path())?;
let ref_bytes = load_raw_rgba(test_file("filter_0_rgb.bin"))?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file("filter_0_rgb.png"))?;
assert!(palette.is_none());
assert_eq!(ref_bytes, bmp.pixels);
Ok(())
@ -676,8 +671,8 @@ mod tests {
#[test]
pub fn loads_filter_1() -> Result<(), PngError> {
let ref_bytes = load_raw_argb(test_file(Path::new("filter_1_rgb.bin")).as_path())?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file(Path::new("filter_1_rgb.png")).as_path())?;
let ref_bytes = load_raw_rgba(test_file("filter_1_rgb.bin"))?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file("filter_1_rgb.png"))?;
assert!(palette.is_none());
assert_eq!(ref_bytes, bmp.pixels);
Ok(())
@ -685,8 +680,8 @@ mod tests {
#[test]
pub fn loads_filter_2() -> Result<(), PngError> {
let ref_bytes = load_raw_argb(test_file(Path::new("filter_2_rgb.bin")).as_path())?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file(Path::new("filter_2_rgb.png")).as_path())?;
let ref_bytes = load_raw_rgba(test_file("filter_2_rgb.bin"))?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file("filter_2_rgb.png"))?;
assert!(palette.is_none());
assert_eq!(ref_bytes, bmp.pixels);
Ok(())
@ -694,8 +689,8 @@ mod tests {
#[test]
pub fn loads_filter_3() -> Result<(), PngError> {
let ref_bytes = load_raw_argb(test_file(Path::new("filter_3_rgb.bin")).as_path())?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file(Path::new("filter_3_rgb.png")).as_path())?;
let ref_bytes = load_raw_rgba(test_file("filter_3_rgb.bin"))?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file("filter_3_rgb.png"))?;
assert!(palette.is_none());
assert_eq!(ref_bytes, bmp.pixels);
Ok(())
@ -703,8 +698,8 @@ mod tests {
#[test]
pub fn loads_filter_4() -> Result<(), PngError> {
let ref_bytes = load_raw_argb(test_file(Path::new("filter_4_rgb.bin")).as_path())?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file(Path::new("filter_4_rgb.png")).as_path())?;
let ref_bytes = load_raw_rgba(test_file("filter_4_rgb.bin"))?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file("filter_4_rgb.png"))?;
assert!(palette.is_none());
assert_eq!(ref_bytes, bmp.pixels);
Ok(())
@ -712,13 +707,13 @@ mod tests {
#[test]
pub fn loads_larger_indexed_256color_images() -> Result<(), PngError> {
let ref_bytes = load_raw_indexed(test_file(Path::new("large_1_indexed.bin")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_png_file(test_file(Path::new("large_1_indexed.png")).as_path())?;
let ref_bytes = load_raw_indexed(test_file("large_1_indexed.bin"))?;
let (bmp, palette) = IndexedBitmap::load_png_file(test_file("large_1_indexed.png"))?;
assert!(palette.is_some());
assert_eq!(ref_bytes, bmp.pixels);
let ref_bytes = load_raw_indexed(test_file(Path::new("large_2_indexed.bin")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_png_file(test_file(Path::new("large_2_indexed.png")).as_path())?;
let ref_bytes = load_raw_indexed(test_file("large_2_indexed.bin"))?;
let (bmp, palette) = IndexedBitmap::load_png_file(test_file("large_2_indexed.png"))?;
assert!(palette.is_some());
assert_eq!(ref_bytes, bmp.pixels);
@ -727,13 +722,13 @@ mod tests {
#[test]
pub fn loads_larger_rgb_images() -> Result<(), PngError> {
let ref_bytes = load_raw_argb(test_file(Path::new("large_1_rgba.bin")).as_path())?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file(Path::new("large_1_rgb.png")).as_path())?;
let ref_bytes = load_raw_rgba(test_file("large_1_rgba.bin"))?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file("large_1_rgb.png"))?;
assert!(palette.is_none());
assert_eq!(ref_bytes, bmp.pixels);
let ref_bytes = load_raw_argb(test_file(Path::new("large_2_rgba.bin")).as_path())?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file(Path::new("large_2_rgb.png")).as_path())?;
let ref_bytes = load_raw_rgba(test_file("large_2_rgba.bin"))?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file("large_2_rgb.png"))?;
assert!(palette.is_none());
assert_eq!(ref_bytes, bmp.pixels);
@ -744,9 +739,9 @@ mod tests {
pub fn load_and_save_indexed_256_color() -> Result<(), PngError> {
let tmp_dir = TempDir::new()?;
let ref_bytes = load_raw_indexed(test_file(Path::new("indexed_8.bin")).as_path())?;
let ref_bytes = load_raw_indexed(test_file("indexed_8.bin"))?;
let (bmp, palette) = IndexedBitmap::load_png_file(test_file(Path::new("indexed_8.png")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_png_file(test_file("indexed_8.png"))?;
assert_eq!(32, bmp.width());
assert_eq!(32, bmp.height());
assert_eq!(bmp.pixels, ref_bytes);
@ -769,9 +764,9 @@ mod tests {
// first image
let ref_bytes = load_raw_indexed(test_file(Path::new("large_1_indexed.bin")).as_path())?;
let ref_bytes = load_raw_indexed(test_file("large_1_indexed.bin"))?;
let (bmp, palette) = IndexedBitmap::load_png_file(test_file(Path::new("large_1_indexed.png")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_png_file(test_file("large_1_indexed.png"))?;
assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels, ref_bytes);
@ -787,9 +782,9 @@ mod tests {
// second image
let ref_bytes = load_raw_indexed(test_file(Path::new("large_2_indexed.bin")).as_path())?;
let ref_bytes = load_raw_indexed(test_file("large_2_indexed.bin"))?;
let (bmp, palette) = IndexedBitmap::load_png_file(test_file(Path::new("large_2_indexed.png")).as_path())?;
let (bmp, palette) = IndexedBitmap::load_png_file(test_file("large_2_indexed.png"))?;
assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels, ref_bytes);
@ -810,9 +805,9 @@ mod tests {
pub fn load_and_save_rgb_color() -> Result<(), PngError> {
let tmp_dir = TempDir::new()?;
let ref_bytes = load_raw_argb(test_file(Path::new("rgb.bin")).as_path())?;
let ref_bytes = load_raw_rgba(test_file("rgb.bin"))?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file(Path::new("rgb.png")).as_path())?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file("rgb.png"))?;
assert_eq!(32, bmp.width());
assert_eq!(32, bmp.height());
assert_eq!(bmp.pixels, ref_bytes);
@ -835,9 +830,9 @@ mod tests {
// first image
let ref_bytes = load_raw_argb(test_file(Path::new("large_1_rgba.bin")).as_path())?;
let ref_bytes = load_raw_rgba(test_file("large_1_rgba.bin"))?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file(Path::new("large_1_rgb.png")).as_path())?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file("large_1_rgb.png"))?;
assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels, ref_bytes);
@ -853,9 +848,9 @@ mod tests {
// second image
let ref_bytes = load_raw_argb(test_file(Path::new("large_2_rgba.bin")).as_path())?;
let ref_bytes = load_raw_rgba(test_file("large_2_rgba.bin"))?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file(Path::new("large_2_rgb.png")).as_path())?;
let (bmp, palette) = RgbaBitmap::load_png_file(test_file("large_2_rgb.png"))?;
assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels, ref_bytes);
@ -874,58 +869,43 @@ mod tests {
#[test]
pub fn load_fails_on_unsupported_formats() -> Result<(), PngError> {
assert_matches!(RgbaBitmap::load_png_file(test_file("unsupported_alpha_8bit.png")), Err(PngError::BadFile(..)));
assert_matches!(
RgbaBitmap::load_png_file(test_file(Path::new("unsupported_alpha_8bit.png")).as_path()),
RgbaBitmap::load_png_file(test_file("unsupported_greyscale_8bit.png")),
Err(PngError::BadFile(..))
);
assert_matches!(
RgbaBitmap::load_png_file(test_file(Path::new("unsupported_greyscale_8bit.png")).as_path()),
Err(PngError::BadFile(..))
);
assert_matches!(
RgbaBitmap::load_png_file(test_file(Path::new("unsupported_indexed_16col.png")).as_path()),
Err(PngError::BadFile(..))
);
assert_matches!(
RgbaBitmap::load_png_file(test_file(Path::new("unsupported_rgb_16bit.png")).as_path()),
Err(PngError::BadFile(..))
);
assert_matches!(
RgbaBitmap::load_png_file(test_file(Path::new("unsupported_rgba_16bit.png")).as_path()),
RgbaBitmap::load_png_file(test_file("unsupported_indexed_16col.png")),
Err(PngError::BadFile(..))
);
assert_matches!(RgbaBitmap::load_png_file(test_file("unsupported_rgb_16bit.png")), Err(PngError::BadFile(..)));
assert_matches!(RgbaBitmap::load_png_file(test_file("unsupported_rgba_16bit.png")), Err(PngError::BadFile(..)));
assert_matches!(
IndexedBitmap::load_png_file(test_file(Path::new("unsupported_alpha_8bit.png")).as_path()),
IndexedBitmap::load_png_file(test_file("unsupported_alpha_8bit.png")),
Err(PngError::BadFile(..))
);
assert_matches!(
IndexedBitmap::load_png_file(test_file(Path::new("unsupported_greyscale_8bit.png")).as_path()),
IndexedBitmap::load_png_file(test_file("unsupported_greyscale_8bit.png")),
Err(PngError::BadFile(..))
);
assert_matches!(
IndexedBitmap::load_png_file(test_file(Path::new("unsupported_indexed_16col.png")).as_path()),
IndexedBitmap::load_png_file(test_file("unsupported_indexed_16col.png")),
Err(PngError::BadFile(..))
);
assert_matches!(
IndexedBitmap::load_png_file(test_file(Path::new("unsupported_rgb_16bit.png")).as_path()),
IndexedBitmap::load_png_file(test_file("unsupported_rgb_16bit.png")),
Err(PngError::BadFile(..))
);
assert_matches!(
IndexedBitmap::load_png_file(test_file(Path::new("unsupported_rgba_16bit.png")).as_path()),
IndexedBitmap::load_png_file(test_file("unsupported_rgba_16bit.png")),
Err(PngError::BadFile(..))
);
// also test the extra formats that IndexedBitmap does not support which RgbaBitmap does
// (anything not 256-color indexed basically ...)
assert_matches!(
IndexedBitmap::load_png_file(test_file(Path::new("rgb.png")).as_path()),
Err(PngError::BadFile(..))
);
assert_matches!(
IndexedBitmap::load_png_file(test_file(Path::new("rgba.png")).as_path()),
Err(PngError::BadFile(..))
);
assert_matches!(IndexedBitmap::load_png_file(test_file("rgb.png")), Err(PngError::BadFile(..)));
assert_matches!(IndexedBitmap::load_png_file(test_file("rgba.png")), Err(PngError::BadFile(..)));
Ok(())
}

View file

@ -31,9 +31,12 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
}
}
/// 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.
/// Sets the pixel at the given coordinates to the color specified.
///
/// # Safety
///
/// 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);
@ -42,8 +45,12 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
/// Sets the pixel at the given coordinates to the color returned by the given function. The
/// given function is one that accepts a color value that corresponds to the current pixel at
/// the given coordinates. The coordinates are not checked for validity, so it is up to you to
/// ensure they lie within the bounds of the bitmap.
/// the given coordinates.
///
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the bitmap.
#[inline]
pub unsafe fn set_custom_pixel_unchecked(&mut self, x: i32, y: i32, pixel_fn: impl Fn(PixelType) -> PixelType) {
let p = self.pixels_at_mut_ptr_unchecked(x, y);
@ -57,8 +64,12 @@ impl<PixelType: Pixel> Bitmap<PixelType> {
self.pixels_at(x, y).map(|pixels| pixels[0])
}
/// 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.
/// Gets the pixel at the given coordinates.
///
/// # Safety
///
/// 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))

View file

@ -1,6 +1,6 @@
use crate::graphics::{
clip_blit, per_pixel_blit, per_pixel_flipped_blit, per_pixel_rotozoom_blit, tint_argb32, BitmapAtlas,
BlendFunction, RgbaBitmap,
clip_blit, per_pixel_blit, per_pixel_flipped_blit, per_pixel_rotozoom_blit, BitmapAtlas, BlendFunction, RgbaBitmap,
RGBA,
};
use crate::math::Rect;
@ -8,7 +8,7 @@ use crate::math::Rect;
pub enum RgbaBlitMethod {
/// Solid blit, no transparency or other per-pixel adjustments.
Solid,
SolidTinted(u32),
SolidTinted(RGBA),
SolidBlended(BlendFunction),
/// Same as [RgbaBlitMethod::Solid] but the drawn image can also be flipped horizontally
/// and/or vertically.
@ -19,7 +19,7 @@ pub enum RgbaBlitMethod {
SolidFlippedTinted {
horizontal_flip: bool,
vertical_flip: bool,
tint_color: u32,
tint_color: RGBA,
},
SolidFlippedBlended {
horizontal_flip: bool,
@ -27,30 +27,30 @@ pub enum RgbaBlitMethod {
blend: BlendFunction,
},
/// Transparent blit, the specified source color pixels are skipped.
Transparent(u32),
Transparent(RGBA),
TransparentTinted {
transparent_color: u32,
tint_color: u32,
transparent_color: RGBA,
tint_color: RGBA,
},
TransparentBlended {
transparent_color: u32,
transparent_color: RGBA,
blend: BlendFunction,
},
/// Same as [RgbaBlitMethod::Transparent] but the drawn image can also be flipped horizontally
/// and/or vertically.
TransparentFlipped {
transparent_color: u32,
transparent_color: RGBA,
horizontal_flip: bool,
vertical_flip: bool,
},
TransparentFlippedTinted {
transparent_color: u32,
transparent_color: RGBA,
horizontal_flip: bool,
vertical_flip: bool,
tint_color: u32,
tint_color: RGBA,
},
TransparentFlippedBlended {
transparent_color: u32,
transparent_color: RGBA,
horizontal_flip: bool,
vertical_flip: bool,
blend: BlendFunction,
@ -58,15 +58,15 @@ pub enum RgbaBlitMethod {
/// 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,
transparent_color: RGBA,
draw_color: RGBA,
},
/// Combination of [RgbaBlitMethod::TransparentFlipped] and [RgbaBlitMethod::TransparentSingle].
TransparentFlippedSingle {
transparent_color: u32,
transparent_color: RGBA,
horizontal_flip: bool,
vertical_flip: bool,
draw_color: u32,
draw_color: RGBA,
},
/// Rotozoom blit, works the same as [RgbaBlitMethod::Solid] except that rotation and scaling is
/// performed.
@ -79,7 +79,7 @@ pub enum RgbaBlitMethod {
angle: f32,
scale_x: f32,
scale_y: f32,
tint_color: u32,
tint_color: RGBA,
},
RotoZoomBlended {
angle: f32,
@ -92,32 +92,36 @@ pub enum RgbaBlitMethod {
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u32,
transparent_color: RGBA,
},
RotoZoomTransparentTinted {
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u32,
tint_color: u32,
transparent_color: RGBA,
tint_color: RGBA,
},
RotoZoomTransparentBlended {
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u32,
transparent_color: RGBA,
blend: BlendFunction,
},
}
impl RgbaBitmap {
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn solid_tinted_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
tint_color: u32,
tint_color: RGBA,
) {
per_pixel_blit(
self, //
@ -126,11 +130,15 @@ impl RgbaBitmap {
dest_x,
dest_y,
|src_pixels, dest_pixels| {
*dest_pixels = tint_argb32(*src_pixels, tint_color);
*dest_pixels = (*src_pixels).tint(tint_color);
},
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn solid_blended_blit(
&mut self,
src: &Self,
@ -146,11 +154,15 @@ impl RgbaBitmap {
dest_x,
dest_y,
|src_pixels, dest_pixels| {
*dest_pixels = blend.blend_1u32(*src_pixels, *dest_pixels);
*dest_pixels = blend.blend(*src_pixels, *dest_pixels);
},
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn solid_flipped_blended_blit(
&mut self,
src: &Self,
@ -170,11 +182,15 @@ impl RgbaBitmap {
horizontal_flip,
vertical_flip,
|src_pixels, dest_pixels| {
*dest_pixels = blend.blend_1u32(*src_pixels, *dest_pixels);
*dest_pixels = blend.blend(*src_pixels, *dest_pixels);
},
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn solid_flipped_tinted_blit(
&mut self,
src: &Self,
@ -183,7 +199,7 @@ impl RgbaBitmap {
dest_y: i32,
horizontal_flip: bool,
vertical_flip: bool,
tint_color: u32,
tint_color: RGBA,
) {
per_pixel_flipped_blit(
self, //
@ -194,19 +210,23 @@ impl RgbaBitmap {
horizontal_flip,
vertical_flip,
|src_pixels, dest_pixels| {
*dest_pixels = tint_argb32(*src_pixels, tint_color);
*dest_pixels = (*src_pixels).tint(tint_color);
},
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn transparent_tinted_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u32,
tint_color: u32,
transparent_color: RGBA,
tint_color: RGBA,
) {
per_pixel_blit(
self, //
@ -216,19 +236,23 @@ impl RgbaBitmap {
dest_y,
|src_pixels, dest_pixels| {
if *src_pixels != transparent_color {
*dest_pixels = tint_argb32(*src_pixels, tint_color);
*dest_pixels = (*src_pixels).tint(tint_color);
}
},
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn transparent_blended_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u32,
transparent_color: RGBA,
blend: BlendFunction,
) {
per_pixel_blit(
@ -239,22 +263,26 @@ impl RgbaBitmap {
dest_y,
|src_pixels, dest_pixels| {
if *src_pixels != transparent_color {
*dest_pixels = blend.blend_1u32(*src_pixels, *dest_pixels);
*dest_pixels = blend.blend(*src_pixels, *dest_pixels);
}
},
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn transparent_flipped_tinted_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u32,
transparent_color: RGBA,
horizontal_flip: bool,
vertical_flip: bool,
tint_color: u32,
tint_color: RGBA,
) {
per_pixel_flipped_blit(
self, //
@ -266,19 +294,23 @@ impl RgbaBitmap {
vertical_flip,
|src_pixels, dest_pixels| {
if *src_pixels != transparent_color {
*dest_pixels = tint_argb32(*src_pixels, tint_color);
*dest_pixels = (*src_pixels).tint(tint_color);
}
},
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn transparent_flipped_blended_blit(
&mut self,
src: &Self,
src_region: &Rect,
dest_x: i32,
dest_y: i32,
transparent_color: u32,
transparent_color: RGBA,
horizontal_flip: bool,
vertical_flip: bool,
blend: BlendFunction,
@ -293,12 +325,16 @@ impl RgbaBitmap {
vertical_flip,
|src_pixels, dest_pixels| {
if *src_pixels != transparent_color {
*dest_pixels = blend.blend_1u32(*src_pixels, *dest_pixels);
*dest_pixels = blend.blend(*src_pixels, *dest_pixels);
}
},
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn rotozoom_tinted_blit(
&mut self,
src: &Self,
@ -308,7 +344,7 @@ impl RgbaBitmap {
angle: f32,
scale_x: f32,
scale_y: f32,
tint_color: u32,
tint_color: RGBA,
) {
per_pixel_rotozoom_blit(
self, //
@ -320,11 +356,15 @@ impl RgbaBitmap {
scale_x,
scale_y,
|src_pixel, dest_bitmap, draw_x, draw_y| {
dest_bitmap.set_pixel(draw_x, draw_y, tint_argb32(src_pixel, tint_color));
dest_bitmap.set_pixel(draw_x, draw_y, src_pixel.tint(tint_color));
},
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn rotozoom_blended_blit(
&mut self,
src: &Self,
@ -347,12 +387,16 @@ impl RgbaBitmap {
scale_y,
|src_pixel, dest_bitmap, draw_x, draw_y| {
if let Some(dest_pixel) = dest_bitmap.get_pixel(draw_x, draw_y) {
dest_bitmap.set_pixel(draw_x, draw_y, blend.blend_1u32(src_pixel, dest_pixel))
dest_bitmap.set_pixel(draw_x, draw_y, blend.blend(src_pixel, dest_pixel))
}
},
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn rotozoom_transparent_tinted_blit(
&mut self,
src: &Self,
@ -362,8 +406,8 @@ impl RgbaBitmap {
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u32,
tint_color: u32,
transparent_color: RGBA,
tint_color: RGBA,
) {
per_pixel_rotozoom_blit(
self, //
@ -376,12 +420,16 @@ impl RgbaBitmap {
scale_y,
|src_pixel, dest_bitmap, draw_x, draw_y| {
if transparent_color != src_pixel {
dest_bitmap.set_pixel(draw_x, draw_y, tint_argb32(src_pixel, tint_color));
dest_bitmap.set_pixel(draw_x, draw_y, src_pixel.tint(tint_color));
}
},
);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
pub unsafe fn rotozoom_transparent_blended_blit(
&mut self,
src: &Self,
@ -391,7 +439,7 @@ impl RgbaBitmap {
angle: f32,
scale_x: f32,
scale_y: f32,
transparent_color: u32,
transparent_color: RGBA,
blend: BlendFunction,
) {
per_pixel_rotozoom_blit(
@ -406,7 +454,7 @@ impl RgbaBitmap {
|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) {
dest_bitmap.set_pixel(draw_x, draw_y, blend.blend_1u32(src_pixel, dest_pixel))
dest_bitmap.set_pixel(draw_x, draw_y, blend.blend(src_pixel, dest_pixel))
}
}
},
@ -479,6 +527,10 @@ impl RgbaBitmap {
};
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
#[inline]
#[rustfmt::skip]
pub unsafe fn blit_region_unchecked(
@ -561,12 +613,20 @@ impl RgbaBitmap {
}
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
#[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);
}
/// # Safety
///
/// Coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// bounds of the source and destination bitmaps.
#[inline]
pub unsafe fn blit_atlas_unchecked(
&mut self,

View file

@ -1,7 +1,7 @@
use byteorder::ReadBytesExt;
use std::path::Path;
use crate::graphics::{to_argb32, to_rgb32, Bitmap, BitmapError, Palette};
use crate::graphics::{Bitmap, BitmapError, Palette, RGBA};
mod blit;
mod primitives;
@ -11,7 +11,7 @@ pub use blit::*;
pub use primitives::*;
pub use triangles::*;
pub type RgbaBitmap = Bitmap<u32>;
pub type RgbaBitmap = Bitmap<RGBA>;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum RgbaPixelFormat {
@ -29,7 +29,7 @@ impl RgbaBitmap {
///
/// returns: `Result<Bitmap, BitmapError>`
pub fn new(width: u32, height: u32) -> Result<Self, BitmapError> {
Self::internal_new(width, height, to_rgb32([0, 0, 0]))
Self::internal_new(width, height, RGBA::from_rgb([0, 0, 0]))
}
pub fn from_bytes<T: ReadBytesExt>(
@ -38,7 +38,7 @@ impl RgbaBitmap {
format: RgbaPixelFormat,
reader: &mut T,
) -> Result<Self, BitmapError> {
let mut bitmap = Self::internal_new(width, height, 0)?;
let mut bitmap = Self::internal_new(width, height, RGBA::from_rgb([0, 0, 0]))?;
for pixel in bitmap.pixels_mut().iter_mut() {
*pixel = match format {
RgbaPixelFormat::RGBA => {
@ -46,22 +46,22 @@ impl RgbaBitmap {
let g = reader.read_u8()?;
let b = reader.read_u8()?;
let a = reader.read_u8()?;
to_argb32([a, r, g, b])
RGBA::from_rgba([r, g, b, a])
}
RgbaPixelFormat::ARGB => {
let a = reader.read_u8()?;
let r = reader.read_u8()?;
let g = reader.read_u8()?;
let b = reader.read_u8()?;
to_argb32([a, r, g, b])
RGBA::from_rgba([r, g, b, a])
}
};
}
Ok(bitmap)
}
pub fn load_file(path: &Path) -> Result<(Self, Option<Palette>), BitmapError> {
if let Some(extension) = path.extension() {
pub fn load_file(path: impl AsRef<Path>) -> Result<(Self, Option<Palette>), BitmapError> {
if let Some(extension) = path.as_ref().extension() {
let extension = extension.to_ascii_lowercase();
match extension.to_str() {
Some("png") => Ok(Self::load_png_file(path)?),

View file

@ -1,85 +1,93 @@
use crate::graphics::{BlendFunction, RgbaBitmap};
use crate::graphics::{BlendFunction, RgbaBitmap, RGBA};
impl RgbaBitmap {
/// Sets the pixel at the given coordinates using a blended color via the specified blend function
/// 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: u32, blend: BlendFunction) {
pub fn set_blended_pixel(&mut self, x: i32, y: i32, color: RGBA, blend: BlendFunction) {
self.set_custom_pixel(
x, //
y,
|dest_color| blend.blend_1u32(color, dest_color),
|dest_color| blend.blend(color, dest_color),
);
}
/// Sets the pixel at the given coordinates using a blended color via the specified blend function,
/// The coordinates are not checked for validity, so it is up to you to ensure they lie within the
/// Sets the pixel at the given coordinates using a blended color via the specified blend function.
///
/// # Safety
///
/// 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: u32, blend: BlendFunction) {
pub unsafe fn set_blended_pixel_unchecked(&mut self, x: i32, y: i32, color: RGBA, blend: BlendFunction) {
self.set_custom_pixel_unchecked(
x, //
y,
|dest_color| blend.blend_1u32(color, dest_color),
|dest_color| blend.blend(color, dest_color),
);
}
/// Draws a line from x1,y1 to x2,y2 by blending the drawn pixels using the given blend function.
pub fn blended_line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: u32, blend: BlendFunction) {
#[inline]
pub fn blended_line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: RGBA, blend: BlendFunction) {
self.line_custom(
x1, //
y1,
x2,
y2,
|dest_color| blend.blend_1u32(color, dest_color),
|dest_color| blend.blend(color, dest_color),
);
}
/// Draws a horizontal line from x1,y to x2,y by blending the drawn pixels using the given
/// blend function.
pub fn blended_horiz_line(&mut self, x1: i32, x2: i32, y: i32, color: u32, blend: BlendFunction) {
#[inline]
pub fn blended_horiz_line(&mut self, x1: i32, x2: i32, y: i32, color: RGBA, blend: BlendFunction) {
self.horiz_line_custom(
x1, //
x2,
y,
|dest_color| blend.blend_1u32(color, dest_color),
|dest_color| blend.blend(color, dest_color),
);
}
/// Draws a vertical line from x,y1 to x,y2 by blending the drawn pixels using the given blend
/// function.
pub fn blended_vert_line(&mut self, x: i32, y1: i32, y2: i32, color: u32, blend: BlendFunction) {
#[inline]
pub fn blended_vert_line(&mut self, x: i32, y1: i32, y2: i32, color: RGBA, blend: BlendFunction) {
self.vert_line_custom(
x, //
y1,
y2,
|dest_color| blend.blend_1u32(color, dest_color),
|dest_color| blend.blend(color, dest_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 function.
pub fn blended_rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: u32, blend: BlendFunction) {
#[inline]
pub fn blended_rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: RGBA, blend: BlendFunction) {
self.rect_custom(
x1, //
y1,
x2,
y2,
|dest_color| blend.blend_1u32(color, dest_color),
|dest_color| blend.blend(color, dest_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 function.
pub fn blended_filled_rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: u32, blend: BlendFunction) {
#[inline]
pub fn blended_filled_rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, color: RGBA, blend: BlendFunction) {
self.filled_rect_custom(
x1, //
y1,
x2,
y2,
|dest_color| blend.blend_1u32(color, dest_color),
|dest_color| blend.blend(color, dest_color),
);
}
}

View file

@ -1,29 +1,27 @@
use std::simd;
use std::simd::prelude::{SimdFloat, SimdUint};
use crate::graphics::{
edge_function, from_argb32_simd, from_rgb32_simd, multiply_argb_simd, per_pixel_triangle_2d, tint_argb_simd,
to_argb32_simd, to_rgb32_simd, BlendFunction, RgbaBitmap,
};
use crate::graphics::{edge_function, per_pixel_triangle_2d, BlendFunction, RgbaBitmap, RGBA};
use crate::math::Vector2;
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum RgbaTriangle2d<'a> {
Solid {
position: [Vector2; 3], //
color: u32,
color: RGBA,
},
SolidBlended {
position: [Vector2; 3], //
color: u32,
color: RGBA,
blend: BlendFunction,
},
SolidMultiColor {
position: [Vector2; 3], //
color: [u32; 3],
color: [RGBA; 3],
},
SolidMultiColorBlended {
position: [Vector2; 3], //
color: [u32; 3],
color: [RGBA; 3],
blend: BlendFunction,
},
SolidTextured {
@ -34,26 +32,26 @@ pub enum RgbaTriangle2d<'a> {
SolidTexturedColored {
position: [Vector2; 3], //
texcoord: [Vector2; 3],
color: u32,
color: RGBA,
bitmap: &'a RgbaBitmap,
},
SolidTexturedColoredBlended {
position: [Vector2; 3], //
texcoord: [Vector2; 3],
color: u32,
color: RGBA,
bitmap: &'a RgbaBitmap,
blend: BlendFunction,
},
SolidTexturedMultiColored {
position: [Vector2; 3], //
texcoord: [Vector2; 3],
color: [u32; 3],
color: [RGBA; 3],
bitmap: &'a RgbaBitmap,
},
SolidTexturedMultiColoredBlended {
position: [Vector2; 3], //
texcoord: [Vector2; 3],
color: [u32; 3],
color: [RGBA; 3],
bitmap: &'a RgbaBitmap,
blend: BlendFunction,
},
@ -61,7 +59,7 @@ pub enum RgbaTriangle2d<'a> {
position: [Vector2; 3], //
texcoord: [Vector2; 3],
bitmap: &'a RgbaBitmap,
tint: u32,
tint: RGBA,
},
SolidTexturedBlended {
position: [Vector2; 3], //
@ -72,7 +70,7 @@ pub enum RgbaTriangle2d<'a> {
}
impl RgbaBitmap {
pub fn solid_triangle_2d(&mut self, positions: &[Vector2; 3], color: u32) {
pub fn solid_triangle_2d(&mut self, positions: &[Vector2; 3], color: RGBA) {
per_pixel_triangle_2d(
self, //
positions[0],
@ -82,24 +80,21 @@ impl RgbaBitmap {
)
}
pub fn solid_blended_triangle_2d(&mut self, positions: &[Vector2; 3], color: u32, blend: BlendFunction) {
let color = from_argb32_simd(color);
pub fn solid_blended_triangle_2d(&mut self, positions: &[Vector2; 3], color: RGBA, blend: BlendFunction) {
per_pixel_triangle_2d(
self, //
positions[0],
positions[1],
positions[2],
|dest_pixels, _w0, _w1, _w2| {
*dest_pixels = to_argb32_simd(blend.blend_simd(color, from_argb32_simd(*dest_pixels)))
},
|dest_pixels, _w0, _w1, _w2| *dest_pixels = blend.blend(color, *dest_pixels),
)
}
pub fn solid_multicolor_triangle_2d(&mut self, positions: &[Vector2; 3], colors: &[u32; 3]) {
pub fn solid_multicolor_triangle_2d(&mut self, positions: &[Vector2; 3], colors: &[RGBA; 3]) {
let area = simd::f32x4::splat(edge_function(positions[0], positions[1], positions[2]));
let color1 = from_rgb32_simd(colors[0]).cast();
let color2 = from_rgb32_simd(colors[1]).cast();
let color3 = from_rgb32_simd(colors[2]).cast();
let color1 = colors[0].0.cast();
let color2 = colors[1].0.cast();
let color3 = colors[2].0.cast();
per_pixel_triangle_2d(
self, //
positions[0],
@ -111,7 +106,7 @@ impl RgbaBitmap {
+ simd::f32x4::splat(w2) * color3)
/ area)
.cast();
*dest_pixels = to_rgb32_simd(color)
*dest_pixels = RGBA(color)
},
)
}
@ -119,13 +114,13 @@ impl RgbaBitmap {
pub fn solid_multicolor_blended_triangle_2d(
&mut self,
positions: &[Vector2; 3],
colors: &[u32; 3],
colors: &[RGBA; 3],
blend: BlendFunction,
) {
let area = simd::f32x4::splat(edge_function(positions[0], positions[1], positions[2]));
let color1 = from_argb32_simd(colors[0]).cast();
let color2 = from_argb32_simd(colors[1]).cast();
let color3 = from_argb32_simd(colors[2]).cast();
let color1 = colors[0].0.cast();
let color2 = colors[1].0.cast();
let color3 = colors[2].0.cast();
per_pixel_triangle_2d(
self, //
positions[0],
@ -137,7 +132,7 @@ impl RgbaBitmap {
+ simd::f32x4::splat(w2) * color3)
/ area)
.cast();
*dest_pixels = to_argb32_simd(blend.blend_simd(color, from_argb32_simd(*dest_pixels)))
*dest_pixels = blend.blend(RGBA(color), *dest_pixels)
},
)
}
@ -166,11 +161,10 @@ impl RgbaBitmap {
&mut self,
positions: &[Vector2; 3],
texcoords: &[Vector2; 3],
color: u32,
color: RGBA,
bitmap: &Self,
) {
let area = simd::f32x2::splat(edge_function(positions[0], positions[1], positions[2]));
let color = from_argb32_simd(color);
let texcoord1 = simd::f32x2::from_array([texcoords[0].x, texcoords[0].y]);
let texcoord2 = simd::f32x2::from_array([texcoords[1].x, texcoords[1].y]);
let texcoord3 = simd::f32x2::from_array([texcoords[2].x, texcoords[2].y]);
@ -184,8 +178,8 @@ impl RgbaBitmap {
+ simd::f32x2::splat(w1) * texcoord2
+ simd::f32x2::splat(w2) * texcoord3)
/ area;
let texel = from_argb32_simd(bitmap.sample_at(texcoord[0], texcoord[1]));
*dest_pixels = to_argb32_simd(multiply_argb_simd(texel, color))
let texel = bitmap.sample_at(texcoord[0], texcoord[1]);
*dest_pixels = texel * color
},
)
}
@ -194,12 +188,11 @@ impl RgbaBitmap {
&mut self,
positions: &[Vector2; 3],
texcoords: &[Vector2; 3],
color: u32,
color: RGBA,
bitmap: &Self,
blend: BlendFunction,
) {
let area = simd::f32x2::splat(edge_function(positions[0], positions[1], positions[2]));
let color = from_argb32_simd(color);
let texcoord1 = simd::f32x2::from_array([texcoords[0].x, texcoords[0].y]);
let texcoord2 = simd::f32x2::from_array([texcoords[1].x, texcoords[1].y]);
let texcoord3 = simd::f32x2::from_array([texcoords[2].x, texcoords[2].y]);
@ -213,9 +206,9 @@ impl RgbaBitmap {
+ simd::f32x2::splat(w1) * texcoord2
+ simd::f32x2::splat(w2) * texcoord3)
/ area;
let texel = from_argb32_simd(bitmap.sample_at(texcoord[0], texcoord[1]));
let src = multiply_argb_simd(texel, color);
*dest_pixels = to_argb32_simd(blend.blend_simd(src, from_argb32_simd(*dest_pixels)))
let texel = bitmap.sample_at(texcoord[0], texcoord[1]);
let src = texel * color;
*dest_pixels = blend.blend(src, *dest_pixels)
},
)
}
@ -224,13 +217,13 @@ impl RgbaBitmap {
&mut self,
positions: &[Vector2; 3],
texcoords: &[Vector2; 3],
colors: &[u32; 3],
colors: &[RGBA; 3],
bitmap: &Self,
) {
let area = simd::f32x4::splat(edge_function(positions[0], positions[1], positions[2]));
let color1 = from_rgb32_simd(colors[0]).cast();
let color2 = from_rgb32_simd(colors[1]).cast();
let color3 = from_rgb32_simd(colors[2]).cast();
let color1 = colors[0].0.cast();
let color2 = colors[1].0.cast();
let color3 = colors[2].0.cast();
// we are using a f32x4 here with two zero's at the end as dummy values just so that we can
// do the texture coordinate interpolation in the inner loop as f32x4 operations.
// however, for the texture coordinates, we only care about the first two lanes in the results ...
@ -248,8 +241,8 @@ impl RgbaBitmap {
let w2 = simd::f32x4::splat(w2);
let color = ((w0 * color1 + w1 * color2 + w2 * color3) / area).cast::<u8>();
let texcoord = (w0 * texcoord1 + w1 * texcoord2 + w2 * texcoord3) / area;
let texel = from_argb32_simd(bitmap.sample_at(texcoord[0], texcoord[1]));
*dest_pixels = to_rgb32_simd(multiply_argb_simd(texel, color))
let texel = bitmap.sample_at(texcoord[0], texcoord[1]);
*dest_pixels = texel * RGBA(color)
},
)
}
@ -258,14 +251,14 @@ impl RgbaBitmap {
&mut self,
positions: &[Vector2; 3],
texcoords: &[Vector2; 3],
colors: &[u32; 3],
colors: &[RGBA; 3],
bitmap: &Self,
blend: BlendFunction,
) {
let area = simd::f32x4::splat(edge_function(positions[0], positions[1], positions[2]));
let color1 = from_argb32_simd(colors[0]).cast();
let color2 = from_argb32_simd(colors[1]).cast();
let color3 = from_argb32_simd(colors[2]).cast();
let color1 = colors[0].0.cast();
let color2 = colors[1].0.cast();
let color3 = colors[2].0.cast();
// we are using a f32x4 here with two zero's at the end as dummy values just so that we can
// do the texture coordinate interpolation in the inner loop as f32x4 operations.
// however, for the texture coordinates, we only care about the first two lanes in the results ...
@ -283,10 +276,9 @@ impl RgbaBitmap {
let w2 = simd::f32x4::splat(w2);
let color = ((w0 * color1 + w1 * color2 + w2 * color3) / area).cast::<u8>();
let texcoord = (w0 * texcoord1 + w1 * texcoord2 + w2 * texcoord3) / area;
let texel = from_argb32_simd(bitmap.sample_at(texcoord[0], texcoord[1]));
let src = multiply_argb_simd(texel, color);
let dest = from_argb32_simd(*dest_pixels);
*dest_pixels = to_argb32_simd(blend.blend_simd(src, dest))
let texel = bitmap.sample_at(texcoord[0], texcoord[1]);
let src = texel * RGBA(color);
*dest_pixels = blend.blend(src, *dest_pixels)
},
)
}
@ -296,10 +288,9 @@ impl RgbaBitmap {
positions: &[Vector2; 3],
texcoords: &[Vector2; 3],
bitmap: &Self,
tint: u32,
tint: RGBA,
) {
let area = simd::f32x2::splat(edge_function(positions[0], positions[1], positions[2]));
let tint = from_argb32_simd(tint);
let texcoord1 = simd::f32x2::from_array([texcoords[0].x, texcoords[0].y]);
let texcoord2 = simd::f32x2::from_array([texcoords[1].x, texcoords[1].y]);
let texcoord3 = simd::f32x2::from_array([texcoords[2].x, texcoords[2].y]);
@ -313,8 +304,8 @@ impl RgbaBitmap {
+ simd::f32x2::splat(w1) * texcoord2
+ simd::f32x2::splat(w2) * texcoord3)
/ area;
let texel = from_argb32_simd(bitmap.sample_at(texcoord[0], texcoord[1]));
*dest_pixels = to_argb32_simd(tint_argb_simd(texel, tint));
let texel = bitmap.sample_at(texcoord[0], texcoord[1]);
*dest_pixels = texel.tint(tint);
},
)
}
@ -340,8 +331,8 @@ impl RgbaBitmap {
+ simd::f32x2::splat(w1) * texcoord2
+ simd::f32x2::splat(w2) * texcoord3)
/ area;
let texel = from_argb32_simd(bitmap.sample_at(texcoord[0], texcoord[1]));
*dest_pixels = to_argb32_simd(blend.blend_simd(texel, from_argb32_simd(*dest_pixels)));
let texel = bitmap.sample_at(texcoord[0], texcoord[1]);
*dest_pixels = blend.blend(texel, *dest_pixels);
},
)
}

View file

@ -1,8 +1,5 @@
use std::simd;
use std::simd::SimdPartialOrd;
use crate::graphics::{Bitmap, Pixel};
use crate::math::{nearly_equal_simd, NearlyEqual, Rect, Vector2};
use crate::math::{NearlyEqual, Rect, Vector2};
#[inline]
pub fn edge_function(a: Vector2, b: Vector2, c: Vector2) -> f32 {
@ -29,32 +26,17 @@ struct TriangleEdge {
y_inc: f32,
is_bottom_right_edge: bool,
origin: f32,
x_inc_simd: simd::f32x4,
y_inc_simd: simd::f32x4,
origin_simd: simd::f32x4,
}
impl TriangleEdge {
pub fn from(v1: Vector2, v2: Vector2, initial_sample_point: Vector2) -> Self {
let x_inc = v1.y - v2.y;
let y_inc = v2.x - v1.x;
let x_inc_simd = simd::f32x4::splat(x_inc * 4.0);
let y_inc_simd = simd::f32x4::splat(y_inc);
let origin = edge_function(v1, v2, initial_sample_point);
let origin_simd = simd::f32x4::from_array([
origin, //
origin + (x_inc * 2.0),
origin + (x_inc * 3.0),
origin + (x_inc * 4.0),
]);
Self {
x_inc,
y_inc,
is_bottom_right_edge: is_bottom_right_edge(v2, v1),
origin,
x_inc_simd,
y_inc_simd,
origin_simd,
origin: edge_function(v1, v2, initial_sample_point),
}
}
@ -65,11 +47,6 @@ impl TriangleEdge {
value <= 0.0
}
#[inline]
pub fn is_inside_simd(&self, value: simd::f32x4) -> simd::mask32x4 {
value.simd_le(simd::f32x4::splat(0.0))
}
#[inline]
pub fn is_on_fill_edge(&self, value: f32) -> bool {
// skip bottom-right edge pixels so we only draw pixels inside the triangle as well as those that lie
@ -77,207 +54,25 @@ impl TriangleEdge {
!(self.is_bottom_right_edge && value.nearly_equal(0.0, f32::EPSILON))
}
#[inline]
pub fn is_on_fill_edge_simd(&self, value: simd::f32x4) -> simd::mask32x4 {
!(self.is_bottom_right_edge & nearly_equal_simd(value, simd::f32x4::splat(0.0), f32::EPSILON))
}
#[inline]
pub fn evaluate(&self, value: f32) -> bool {
self.is_inside(value) && self.is_on_fill_edge(value)
}
#[inline]
pub fn evaluate_simd(&self, value: simd::f32x4) -> simd::mask32x4 {
self.is_inside_simd(value) & self.is_on_fill_edge_simd(value)
}
#[inline]
pub fn step_x(&self, value: f32) -> f32 {
value + self.x_inc
}
#[inline]
pub fn step_x_simd(&self, value: simd::f32x4) -> simd::f32x4 {
value + self.x_inc_simd
}
#[inline]
pub fn step_y(&self, value: f32) -> f32 {
value + self.y_inc
}
#[inline]
pub fn step_y_simd(&self, value: simd::f32x4) -> simd::f32x4 {
value + self.y_inc_simd
}
#[inline]
pub fn origin(&self) -> f32 {
self.origin
}
#[inline]
pub fn origin_simd(&self) -> simd::f32x4 {
self.origin_simd
}
}
fn triangle_2d_4x_width<PixelType: Pixel>(
dest: &mut Bitmap<PixelType>,
edge_bc: TriangleEdge,
edge_ca: TriangleEdge,
edge_ab: TriangleEdge,
bounds: Rect,
pixel_fn: impl Fn(&mut PixelType, f32, f32, f32),
) {
let draw_width = bounds.width as usize;
let next_row_inc = dest.width() as usize;
let mut pixels = unsafe { dest.pixels_at_mut_ptr_unchecked(bounds.x, bounds.y) };
let mut w0_row = edge_bc.origin_simd();
let mut w1_row = edge_ca.origin_simd();
let mut w2_row = edge_ab.origin_simd();
for _ in bounds.y..=bounds.bottom() {
let mut w0 = w0_row;
let mut w1 = w1_row;
let mut w2 = w2_row;
let row_pixels = unsafe { std::slice::from_raw_parts_mut(pixels, draw_width) };
for x in (0..draw_width).step_by(4) {
let mask = edge_bc.evaluate_simd(w0) & edge_ca.evaluate_simd(w1) & edge_ab.evaluate_simd(w2);
if mask.any() {
if unsafe { mask.test_unchecked(0) } {
pixel_fn(unsafe { row_pixels.get_unchecked_mut(x) }, w0[0], w1[0], w2[0]);
}
if unsafe { mask.test_unchecked(1) } {
pixel_fn(unsafe { row_pixels.get_unchecked_mut(x + 1) }, w0[1], w1[1], w2[1]);
}
if unsafe { mask.test_unchecked(2) } {
pixel_fn(unsafe { row_pixels.get_unchecked_mut(x + 2) }, w0[2], w1[2], w2[2]);
}
if unsafe { mask.test_unchecked(3) } {
pixel_fn(unsafe { row_pixels.get_unchecked_mut(x + 3) }, w0[3], w1[3], w2[3]);
}
}
w0 = edge_bc.step_x_simd(w0);
w1 = edge_ca.step_x_simd(w1);
w2 = edge_ab.step_x_simd(w2);
}
w0_row = edge_bc.step_y_simd(w0_row);
w1_row = edge_ca.step_y_simd(w1_row);
w2_row = edge_ab.step_y_simd(w2_row);
pixels = unsafe { pixels.add(next_row_inc) };
}
}
fn triangle_2d_4x_width_and_remainder<PixelType: Pixel>(
dest: &mut Bitmap<PixelType>,
edge_bc: TriangleEdge,
edge_ca: TriangleEdge,
edge_ab: TriangleEdge,
bounds: Rect,
pixel_fn: impl Fn(&mut PixelType, f32, f32, f32),
) {
let draw_width = bounds.width as usize;
let next_row_inc = dest.width() as usize;
let mut pixels = unsafe { dest.pixels_at_mut_ptr_unchecked(bounds.x, bounds.y) };
let x_remainder_start = draw_width - (draw_width & 3);
let mut w0_row = edge_bc.origin_simd();
let mut w1_row = edge_ca.origin_simd();
let mut w2_row = edge_ab.origin_simd();
for _ in bounds.y..=bounds.bottom() {
let mut w0 = w0_row;
let mut w1 = w1_row;
let mut w2 = w2_row;
let row_pixels = unsafe { std::slice::from_raw_parts_mut(pixels, draw_width) };
for x in (0..draw_width).step_by(4) {
let mask = edge_bc.evaluate_simd(w0) & edge_ca.evaluate_simd(w1) & edge_ab.evaluate_simd(w2);
if mask.any() {
if unsafe { mask.test_unchecked(0) } {
pixel_fn(unsafe { row_pixels.get_unchecked_mut(x) }, w0[0], w1[0], w2[0]);
}
if unsafe { mask.test_unchecked(1) } {
pixel_fn(unsafe { row_pixels.get_unchecked_mut(x + 1) }, w0[1], w1[1], w2[1]);
}
if unsafe { mask.test_unchecked(2) } {
pixel_fn(unsafe { row_pixels.get_unchecked_mut(x + 2) }, w0[2], w1[2], w2[2]);
}
if unsafe { mask.test_unchecked(3) } {
pixel_fn(unsafe { row_pixels.get_unchecked_mut(x + 3) }, w0[3], w1[3], w2[3]);
}
}
w0 = edge_bc.step_x_simd(w0);
w1 = edge_ca.step_x_simd(w1);
w2 = edge_ab.step_x_simd(w2);
}
let mut w0 = w0[3];
let mut w1 = w1[3];
let mut w2 = w2[3];
let row_pixels = &mut row_pixels[x_remainder_start..draw_width];
for pixel in row_pixels.iter_mut() {
if edge_bc.evaluate(w0) && edge_ca.evaluate(w1) && edge_ab.evaluate(w2) {
pixel_fn(pixel, w0, w1, w2)
}
w0 = edge_bc.step_x(w0);
w1 = edge_ca.step_x(w1);
w2 = edge_ab.step_x(w2);
}
w0_row = edge_bc.step_y_simd(w0_row);
w1_row = edge_ca.step_y_simd(w1_row);
w2_row = edge_ab.step_y_simd(w2_row);
pixels = unsafe { pixels.add(next_row_inc) };
}
}
fn triangle_2d_any_width<PixelType: Pixel>(
dest: &mut Bitmap<PixelType>,
edge_bc: TriangleEdge,
edge_ca: TriangleEdge,
edge_ab: TriangleEdge,
bounds: Rect,
pixel_fn: impl Fn(&mut PixelType, f32, f32, f32),
) {
let draw_width = bounds.width as usize;
let next_row_inc = dest.width() as usize;
let mut pixels = unsafe { dest.pixels_at_mut_ptr_unchecked(bounds.x, bounds.y) };
let mut w0_row = edge_bc.origin();
let mut w1_row = edge_ca.origin();
let mut w2_row = edge_ab.origin();
for _ in bounds.y..=bounds.bottom() {
let mut w0 = w0_row;
let mut w1 = w1_row;
let mut w2 = w2_row;
let row_pixels = unsafe { std::slice::from_raw_parts_mut(pixels, draw_width) };
for pixel in row_pixels.iter_mut() {
if edge_bc.evaluate(w0) && edge_ca.evaluate(w1) && edge_ab.evaluate(w2) {
pixel_fn(pixel, w0, w1, w2)
}
w0 = edge_bc.step_x(w0);
w1 = edge_ca.step_x(w1);
w2 = edge_ab.step_x(w2);
}
w0_row = edge_bc.step_y(w0_row);
w1_row = edge_ca.step_y(w1_row);
w2_row = edge_ab.step_y(w2_row);
pixels = unsafe { pixels.add(next_row_inc) };
}
}
#[inline]
@ -308,11 +103,33 @@ pub fn per_pixel_triangle_2d<PixelType: Pixel>(
let edge_ca = TriangleEdge::from(c, a, p);
let edge_ab = TriangleEdge::from(a, b, p);
if bounds.width % 4 == 0 {
triangle_2d_4x_width(dest, edge_bc, edge_ca, edge_ab, bounds, pixel_fn);
} else if bounds.width > 4 {
triangle_2d_4x_width_and_remainder(dest, edge_bc, edge_ca, edge_ab, bounds, pixel_fn);
} else {
triangle_2d_any_width(dest, edge_bc, edge_ca, edge_ab, bounds, pixel_fn);
let mut w0_row = edge_bc.origin();
let mut w1_row = edge_ca.origin();
let mut w2_row = edge_ab.origin();
let draw_width = bounds.width as usize;
let next_row_inc = dest.width() as usize;
let mut pixels = unsafe { dest.pixels_at_mut_ptr_unchecked(bounds.x, bounds.y) };
for _ in bounds.y..=bounds.bottom() {
let mut w0 = w0_row;
let mut w1 = w1_row;
let mut w2 = w2_row;
let row_pixels = unsafe { std::slice::from_raw_parts_mut(pixels, draw_width) };
for pixel in row_pixels.iter_mut() {
if edge_bc.evaluate(w0) && edge_ca.evaluate(w1) && edge_ab.evaluate(w2) {
pixel_fn(pixel, w0, w1, w2)
}
w0 = edge_bc.step_x(w0);
w1 = edge_ca.step_x(w1);
w2 = edge_ab.step_x(w2);
}
w0_row = edge_bc.step_y(w0_row);
w1_row = edge_ca.step_y(w1_row);
w2_row = edge_ab.step_y(w2_row);
pixels = unsafe { pixels.add(next_row_inc) };
}
}

View file

@ -1,14 +1,26 @@
use std::fs::File;
use std::io::{BufReader, BufWriter, Read, Write};
use std::ops::Index;
use std::path::Path;
use thiserror::Error;
use crate::graphics::GeneralBitmap;
use crate::graphics::{GeneralBitmap, GeneralBlitMethod};
use crate::math::Rect;
#[derive(Error, Debug)]
pub enum BitmapAtlasError {
#[error("Region is out of bounds for the Bitmap used by the BitmapAtlas")]
OutOfBounds,
#[error("Tile index {0} is invalid / out of range")]
InvalidTileIndex(usize),
#[error("Invalid dimensions for region")]
InvalidDimensions,
#[error("No bitmap atlas entries in the descriptor")]
NoEntries,
}
#[derive(Debug, Clone, Eq, PartialEq)]
@ -34,7 +46,31 @@ where
}
}
pub fn from_descriptor(descriptor: &BitmapAtlasDescriptor, bitmap: BitmapType) -> Result<Self, BitmapAtlasError> {
if descriptor.tiles.is_empty() {
return Err(BitmapAtlasError::NoEntries);
}
let mut atlas = BitmapAtlas::new(bitmap);
for entry in descriptor.tiles.iter() {
use BitmapAtlasDescriptorEntry::*;
match entry {
Tile { x, y, width, height } => {
atlas.add(Rect::new(*x as i32, *y as i32, *width, *height))?;
}
Autogrid { x, y, tile_width, tile_height, num_tiles_x, num_tiles_y, border } => {
atlas.add_custom_grid(*x, *y, *tile_width, *tile_height, *num_tiles_x, *num_tiles_y, *border)?;
}
}
}
Ok(atlas)
}
pub fn add(&mut self, rect: Rect) -> Result<usize, BitmapAtlasError> {
if rect.width == 0 || rect.height == 0 {
return Err(BitmapAtlasError::InvalidDimensions);
}
if !self.bounds.contains_rect(&rect) {
return Err(BitmapAtlasError::OutOfBounds);
}
@ -44,6 +80,9 @@ where
}
pub fn add_grid(&mut self, tile_width: u32, tile_height: u32) -> Result<usize, BitmapAtlasError> {
if tile_width == 0 || tile_height == 0 {
return Err(BitmapAtlasError::InvalidDimensions);
}
if self.bounds.width < tile_width || self.bounds.height < tile_height {
return Err(BitmapAtlasError::OutOfBounds);
}
@ -70,6 +109,10 @@ where
y_tiles: u32,
border: u32,
) -> Result<usize, BitmapAtlasError> {
if tile_width == 0 || tile_height == 0 {
return Err(BitmapAtlasError::InvalidDimensions);
}
// figure out of the grid properties given would result in us creating any
// rects that lie out of the bounds of this bitmap
let grid_region = Rect::new(
@ -114,10 +157,31 @@ where
self.tiles.get(index)
}
pub fn get_uv(&self, index: usize) -> Option<[f32; 4]> {
self.tiles.get(index).map(|rect| {
[
(rect.x as f32 / self.bitmap.width() as f32),
(rect.y as f32 / self.bitmap.height() as f32),
((rect.x + rect.width as i32) as f32 / self.bitmap.width() as f32),
((rect.y + rect.height as i32) as f32 / self.bitmap.height() as f32),
]
})
}
#[inline]
pub fn bitmap(&self) -> &BitmapType {
&self.bitmap
}
pub fn clone_tile(&self, index: usize) -> Result<BitmapType, BitmapAtlasError> {
if let Some(tile_rect) = self.get(index) {
let mut tile_bitmap = BitmapType::new(tile_rect.width, tile_rect.height).unwrap();
tile_bitmap.blit_region(GeneralBlitMethod::Solid, &self.bitmap, tile_rect, 0, 0);
Ok(tile_bitmap)
} else {
Err(BitmapAtlasError::InvalidTileIndex(index))
}
}
}
impl<BitmapType> Index<usize> for BitmapAtlas<BitmapType>
@ -132,6 +196,70 @@ where
}
}
#[derive(Error, Debug)]
pub enum BitmapAtlasDescriptorError {
#[error("Serde Json serialization/deserialization error: {0}")]
SerdeJsonError(String),
#[error("I/O error")]
IOError(#[from] std::io::Error),
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "lowercase")]
pub enum BitmapAtlasDescriptorEntry {
Tile {
x: u32, //
y: u32,
width: u32,
height: u32,
},
Autogrid {
x: u32, //
y: u32,
tile_width: u32,
tile_height: u32,
num_tiles_x: u32,
num_tiles_y: u32,
border: u32,
},
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct BitmapAtlasDescriptor {
pub bitmap: String,
pub tiles: Vec<BitmapAtlasDescriptorEntry>,
}
impl BitmapAtlasDescriptor {
pub fn load_from_file(path: impl AsRef<Path>) -> Result<Self, BitmapAtlasDescriptorError> {
let f = File::open(path)?;
let mut reader = BufReader::new(f);
Self::load_from_bytes(&mut reader)
}
pub fn load_from_bytes<T: Read>(reader: &mut T) -> Result<Self, BitmapAtlasDescriptorError> {
match serde_json::from_reader(reader) {
Ok(desc) => Ok(desc),
Err(error) => Err(BitmapAtlasDescriptorError::SerdeJsonError(error.to_string())),
}
}
pub fn to_file(&self, path: impl AsRef<Path>) -> Result<(), BitmapAtlasDescriptorError> {
let f = File::create(path)?;
let mut writer = BufWriter::new(f);
self.to_bytes(&mut writer)
}
pub fn to_bytes<T: Write>(&self, writer: &mut T) -> Result<(), BitmapAtlasDescriptorError> {
if let Err(error) = serde_json::to_writer_pretty(writer, &self) {
Err(BitmapAtlasDescriptorError::SerdeJsonError(error.to_string()))
} else {
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use claim::*;
@ -170,6 +298,16 @@ mod tests {
let bmp = IndexedBitmap::new(64, 64).unwrap();
let mut atlas = BitmapAtlas::new(bmp);
assert_eq!(3, atlas.add_grid(32, 32).unwrap());
assert_eq!(4, atlas.len());
assert_eq!(Rect::new(0, 0, 32, 32), atlas[0]);
assert_eq!(Rect::new(32, 0, 32, 32), atlas[1]);
assert_eq!(Rect::new(0, 32, 32, 32), atlas[2]);
assert_eq!(Rect::new(32, 32, 32, 32), atlas[3]);
atlas.clear();
assert_eq!(0, atlas.len());
assert_eq!(3, atlas.add_custom_grid(0, 0, 8, 8, 2, 2, 0).unwrap());
assert_eq!(4, atlas.len());
assert_eq!(Rect::new(0, 0, 8, 8), atlas[0]);
@ -187,4 +325,26 @@ mod tests {
assert_eq!(Rect::new(0, 9, 4, 8), atlas[2]);
assert_eq!(Rect::new(5, 9, 4, 8), atlas[3]);
}
#[test]
pub fn adding_with_invalid_dimensions_fails() {
let bmp = IndexedBitmap::new(64, 64).unwrap();
let mut atlas = BitmapAtlas::new(bmp);
assert_matches!(atlas.add(Rect::new(0, 0, 0, 0)), Err(BitmapAtlasError::InvalidDimensions));
assert_matches!(atlas.add(Rect::new(16, 16, 0, 0)), Err(BitmapAtlasError::InvalidDimensions));
assert_matches!(atlas.add(Rect::new(16, 16, 8, 0)), Err(BitmapAtlasError::InvalidDimensions));
assert_matches!(atlas.add(Rect::new(16, 16, 0, 8)), Err(BitmapAtlasError::InvalidDimensions));
assert_eq!(0, atlas.len());
assert_matches!(atlas.add_grid(0, 0), Err(BitmapAtlasError::InvalidDimensions));
assert_matches!(atlas.add_grid(8, 0), Err(BitmapAtlasError::InvalidDimensions));
assert_matches!(atlas.add_grid(0, 8), Err(BitmapAtlasError::InvalidDimensions));
assert_eq!(0, atlas.len());
assert_matches!(atlas.add_custom_grid(0, 0, 0, 0, 2, 2, 1), Err(BitmapAtlasError::InvalidDimensions));
assert_matches!(atlas.add_custom_grid(0, 0, 8, 0, 2, 2, 1), Err(BitmapAtlasError::InvalidDimensions));
assert_matches!(atlas.add_custom_grid(0, 0, 0, 8, 2, 2, 1), Err(BitmapAtlasError::InvalidDimensions));
assert_eq!(0, atlas.len());
}
}

View file

@ -5,7 +5,7 @@ use std::path::Path;
use byteorder::{ReadBytesExt, WriteBytesExt};
use thiserror::Error;
use crate::graphics::{from_rgb32, luminance, Palette};
use crate::graphics::Palette;
use crate::math::lerp;
use crate::utils::ReadFixedLengthByteArray;
@ -76,8 +76,7 @@ impl BlendMap {
let mut blend_map = Self::new(source_color, source_color);
for idx in 0..=255 {
let rgb = from_rgb32(palette[idx]);
let lit = (luminance(rgb) * 255.0) as u8;
let lit = (palette[idx].luminance() * 255.0) as u8;
blend_map
.set_mapping(
source_color,
@ -105,11 +104,9 @@ impl BlendMap {
let mut blend_map = BlendMap::new(0, 255);
for source_color in 0..=255 {
let source_rgb = from_rgb32(palette[source_color]);
let source_luminance = luminance(source_rgb);
let source_luminance = palette[source_color].luminance();
for dest_color in 0..=255 {
let dest_rgb = from_rgb32(palette[dest_color]);
let destination_luminance = luminance(dest_rgb);
let destination_luminance = palette[dest_color].luminance();
let weight = (f(source_luminance, destination_luminance) * 255.0) as u8;
blend_map
.set_mapping(
@ -135,10 +132,10 @@ impl BlendMap {
pub fn new_translucency_map(blend_r: f32, blend_g: f32, blend_b: f32, palette: &Palette) -> Self {
let mut blend_map = BlendMap::new(0, 255);
for source in 0..=255 {
let [source_r, source_g, source_b] = from_rgb32(palette[source]);
let [_, source_r, source_g, source_b] = palette[source].to_array();
let mapping = blend_map.get_mapping_mut(source).unwrap();
for dest in 0..=255 {
let [dest_r, dest_g, dest_b] = from_rgb32(palette[dest]);
let [_, dest_r, dest_g, dest_b] = palette[dest].to_array();
let find_r = lerp(dest_r as f32, source_r as f32, blend_r) as u8;
let find_g = lerp(dest_g as f32, source_g as f32, blend_g) as u8;
@ -242,7 +239,7 @@ impl BlendMap {
self.get_mapping(source_color).map(|mapping| mapping[dest_color as usize])
}
pub fn load_from_file(path: &Path) -> Result<Self, BlendMapError> {
pub fn load_from_file(path: impl AsRef<Path>) -> Result<Self, BlendMapError> {
let f = File::open(path)?;
let mut reader = BufReader::new(f);
Self::load_from_bytes(&mut reader)
@ -271,7 +268,7 @@ impl BlendMap {
})
}
pub fn to_file(&self, path: &Path) -> Result<(), BlendMapError> {
pub fn to_file(&self, path: impl AsRef<Path>) -> Result<(), BlendMapError> {
let f = File::create(path)?;
let mut writer = BufWriter::new(f);
self.to_bytes(&mut writer)

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
use std::fs::File;
use std::io::{BufReader, BufWriter, Cursor};
use std::ops::{Index, IndexMut};
use std::path::Path;
use byteorder::{ReadBytesExt, WriteBytesExt};
@ -21,6 +22,15 @@ pub enum FontError {
#[error("Font I/O error")]
IOError(#[from] std::io::Error),
#[error("Invalid character dimensions")]
InvalidCharacterDimensions,
#[error("Invalid number of characters")]
InvalidNumberOfCharacters,
#[error("Invalid line height")]
InvalidLineHeight,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
@ -47,12 +57,38 @@ pub trait Font {
PixelType: Pixel;
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct BitmaskCharacter {
bytes: [u8; CHAR_HEIGHT],
bounds: Rect,
}
impl Default for BitmaskCharacter {
fn default() -> Self {
BitmaskCharacter {
//
bytes: [0u8; CHAR_HEIGHT],
bounds: Rect::new(0, 0, CHAR_FIXED_WIDTH as u32, CHAR_HEIGHT as u32),
}
}
}
impl Index<u8> for BitmaskCharacter {
type Output = u8;
#[inline]
fn index(&self, index: u8) -> &Self::Output {
&self.bytes[index as usize]
}
}
impl IndexMut<u8> for BitmaskCharacter {
#[inline]
fn index_mut(&mut self, index: u8) -> &mut Self::Output {
&mut self.bytes[index as usize]
}
}
impl Character for BitmaskCharacter {
#[inline]
fn bounds(&self) -> &Rect {
@ -94,6 +130,42 @@ impl Character for BitmaskCharacter {
}
}
impl BitmaskCharacter {
pub fn new(bytes: [u8; CHAR_HEIGHT], width: usize) -> Result<BitmaskCharacter, FontError> {
if width < 1 || width > CHAR_FIXED_WIDTH {
return Err(FontError::InvalidCharacterDimensions);
}
Ok(BitmaskCharacter { bytes, bounds: Rect::new(0, 0, width as u32, CHAR_HEIGHT as u32) })
}
#[inline]
pub fn width(&self) -> u8 {
self.bounds.width as u8
}
pub fn set_width(&mut self, width: u8) -> Result<(), FontError> {
if width < 1 || width > CHAR_FIXED_WIDTH as u8 {
return Err(FontError::InvalidCharacterDimensions);
}
self.bounds.width = width as u32;
Ok(())
}
#[inline]
pub fn height(&self) -> u8 {
self.bounds.height as u8
}
pub fn set_height(&mut self, height: u8) -> Result<(), FontError> {
if height < 1 || height > CHAR_HEIGHT as u8 {
return Err(FontError::InvalidCharacterDimensions);
}
self.bounds.height = height as u32;
Ok(())
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct BitmaskFont {
characters: Box<[BitmaskCharacter]>,
@ -116,7 +188,28 @@ impl BitmaskFont {
BitmaskFont::load_from_bytes(&mut Cursor::new(VGA_FONT_BYTES))
}
pub fn load_from_file(path: &Path) -> Result<BitmaskFont, FontError> {
pub fn new(characters: &[BitmaskCharacter], line_height: usize) -> Result<BitmaskFont, FontError> {
if characters.len() != NUM_CHARS {
return Err(FontError::InvalidNumberOfCharacters);
}
if line_height < 1 || line_height > CHAR_HEIGHT {
return Err(FontError::InvalidLineHeight);
}
let mut font = BitmaskFont {
characters: Box::from(characters),
line_height: line_height as u8,
space_width: characters[' ' as usize].bounds.width as u8,
};
for i in 0..NUM_CHARS {
font.characters[i].bounds.height = line_height as u32;
}
Ok(font)
}
pub fn load_from_file(path: impl AsRef<Path>) -> Result<BitmaskFont, FontError> {
let f = File::open(path)?;
let mut reader = BufReader::new(f);
@ -139,26 +232,17 @@ impl BitmaskFont {
}
// read character widths (used for rendering)
for i in 0..NUM_CHARS {
characters[i].bounds.width = reader.read_u8()? as u32;
for character in characters.iter_mut().take(NUM_CHARS) {
character.bounds.width = reader.read_u8()? as u32;
}
// read global font height (used for rendering)
let line_height = reader.read_u8()?;
for i in 0..NUM_CHARS {
characters[i].bounds.height = line_height as u32;
}
let space_width = characters[' ' as usize].bounds.width as u8;
Ok(BitmaskFont {
characters: characters.into_boxed_slice(), //
line_height,
space_width,
})
Self::new(&characters, line_height as usize)
}
pub fn to_file(&self, path: &Path) -> Result<(), FontError> {
pub fn to_file(&self, path: impl AsRef<Path>) -> Result<(), FontError> {
let f = File::create(path)?;
let mut writer = BufWriter::new(f);
self.to_bytes(&mut writer)
@ -243,13 +327,13 @@ mod tests {
const BASE_PATH: &str = "./test-assets/font/";
fn test_file(file: &Path) -> PathBuf {
fn test_file(file: impl AsRef<Path>) -> PathBuf {
PathBuf::from(BASE_PATH).join(file)
}
#[test]
pub fn load_font() -> Result<(), FontError> {
let font = BitmaskFont::load_from_file(test_file(Path::new("vga.fnt")).as_path())?;
let font = BitmaskFont::load_from_file(test_file("vga.fnt"))?;
assert_eq!(256, font.characters.len());
assert_eq!(CHAR_FIXED_WIDTH as u8, font.space_width);
for character in font.characters.iter() {
@ -263,7 +347,7 @@ mod tests {
#[test]
pub fn measure_text() -> Result<(), FontError> {
{
let font = BitmaskFont::load_from_file(test_file(Path::new("vga.fnt")).as_path())?;
let font = BitmaskFont::load_from_file(test_file("vga.fnt"))?;
assert_eq!((40, 8), font.measure("Hello", FontRenderOpts::<u8>::None));
assert_eq!((40, 16), font.measure("Hello\nthere", FontRenderOpts::<u8>::None));
@ -277,7 +361,7 @@ mod tests {
}
{
let font = BitmaskFont::load_from_file(test_file(Path::new("small.fnt")).as_path())?;
let font = BitmaskFont::load_from_file(test_file("small.fnt"))?;
assert_eq!((22, 7), font.measure("Hello", FontRenderOpts::<u8>::None));
assert_eq!((24, 14), font.measure("Hello\nthere", FontRenderOpts::<u8>::None));

View file

@ -1,4 +1,3 @@
use num_traits::{PrimInt, Unsigned};
use std::fmt::Display;
mod bitmap;
@ -16,5 +15,5 @@ pub use font::*;
pub use palette::*;
/// Common trait to represent single pixel/colour values.
pub trait Pixel: PrimInt + Unsigned + Default + Display {}
impl<T> Pixel for T where T: PrimInt + Unsigned + Default + Display {}
pub trait Pixel: Default + Display + Eq + Copy + Clone {}
impl<T> Pixel for T where T: Default + Display + Eq + Copy + Clone {}

View file

@ -7,7 +7,7 @@ use std::path::Path;
use byteorder::{ReadBytesExt, WriteBytesExt};
use thiserror::Error;
use crate::graphics::{from_rgb32, lerp_rgb32, to_argb32, to_rgb32, IndexedBitmap};
use crate::graphics::{IndexedBitmap, RGBA};
use crate::utils::abs_diff;
const NUM_COLORS: usize = 256;
@ -29,67 +29,63 @@ fn to_6bit(value: u8) -> u8 {
}
// vga bios (0-63) format
fn read_palette_6bit<T: ReadBytesExt>(reader: &mut T, num_colors: usize) -> Result<[u32; NUM_COLORS], PaletteError> {
fn read_palette_6bit<T: ReadBytesExt>(reader: &mut T, num_colors: usize) -> Result<[RGBA; NUM_COLORS], PaletteError> {
if num_colors > NUM_COLORS {
return Err(PaletteError::OutOfRange(num_colors));
}
let mut colors = [to_argb32([255, 0, 0, 0]); NUM_COLORS];
for i in 0..num_colors {
let mut colors = [RGBA::from_rgba([0, 0, 0, 255]); NUM_COLORS];
for color in colors.iter_mut().take(num_colors) {
let r = reader.read_u8()?;
let g = reader.read_u8()?;
let b = reader.read_u8()?;
let color = to_rgb32([from_6bit(r), from_6bit(g), from_6bit(b)]);
colors[i] = color;
*color = RGBA::from_rgb([from_6bit(r), from_6bit(g), from_6bit(b)]);
}
Ok(colors)
}
fn write_palette_6bit<T: WriteBytesExt>(
writer: &mut T,
colors: &[u32; NUM_COLORS],
colors: &[RGBA; NUM_COLORS],
num_colors: usize,
) -> Result<(), PaletteError> {
if num_colors > NUM_COLORS {
return Err(PaletteError::OutOfRange(num_colors));
}
for i in 0..num_colors {
let [r, g, b] = from_rgb32(colors[i]);
writer.write_u8(to_6bit(r))?;
writer.write_u8(to_6bit(g))?;
writer.write_u8(to_6bit(b))?;
for color in colors.iter().take(num_colors) {
writer.write_u8(to_6bit(color.r()))?;
writer.write_u8(to_6bit(color.g()))?;
writer.write_u8(to_6bit(color.b()))?;
}
Ok(())
}
// normal (0-255) format
fn read_palette_8bit<T: ReadBytesExt>(reader: &mut T, num_colors: usize) -> Result<[u32; NUM_COLORS], PaletteError> {
fn read_palette_8bit<T: ReadBytesExt>(reader: &mut T, num_colors: usize) -> Result<[RGBA; NUM_COLORS], PaletteError> {
if num_colors > NUM_COLORS {
return Err(PaletteError::OutOfRange(num_colors));
}
let mut colors = [to_argb32([255, 0, 0, 0]); NUM_COLORS];
for i in 0..num_colors {
let mut colors = [RGBA::from_rgba([0, 0, 0, 255]); NUM_COLORS];
for color in colors.iter_mut().take(num_colors) {
let r = reader.read_u8()?;
let g = reader.read_u8()?;
let b = reader.read_u8()?;
let color = to_rgb32([r, g, b]);
colors[i] = color;
*color = RGBA::from_rgb([r, g, b]);
}
Ok(colors)
}
fn write_palette_8bit<T: WriteBytesExt>(
writer: &mut T,
colors: &[u32; NUM_COLORS],
colors: &[RGBA; NUM_COLORS],
num_colors: usize,
) -> Result<(), PaletteError> {
if num_colors > NUM_COLORS {
return Err(PaletteError::OutOfRange(num_colors));
}
for i in 0..num_colors {
let [r, g, b] = from_rgb32(colors[i]);
writer.write_u8(r)?;
writer.write_u8(g)?;
writer.write_u8(b)?;
for color in colors.iter().take(num_colors) {
writer.write_u8(color.r())?;
writer.write_u8(color.g())?;
writer.write_u8(color.b())?;
}
Ok(())
}
@ -114,19 +110,18 @@ pub enum PaletteFormat {
/// colors are all stored individually as 32-bit packed values in the format 0xAARRGGBB.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Palette {
colors: [u32; NUM_COLORS],
colors: [RGBA; NUM_COLORS],
}
impl Palette {
/// Creates a new Palette with all black colors.
pub fn new() -> Palette {
Palette { colors: [0; NUM_COLORS] }
Palette { colors: [RGBA::from_rgb([0, 0, 0]); NUM_COLORS] }
}
/// Creates a new Palette with all initial colors having the RGB values specified.
pub fn new_with_default(r: u8, g: u8, b: u8) -> Palette {
let rgb = to_rgb32([r, g, b]);
Palette { colors: [rgb; NUM_COLORS] }
Palette { colors: [RGBA::from_rgb([r, g, b]); NUM_COLORS] }
}
/// Creates a new Palette, pre-loaded with the default VGA BIOS colors.
@ -140,7 +135,7 @@ impl Palette {
///
/// * `path`: the path of the palette file to be loaded
/// * `format`: the format that the palette data is expected to be in
pub fn load_from_file(path: &Path, format: PaletteFormat) -> Result<Palette, PaletteError> {
pub fn load_from_file(path: impl AsRef<Path>, format: PaletteFormat) -> Result<Palette, PaletteError> {
let f = File::open(path)?;
let mut reader = BufReader::new(f);
Self::load_from_bytes(&mut reader, format)
@ -171,7 +166,7 @@ impl Palette {
/// * `format`: the format that the palette data is expected to be in
/// * `num_colors`: the expected number of colors in the palette to be loaded (<= 256)
pub fn load_num_colors_from_file(
path: &Path,
path: impl AsRef<Path>,
format: PaletteFormat,
num_colors: usize,
) -> Result<Palette, PaletteError> {
@ -211,7 +206,7 @@ impl Palette {
///
/// * `path`: the path of the file to save the palette to
/// * `format`: the format to write the palette data in
pub fn to_file(&self, path: &Path, format: PaletteFormat) -> Result<(), PaletteError> {
pub fn to_file(&self, path: impl AsRef<Path>, format: PaletteFormat) -> Result<(), PaletteError> {
let f = File::create(path)?;
let mut writer = BufWriter::new(f);
self.to_bytes(&mut writer, format)
@ -242,7 +237,7 @@ impl Palette {
/// * `num_colors`: the number of colors from this palette to write out to the file (<= 256)
pub fn num_colors_to_file(
&self,
path: &Path,
path: impl AsRef<Path>,
format: PaletteFormat,
num_colors: usize,
) -> Result<(), PaletteError> {
@ -295,7 +290,9 @@ impl Palette {
pub fn fade_color_toward_rgb(&mut self, color: u8, target_r: u8, target_g: u8, target_b: u8, step: u8) -> bool {
let mut modified = false;
let [mut r, mut g, mut b] = from_rgb32(self.colors[color as usize]);
let mut r = self.colors[color as usize].r();
let mut g = self.colors[color as usize].g();
let mut b = self.colors[color as usize].b();
if r != target_r {
modified = true;
@ -328,7 +325,7 @@ impl Palette {
}
if modified {
self.colors[color as usize] = to_rgb32([r, g, b]);
self.colors[color as usize] = RGBA::from_rgb([r, g, b]);
}
(target_r == r) && (target_g == g) && (target_b == b)
@ -381,8 +378,7 @@ impl Palette {
pub fn fade_colors_toward_palette<T: ColorRange>(&mut self, colors: T, palette: &Palette, step: u8) -> bool {
let mut all_faded = true;
for color in colors {
let [r, g, b] = from_rgb32(palette[color]);
if !self.fade_color_toward_rgb(color, r, g, b, step) {
if !self.fade_color_toward_rgb(color, palette[color].r(), palette[color].g(), palette[color].b(), step) {
all_faded = false;
}
}
@ -400,7 +396,7 @@ impl Palette {
/// * `t`: the amount to interpolate between the two palettes, specified as a fraction
pub fn lerp<T: ColorRange>(&mut self, colors: T, a: &Palette, b: &Palette, t: f32) {
for color in colors {
self[color] = lerp_rgb32(a[color], b[color], t);
self[color] = a[color].lerp(b[color], t);
}
}
@ -439,15 +435,14 @@ impl Palette {
let mut closest = 0;
for (index, color) in self.colors.iter().enumerate() {
let [this_r, this_g, this_b] = from_rgb32(*color);
if r == this_r && g == this_g && b == this_b {
if r == color.r() && g == color.g() && b == color.b() {
return index as u8;
} else {
// this comparison method is using the sRGB Euclidean formula described here:
// https://en.wikipedia.org/wiki/Color_difference
let distance = abs_diff(this_r, r) as u32 + abs_diff(this_g, g) as u32 + abs_diff(this_b, b) as u32;
let distance =
abs_diff(color.r(), r) as u32 + abs_diff(color.g(), g) as u32 + abs_diff(color.b(), b) as u32;
if distance < closest_distance {
closest = index as u8;
@ -474,8 +469,14 @@ impl Palette {
}
}
impl Default for Palette {
fn default() -> Self {
Self::new()
}
}
impl Index<u8> for Palette {
type Output = u32;
type Output = RGBA;
#[inline]
fn index(&self, index: u8) -> &Self::Output {
@ -502,18 +503,18 @@ mod tests {
const BASE_PATH: &str = "./test-assets/palette/";
fn test_file(file: &Path) -> PathBuf {
fn test_file(file: impl AsRef<Path>) -> PathBuf {
PathBuf::from(BASE_PATH).join(file)
}
#[test]
fn get_and_set_colors() {
let mut palette = Palette::new();
assert_eq!(0, palette[0]);
assert_eq!(0, palette[1]);
palette[0] = 0x11223344;
assert_eq!(0x11223344, palette[0]);
assert_eq!(0, palette[1]);
assert_eq!(RGBA::from_rgb([0, 0, 0]), palette[0]);
assert_eq!(RGBA::from_rgb([0, 0, 0]), palette[1]);
palette[0] = 0x11223344.into();
assert_eq!(RGBA::from(0x11223344), palette[0]);
assert_eq!(RGBA::from_rgb([0, 0, 0]), palette[1]);
}
fn assert_ega_colors(palette: &Palette) {
@ -541,7 +542,7 @@ mod tests {
// vga rgb format (6-bit)
let palette = Palette::load_from_file(test_file(Path::new("vga.pal")).as_path(), PaletteFormat::Vga)?;
let palette = Palette::load_from_file(test_file("vga.pal"), PaletteFormat::Vga)?;
assert_ega_colors(&palette);
let save_path = tmp_dir.path().join("test_save_vga_format.pal");
@ -551,7 +552,7 @@ mod tests {
// normal rgb format (8-bit)
let palette = Palette::load_from_file(test_file(Path::new("dp2.pal")).as_path(), PaletteFormat::Normal)?;
let palette = Palette::load_from_file(test_file("dp2.pal"), PaletteFormat::Normal)?;
let save_path = tmp_dir.path().join("test_save_normal_format.pal");
palette.to_file(&save_path, PaletteFormat::Normal)?;
@ -567,8 +568,7 @@ mod tests {
// vga rgb format (6-bit)
let palette =
Palette::load_num_colors_from_file(test_file(Path::new("ega_6bit.pal")).as_path(), PaletteFormat::Vga, 16)?;
let palette = Palette::load_num_colors_from_file(test_file("ega_6bit.pal"), PaletteFormat::Vga, 16)?;
assert_ega_colors(&palette);
let save_path = tmp_dir.path().join("test_save_vga_format_16_colors.pal");
@ -578,11 +578,7 @@ mod tests {
// normal rgb format (8-bit)
let palette = Palette::load_num_colors_from_file(
test_file(Path::new("ega_8bit.pal")).as_path(),
PaletteFormat::Normal,
16,
)?;
let palette = Palette::load_num_colors_from_file(test_file("ega_8bit.pal"), PaletteFormat::Normal, 16)?;
let save_path = tmp_dir.path().join("test_save_normal_format_16_colors.pal");
palette.to_file(&save_path, PaletteFormat::Normal)?;

View file

@ -21,7 +21,8 @@ mod tests {
use std::io::{BufReader, Read};
use std::path::{Path, PathBuf};
use byteorder::{LittleEndian, ReadBytesExt};
use crate::graphics::RGBA;
use crate::utils::ReadType;
#[allow(dead_code)]
const ASSETS_PATH: &str = "./assets/";
@ -29,15 +30,15 @@ mod tests {
const TEST_ASSETS_PATH: &str = "./test-assets/";
#[allow(dead_code)]
pub fn assets_file(file: &Path) -> PathBuf {
pub fn assets_file(file: impl AsRef<Path>) -> PathBuf {
PathBuf::from(ASSETS_PATH).join(file)
}
pub fn test_assets_file(file: &Path) -> PathBuf {
pub fn test_assets_file(file: impl AsRef<Path>) -> PathBuf {
PathBuf::from(TEST_ASSETS_PATH).join(file)
}
pub fn load_raw_indexed(bin_file: &Path) -> Result<Box<[u8]>, io::Error> {
pub fn load_raw_indexed(bin_file: impl AsRef<Path>) -> Result<Box<[u8]>, io::Error> {
let f = File::open(bin_file)?;
let mut reader = BufReader::new(f);
let mut buffer = Vec::new();
@ -45,12 +46,12 @@ mod tests {
Ok(buffer.into_boxed_slice())
}
pub fn load_raw_argb(bin_file: &Path) -> Result<Box<[u32]>, io::Error> {
pub fn load_raw_rgba(bin_file: impl AsRef<Path>) -> Result<Box<[RGBA]>, io::Error> {
let f = File::open(bin_file)?;
let mut reader = BufReader::new(f);
let mut buffer = Vec::new();
loop {
buffer.push(match reader.read_u32::<LittleEndian>() {
buffer.push(match RGBA::read(&mut reader) {
Ok(value) => value,
Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => break,
Err(err) => return Err(err),

View file

@ -24,8 +24,7 @@ impl Circle {
let mut max_x = min_x;
let mut max_y = min_y;
for i in 0..points.len() {
let point = &points[i];
for point in points.iter() {
min_x = point.x.min(min_x);
min_y = point.y.min(min_y);
max_x = point.x.max(max_x);

View file

@ -1,6 +1,6 @@
use std::ops::{Add, Div, Mul, Sub};
use std::simd;
use std::simd::{SimdFloat, SimdPartialOrd};
use std::simd::prelude::{SimdFloat, SimdPartialOrd};
mod circle;
mod matrix3x3;
@ -268,6 +268,7 @@ mod tests {
assert!(nearly_equal(0.0, angle, 0.0001));
}
#[allow(clippy::approx_constant)]
#[test]
pub fn test_angle_to_direction() {
let (x, y) = angle_to_direction(RADIANS_0);

View file

@ -435,6 +435,7 @@ mod tests {
assert!(nearly_equal(crate::math::RIGHT, Vector2::RIGHT.angle(), 0.0001));
}
#[allow(clippy::approx_constant)]
#[test]
pub fn test_from_angle() {
let v = Vector2::from_angle(RADIANS_0);

View file

@ -445,6 +445,12 @@ impl<ContextType> States<ContextType> {
}
}
impl<ContextType> Default for States<ContextType> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use claim::*;

View file

@ -1,7 +1,6 @@
use byte_slice_cast::AsByteSlice;
use thiserror::Error;
use crate::graphics::{IndexedBitmap, Palette, RgbaBitmap};
use crate::graphics::{ColorsAsBytes, IndexedBitmap, Palette, RgbaBitmap, RGBA};
pub fn calculate_logical_screen_size(window_width: u32, window_height: u32, scale_factor: u32) -> (u32, u32) {
let logical_width = (window_width as f32 / scale_factor as f32).ceil() as u32;
@ -24,7 +23,7 @@ pub enum SdlFramebufferError {
pub struct SdlFramebuffer {
sdl_texture: sdl2::render::Texture,
sdl_texture_pitch: usize,
intermediate_texture: Option<Box<[u32]>>,
intermediate_texture: Option<Box<[RGBA]>>,
}
// TODO: i'm not totally happy with this implementation. i don't like the two display methods and how the caller
@ -46,14 +45,13 @@ impl SdlFramebuffer {
return Err(SdlFramebufferError::SDLError(error.to_string()));
}
let sdl_texture = match canvas.create_texture_streaming(
Some(sdl2::pixels::PixelFormatEnum::ARGB8888),
logical_screen_width,
logical_screen_height,
) {
Ok(texture) => texture,
Err(error) => return Err(SdlFramebufferError::SDLError(error.to_string())),
};
let format = sdl2::pixels::PixelFormatEnum::ABGR8888;
let sdl_texture =
match canvas.create_texture_streaming(Some(format), logical_screen_width, logical_screen_height) {
Ok(texture) => texture,
Err(error) => return Err(SdlFramebufferError::SDLError(error.to_string())),
};
let sdl_texture_pitch = sdl_texture.query().width as usize * SCREEN_TEXTURE_PIXEL_SIZE;
let intermediate_texture = if create_intermediate_texture {
@ -61,9 +59,8 @@ impl SdlFramebuffer {
// SDL texture uploads each frame. necessary as applications are dealing with 8-bit indexed
// bitmaps, not 32-bit RGBA pixels, so this temporary buffer is where we convert the final
// application framebuffer to 32-bit RGBA pixels before it is uploaded to the SDL texture
let texture_pixels_size =
(logical_screen_width * logical_screen_height) as usize * SCREEN_TEXTURE_PIXEL_SIZE;
Some(vec![0u32; texture_pixels_size].into_boxed_slice())
let texture_pixels_size = (logical_screen_width * logical_screen_height) as usize;
Some(vec![RGBA::default(); texture_pixels_size].into_boxed_slice())
} else {
None
};
@ -81,9 +78,9 @@ impl SdlFramebuffer {
"Calls to display_indexed_bitmap should only occur on SdlFramebuffers with an intermediate_texture",
);
src.copy_as_argb_to(intermediate_texture, palette);
src.copy_as_rgba_to(intermediate_texture, palette);
let texture_pixels = intermediate_texture.as_byte_slice();
let texture_pixels = intermediate_texture.as_bytes();
if let Err(error) = self.sdl_texture.update(None, texture_pixels, self.sdl_texture_pitch) {
return Err(SdlFramebufferError::SDLError(error.to_string()));
}
@ -106,7 +103,7 @@ impl SdlFramebuffer {
"Calls to display should only occur on SdlFramebuffers without an intermediate_texture"
);
let texture_pixels = src.pixels().as_byte_slice();
let texture_pixels = src.pixels().as_bytes();
if let Err(error) = self.sdl_texture.update(None, texture_pixels, self.sdl_texture_pitch) {
return Err(SdlFramebufferError::SDLError(error.to_string()));
}

View file

@ -55,6 +55,12 @@ impl Keyboard {
}
}
impl Default for Keyboard {
fn default() -> Self {
Self::new()
}
}
impl InputDevice for Keyboard {
fn update(&mut self) {
for state in self.keyboard.iter_mut() {

View file

@ -1,4 +1,4 @@
use crate::graphics::{GeneralBitmap, GeneralBlitMethod, IndexedBitmap, RgbaBitmap};
use crate::graphics::{ColorsAsBytes, GeneralBitmap, GeneralBlitMethod, IndexedBitmap, RgbaBitmap, RGBA};
use crate::math::Rect;
use crate::system::Mouse;
@ -202,6 +202,16 @@ where
}
}
impl<BitmapType> Default for CustomMouseCursor<BitmapType>
where
Self: DefaultMouseCursorBitmaps<BitmapType>,
BitmapType: GeneralBitmap,
{
fn default() -> Self {
Self::new()
}
}
impl DefaultMouseCursorBitmaps<IndexedBitmap> for CustomMouseCursor<IndexedBitmap> {
fn get_default() -> MouseCursorBitmap<IndexedBitmap> {
#[rustfmt::skip]
@ -240,34 +250,34 @@ impl DefaultMouseCursorBitmaps<IndexedBitmap> for CustomMouseCursor<IndexedBitma
impl DefaultMouseCursorBitmaps<RgbaBitmap> for CustomMouseCursor<RgbaBitmap> {
fn get_default() -> MouseCursorBitmap<RgbaBitmap> {
#[rustfmt::skip]
const CURSOR_PIXELS: [u32; DEFAULT_MOUSE_CURSOR_WIDTH * DEFAULT_MOUSE_CURSOR_HEIGHT] = [
0x00000000, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
0x00000000, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
0x00000000, 0x00000000, 0xffff00ff, 0xffff00ff, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0x00000000, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff
const CURSOR_PIXELS: [u8; DEFAULT_MOUSE_CURSOR_WIDTH * DEFAULT_MOUSE_CURSOR_HEIGHT * 4] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff
];
let mut cursor =
RgbaBitmap::new(DEFAULT_MOUSE_CURSOR_WIDTH as u32, DEFAULT_MOUSE_CURSOR_HEIGHT as u32).unwrap();
cursor.pixels_mut().copy_from_slice(&CURSOR_PIXELS);
cursor.pixels_mut().as_bytes_mut().copy_from_slice(&CURSOR_PIXELS);
MouseCursorBitmap {
cursor,
hotspot_x: DEFAULT_MOUSE_CURSOR_HOTSPOT_X,
hotspot_y: DEFAULT_MOUSE_CURSOR_HOTSPOT_Y,
transparent_color: 0xffff00ff,
transparent_color: RGBA::from_rgba([255, 0, 255, 255]),
}
}
}

View file

@ -103,6 +103,12 @@ impl Mouse {
}
}
impl Default for Mouse {
fn default() -> Self {
Self::new()
}
}
impl InputDevice for Mouse {
fn update(&mut self) {
self.x_delta = 0;

View file

@ -1,6 +1,7 @@
use thiserror::Error;
use crate::audio::AudioError;
use crate::utils::app_root_dir;
mod event;
mod framebuffer;
@ -51,6 +52,9 @@ pub enum SystemError {
#[error("SystemResources error: {0}")]
SystemResourcesError(#[from] SystemResourcesError),
#[error("System I/O error")]
IOError(#[from] std::io::Error),
}
/// Builder for configuring and constructing an instance of [`System`].
@ -195,6 +199,8 @@ impl SystemBuilder {
let event_pump = SystemEventPump::from(sdl_event_pump);
let app_root_dir = app_root_dir()?;
Ok(System {
sdl_context,
sdl_audio_subsystem,
@ -202,6 +208,7 @@ impl SystemBuilder {
sdl_timer_subsystem,
res: system_resources,
event_pump,
app_root_dir,
vsync: self.vsync,
target_framerate: self.target_framerate,
target_framerate_delta: None,
@ -210,6 +217,12 @@ impl SystemBuilder {
}
}
impl Default for SystemBuilder {
fn default() -> Self {
Self::new()
}
}
/// Holds all primary structures necessary for interacting with the operating system and for
/// applications to render to the display, react to input device events, etc. through the
/// "virtual machine" exposed by this library.
@ -231,6 +244,8 @@ where
pub res: SystemResType,
pub event_pump: SystemEventPump,
pub app_root_dir: std::path::PathBuf,
}
impl<SystemResType> std::fmt::Debug for System<SystemResType>

View file

@ -1,4 +1,8 @@
use std::io::{Error, SeekFrom};
use byteorder::{ReadBytesExt, WriteBytesExt};
use std::{
io::{Error, SeekFrom},
path::PathBuf,
};
/// Provides a convenience method for determining the total size of a stream. This is provided
/// as a temporary alternative to [std::io::Seek::stream_len] which is currently marked unstable.
@ -20,3 +24,41 @@ impl<T: std::io::Read + std::io::Seek> StreamSize for T {
Ok(len)
}
}
pub trait ReadType {
type OutputType;
type ErrorType;
fn read<T: ReadBytesExt>(reader: &mut T) -> Result<Self::OutputType, Self::ErrorType>;
}
pub trait WriteType {
type ErrorType;
fn write<T: WriteBytesExt>(&self, writer: &mut T) -> Result<(), Self::ErrorType>;
}
/// Returns the application root directory (the directory that the application executable is
/// located in).
///
/// First tries to automatically detect this from the `CARGO_MANIFEST_DIR` environment variable,
/// if present, to catch scenarios where the application is running from a Cargo workspace as a
/// sub-project/binary within that workspace. In such a case, the application root directory is
/// the same as that sub-project/binary's `Cargo.toml`.
///
/// If `CARGO_MANIFEST_DIR` is not present, then an attempt is made to determine the application
/// root directory from the running executable's location.
///
/// If this fails for some reason, then this returns the current working directory.
pub fn app_root_dir() -> Result<PathBuf, Error> {
if let Some(manifest_path) = std::env::var_os("CARGO_MANIFEST_DIR") {
return Ok(PathBuf::from(manifest_path));
}
let mut exe_path = std::env::current_exe()?.canonicalize()?;
if exe_path.pop() {
return Ok(exe_path);
}
std::env::current_dir()
}

Some files were not shown because too many files have changed in this diff Show more