mindustry logic execution, map- and schematic- parsing and rendering
dont allocate, store Image<&[u8>
| -rw-r--r-- | Cargo.toml | 3 | ||||
| -rw-r--r-- | build.rs | 30 | ||||
| -rw-r--r-- | src/block/distribution.rs | 19 | ||||
| -rw-r--r-- | src/block/liquid.rs | 2 | ||||
| -rw-r--r-- | src/block/logic.rs | 68 | ||||
| -rw-r--r-- | src/block/mod.rs | 20 | ||||
| -rw-r--r-- | src/block/payload.rs | 15 | ||||
| -rw-r--r-- | src/block/simple.rs | 2 | ||||
| -rw-r--r-- | src/block/turrets.rs | 4 | ||||
| -rw-r--r-- | src/block/units.rs | 34 | ||||
| -rw-r--r-- | src/block/walls.rs | 2 | ||||
| -rw-r--r-- | src/content.rs | 5 | ||||
| -rw-r--r-- | src/data/autotile.rs | 12 | ||||
| -rw-r--r-- | src/data/map.rs | 17 | ||||
| -rw-r--r-- | src/data/renderer.rs | 248 | ||||
| -rw-r--r-- | src/data/schematic.rs | 16 | ||||
| -rw-r--r-- | src/exe/draw.rs | 3 | ||||
| -rw-r--r-- | src/exe/map.rs | 10 | ||||
| -rw-r--r-- | src/lib.rs | 10 | ||||
| -rw-r--r-- | src/team.rs | 11 | ||||
| -rw-r--r-- | src/utils/image.rs | 625 | ||||
| -rw-r--r-- | src/utils/lazy.rs | 45 | ||||
| -rw-r--r-- | src/utils/mod.rs | 4 |
23 files changed, 730 insertions, 475 deletions
@@ -16,12 +16,13 @@ flate2 = { version = "1.0", features = ["zlib"], default-features = false } base64 = "0.21" paste = "1.0" strconv = "0.1" -image = { version = "0.24", features = [], default-features = false } +image = { version = "0.24", features = [], default-features = false, optional = true } color-hex = "0.2" thiserror = "1.0" bobbin-bits = "0.1" blurslice = { version = "0.1" } enum_dispatch = "0.3" +fast_image_resize = "2.7.3" [features] bin = ["image/png"] @@ -35,30 +35,25 @@ fn main() { // let mut half = File::create(o.join("half.rs")).unwrap(); let mut quar = File::create(o.join("quar.rs")).unwrap(); let mut eigh = File::create(o.join("eigh.rs")).unwrap(); - let mut n = 23usize; + let mut n = 22usize; wr!(full => "pub mod full {{"); - wr!(full => "pub static EMPTY: LazyLock<RgbaImage> = LazyLock::new(|| RgbaImage::new(32, 32));"); + wr!(full => "pub static EMPTY: Image<&[u8], 4> = Image::new(unsafe {{ std::num::NonZeroU32::new_unchecked(32) }}, unsafe {{ std::num::NonZeroU32::new_unchecked(32) }}, &[0; 32 * 32 * 4]);"); wr!(quar => "pub mod quar {{"); - wr!(quar => "pub static EMPTY: LazyLock<RgbaImage> = LazyLock::new(|| RgbaImage::new(8, 8));"); + // forced to do this because try_into isnt const + wr!(quar => "pub static EMPTY: Image<&[u8], 4> = Image::new(unsafe {{ std::num::NonZeroU32::new_unchecked(8) }}, unsafe {{ std::num::NonZeroU32::new_unchecked(8) }}, &[0; 8 * 8 * 4]);"); wr!(eigh => "pub mod eigh {{"); - wr!(eigh => "pub static EMPTY: LazyLock<RgbaImage> = LazyLock::new(|| RgbaImage::new(4, 4));"); + wr!(eigh => "pub static EMPTY: Image<&[u8], 4> = Image::new(unsafe {{ std::num::NonZeroU32::new_unchecked(4) }}, unsafe {{ std::num::NonZeroU32::new_unchecked(4) }}, &[0; 4 * 4 * 4]);"); for mut file in [&full, &quar, &eigh] { - file.write_all(b"macro_rules!img{($v:expr)=>{{static TMP:LazyLock<RgbaImage>=LazyLock::new(||$v);&TMP}};}\n").unwrap(); - wr!(file => "use image::RgbaImage;"); - wr!(file => "use crate::utils::Lock as LazyLock;"); - wr!(file => "pub static CLIFF: &LazyLock<RgbaImage> = &EMPTY;"); + wr!(file => "use crate::utils::Image;"); + wr!(file => "pub static CLIFF: Image<&[u8], 4> = EMPTY.copy();"); for i in 1..=16 { - wr!(file => "pub static BUILD{}: &LazyLock<RgbaImage> = &EMPTY;", i); + wr!(file => "pub static BUILD{}: Image<&[u8], 4> = EMPTY.copy();", i); } } - let mut warmup = File::create(o.join("warmup.rs")).unwrap(); - wr!(warmup => "/// # Safety\n///\n/// this function must only be called once."); - wr!(warmup => "pub unsafe fn warmup() {{"); - wr!(warmup => "LazyLock::load(&EMPTY);"); for e in walkdir.into_iter().filter_map(Result::ok) { let path = e.path(); if path.is_file() && let Some(e) = path.extension() && e == "png" { @@ -89,7 +84,7 @@ fn main() { || matches!(path.as_str(), "YELLOWCORAL" | "WHITE_TREE" | "WHITE_TREE_DEAD" | "REDWEED" | "SPORE_CLUSTER" | "CRYSTAL_BLOCKS" | "CRYSTAL_CLUSTER" | "VIBRANT_CRYSTAL_CLUSTER" | "CRYSTAL_ORBS") { (32, 32) } else { - (p.height(), p.width()) + (p.height(), p.height()) }; image::imageops::resize( &p, @@ -102,7 +97,7 @@ fn main() { let y = new.height(); buf.write_all(&new.into_raw()).unwrap(); wr!($ext => - r#"pub(crate) static {path}: &LazyLock<RgbaImage> = img!(unsafe {{ RgbaImage::from_vec({x}, {y}, include_bytes!(concat!(env!("OUT_DIR"), "/{n}-{}")).to_vec()).unwrap_unchecked() }});"#, + r#"pub(crate) static {path}: Image<&[u8], 4> = Image::new(unsafe {{ std::num::NonZeroU32::new_unchecked( {x} ) }}, unsafe {{ std::num::NonZeroU32::new_unchecked( {y} ) }}, include_bytes!(concat!(env!("OUT_DIR"), "/{n}-{}")));"#, stringify!($ext) ); }; @@ -111,15 +106,10 @@ fn main() { // writ!(half + 0.5); writ!(quar / 4); writ!(eigh / 8); - wr!(warmup => "LazyLock::load({path});"); n += 1; } } - warmup.write_all(b"}").unwrap(); for mut f in [full, eigh, quar] { - // brazillian literal - f.write_all(br#"include!(concat!(env!("OUT_DIR"), "/warmup.rs"));"#) - .unwrap(); f.write_all(b"}").unwrap(); } } diff --git a/src/block/distribution.rs b/src/block/distribution.rs index 4cc68c6..62b071f 100644 --- a/src/block/distribution.rs +++ b/src/block/distribution.rs @@ -53,18 +53,18 @@ fn draw_stack( ctx: Option<&RenderingContext>, rot: Rotation, s: Scale, -) -> ImageHolder { +) -> ImageHolder<4> { let ctx = ctx.unwrap(); let mask = mask(ctx, rot, name); let edge = load!(concat edge => name which is ["surge-conveyor" | "plastanium-conveyor"], s); - let edgify = |skip, to: &mut RgbaImage| { + let edgify = |skip, mut to: Image<&mut [u8], 4>| { for i in 0..4 { if i == skip { continue; } let mut edge = edge.clone(); edge.rotate(i); - to.overlay(&edge); + to.overlay(edge.borrow()); } }; let gimme = |n: u8| match n { @@ -77,18 +77,21 @@ fn draw_stack( if rot.mirrored(true, true).mask() == mask && empty && name != "surge-conveyor" { // end let mut base = gimme(2); - edgify(rot.mirrored(true, true).rotated(false).count(), &mut base); + edgify( + rot.mirrored(true, true).rotated(false).count(), + base.borrow_mut(), + ); base } else if mask == B0000 && empty { // single let mut base = gimme(0); base.rotate(rot.rotated(false).count()); - edgify(5, &mut base); + edgify(5, base.borrow_mut()); base } else if mask == B0000 { // input let mut base = gimme(1); - edgify(rot.rotated(false).count(), &mut base); + edgify(rot.rotated(false).count(), base.borrow_mut()); base } else { // directional @@ -221,7 +224,7 @@ impl BlockLogic for ItemBlock { _: Option<&RenderingContext>, rot: Rotation, s: Scale, - ) -> ImageHolder { + ) -> ImageHolder<4> { let mut p = load!(from name which is ["sorter" | "inverted-sorter" | "duct-router" | "duct-unloader" | "unit-cargo-unload-point" | "unloader" | "item-source"], s); if let Some(state) = state { if let Some(item) = Self::get_state(state) { @@ -471,7 +474,7 @@ impl BlockLogic for BridgeBlock { _: Option<&RenderingContext>, r: Rotation, s: Scale, - ) -> ImageHolder { + ) -> ImageHolder<4> { match name { "mass-driver" => { let mut base = load!("mass-driver-base", s); diff --git a/src/block/liquid.rs b/src/block/liquid.rs index 5e40f64..e090724 100644 --- a/src/block/liquid.rs +++ b/src/block/liquid.rs @@ -111,7 +111,7 @@ impl BlockLogic for FluidBlock { _: Option<&RenderingContext>, _: Rotation, s: Scale, - ) -> ImageHolder { + ) -> ImageHolder<4> { let mut p = load!("liquid-source", s); if let Some(state) = state { if let Some(liq) = Self::get_state(state) { diff --git a/src/block/logic.rs b/src/block/logic.rs index f76fa5e..5cdae40 100644 --- a/src/block/logic.rs +++ b/src/block/logic.rs @@ -2,8 +2,6 @@ use std::borrow::Cow; use std::string::FromUtf8Error; -use image::{Rgb, RgbImage}; - use crate::block::simple::*; use crate::data::dynamic::{DynSerializer, DynType}; use crate::{block::*, Serializer}; @@ -47,11 +45,12 @@ pub struct CanvasBlock { } macro_rules! h { - ($x:literal) => { - Rgb(color_hex::color_from_hex!($x)) - }; + ($x:literal) => {{ + let v = color_hex::color_from_hex!($x); + (v[0], v[1], v[2]) + }}; } -const PALETTE: &[Rgb<u8>; 8] = &[ +const PALETTE: &[(u8, u8, u8); 8] = &[ h!("#362944"), h!("#c45d9f"), h!("#e39aac"), @@ -75,11 +74,11 @@ impl CanvasBlock { } } - state_impl!(pub RgbImage); + state_impl!(pub Image<Vec<u8>, 1>); } -fn deser_canvas_image(b: &[u8], size: usize) -> RgbImage { - let mut p = RgbImage::new(size as u32, size as u32); +fn deser_canvas_image(b: &[u8], size: usize) -> Image<Vec<u8>, 1> { + let mut p = Image::alloc(size as u32, size as u32); for i in 0..(size * size) { let offset = i * 3; let mut n = 0; @@ -87,11 +86,7 @@ fn deser_canvas_image(b: &[u8], size: usize) -> RgbImage { let word = (i + offset) >> 3; n |= (((b[word] & (1 << ((i + offset) & 7))) != 0) as u8) << i; } - p.put_pixel( - i as u32 % size as u32, - i as u32 / size as u32, - PALETTE[n as usize], - ); + unsafe { p.set_pixel(i as u32 % size as u32, i as u32 / size as u32, [n]) }; } p } @@ -120,11 +115,12 @@ impl BlockLogic for CanvasBlock { let mut o = vec![0; self.canvas_size as usize * self.canvas_size as usize * 3]; let p = Self::get_state(state); for i in 0..(self.canvas_size * self.canvas_size) as usize { - let color = p.get_pixel( - i as u32 % self.canvas_size as u32, - i as u32 / self.canvas_size as u32, - ); - let index = PALETTE.iter().position(|v| v == color).unwrap(); + let index = unsafe { + p.pixel( + i as u32 % self.canvas_size as u32, + i as u32 / self.canvas_size as u32, + )[0] + }; let offset = i * 3; for i in 0..3 { let word = (i + offset) >> 3; @@ -146,7 +142,7 @@ impl BlockLogic for CanvasBlock { _: Option<&RenderingContext>, _: Rotation, s: Scale, - ) -> ImageHolder { + ) -> ImageHolder<4> { if let Some(state) = state { let p = Self::get_state(state); let offset = match s { @@ -155,26 +151,22 @@ impl BlockLogic for CanvasBlock { Scale::Quarter => 2, Scale::Eigth => 1, }; - let p = DynamicImage::from( - RgbImage::from_raw( - self.canvas_size as u32, - self.canvas_size as u32, - p.clone().into_raw(), - ) - .unwrap(), - ) - .into_rgba8() - .scale((s * self.size as u32) - offset * 2); + let mut img = Image::alloc(p.width(), p.height()); + for ([r, g, b], &y) in img.buffer.array_chunks_mut::<3>().zip(p.buffer.iter()) { + (*r, *g, *b) = PALETTE[y as usize]; + } + let img = img.as_mut().scale((s * self.size as u32) - offset * 2); let mut borders = load!("canvas", s); - borders.overlay_at(&p, offset, offset); + borders + .borrow_mut() + .overlay_at(img.as_ref(), offset, offset); return borders; } - let mut def = RgbaImage::new(s * self.size as u32, s * self.size as u32); - for image::Rgba([r, g, b, _]) in def.pixels_mut() { - *r = PALETTE[0][0]; - *g = PALETTE[0][1]; - *b = PALETTE[0][2]; + let mut def = Image::alloc(s * self.size as u32, s * self.size as u32); + for [r, g, b, a] in def.buffer.array_chunks_mut::<4>() { + (*r, *g, *b) = PALETTE[0]; + *a = 255; } ImageHolder::from(def) } @@ -234,7 +226,7 @@ impl BlockLogic for MessageLogic { _: Option<&RenderingContext>, _: Rotation, _: Scale, - ) -> ImageHolder { + ) -> ImageHolder<4> { unreachable!() } @@ -329,7 +321,7 @@ impl BlockLogic for SwitchLogic { _: Option<&RenderingContext>, _: Rotation, s: Scale, - ) -> ImageHolder { + ) -> ImageHolder<4> { let mut base = load!("switch", s); if let Some(state) = state { if *Self::get_state(state) { diff --git a/src/block/mod.rs b/src/block/mod.rs index 5e3bd50..518e059 100644 --- a/src/block/mod.rs +++ b/src/block/mod.rs @@ -13,7 +13,6 @@ use crate::data::{self, renderer::*, CompressError}; use crate::data::{DataRead, GridPos, ReadError as DataReadError}; use crate::item::storage::ItemStorage; use crate::registry::RegistryEntry; -use crate::utils::Lock; macro_rules! mods { ($($mod:ident)*) => { @@ -151,7 +150,7 @@ stater! { String(String), Item(Option<crate::item::Type>), Fluid(Option<crate::fluid::Type>), - Image(image::RgbImage), + Image(Image<Vec<u8>, 1>), Point(Option<(i32, i32)>), Bool(bool), Processor(logic::ProcessorState), @@ -205,7 +204,7 @@ pub trait BlockLogic { context: Option<&RenderingContext>, rot: Rotation, scale: Scale, - ) -> ImageHolder { + ) -> ImageHolder<4> { unimplemented!("{name}") } @@ -304,7 +303,7 @@ impl SerializeError { /// a block. put it in stuff! pub struct Block { - image: Option<[&'static Lock<RgbaImage>; 3]>, + image: Option<[Image<&'static [u8], 4>; 3]>, name: &'static str, logic: BlockLogicEnum, } @@ -321,7 +320,7 @@ impl Block { pub(crate) const fn new( name: &'static str, logic: BlockLogicEnum, - image: Option<[&'static Lock<RgbaImage>; 3]>, + image: Option<[Image<&'static [u8], 4>; 3]>, ) -> Self { Self { image, name, logic } } @@ -342,19 +341,16 @@ impl Block { } /// draw this block, with this state - /// # Safety - /// - /// UB if called before [`warmup`](crate::warmup) #[must_use] - pub unsafe fn image( + pub fn image( &self, state: Option<&State>, context: Option<&RenderingContext>, rot: Rotation, scale: Scale, - ) -> ImageHolder { - if let Some(imgs) = self.image { - return ImageHolder::from(Lock::get(imgs[scale as usize])); + ) -> ImageHolder<4> { + if let Some(imgs) = &self.image { + return ImageHolder::from((imgs[scale as usize]).copy()); } self.logic.draw(self.name, state, context, rot, scale) } diff --git a/src/block/payload.rs b/src/block/payload.rs index 64ea9ba..37151e4 100644 --- a/src/block/payload.rs +++ b/src/block/payload.rs @@ -20,14 +20,12 @@ make_simple!(SimplePayloadBlock, |_, n, _, _, r: Rotation, scl| { "small-deconstructor" => "factory-in-3", _ => "factory-in-5", }); - base.overlay(r#in.rotate(r.rotated(false).count())).overlay( - load!(scl -> match n { + base.overlay(r#in.rotate(r.rotated(false).count())) + .overlay(&load!(scl -> match n { "small-deconstructor" => "small-deconstructor-top", "deconstructor" => "deconstructor-top", _ => "payload-void-top", - }) - .borrow(), - ); + })); base } // "payload-loader" | "payload-unloader" @@ -38,8 +36,7 @@ make_simple!(SimplePayloadBlock, |_, n, _, _, r: Rotation, scl| { base.overlay(input.rotate(r.rotated(false).count())) .overlay(output.rotate(r.rotated(false).count())) .overlay( - load!(concat top => n which is ["payload-loader" | "payload-unloader"], scl) - .borrow(), + &load!(concat top => n which is ["payload-loader" | "payload-unloader"], scl), ); base } @@ -114,7 +111,7 @@ impl BlockLogic for PayloadBlock { _: Option<&RenderingContext>, r: Rotation, s: Scale, - ) -> ImageHolder { + ) -> ImageHolder<4> { match name { "payload-router" | "reinforced-payload-router" => { let mut base = @@ -133,7 +130,7 @@ impl BlockLogic for PayloadBlock { }); out.rotate(r.rotated(false).count()); base.overlay(&out); - base.overlay(load!(concat top => name which is ["constructor" | "large-constructor" | "payload-source"], s).borrow()); + base.overlay(&load!(concat top => name which is ["constructor" | "large-constructor" | "payload-source"], s)); base } } diff --git a/src/block/simple.rs b/src/block/simple.rs index 329e58d..af4047e 100644 --- a/src/block/simple.rs +++ b/src/block/simple.rs @@ -87,7 +87,7 @@ macro_rules! make_simple { context: Option<&crate::data::renderer::RenderingContext>, rot: crate::block::Rotation, scale: crate::data::renderer::Scale, - ) -> crate::data::renderer::ImageHolder { + ) -> crate::data::renderer::ImageHolder<4> { #[allow(clippy::redundant_closure_call)] $draw(self, name, state, context, rot, scale) } diff --git a/src/block/turrets.rs b/src/block/turrets.rs index 0a3cf0e..522906a 100644 --- a/src/block/turrets.rs +++ b/src/block/turrets.rs @@ -43,7 +43,7 @@ fn draw_turret( _: Option<&RenderingContext>, _: Rotation, s: Scale, -) -> ImageHolder { +) -> ImageHolder<4> { let size = me.get_size(); let mut base = match name { "breach" | "diffuse" | "sublimate" | "titan" | "disperse" | "afflict" | "lustre" @@ -59,7 +59,7 @@ fn draw_turret( 4 => "block-4", }), }; - base.overlay(load!(from name which is ["duo" | "scatter" | "scorch" | "hail" | "wave" | "tsunami" | "lancer" | "arc" | "parallax" | "swarmer" | "salvo" | "segment" | "fuse" | "ripple" | "cyclone" | "foreshadow" | "spectre" | "meltdown" | "breach" | "diffuse" | "sublimate" | "titan" | "disperse" | "afflict" | "lustre" | "scathe" | "malign" | "smite"], s).borrow()); + base.overlay(&load!(from name which is ["duo" | "scatter" | "scorch" | "hail" | "wave" | "tsunami" | "lancer" | "arc" | "parallax" | "swarmer" | "salvo" | "segment" | "fuse" | "ripple" | "cyclone" | "foreshadow" | "spectre" | "meltdown" | "breach" | "diffuse" | "sublimate" | "titan" | "disperse" | "afflict" | "lustre" | "scathe" | "malign" | "smite"], s)); base } diff --git a/src/block/units.rs b/src/block/units.rs index c34f944..3587c9f 100644 --- a/src/block/units.rs +++ b/src/block/units.rs @@ -46,7 +46,7 @@ make_simple!( } .rotate(rot.rotated(false).count()), ); - base.overlay(load!(concat top => name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s).borrow()); + base.overlay(&load!(concat top => name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s)); base }, |_, reg, map, buff| read_assembler(reg, map, buff) @@ -178,8 +178,6 @@ impl BlockLogic for ConstructorBlock { } } - - fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError> { Ok(Self::get_state(state).map_or(DynData::Empty, DynData::UnitCommand)) } @@ -191,7 +189,7 @@ impl BlockLogic for ConstructorBlock { _: Option<&RenderingContext>, rot: Rotation, s: Scale, - ) -> ImageHolder { + ) -> ImageHolder<4> { let mut base = load!(from name which is ["additive-reconstructor" | "multiplicative-reconstructor" | "exponential-reconstructor" | "tetrative-reconstructor" | "tank-refabricator" | "mech-refabricator" | "ship-refabricator" | "prime-refabricator"], s); let times = rot.rotated(false).count(); let mut out = load!(s -> match name { @@ -236,7 +234,7 @@ impl BlockLogic for ConstructorBlock { // } // } - base.overlay(load!(concat top => name which is ["additive-reconstructor" | "multiplicative-reconstructor" | "exponential-reconstructor" | "tetrative-reconstructor" | "tank-refabricator" | "mech-refabricator" | "ship-refabricator" | "prime-refabricator"], s).borrow()); + base.overlay(&load!(concat top => name which is ["additive-reconstructor" | "multiplicative-reconstructor" | "exponential-reconstructor" | "tetrative-reconstructor" | "tank-refabricator" | "mech-refabricator" | "ship-refabricator" | "prime-refabricator"], s)); base } @@ -320,8 +318,6 @@ impl BlockLogic for UnitFactory { } } - - fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError> { if let Some(state) = Self::get_state(state) { for (i, curr) in self.valid.iter().enumerate() { @@ -344,23 +340,21 @@ impl BlockLogic for UnitFactory { _: Option<&RenderingContext>, rot: Rotation, s: Scale, - ) -> ImageHolder { + ) -> ImageHolder<4> { let mut base = load!(from name which is ["ground-factory" | "air-factory" | "naval-factory" | "tank-fabricator" | "ship-fabricator" | "mech-fabricator"], s); - let mut out = load!(s -> match name { - "ground-factory" | "air-factory" | "naval-factory" => "factory-out-3", - _ => "factory-out-3-dark", - }); - out.rotate(rot.rotated(false).count()); - base.overlay(&out); base.overlay( load!(s -> match name { - "ground-factory" | "air-factory" | "naval-factory" => "factory-top-3", - "tank-fabricator" => "tank-fabricator-top", - "ship-fabricator" => "ship-fabricator-top", - "mech-fabricator" => "mech-fabricator-top", + "ground-factory" | "air-factory" | "naval-factory" => "factory-out-3", + _ => "factory-out-3-dark", }) - .borrow(), - ); + .rotate(rot.rotated(false).count()), + ) + .overlay(&load!(s -> match name { + "ground-factory" | "air-factory" | "naval-factory" => "factory-top-3", + "tank-fabricator" => "tank-fabricator-top", + "ship-fabricator" => "ship-fabricator-top", + "mech-fabricator" => "mech-fabricator-top", + })); base } diff --git a/src/block/walls.rs b/src/block/walls.rs index 0108dc3..f2dcb01 100644 --- a/src/block/walls.rs +++ b/src/block/walls.rs @@ -73,7 +73,7 @@ impl BlockLogic for DoorBlock { _: Option<&RenderingContext>, _: Rotation, s: Scale, - ) -> ImageHolder { + ) -> ImageHolder<4> { if let Some(state) = state { if *Self::get_state(state) { return load!(s -> match name { diff --git a/src/content.rs b/src/content.rs index 65619e4..6c2872c 100644 --- a/src/content.rs +++ b/src/content.rs @@ -96,10 +96,11 @@ macro_rules! color_content_enum { impl Type { #[must_use] - pub const fn color(&self) -> image::Rgb<u8> { + pub const fn color(&self) -> (u8, u8, u8) { match &self { $(Self::[<$val:camel>] => { - image::Rgb(color_hex::color_from_hex!($col)) + let v = color_hex::color_from_hex!($col); + (v[0], v[1], v[2]) },)* } } diff --git a/src/data/autotile.rs b/src/data/autotile.rs index c7af8e1..2ca069f 100644 --- a/src/data/autotile.rs +++ b/src/data/autotile.rs @@ -92,7 +92,7 @@ fn print_crosses(v: Vec<Cross<'_>>, height: usize) -> String { s } -pub fn tile(ctx: &RenderingContext<'_>, name: &str, rot: Rotation, s: Scale) -> ImageHolder { +pub fn tile(ctx: &RenderingContext<'_>, name: &str, rot: Rotation, s: Scale) -> ImageHolder<4> { rotations2tile(mask2rotations(mask(ctx, rot, name), rot), name, s) } @@ -212,7 +212,7 @@ pub fn mask2rotations(mask: U4, rot: Rotation) -> (u8, u8, u8) { pub const FLIP_X: u8 = 1; pub const FLIP_Y: u8 = 2; -pub fn flrot(flip: u8, rot: u8, with: &mut ImageHolder) { +pub fn flrot(flip: u8, rot: u8, with: &mut ImageHolder<4>) { if (flip & FLIP_X) != 0 { with.flip_h(); } @@ -223,7 +223,11 @@ pub fn flrot(flip: u8, rot: u8, with: &mut ImageHolder) { } /// TODO figure out if a flip is cheaper than a `rotate_270` -pub fn rotations2tile((index, rot, flip): (u8, u8, u8), name: &str, scale: Scale) -> ImageHolder { +pub fn rotations2tile( + (index, rot, flip): (u8, u8, u8), + name: &str, + scale: Scale, +) -> ImageHolder<4> { let mut p = match index { 0 => { load!(concat 0 => name which is ["reinforced-conduit" | "armored-duct" | "pulse-conduit" | "plated-conduit" | "conduit" | "conveyor" | "titanium-conveyor" | "armored-conveyor" | "duct"], scale) @@ -241,7 +245,7 @@ pub fn rotations2tile((index, rot, flip): (u8, u8, u8), name: &str, scale: Scale load!(concat 4 => name which is ["reinforced-conduit" | "armored-duct" | "pulse-conduit" | "plated-conduit" | "conduit" | "conveyor" | "titanium-conveyor" | "armored-conveyor" | "duct"], scale) } }; - flrot(flip, rot, p.borrow_mut()); + flrot(flip, rot, &mut p); p } diff --git a/src/data/map.rs b/src/data/map.rs index 6284ac7..4660d90 100644 --- a/src/data/map.rs +++ b/src/data/map.rs @@ -146,7 +146,7 @@ impl<'l> Tile<'l> { 1 } - pub(crate) unsafe fn floor(&self, s: Scale) -> ImageHolder { + pub(crate) fn floor(&self, s: Scale) -> ImageHolder<4> { lo!(self.floor => [ | "darksand" | "sand-floor" @@ -186,7 +186,7 @@ impl<'l> Tile<'l> { } #[must_use] - pub(crate) unsafe fn ore(&self, s: Scale) -> ImageHolder { + pub(crate) fn ore(&self, s: Scale) -> ImageHolder<4> { lo!(self.ore => ["ore-copper" | "ore-beryllium" | "ore-lead" | "ore-scrap" | "ore-coal" | "ore-thorium" | "ore-titanium" | "ore-tungsten" | "pebbles" | "tendrils" | "ore-wall-tungsten" | "ore-wall-beryllium" | "ore-wall-thorium" | "spawn" | "ore-crystal-thorium"], s) } @@ -196,12 +196,8 @@ impl<'l> Tile<'l> { } /// Draw the floor of this tile - /// - /// # Safety - /// - /// UB if called before [`warmup`](crate::warmup) #[must_use] - pub unsafe fn floor_image(&self, s: Scale) -> ImageHolder { + pub fn floor_image(&self, s: Scale) -> ImageHolder<4> { let mut floor = self.floor(s); if self.has_ore() { floor.overlay(&self.ore(s)); @@ -210,11 +206,8 @@ impl<'l> Tile<'l> { } /// Draw this tiles build. - /// - /// # Safety - /// UB if called before [`warmup`](crate::warmup) #[must_use] - pub unsafe fn build_image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder { + pub fn build_image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder<4> { // building covers floore let Some(b) = &self.build else { unreachable!(); @@ -300,7 +293,7 @@ impl<'l> Build<'l> { } } - unsafe fn image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder { + fn image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder<4> { self.block .image(self.state.as_ref(), context, self.rotation, s) } diff --git a/src/data/renderer.rs b/src/data/renderer.rs index 6230e71..93f1a7e 100644 --- a/src/data/renderer.rs +++ b/src/data/renderer.rs @@ -2,91 +2,13 @@ pub(crate) use super::autotile::*; use super::schematic::Schematic; use super::GridPos; -use crate::block::environment::METAL_FLOOR; use crate::block::Rotation; -pub(crate) use crate::utils::{ImageUtils, Overlay, Repeat}; +pub(crate) use crate::utils::{Image, ImageHolder, ImageUtils, Overlay, Repeat}; use crate::Map; -pub(crate) use image::{ - DynamicImage, GenericImage, GenericImageView, Pixel, Rgb, RgbImage, Rgba, RgbaImage, -}; -pub(crate) use std::borrow::{Borrow, BorrowMut}; -use std::ops::{Deref, DerefMut}; include!(concat!(env!("OUT_DIR"), "/full.rs")); include!(concat!(env!("OUT_DIR"), "/quar.rs")); include!(concat!(env!("OUT_DIR"), "/eigh.rs")); -pub enum ImageHolder { - Borrow(&'static RgbaImage), - Own(RgbaImage), -} - -impl ImageHolder { - #[must_use] - pub fn own(self) -> RgbaImage { - match self { - Self::Own(x) => x, - Self::Borrow(x) => x.clone(), - } - } - - pub fn rotate(&mut self, times: u8) -> &mut Self { - if times == 0 { - return self; - } - let p: &mut RgbaImage = self.borrow_mut(); - p.rotate(times); - self - } -} - -impl Borrow<RgbaImage> for ImageHolder { - fn borrow(&self) -> &RgbaImage { - match self { - Self::Own(x) => x, - Self::Borrow(x) => x, - } - } -} - -impl BorrowMut<RgbaImage> for ImageHolder { - fn borrow_mut(&mut self) -> &mut RgbaImage { - match self { - Self::Own(x) => x, - Self::Borrow(_) => { - *self = Self::from(std::mem::replace(self, Self::from(RgbaImage::new(0, 0))).own()); - self.borrow_mut() - } - } - } -} - -impl Deref for ImageHolder { - type Target = RgbaImage; - fn deref(&self) -> &Self::Target { - self.borrow() - } -} - -impl DerefMut for ImageHolder { - fn deref_mut(&mut self) -> &mut Self::Target { - self.borrow_mut() - } -} - -impl From<&'static RgbaImage> for ImageHolder { - fn from(value: &'static RgbaImage) -> Self { - debug_assert_ne!(value.width(), 0); - debug_assert_ne!(value.height(), 0); - Self::Borrow(value) - } -} - -impl From<RgbaImage> for ImageHolder { - fn from(value: RgbaImage) -> Self { - Self::Own(value) - } -} - #[derive(Debug, Copy, Clone)] #[repr(u8)] pub enum Scale { @@ -116,22 +38,22 @@ impl std::ops::Mul<u32> for Scale { #[macro_export] macro_rules! load { - ("empty", $scale:ident) => { - ImageHolder::from(unsafe { $crate::utils::Lock::get(match $scale { + ("empty", $scale:expr) => { + ImageHolder::from(match $scale { $crate::data::renderer::Scale::Quarter => &$crate::data::renderer::quar::EMPTY, $crate::data::renderer::Scale::Eigth => &$crate::data::renderer::eigh::EMPTY, $crate::data::renderer::Scale::Full => &$crate::data::renderer::full::EMPTY, - })}) + }.copy()) }; - ($name:literal, $scale:ident) => { paste::paste! { - ImageHolder::from(unsafe { $crate::utils::Lock::get(match $scale { - $crate::data::renderer::Scale::Quarter => $crate::data::renderer::quar::[<$name:snake:upper>], - $crate::data::renderer::Scale::Eigth => $crate::data::renderer::eigh::[<$name:snake:upper>], - $crate::data::renderer::Scale::Full => $crate::data::renderer::full::[<$name:snake:upper>], - })}) + ($name:literal, $scale:expr) => { paste::paste! { + ImageHolder::from(match $scale { + $crate::data::renderer::Scale::Quarter => &$crate::data::renderer::quar::[<$name:snake:upper>], + $crate::data::renderer::Scale::Eigth => &$crate::data::renderer::eigh::[<$name:snake:upper>], + $crate::data::renderer::Scale::Full => &$crate::data::renderer::full::[<$name:snake:upper>], + }.copy()) } }; ($name: literal) => { paste::paste! { - [$crate::data::renderer::full::[<$name:snake:upper>], $crate::data::renderer::quar::[<$name:snake:upper>], $crate::data::renderer::eigh::[<$name:snake:upper>]] + [$crate::data::renderer::full::[<$name:snake:upper>].copy(), $crate::data::renderer::quar::[<$name:snake:upper>].copy(), $crate::data::renderer::eigh::[<$name:snake:upper>].copy()] } }; (from $v:ident which is [$($k:literal $(|)?)+], $scale: ident) => { $crate::data::renderer::load!($scale -> match $v { @@ -149,11 +71,11 @@ macro_rules! load { (concat $x:expr => $v:ident which is [$($k:literal $(|)?)+], $scale: ident) => { paste::paste! { match $v { $($k => - ImageHolder::from(unsafe { $crate::utils::Lock::get(match $scale { - $crate::data::renderer::Scale::Quarter => $crate::data::renderer::quar::[<$k:snake:upper _ $x:snake:upper>], - $crate::data::renderer::Scale::Eigth => $crate::data::renderer::eigh::[<$k:snake:upper _ $x:snake:upper>], - $crate::data::renderer::Scale::Full => $crate::data::renderer::full::[<$k:snake:upper _ $x:snake:upper>], - }) }), + ImageHolder::from(match $scale { + $crate::data::renderer::Scale::Quarter => &$crate::data::renderer::quar::[<$k:snake:upper _ $x:snake:upper>], + $crate::data::renderer::Scale::Eigth => &$crate::data::renderer::eigh::[<$k:snake:upper _ $x:snake:upper>], + $crate::data::renderer::Scale::Full => &$crate::data::renderer::full::[<$k:snake:upper _ $x:snake:upper>], + }.copy()), )+ #[allow(unreachable_patterns)] n => unreachable!("{n:?}"), @@ -165,11 +87,8 @@ pub(crate) use load; /// trait for renderable objects pub trait Renderable { /// create a picture - /// - /// # Safety - /// - /// UB if called before [`warmup`](crate::warmup) - unsafe fn render(&self) -> RgbImage; + #[must_use = "i did so much work for you"] + fn render(&self) -> Image<Vec<u8>, 3>; } impl Renderable for Schematic<'_> { @@ -180,28 +99,15 @@ impl Renderable for Schematic<'_> { /// s.put(0, 0, &block::distribution::DISTRIBUTOR); /// s.put(0, 2, &block::distribution::ROUTER); /// s.put(1, 2, &block::walls::COPPER_WALL); - /// // warm up the images for the first time - /// unsafe { warmup(); } - /// // this is now safe, because we have warmed up - /// let output /*: RgbImage */ = unsafe { s.render() }; + /// let output /*: Image */ = s.render(); /// ``` - /// - /// # Safety - /// - /// UB if called before [`warmup`](crate::warmup) - unsafe fn render(&self) -> RgbImage { + fn render(&self) -> Image<Vec<u8>, 3> { // fill background - let mut bg = RgbImage::repeated( - &DynamicImage::from( - METAL_FLOOR - .image(None, None, Rotation::Up, Scale::Full) - .own(), - ) - .into_rgb8(), + let mut bg = load!("metal-floor", Scale::Full).borrow().repeated( ((self.width + 2) * 32) as u32, ((self.height + 2) * 32) as u32, ); - let mut canvas = RgbaImage::new( + let mut canvas = Image::alloc( ((self.width + 2) * 32) as u32, ((self.height + 2) * 32) as u32, ); @@ -221,7 +127,7 @@ impl Renderable for Schematic<'_> { }; let x = x as u32 - ((tile.block.get_size() - 1) / 2) as u32; let y = self.height as u32 - y as u32 - ((tile.block.get_size() / 2) + 1) as u32; - canvas.overlay_at( + canvas.as_mut().overlay_at( tile.image( ctx.as_ref(), tile.get_rotation().unwrap_or(Rotation::Up), @@ -232,41 +138,32 @@ impl Renderable for Schematic<'_> { (y + 1) * 32, ); } - canvas.shadow(); + canvas.as_mut().shadow(); for x in 0..canvas.width() { for y in 0..canvas.height() { - let p2 = unsafe { canvas.unsafe_get_pixel(x, y) }; - let Rgb([r2, g2, b2]) = unsafe { bg.unsafe_get_pixel(x, y) }; - let mut p = Rgba([r2, g2, b2, u8::MAX]); - p.blend(&p2); - let Rgba([r, g, b, a]) = p; - let a = a as f32 / 255.; - let p = Rgb([ - (((r as f32 / 255.) * a) * 255.) as u8, - (((g as f32 / 255.) * a) * 255.) as u8, - (((b as f32 / 255.) * a) * 255.) as u8, - ]); - unsafe { bg.unsafe_put_pixel(x, y, p) }; + // canvas has a shadow + let p2 = unsafe { canvas.pixel(x, y) }; + let p = unsafe { bg.pixel_mut(x, y) }; + crate::utils::image::blend(p.try_into().unwrap(), p2); } } - bg + bg.remove_channel() } } impl Renderable for Map<'_> { /// Draws a map - /// - /// # Safety - /// UB if called before [`warmup`](crate::warmup) - unsafe fn render(&self) -> RgbImage { + fn render(&self) -> Image<Vec<u8>, 3> { let scale = if self.width + self.height < 2000 { Scale::Quarter } else { Scale::Eigth }; // todo combine these (beware of floor drawing atop buildings) (planned solution:? ptr blocks) - let mut floor = RgbImage::new(scale * self.width as u32, scale * self.height as u32); - let mut top = RgbaImage::new(scale * self.width as u32, scale * self.height as u32); + let mut floor: Image<_, 3> = + Image::alloc(scale * self.width as u32, scale * self.height as u32); + let mut top: Image<_, 4> = + Image::alloc(scale * self.width as u32, scale * self.height as u32); for (x, y, j, tile) in self.tiles.iter().enumerate().map(|(j, t)| { ( (j % self.width), @@ -277,16 +174,18 @@ impl Renderable for Map<'_> { ) }) { // draw the floor first. - let flo: &RgbaImage = &tile.floor(scale); // println!("draw {tile:?} ({x}, {y}) + {scale:?}"); - // debug_assert_eq!(floor.width(), scale.px() as u32); - // debug_assert_eq!(floor.height(), scale.px() as u32); - floor.overlay_at(flo, scale * x as u32, scale * y as u32); + floor.as_mut().overlay_at( + tile.floor(scale).borrow(), + scale * x as u32, + scale * y as u32, + ); if tile.has_ore() { - let ore: &RgbaImage = &tile.ore(scale); - // debug_assert_eq!(ore.width(), scale.px() as u32); - // debug_assert_eq!(ore.height(), scale.px() as u32); - floor.overlay_at(ore, scale * x as u32, scale * y as u32); + floor.as_mut().overlay_at( + tile.ore(scale).borrow(), + scale * x as u32, + scale * y as u32, + ); } if let Some(build) = tile.build() { @@ -307,41 +206,22 @@ impl Renderable for Map<'_> { } else { None }; - let img: &RgbaImage = &tile.build_image(ctx.as_ref(), scale); - // debug_assert_eq!(img.width(), scale * build.block.get_size() as u32); - // debug_assert_eq!(img.height(), scale * build.block.get_size() as u32); - top.overlay_at(img, scale * x as u32, scale * y as u32); + top.as_mut().overlay_at( + tile.build_image(ctx.as_ref(), scale).borrow(), + scale * x as u32, + scale * y as u32, + ); } } - floor.overlay_at(&top, 0, 0); + floor.as_mut().overlay_at(top.as_ref(), 0, 0); floor } } -#[allow(clippy::needless_doctest_main)] -/// Loads all the images into memory (about 300mb) -/// This is a necessary function. Call it once in main. -/// -/// ``` -/// fn main() { -/// unsafe { mindus::warmup(); } -/// } -/// ``` -/// -/// # Safety -/// -/// only call once, else UB -pub unsafe fn warmup() { - full::warmup(); - quar::warmup(); - eigh::warmup(); -} - #[test] fn all_blocks() { use crate::block::content::Type; use crate::content::Content; - unsafe { warmup() }; let reg = crate::block::build_registry(); for t in 19..Type::WorldMessage as u16 { let t = Type::try_from(t).unwrap(); @@ -357,20 +237,18 @@ fn all_blocks() { } let name = dbg!(t.get_name()); let t = reg.get(name).unwrap(); - let _ = unsafe { - t.image( - None, - Some(&RenderingContext { - cross: [None; 4], - position: PositionContext { - position: GridPos(0, 0), - width: 5, - height: 5, - }, - }), - Rotation::Up, - Scale::Quarter, - ) - }; + let _ = t.image( + None, + Some(&RenderingContext { + cross: [None; 4], + position: PositionContext { + position: GridPos(0, 0), + width: 5, + height: 5, + }, + }), + Rotation::Up, + Scale::Quarter, + ); } } diff --git a/src/data/schematic.rs b/src/data/schematic.rs index 6dea884..31888c2 100644 --- a/src/data/schematic.rs +++ b/src/data/schematic.rs @@ -41,7 +41,7 @@ impl fmt::Debug for Placement<'_> { impl<'l> Placement<'l> { /// make a placement from a block #[must_use] - pub fn new(block: &'l Block) -> Self { + pub const fn new(block: &'l Block) -> Self { Self { block, rot: Rotation::Up, @@ -51,7 +51,7 @@ impl<'l> Placement<'l> { /// gets the current state of this placement. you can cast it with `placement.block::get_state(placement.get_state()?)?` #[must_use] - pub fn get_state(&self) -> Option<&State> { + pub const fn get_state(&self) -> Option<&State> { self.state.as_ref() } @@ -65,12 +65,12 @@ impl<'l> Placement<'l> { /// # Safety /// UB if called before [`warmup`](crate::warmup) #[must_use] - pub unsafe fn image( + pub fn image( &self, context: Option<&RenderingContext>, rot: Rotation, s: Scale, - ) -> ImageHolder { + ) -> ImageHolder<4> { self.block.image(self.get_state(), context, rot, s) } @@ -716,10 +716,10 @@ mod test { let parsed2 = unwrap_pretty(ser.deserialize_base64(&unparsed)); println!("\x1b[38;5;2mredeserialized\x1b[0m {}", parsed.tags.get("name").unwrap()); if parsed != parsed2 { - unsafe { crate::warmup() }; - // SAFETY: we just warmed up, its fine - unsafe { parsed2.render() }.save("p2.png").unwrap(); - unsafe { parsed.render() }.save("p1.png").unwrap(); + #[cfg(feature = "bin")] + parsed2.render().save("p2.png"); + #[cfg(feature = "bin")] + parsed.render().save("p1.png"); panic!("DIFFERENT! see `p1.png` != `p2.png`") } )* diff --git a/src/exe/draw.rs b/src/exe/draw.rs index c7de323..964bcac 100644 --- a/src/exe/draw.rs +++ b/src/exe/draw.rs @@ -6,7 +6,6 @@ use std::env::Args; use crate::print_err; pub fn main(args: Args) { - unsafe { mindus::warmup() }; let reg = build_registry(); let mut ss = SchematicSerializer(®); @@ -14,7 +13,7 @@ pub fn main(args: Args) { for curr in args { match ss.deserialize_base64(&curr) { Ok(s) => { - unsafe { s.render() }.save("x.png").unwrap(); + s.render().save("x.png"); } // continue processing literals & maybe interactive mode Err(e) => { diff --git a/src/exe/map.rs b/src/exe/map.rs index 51c05c6..cbb6477 100644 --- a/src/exe/map.rs +++ b/src/exe/map.rs @@ -1,4 +1,3 @@ -use mindus::data::renderer::warmup; use mindus::data::DataRead; use mindus::{build_registry, Renderable}; use mindus::{MapSerializer, Serializer}; @@ -14,8 +13,6 @@ pub fn main(args: Args) { // process schematics from command line println!("starting timing"); let then = Instant::now(); - unsafe { warmup() }; - let warmup_took = then.elapsed(); for curr in args { let Ok(s) = std::fs::read(curr) else { continue; @@ -27,22 +24,21 @@ pub fn main(args: Args) { let deser_took = starting_deser.elapsed(); if let Ok(v) = std::env::var("SAVE") { if v == "1" { - unsafe { m.render() }.save("x.png").unwrap(); + m.render().save("x.png"); continue; } } let starting_render = Instant::now(); for _ in 0..runs { - unsafe { m.render() }; + drop(m.render()); } let renders_took = starting_render.elapsed(); let took = then.elapsed(); println!( - "μ total: {:.2}s ({} runs) (deser: {}ms, warmup: {}ms, render: {:.2}s) on map {}", + "μ total: {:.2}s ({} runs) (deser: {}ms, render: {:.2}s) on map {}", took.as_secs_f32() / runs as f32, runs, deser_took.as_millis(), - warmup_took.as_millis(), renders_took.as_secs_f32() / runs as f32, m.tags.get("mapname").unwrap(), ); @@ -1,5 +1,11 @@ //! crate for dealing with mindustry -#![feature(array_chunks, const_trait_impl, unchecked_math, core_intrinsics)] +#![feature( + array_chunks, + const_trait_impl, + unchecked_math, + slice_as_chunks, + slice_swap_unchecked +)] pub mod block; mod content; pub mod data; @@ -17,7 +23,7 @@ pub use { data::{ dynamic::DynData, map::{Map, MapSerializer}, - renderer::{warmup, Renderable}, + renderer::Renderable, schematic::{Schematic, SchematicSerializer}, Serializer, }, diff --git a/src/team.rs b/src/team.rs index c2f8510..35a651f 100644 --- a/src/team.rs +++ b/src/team.rs @@ -1,7 +1,5 @@ use std::fmt; -use image::Rgb; - use crate::content::{Content, Type}; #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] @@ -109,11 +107,12 @@ impl Content for Team { } impl Team { - pub const fn color(self) -> Rgb<u8> { + pub const fn color(self) -> (u8, u8, u8) { macro_rules! h { - ($x:literal) => { - Rgb(color_hex::color_from_hex!($x)) - }; + ($x:literal) => {{ + let v = color_hex::color_from_hex!($x); + (v[0], v[1], v[2]) + }}; } match self { SHARDED => h!("ffd37f"), diff --git a/src/utils/image.rs b/src/utils/image.rs index 262f1e1..70f6261 100644 --- a/src/utils/image.rs +++ b/src/utils/image.rs @@ -1,21 +1,24 @@ -use image::*; +use fast_image_resize as fr; +use std::{num::NonZeroU32, slice::SliceIndex}; pub trait Overlay<W> { /// Overlay with => self at coordinates x, y, without blending - fn overlay_at(&mut self, with: &W, x: u32, y: u32) -> &mut Self; + fn overlay_at(&mut self, with: W, x: u32, y: u32) -> &mut Self; } pub trait RepeatNew { - /// Repeat with over self - fn repeated(with: &Self, x: u32, y: u32) -> Self; + type Output; + /// Repeat self till it fills x, y + fn repeated(self, x: u32, y: u32) -> Self::Output; } pub trait ImageUtils { + type With<'a>; /// Tint this image with the color - fn tint(&mut self, color: Rgb<u8>) -> &mut Self; + fn tint(&mut self, color: (u8, u8, u8)) -> &mut Self; /// Overlay with => self (does not blend) - fn overlay(&mut self, with: &Self) -> &mut Self; - /// rotate + fn overlay(&mut self, with: Self::With<'_>) -> &mut Self; + /// rotate (squares only) fn rotate(&mut self, times: u8) -> &mut Self; /// flip along the horizontal axis fn flip_h(&mut self) -> &mut Self; @@ -23,10 +26,8 @@ pub trait ImageUtils { fn flip_v(&mut self) -> &mut Self; /// shadow fn shadow(&mut self) -> &mut Self; - /// silhouette - fn silhouette(&mut self) -> &mut Self; /// scale a image - fn scale(&self, to: u32) -> Self; + fn scale(self, to: u32) -> Image<Vec<u8>, 4>; } macro_rules! unsafe_assert { @@ -37,19 +38,19 @@ macro_rules! unsafe_assert { }}; } -impl Overlay<RgbImage> for RgbImage { - fn overlay_at(&mut self, with: &RgbImage, x: u32, y: u32) -> &mut Self { - unsafe_assert!(with.height() != 0); - unsafe_assert!(with.width() != 0); +impl Overlay<Image<&[u8], 3>> for Image<&mut [u8], 3> { + fn overlay_at(&mut self, with: Image<&[u8], 3>, x: u32, y: u32) -> &mut Self { for j in 0..with.height() { for i in 0..with.width() { unsafe { - let with_index = really_unsafe_index(i, j, with.width()).unchecked_mul(3); - let their_px = with.get_unchecked(with_index..with_index.unchecked_add(3)); + let with_index = with.slice(i, j); + let their_px = with.buffer.get_unchecked(with_index); let our_index = really_unsafe_index(i.unchecked_add(x), j.unchecked_add(y), self.width()) .unchecked_mul(3); - let our_px = self.get_unchecked_mut(our_index..our_index.unchecked_add(3)); + let our_px = self + .buffer + .get_unchecked_mut(our_index..our_index.unchecked_add(3)); std::ptr::copy_nonoverlapping(their_px.as_ptr(), our_px.as_mut_ptr(), 3); } } @@ -58,25 +59,26 @@ impl Overlay<RgbImage> for RgbImage { } } -impl Overlay<RgbaImage> for RgbImage { - fn overlay_at(&mut self, with: &RgbaImage, x: u32, y: u32) -> &mut Self { - // TODO NonZeroU32 - unsafe_assert!(with.height() != 0); - unsafe_assert!(with.width() != 0); +impl Overlay<Image<&[u8], 4>> for Image<&mut [u8], 3> { + fn overlay_at(&mut self, with: Image<&[u8], 4>, x: u32, y: u32) -> &mut Self { for j in 0..with.height() { for i in 0..with.width() { unsafe { let with_index = really_unsafe_index(i, j, with.width()).unchecked_mul(4); // solidity - if with.get_unchecked(with_index.unchecked_add(3)) > &128 { - let their_px = with.get_unchecked(with_index..with_index.unchecked_add(3)); + if *with.buffer.get_unchecked(with_index.unchecked_add(3)) > 128 { + let their_px = with + .buffer + .get_unchecked(with_index..with_index.unchecked_add(3)); let our_index = really_unsafe_index( i.unchecked_add(x), j.unchecked_add(y), self.width(), ) .unchecked_mul(3); - let our_px = self.get_unchecked_mut(our_index..our_index.unchecked_add(3)); + let our_px = self + .buffer + .get_unchecked_mut(our_index..our_index.unchecked_add(3)); std::ptr::copy_nonoverlapping(their_px.as_ptr(), our_px.as_mut_ptr(), 3); } } @@ -86,15 +88,15 @@ impl Overlay<RgbaImage> for RgbImage { } } -impl Overlay<RgbaImage> for RgbaImage { - fn overlay_at(&mut self, with: &RgbaImage, x: u32, y: u32) -> &mut Self { - unsafe_assert!(with.height() != 0); - unsafe_assert!(with.width() != 0); +impl Overlay<Image<&[u8], 4>> for Image<&mut [u8], 4> { + fn overlay_at(&mut self, with: Image<&[u8], 4>, x: u32, y: u32) -> &mut Self { for j in 0..with.height() { for i in 0..with.width() { unsafe { let with_index = really_unsafe_index(i, j, with.width()).unchecked_mul(4); - let their_px = with.get_unchecked(with_index..with_index.unchecked_add(4)); + let their_px = with + .buffer + .get_unchecked(with_index..with_index.unchecked_add(4)); if their_px.get_unchecked(3) > &128 { let our_index = really_unsafe_index( i.unchecked_add(x), @@ -102,7 +104,9 @@ impl Overlay<RgbaImage> for RgbaImage { self.width(), ) .unchecked_mul(4); - let our_px = self.get_unchecked_mut(our_index..our_index.unchecked_add(4)); + let our_px = self + .buffer + .get_unchecked_mut(our_index..our_index.unchecked_add(4)); std::ptr::copy_nonoverlapping(their_px.as_ptr(), our_px.as_mut_ptr(), 4); } } @@ -112,54 +116,136 @@ impl Overlay<RgbaImage> for RgbaImage { } } -impl RepeatNew for RgbImage { - fn repeated(with: &Self, x: u32, y: u32) -> Self { - let mut img = RgbImage::new(x, y); // could probably optimize this a ton but eh - for x in 0..(x / with.width()) { - for y in 0..(y / with.height()) { - img.overlay_at(with, x * with.width(), y * with.height()); +impl RepeatNew for Image<&[u8], 4> { + type Output = Image<Vec<u8>, 4>; + fn repeated(self, x: u32, y: u32) -> Self::Output { + let mut img = Image::alloc(x, y); // could probably optimize this a ton but eh + for x in 0..(x / self.width()) { + for y in 0..(y / self.height()) { + let a: &mut Image<&mut [u8], 4> = &mut img.as_mut(); + a.overlay_at(self.copy(), x * self.width(), y * self.height()); } } img } } -impl ImageUtils for RgbaImage { +unsafe fn flip_v<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { + for y in 0..img.height() / 2 { + for x in 0..img.width() { + let y2 = img.height() - y - 1; + let p2 = img.pixel(x, y2); + let p = img.pixel(x, y); + img.set_pixel(x, y2, p); + img.set_pixel(x, y, p2); + } + } +} + +unsafe fn flip_h<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { + for y in 0..img.height() { + for x in 0..img.width() / 2 { + let x2 = img.width() - x - 1; + let p2 = img.pixel(x2, y); + let p = img.pixel(x, y); + img.set_pixel(x2, y, p); + img.set_pixel(x, y, p2); + } + } +} + +unsafe fn rot_180<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { + for y in 0..img.height() / 2 { + for x in 0..img.width() { + let p = img.pixel(x, y); + let x2 = img.width() - x - 1; + let y2 = img.height() - y - 1; + let p2 = img.pixel(x2, y2); + img.set_pixel(x, y, p2); + img.set_pixel(x2, y2, p); + } + } + + if img.height() % 2 != 0 { + let middle = img.height() / 2; + + for x in 0..img.width() / 2 { + let p = img.pixel(x, middle); + let x2 = img.width() - x - 1; + + let p2 = img.pixel(x2, middle); + img.set_pixel(x, middle, p2); + img.set_pixel(x2, middle, p); + } + } +} + +/// only works with squares! +unsafe fn rot_90<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { + debug_assert_eq!(img.width(), img.height()); + let size = img.width(); + flip_v(img); + for i in 0..size { + for j in i..size { + for c in 0..CHANNELS { + img.buffer.swap( + (i * size + j) as usize * CHANNELS + c, + (j * size + i) as usize * CHANNELS + c, + ); + } + } + } +} + +/// only works with squares! +unsafe fn rot_270<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { + debug_assert_eq!(img.width(), img.height()); + flip_h(img); + let size = img.width(); + for i in 0..size { + for j in i..size { + for c in 0..CHANNELS { + img.buffer.swap( + (i * size + j) as usize * CHANNELS + c, + (j * size + i) as usize * CHANNELS + c, + ); + } + } + } +} +impl ImageUtils for Image<&mut [u8], 4> { fn rotate(&mut self, times: u8) -> &mut Self { - use image::imageops::{rotate180, rotate270, rotate90}; - match times { - 2 => *self = rotate180(self), - 1 => *self = rotate90(self), - 3 => *self = rotate270(self), - _ => {} + unsafe { + match times { + 2 => rot_180(self), + 1 => rot_90(self), + 3 => rot_270(self), + _ => {} + } } self } - fn tint(&mut self, color: Rgb<u8>) -> &mut Self { - let [tr, tg, tb] = [ - color[0] as f32 / 255.0, - color[1] as f32 / 255.0, - color[2] as f32 / 255.0, - ]; - for Rgba([r, g, b, _]) in self.pixels_mut() { + fn tint(&mut self, (r, g, b): (u8, u8, u8)) -> &mut Self { + let [tr, tg, tb] = [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0]; + for [r, g, b, _] in self.buffer.array_chunks_mut::<4>() { *r = (*r as f32 * tr) as u8; *g = (*g as f32 * tg) as u8; *b = (*b as f32 * tb) as u8; } self } - - fn overlay(&mut self, with: &RgbaImage) -> &mut Self { + type With<'a> = Image<&'a [u8], 4>; + fn overlay(&mut self, with: Image<&[u8], 4>) -> &mut Self { debug_assert_eq!(self.width(), with.width()); debug_assert_eq!(self.height(), with.height()); - if self.len() % 4 != 0 || with.len() % 4 != 0 || with.height() == 0 || with.width() == 0 { - unsafe { std::hint::unreachable_unchecked() }; - } - for (i, other_pixels) in with.array_chunks::<4>().enumerate() { + unsafe_assert!(self.buffer.len() % 4 == 0); + unsafe_assert!(with.buffer.len() % 4 == 0); + for (i, other_pixels) in with.buffer.array_chunks::<4>().enumerate() { if other_pixels[3] > 128 { unsafe { let own_pixels = self + .buffer .get_unchecked_mut(i.unchecked_mul(4)..i.unchecked_mul(4).unchecked_add(4)); std::ptr::copy_nonoverlapping( other_pixels.as_ptr(), @@ -172,56 +258,60 @@ impl ImageUtils for RgbaImage { self } - fn scale(&self, to: u32) -> Self { - imageops::resize(self, to, to, imageops::Nearest) + // this function is very cold but im removing image so might as well use fir + fn scale(self, to: u32) -> Image<Vec<u8>, 4> { + let from = + fr::Image::from_slice_u8(self.width, self.height, self.buffer, fr::PixelType::U8x4) + .unwrap(); + let mut dst = fr::Image::new( + to.try_into().unwrap(), + to.try_into().unwrap(), + fr::PixelType::U8x4, + ); + fr::Resizer::new(fr::ResizeAlg::Nearest) + .resize(&from.view(), &mut dst.view_mut()) + .unwrap(); + Image::new(self.width, self.height, dst.into_vec()) } - fn silhouette(&mut self) -> &mut Self { - for pixel in self.pixels_mut() { - if pixel[3] < 128 { - pixel[2] /= 10; - pixel[1] /= 10; - pixel[0] /= 10; + fn shadow(&mut self) -> &mut Self { + let mut shadow: Image<Vec<u8>, 4> = + Image::new(self.width, self.height, self.buffer.to_vec()); + for [r, g, b, a] in shadow.buffer.array_chunks_mut() { + if *a < 128 { + *r /= 10; + *g /= 10; + *b /= 10; } } - self - } - - fn shadow(&mut self) -> &mut Self { - let mut shadow = self.clone(); - shadow.silhouette(); - let samples = shadow.as_flat_samples_mut(); blurslice::gaussian_blur_bytes::<4>( - samples.samples, + &mut shadow.buffer, self.width() as usize, self.height() as usize, 9.0, ) .unwrap(); - for x in 0..shadow.width() { - for y in 0..shadow.height() { - let Rgba([r, g, b, a]) = self.get_pixel_mut(x, y); - if *a == 0 { - let p = unsafe { shadow.unsafe_get_pixel(x, y) }; - *r = p[0]; - *g = p[0]; - *b = p[0]; - *a = p[1]; - } + for ([r, g, b, a], &[from_r, from_g, from_b, from_a]) in self + .buffer + .array_chunks_mut() + .zip(shadow.buffer.array_chunks()) + { + if *a == 0 { + (*r, *g, *b, *a) = (from_r, from_g, from_b, from_a); } } self } - #[inline(always)] + #[inline] fn flip_h(&mut self) -> &mut Self { - imageops::flip_horizontal_in_place(self); + unsafe { flip_h(self) }; self } #[inline(always)] fn flip_v(&mut self) -> &mut Self { - imageops::flip_vertical_in_place(self); + unsafe { flip_v(self) }; self } } @@ -232,3 +322,366 @@ unsafe fn really_unsafe_index(x: u32, y: u32, w: u32) -> usize { .unchecked_mul(w as usize) .unchecked_add(x as usize) } + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Image<T, const CHANNELS: usize> { + pub buffer: T, + pub width: NonZeroU32, + pub height: NonZeroU32, +} + +impl<const CHANNELS: usize> Default for Image<&'static [u8], CHANNELS> { + fn default() -> Self { + Self { + buffer: &[0; CHANNELS], + width: NonZeroU32::new(1).unwrap(), + height: NonZeroU32::new(1).unwrap(), + } + } +} + +impl<T, const CHANNELS: usize> Image<T, CHANNELS> { + #[inline] + pub fn height(&self) -> u32 { + self.height.into() + } + + #[inline] + pub fn width(&self) -> u32 { + self.width.into() + } + + #[inline] + pub const fn new(width: NonZeroU32, height: NonZeroU32, buffer: T) -> Self { + Image { + width, + height, + buffer, + } + } +} + +impl<const CHANNELS: usize> Image<&[u8], CHANNELS> { + #[inline] + pub const fn copy(&self) -> Self { + Self { + width: self.width, + height: self.height, + buffer: self.buffer, + } + } +} + +impl<T: std::ops::Deref<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS> { + /// # Safety + /// + /// - UB if x, y is out of bounds + /// - UB if buffer is too small + pub unsafe fn slice(&self, x: u32, y: u32) -> impl SliceIndex<[u8], Output = [u8]> { + debug_assert!(x < self.width(), "x out of bounds"); + debug_assert!(y < self.height(), "y out of bounds"); + let index = really_unsafe_index(x, y, self.width()).unchecked_mul(CHANNELS); + debug_assert!(self.buffer.len() > index); + index..index.unchecked_add(CHANNELS) + } + + #[inline] + /// Return a pixel at (x, y). + /// # Safety + /// + /// Refer to [`slice`] + pub unsafe fn pixel(&self, x: u32, y: u32) -> [u8; CHANNELS] { + *(self.buffer.get_unchecked(self.slice(x, y)).as_ptr().cast()) + } +} +impl<T: std::ops::DerefMut<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS> { + #[inline] + /// Return a mutable reference to a pixel at (x, y). + /// # Safety + /// + /// Refer to [`slice`] + pub unsafe fn pixel_mut(&mut self, x: u32, y: u32) -> &mut [u8] { + let idx = self.slice(x, y); + self.buffer.get_unchecked_mut(idx) + } + #[inline] + pub unsafe fn set_pixel(&mut self, x: u32, y: u32, px: [u8; CHANNELS]) { + std::ptr::copy_nonoverlapping(px.as_ptr(), self.pixel_mut(x, y).as_mut_ptr(), CHANNELS); + } +} + +impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> { + pub fn alloc(width: u32, height: u32) -> Self { + Image { + width: width.try_into().unwrap(), + height: height.try_into().unwrap(), + buffer: vec![0; CHANNELS * width as usize * height as usize], + } + } + + pub fn as_ref(&self) -> Image<&[u8], CHANNELS> { + Image::new(self.width, self.height, &self.buffer) + } + + pub fn as_mut(&mut self) -> Image<&mut [u8], CHANNELS> { + Image::new(self.width, self.height, &mut self.buffer) + } +} + +impl Image<Vec<u8>, 4> { + pub fn remove_channel(&mut self) -> Image<Vec<u8>, 3> { + let mut new = vec![0; self.width() as usize * self.height() as usize * 3]; + for (&[r, g, b, _], [nr, ng, nb]) in self + .buffer + .array_chunks::<4>() + .zip(new.array_chunks_mut::<3>()) + { + (*nr, *ng, *nb) = (r, g, b); + } + Image::new(self.width, self.height, new) + } +} + +impl Image<Vec<u8>, 3> { + #[cfg(feature = "bin")] + pub fn save(&self, f: impl AsRef<std::path::Path>) { + image::save_buffer( + f, + &self.buffer, + self.width(), + self.height(), + image::ColorType::Rgb8, + ) + .unwrap(); + } +} + +#[derive(Clone, Debug)] +pub enum ImageHolder<const CHANNELS: usize> { + Borrow(Image<&'static [u8], CHANNELS>), + Own(Image<Vec<u8>, CHANNELS>), +} + +impl<const CHANNELS: usize> ImageHolder<CHANNELS> { + #[must_use] + pub fn own(self) -> Image<Vec<u8>, CHANNELS> { + match self { + Self::Own(x) => x, + Self::Borrow(x) => Image::new(x.width, x.height, x.buffer.to_vec()), + } + } +} + +impl<const CHANNELS: usize> ImageHolder<CHANNELS> { + #[must_use] + #[inline] + pub fn borrow(&self) -> Image<&[u8], CHANNELS> { + match self { + Self::Own(x) => x.as_ref(), + Self::Borrow(x) => x.clone(), + } + } + + #[must_use] + #[inline] + pub fn borrow_mut(&mut self) -> Image<&mut [u8], CHANNELS> { + match self { + Self::Own(x) => Image::new(x.width, x.height, &mut x.buffer), + Self::Borrow(_) => { + *self = Self::from(std::mem::replace(self, Self::from(Image::default())).own()); + self.borrow_mut() + } + } + } +} + +impl<'a> Overlay<&'a ImageHolder<4>> for ImageHolder<4> { + fn overlay_at(&mut self, with: &'a ImageHolder<4>, x: u32, y: u32) -> &mut Self { + self.borrow_mut().overlay_at(with.borrow(), x, y); + self + } +} + +impl ImageUtils for ImageHolder<4> { + fn tint(&mut self, color: (u8, u8, u8)) -> &mut Self { + self.borrow_mut().tint(color); + self + } + type With<'a> = &'a Self; + fn overlay(&mut self, with: &Self) -> &mut Self { + self.borrow_mut().overlay(with.borrow()); + self + } + + fn rotate(&mut self, times: u8) -> &mut Self { + if times == 0 { + return self; + } + // borrow mut may clone, so try to avoid + self.borrow_mut().rotate(times); + self + } + + fn flip_h(&mut self) -> &mut Self { + self.borrow_mut().flip_h(); + self + } + + fn flip_v(&mut self) -> &mut Self { + self.borrow_mut().flip_v(); + self + } + + fn shadow(&mut self) -> &mut Self { + self.borrow_mut().shadow(); + self + } + + fn scale(mut self, to: u32) -> Image<Vec<u8>, 4> { + self.borrow_mut().scale(to) + } +} + +impl<const CHANNELS: usize> From<Image<&'static [u8], CHANNELS>> for ImageHolder<CHANNELS> { + fn from(value: Image<&'static [u8], CHANNELS>) -> Self { + Self::Borrow(value) + } +} + +impl<const CHANNELS: usize> From<Image<Vec<u8>, CHANNELS>> for ImageHolder<CHANNELS> { + fn from(value: Image<Vec<u8>, CHANNELS>) -> Self { + Self::Own(value) + } +} + +#[cfg(test)] +mod tests { + use super::*; + macro_rules! img { + [[$($v:literal),+] [$($v2:literal),+]] => {{ + let from: Image<Vec<u8>, 1> = Image::new( + 2.try_into().unwrap(), + 2.try_into().unwrap(), + vec![$($v,)+ $($v2,)+] + ); + from + }} + } + + #[test] + fn rem_chnl_test() { + let mut img: Image<_, 4> = Image::alloc(2, 1); + unsafe { img.set_pixel(1, 0, [255, 165, 0, 241]) }; + assert_eq!(unsafe { img.pixel(1, 0) }, [255, 165, 0, 241]); + assert_eq!(unsafe { img.pixel(0, 0) }, [0, 0, 0, 0]); + let img = img.remove_channel(); + assert_eq!(unsafe { img.pixel(1, 0) }, [255, 165, 0]); + } + + #[test] + fn rotate_90() { + let mut from = img![ + [00, 01] + [02, 10] + ]; + unsafe { rot_90(&mut from.as_mut()) }; + assert_eq!( + from, + img![ + [02, 00] + [10, 01] + ] + ); + } + + #[test] + fn rotate_180() { + let mut from = img![ + [00, 01] + [02, 10] + ]; + unsafe { rot_180(&mut from.as_mut()) }; + assert_eq!( + from, + img![ + [10, 02] + [01, 00] + ] + ); + } + + #[test] + fn rotate_270() { + let mut from = img![ + [00, 01] + [20, 10] + ]; + unsafe { rot_270(&mut from.as_mut()) }; + assert_eq!( + from, + img![ + [01, 10] + [00, 20] + ] + ); + } + + #[test] + fn flip_vertical() { + let mut from = img![ + [90, 01] + [21, 42] + ]; + unsafe { flip_v(&mut from.as_mut()) }; + assert_eq!( + from, + img![ + [21, 42] + [90, 01] + ] + ) + } + #[test] + fn flip_horizontal() { + let mut from = img![ + [90, 01] + [21, 42] + ]; + unsafe { flip_h(&mut from.as_mut()) }; + assert_eq!( + from, + img![ + [01, 90] + [42, 21] + ] + ) + } +} + +pub fn blend(bg: &mut [u8; 4], fg: [u8; 4]) { + if fg[3] == 0 { + return; + } + if fg[3] == 255 { + *bg = fg; + return; + } + let bg_a = bg[3] as f32 / 255.0; + let fg_a = fg[3] as f32 / 255.0; + let a = bg_a + fg_a - bg_a * fg_a; + if a == 0.0 { + return; + }; + *bg = [ + (255.0 + * ((((fg[0] as f32 / 255.0) * fg_a) + ((bg[0] as f32 / 255.0) * bg_a) * (1.0 - fg_a)) + / a)) as u8, + (255.0 + * ((((fg[1] as f32 / 255.0) * fg_a) + ((bg[1] as f32 / 255.0) * bg_a) * (1.0 - fg_a)) + / a)) as u8, + (255.0 + * ((((fg[2] as f32 / 255.0) * fg_a) + ((bg[2] as f32 / 255.0) * bg_a) * (1.0 - fg_a)) + / a)) as u8, + (255.0 * a) as u8, + ] +} diff --git a/src/utils/lazy.rs b/src/utils/lazy.rs deleted file mode 100644 index 4d30aa2..0000000 --- a/src/utils/lazy.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! [`LazyLock`](std::sync::LazyLock) copy -use std::cell::UnsafeCell; -use std::mem::ManuallyDrop; -use std::panic::{RefUnwindSafe, UnwindSafe}; - -union Data<T, F> { - value: ManuallyDrop<T>, - f: ManuallyDrop<F>, -} - -pub struct Lock<T, F = fn() -> T> { - data: UnsafeCell<Data<T, F>>, -} - -impl<T, F: FnOnce() -> T> Lock<T, F> { - #[inline] - pub const fn new(f: F) -> Lock<T, F> { - Lock { - data: UnsafeCell::new(Data { - f: ManuallyDrop::new(f), - }), - } - } - - #[inline] - /// SAFETY: CALL ONLY ONCE! NOT CHECKED - pub unsafe fn load(this: &Lock<T, F>) { - let data = &mut *this.data.get(); - let f = ManuallyDrop::take(&mut data.f); - let value = f(); - data.value = ManuallyDrop::new(value); - } -} - -impl<T, F> Lock<T, F> { - #[inline] - /// SAFETY: CALL [load] FIRST! - pub unsafe fn get(&self) -> &T { - &(*self.data.get()).value - } -} - -unsafe impl<T: Sync + Send, F: Send> Sync for Lock<T, F> {} -impl<T: RefUnwindSafe + UnwindSafe, F: UnwindSafe> RefUnwindSafe for Lock<T, F> {} -impl<T: UnwindSafe, F: UnwindSafe> UnwindSafe for Lock<T, F> {} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 7314caa..6e781fe 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,5 +1,3 @@ pub mod array; pub mod image; -pub use self::image::{ImageUtils, Overlay, RepeatNew as Repeat}; -pub mod lazy; -pub use lazy::Lock; +pub use self::image::{Image, ImageHolder, ImageUtils, Overlay, RepeatNew as Repeat}; |