diff --git a/Cargo.lock b/Cargo.lock index 096e6ed..2767843 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3010,7 +3010,6 @@ name = "dilate" version = "0.1.0" dependencies = [ "clap 4.5.23", - "graphics", "image 0.1.0", "oxipng", "rayon", @@ -5211,7 +5210,9 @@ name = "image" version = "0.1.0" dependencies = [ "anyhow", + "image 0.25.5", "png", + "rayon", ] [[package]] diff --git a/examples/wasm-modules/graphics/src/lib.rs b/examples/wasm-modules/graphics/src/lib.rs index 0438b6c..93e42a5 100644 --- a/examples/wasm-modules/graphics/src/lib.rs +++ b/examples/wasm-modules/graphics/src/lib.rs @@ -6,9 +6,7 @@ pub mod handles; pub mod graphics_mt { pub use ::graphics::graphics_mt::*; } -pub mod image { - pub use ::graphics::image::*; -} + pub mod quad_container { pub use ::graphics::quad_container::*; } diff --git a/game/client-containers/src/container.rs b/game/client-containers/src/container.rs index c627c3f..2e0fd7b 100644 --- a/game/client-containers/src/container.rs +++ b/game/client-containers/src/container.rs @@ -21,7 +21,7 @@ use either::Either; use game_interface::types::resource_key::ResourceKey; use graphics::{ graphics::graphics::Graphics, graphics_mt::GraphicsMultiThreaded, - handles::texture::texture::GraphicsTextureHandle, image::texture_2d_to_3d, + handles::texture::texture::GraphicsTextureHandle, }; use graphics_types::{ commands::TexFlags, @@ -29,7 +29,10 @@ use graphics_types::{ }; use hashlink::LinkedHashMap; use hiarc::Hiarc; -use image::png::{is_png_image_valid, load_png_image, PngResultPersistent}; +use image::{ + png::{is_png_image_valid, load_png_image_as_rgba, PngResultPersistent, PngValidatorOptions}, + utils::texture_2d_to_3d, +}; use log::info; use sound::{ ogg_vorbis::verify_ogg_vorbis, scene_object::SceneObject, sound::SoundManager, @@ -415,10 +418,20 @@ where } /// Verifies a resource, prints warnings on error - fn verify_resource(file_ty: &str, file_name: &str, file: &[u8]) -> bool { + fn verify_resource(file_ty: &str, file_name: &str, file: &[u8], allow_hq_assets: bool) -> bool { match file_ty { "png" => { - if let Err(err) = is_png_image_valid(file, Default::default()) { + if let Err(err) = is_png_image_valid( + file, + if allow_hq_assets { + PngValidatorOptions { + max_width: 4096.try_into().unwrap(), + max_height: 4096.try_into().unwrap(), + } + } else { + Default::default() + }, + ) { log::warn!( "downloaded image resource (png) {}\ is not a valid png file: {}", @@ -523,6 +536,8 @@ where game_server_http: Option, resource_http_download: HttpIndexAndUrl, ) -> anyhow::Result { + let allow_hq_assets = false; + let read_tar = |file: &[u8]| { let mut file = tar::Archive::new(std::io::Cursor::new(file)); match file.entries() { @@ -637,6 +652,7 @@ where name.extension().and_then(|s| s.to_str()).unwrap_or(""), name.file_stem().and_then(|s| s.to_str()).unwrap_or(""), file, + allow_hq_assets, ) { verified = false; break; @@ -663,7 +679,7 @@ where }) { let _g = http_download_tasks.acquire().await?; if let Ok(file) = http.download_binary(game_server_http, &hash).await { - if Self::verify_resource("png", &name, &file) { + if Self::verify_resource("png", &name, &file, allow_hq_assets) { save_to_disk(&name, &file).await; files = Some(ContainerLoadedItem::SingleFile(file.to_vec())); } @@ -799,6 +815,7 @@ where name.extension().and_then(|s| s.to_str()).unwrap_or(""), name.file_stem().and_then(|s| s.to_str()).unwrap_or(""), file, + allow_hq_assets, ) { verified = false; break; @@ -816,7 +833,7 @@ where false } } else if ty == "png" { - if Self::verify_resource("png", &name, &file) { + if Self::verify_resource("png", &name, &file, allow_hq_assets) { files = Some(ContainerLoadedItem::SingleFile(file.to_vec())); true } else { @@ -1372,7 +1389,7 @@ pub fn load_file_part_as_png_ex( allow_default, )?; let mut img_data = Vec::::new(); - let part_img = load_png_image(file.data, |width, height, bytes_per_pixel| { + let part_img = load_png_image_as_rgba(file.data, |width, height, bytes_per_pixel| { img_data = vec![0; width * height * bytes_per_pixel]; &mut img_data })?; @@ -1548,7 +1565,7 @@ pub fn load_file_part_as_png_and_convert_3d( true, )?; let mut img_data = Vec::::new(); - let part_img = load_png_image(file.data, |width, height, bytes_per_pixel| { + let part_img = load_png_image_as_rgba(file.data, |width, height, bytes_per_pixel| { img_data = vec![0; width * height * bytes_per_pixel]; &mut img_data })?; diff --git a/game/client-containers/src/emoticons.rs b/game/client-containers/src/emoticons.rs index 66342c5..3d73c56 100644 --- a/game/client-containers/src/emoticons.rs +++ b/game/client-containers/src/emoticons.rs @@ -34,7 +34,7 @@ impl LoadEmoticons { fn load_full(files: &mut FxHashMap>, file: Vec) -> anyhow::Result<()> { let mut mem: Vec = Default::default(); let img: image::png::PngResult<'_> = - image::png::load_png_image(&file, |width, height, bytes_per_pixel| { + image::png::load_png_image_as_rgba(&file, |width, height, bytes_per_pixel| { mem.resize(width * height * bytes_per_pixel, Default::default()); &mut mem })?; diff --git a/game/client-containers/src/hud.rs b/game/client-containers/src/hud.rs index c8445e7..aa8a6eb 100644 --- a/game/client-containers/src/hud.rs +++ b/game/client-containers/src/hud.rs @@ -95,7 +95,7 @@ impl LoadHud { fn load_full(files: &mut FxHashMap>, file: Vec) -> anyhow::Result<()> { let mut mem: Vec = Default::default(); let img: image::png::PngResult<'_> = - image::png::load_png_image(&file, |width, height, bytes_per_pixel| { + image::png::load_png_image_as_rgba(&file, |width, height, bytes_per_pixel| { mem.resize(width * height * bytes_per_pixel, Default::default()); &mut mem })?; diff --git a/game/client-containers/src/particles.rs b/game/client-containers/src/particles.rs index e3519c5..53a8144 100644 --- a/game/client-containers/src/particles.rs +++ b/game/client-containers/src/particles.rs @@ -96,7 +96,7 @@ impl LoadParticle { fn load_full(files: &mut FxHashMap>, file: Vec) -> anyhow::Result<()> { let mut mem: Vec = Default::default(); let img: image::png::PngResult<'_> = - image::png::load_png_image(&file, |width, height, bytes_per_pixel| { + image::png::load_png_image_as_rgba(&file, |width, height, bytes_per_pixel| { mem.resize(width * height * bytes_per_pixel, Default::default()); &mut mem })?; diff --git a/game/client-containers/src/skins.rs b/game/client-containers/src/skins.rs index d57b7ed..67088cf 100644 --- a/game/client-containers/src/skins.rs +++ b/game/client-containers/src/skins.rs @@ -278,7 +278,7 @@ impl LoadSkinTexturesData { ) -> anyhow::Result<()> { let mut mem: Vec = Default::default(); let img: image::png::PngResult<'_> = - image::png::load_png_image(&file, |width, height, bytes_per_pixel| { + image::png::load_png_image_as_rgba(&file, |width, height, bytes_per_pixel| { mem.resize(width * height * bytes_per_pixel, Default::default()); &mut mem })?; diff --git a/game/client-render-base/src/map/render_map_base.rs b/game/client-render-base/src/map/render_map_base.rs index 9ab25c9..9dff3f4 100644 --- a/game/client-render-base/src/map/render_map_base.rs +++ b/game/client-render-base/src/map/render_map_base.rs @@ -29,10 +29,12 @@ use graphics::{ stream::stream::GraphicsStreamHandle, texture::texture::{GraphicsTextureHandle, TextureContainer, TextureContainer2dArray}, }, - image::{highest_bit, resize, texture_2d_to_3d}, }; use graphics_types::{commands::TexFlags, types::GraphicsMemoryAllocationType}; -use image::png::{is_png_image_valid, load_png_image}; +use image::{ + png::{is_png_image_valid, load_png_image_as_rgba, resize_rgba, PngValidatorOptions}, + utils::{highest_bit, texture_2d_to_3d}, +}; use map::map::Map; use math::math::vector::vec2; use rayon::iter::{IntoParallelIterator, ParallelIterator}; @@ -195,6 +197,7 @@ impl RenderMapLoading { file_ty.as_str(), file_name.as_str(), &file, + load_hq_assets, )?; let file_path: &Path = read_file_path.as_ref(); if let Some(dir) = file_path.parent() { @@ -254,19 +257,23 @@ impl RenderMapLoading { || convert_height == 0 || (convert_height % 16) != 0 { - // TODO sys.log("image").msg("3D/2D array texture was resized"); let new_width = std::cmp::max(highest_bit(convert_width as u32) as usize, 16); let new_height = std::cmp::max(highest_bit(convert_height as u32) as usize, 16); - conv_data = resize( - &runtime_tp, - upload_data, + conv_data = resize_rgba( + upload_data.into(), + convert_width as u32, + convert_height as u32, + new_width as u32, + new_height as u32, + ); + log::warn!( + "3D/2D array texture had to be resized, {}x{} to {}x{}", convert_width, convert_height, new_width, - new_height, - image_color_channels, + new_height ); convert_width = new_width; @@ -314,7 +321,7 @@ impl RenderMapLoading { .into_par_iter() .map(|(hash, file)| { let mut img_data: Vec = Default::default(); - let img = load_png_image( + let img = load_png_image_as_rgba( &file, |width, height, color_channel_count| { img_data.resize( @@ -456,10 +463,25 @@ impl RenderMapLoading { } } - fn verify_resource(file_ty: &str, file_name: &str, file: &[u8]) -> anyhow::Result<()> { + fn verify_resource( + file_ty: &str, + file_name: &str, + file: &[u8], + allow_hq_assets: bool, + ) -> anyhow::Result<()> { match file_ty { "png" => { - if let Err(err) = is_png_image_valid(file, Default::default()) { + if let Err(err) = is_png_image_valid( + file, + if allow_hq_assets { + PngValidatorOptions { + max_width: 4096.try_into().unwrap(), + max_height: 4096.try_into().unwrap(), + } + } else { + Default::default() + }, + ) { return Err(anyhow!( "downloaded image resource (png) {}\ is not a valid png file: {}", diff --git a/game/client-ui/src/main_menu/leftbar/main_frame.rs b/game/client-ui/src/main_menu/leftbar/main_frame.rs index b2e644c..372ce1f 100644 --- a/game/client-ui/src/main_menu/leftbar/main_frame.rs +++ b/game/client-ui/src/main_menu/leftbar/main_frame.rs @@ -9,7 +9,7 @@ use graphics::handles::{ canvas::canvas::GraphicsCanvasHandle, stream::stream::GraphicsStreamHandle, }; use graphics_types::{commands::TexFlags, types::GraphicsMemoryAllocationType}; -use image::png::load_png_image; +use image::png::load_png_image_as_rgba; use math::math::vector::vec2; use ui_base::{style::bg_frame_color, types::UiState}; @@ -39,7 +39,7 @@ fn update_communities(user_data: &mut UserData) { let icon = http.download_binary_secure(url).await?.to_vec(); let mut img_mem = None; - let img = load_png_image(&icon, |width, height, _| { + let img = load_png_image_as_rgba(&icon, |width, height, _| { img_mem = Some(graphics_mt.mem_alloc( GraphicsMemoryAllocationType::TextureRgbaU8 { width: width.try_into().unwrap(), diff --git a/game/editor/src/action_logic.rs b/game/editor/src/action_logic.rs index 582e567..ed4d954 100644 --- a/game/editor/src/action_logic.rs +++ b/game/editor/src/action_logic.rs @@ -9,10 +9,9 @@ use graphics::{ buffer_object::buffer_object::GraphicsBufferObjectHandle, texture::texture::GraphicsTextureHandle, }, - image::texture_2d_to_3d, }; use graphics_types::{commands::TexFlags, types::GraphicsMemoryAllocationType}; -use image::png::load_png_image; +use image::{png::load_png_image_as_rgba, utils::texture_2d_to_3d}; use map::{ map::groups::layers::{ design::{ @@ -628,7 +627,7 @@ pub fn do_action( act.base.index ); let mut img_mem = None; - let _ = load_png_image(&act.base.file, |width, height, _| { + let _ = load_png_image_as_rgba(&act.base.file, |width, height, _| { img_mem = Some(backend_handle.mem_alloc( GraphicsMemoryAllocationType::TextureRgbaU8 { width: width.try_into().unwrap(), @@ -658,7 +657,7 @@ pub fn do_action( act.base.index ); let mut png = Vec::new(); - let img = load_png_image(&act.base.file, |width, height, _| { + let img = load_png_image_as_rgba(&act.base.file, |width, height, _| { png = vec![0; width * height * 4]; &mut png })?; diff --git a/game/editor/src/editor.rs b/game/editor/src/editor.rs index 1d4f9f2..df7c339 100644 --- a/game/editor/src/editor.rs +++ b/game/editor/src/editor.rs @@ -41,11 +41,10 @@ use graphics::{ stream::stream::GraphicsStreamHandle, texture::texture::{GraphicsTextureHandle, TextureContainer, TextureContainer2dArray}, }, - image::texture_2d_to_3d, }; use graphics_types::{commands::TexFlags, types::GraphicsMemoryAllocationType}; use hiarc::HiarcTrait; -use image::png::load_png_image; +use image::{png::load_png_image_as_rgba, utils::texture_2d_to_3d}; use map::{ map::{ animations::{AnimBase, AnimPointCurveType, AnimPointPos}, @@ -504,7 +503,7 @@ impl Editor { let file = resources.get(&meta.blake3_hash).unwrap(); let mut mem = None; - let _ = load_png_image(file, |width, height, _| { + let _ = load_png_image_as_rgba(file, |width, height, _| { mem = Some(graphics_mt.mem_alloc( GraphicsMemoryAllocationType::TextureRgbaU8 { width: width.try_into().unwrap(), @@ -531,7 +530,7 @@ impl Editor { let file = resources.get(&meta.blake3_hash).unwrap(); let mut png = Vec::new(); - let img = load_png_image( + let img = load_png_image_as_rgba( file, |width, height, color_chanel_count| { png.resize( diff --git a/game/editor/src/tools/tile_layer/auto_mapper.rs b/game/editor/src/tools/tile_layer/auto_mapper.rs index 5569703..98afd56 100644 --- a/game/editor/src/tools/tile_layer/auto_mapper.rs +++ b/game/editor/src/tools/tile_layer/auto_mapper.rs @@ -5,7 +5,7 @@ use base::hash::{fmt_hash, Hash}; use base_io::{io::IoFileSys, runtime::IoRuntimeTask}; use egui::{vec2, ColorImage, Rect, TextBuffer, TextureHandle}; use egui_file_dialog::FileDialog; -use graphics::image::texture_2d_to_3d; +use image::utils::texture_2d_to_3d; use map::map::groups::layers::tiles::{TileBase, TileFlags}; use math::math::vector::{ivec2, vec2_base}; use rand::SeedableRng; @@ -393,50 +393,50 @@ impl TileLayerAutoMapper { } else { let image = load_task.image.unwrap(); let mut img_mem = Vec::new(); - if let Some(tile_textures) = - image::png::load_png_image(&image, |w, h, color_channel_count| { + if let Some(tile_textures) = image::png::load_png_image_as_rgba( + &image, + |w, h, color_channel_count| { img_mem.resize(w * h * color_channel_count, 0); img_mem.as_mut() - }) - .ok() - .and_then(|img| { - let mut tex_3d = - vec![0; img.width as usize * img.height as usize * 4]; - let mut image_3d_width = 0; - let mut image_3d_height = 0; - if !texture_2d_to_3d( - &self.tp, - img.data, - img.width as usize, - img.height as usize, - 4, - 16, - 16, - tex_3d.as_mut_slice(), - &mut image_3d_width, - &mut image_3d_height, - ) { - None - } else { - let tile_textures: Vec<_> = tex_3d - .chunks_exact(image_3d_width * image_3d_height * 4) - .map(|chunk| { - load_task.ctx.load_texture( - name.clone(), - egui::ImageData::Color(Arc::new( - ColorImage::from_rgba_unmultiplied( - [image_3d_width, image_3d_height], - chunk, - ), - )), - Default::default(), - ) - }) - .collect::<_>(); - Some(tile_textures) - } - }) - { + }, + ) + .ok() + .and_then(|img| { + let mut tex_3d = vec![0; img.width as usize * img.height as usize * 4]; + let mut image_3d_width = 0; + let mut image_3d_height = 0; + if !texture_2d_to_3d( + &self.tp, + img.data, + img.width as usize, + img.height as usize, + 4, + 16, + 16, + tex_3d.as_mut_slice(), + &mut image_3d_width, + &mut image_3d_height, + ) { + None + } else { + let tile_textures: Vec<_> = tex_3d + .chunks_exact(image_3d_width * image_3d_height * 4) + .map(|chunk| { + load_task.ctx.load_texture( + name.clone(), + egui::ImageData::Color(Arc::new( + ColorImage::from_rgba_unmultiplied( + [image_3d_width, image_3d_height], + chunk, + ), + )), + Default::default(), + ) + }) + .collect::<_>(); + Some(tile_textures) + } + }) { if let Some(Ok(mut rule_base)) = load_task.rule.map(|rule| { serde_json::from_str::( String::from_utf8_lossy(&rule).as_str(), diff --git a/game/game-base/src/datafile.rs b/game/game-base/src/datafile.rs index 632a420..664cb70 100644 --- a/game/game-base/src/datafile.rs +++ b/game/game-base/src/datafile.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Cow, collections::HashMap, ffi::{CStr, CString}, io::{Read, Write}, @@ -14,7 +15,10 @@ use base::{ use flate2::{read::ZlibDecoder, write::ZlibEncoder, Compression}; use hashlink::LinkedHashMap; use hiarc::Hiarc; -use image::png::{load_png_image, save_png_image}; +use image::{ + png::{load_png_image_as_rgba, resize_rgba, save_png_image, PngValidatorOptions}, + utils, +}; use math::math::{ f2fx, fx2f, vector::{ffixed, fvec2, fvec3, ivec2, ivec4, nffixed, nfvec4, uffixed, ufvec2, vec1_base}, @@ -1442,7 +1446,13 @@ impl CDatafileWrapper { } /// images are external images - pub fn into_map(self, images: &[Vec]) -> anyhow::Result { + pub fn into_map( + self, + thread_pool: &rayon::ThreadPool, + images: &[Vec], + png_validation: PngValidatorOptions, + dilate: bool, + ) -> anyhow::Result { let mut image_resources: HashMap = Default::default(); let mut sound_resources: HashMap = Default::default(); @@ -1618,12 +1628,77 @@ impl CDatafileWrapper { } } + fn check_size_and_dilate<'a>( + thread_pool: &rayon::ThreadPool, + img: Cow<'a, [u8]>, + mut width: u32, + mut height: u32, + png_validation: PngValidatorOptions, + dilate: bool, + in_tile_layer_only: bool, + ) -> (Cow<'a, [u8]>, u32, u32) { + let mut res = img; + if width > png_validation.max_width.get() + || height > png_validation.max_height.get() + { + let width_ratio = + (width as f64 / png_validation.max_width.get() as f64).clamp(1.0, f64::MAX); + let height_ratio = (height as f64 / png_validation.max_height.get() as f64) + .clamp(1.0, f64::MAX); + + let ratio = width_ratio.max(height_ratio); + + let new_width = ((width as f64 / ratio) as u32).clamp(1, u32::MAX); + let new_height = ((height as f64 / ratio) as u32).clamp(1, u32::MAX); + + res = resize_rgba(res, width, height, new_width, new_height).into(); + + width = new_width; + height = new_height; + } + if dilate { + if in_tile_layer_only && width % 16 == 0 && height % 16 == 0 { + let sub_width = width / 16; + let sub_height = height / 16; + for y in 0..16 { + for x in 0..16 { + utils::dilate_image_sub( + thread_pool, + res.to_mut(), + width as usize, + height as usize, + 4, + x * sub_width as usize, + y * sub_height as usize, + sub_width as usize, + sub_height as usize, + ); + } + } + } else { + utils::dilate_image( + thread_pool, + res.to_mut(), + width as usize, + height as usize, + 4, + ); + } + } + (res, width, height) + } + let (hash, png_data) = if let Some(internal_img) = image.internal_img { - let img = save_png_image( - &internal_img, + let (internal_img, width, height) = check_size_and_dilate( + thread_pool, + internal_img.into(), image.item_data.width as u32, image.item_data.height as u32, - )?; + png_validation, + dilate, + in_tile_layer && !in_quad_layer, + ); + let img = save_png_image(&internal_img, width, height)?; (Map::generate_hash_for(&img), img) } else { let img = images @@ -1634,11 +1709,20 @@ impl CDatafileWrapper { }) .ok_or_else(|| anyhow!("image with name {} was not loaded", image.img_name))?; let mut img_data: Vec = Vec::new(); - let img = load_png_image(img, |width, height, color_channel_count| { + let img = load_png_image_as_rgba(img, |width, height, color_channel_count| { img_data.resize(width * height * color_channel_count, Default::default()); &mut img_data })?; - let img = save_png_image(img.data, img.width as u32, img.height as u32)?; + let (img_data, width, height) = check_size_and_dilate( + thread_pool, + img.data.into(), + img.width as u32, + img.height as u32, + png_validation, + dilate, + in_tile_layer && !in_quad_layer, + ); + let img = save_png_image(&img_data, width, height)?; (Map::generate_hash_for(&img), img) }; let res_ref = MapResourceRef { @@ -2337,7 +2421,7 @@ impl CDatafileWrapper { .push(data_items.len() as i32); let mut img_data: Vec = Vec::new(); - let img = load_png_image( + let img = load_png_image_as_rgba( images.get(index).unwrap_or_else(|| { panic!("did not find image with name: {}", image.name.as_str()) }), @@ -2395,7 +2479,7 @@ impl CDatafileWrapper { .push(data_items.len() as i32); let mut img_data: Vec = Vec::new(); - let img = load_png_image( + let img = load_png_image_as_rgba( image_arrays.get(index).unwrap_or_else(|| { panic!("did not find image with name: {}", image.name.as_str()) }), diff --git a/game/map-convert-lib/src/legacy_to_new.rs b/game/map-convert-lib/src/legacy_to_new.rs index 342365f..60a61b5 100644 --- a/game/map-convert-lib/src/legacy_to_new.rs +++ b/game/map-convert-lib/src/legacy_to_new.rs @@ -114,7 +114,7 @@ pub async fn legacy_to_new_from_buf_async( let benchmark = Benchmark::new(true); benchmark.bench("encoding images to png"); - let mut map_output = map_legacy.into_map(&images)?; + let mut map_output = map_legacy.into_map(thread_pool, &images, Default::default(), true)?; benchmark.bench("converting map"); if optimize { @@ -143,7 +143,7 @@ pub async fn legacy_to_new_from_buf_async( }); anyhow::Ok(()) - })? + })?; } thread_pool.install(|| { diff --git a/lib/graphics/src/image.rs b/lib/graphics/src/image.rs deleted file mode 100644 index 6548914..0000000 --- a/lib/graphics/src/image.rs +++ /dev/null @@ -1,571 +0,0 @@ -use std::sync::Arc; - -use rayon::{ - prelude::{IndexedParallelIterator, ParallelIterator}, - slice::{ParallelSlice, ParallelSliceMut}, -}; - -const TW_DILATE_ALPHA_THRESHOLD: u8 = 10; - -pub fn dilate( - thread_pool: &Arc, - w: usize, - h: usize, - bpp: usize, - src_buff: &[u8], - dest_buff: &mut [u8], - alpha_threshold: u8, -) { - let dirs_x = [0, -1, 1, 0]; - let dirs_y = [-1, 0, 0, 1]; - - let alpha_comp_index = bpp - 1; - - thread_pool.install(|| { - dest_buff - .par_chunks_exact_mut(bpp) - .enumerate() - .take(w * h) - .for_each(|(i, dst)| { - let x = i % w; - let y = i / w; - - let m = y * w * bpp + x * bpp; - dst.copy_from_slice(&src_buff[m..(bpp + m)]); - if src_buff[m + alpha_comp_index] > alpha_threshold { - return; - } - - // clear pixels that are considered transparent - // this allows the image to always be black where no dilate is needed - dst[0..(bpp - 1)].fill(0); - - let mut sums_of_opaque = [0, 0, 0]; - let mut counter = 0; - for c in 0..4 { - let ix = (x as i64 + dirs_x[c]).clamp(0, w as i64 - 1) as usize; - let iy = (y as i64 + dirs_y[c]).clamp(0, h as i64 - 1) as usize; - let k = iy * w * bpp + ix * bpp; - if src_buff[k + alpha_comp_index] > alpha_threshold { - for p in 0..bpp - 1 { - // Seems safe for BPP = 3, 4 which we use. - sums_of_opaque[p] += src_buff[k + p] as u32; - } - counter += 1; - break; - } - } - - if counter > 0 { - for i in 0..bpp - 1 { - sums_of_opaque[i] /= counter; - dst[i] = sums_of_opaque[i] as u8; - } - - dst[alpha_comp_index] = 255; - } - }); - }); -} - -fn copy_color_values( - thread_pool: &Arc, - w: usize, - h: usize, - bpp: usize, - src_buffer: &[u8], - dest_buffer: &mut [u8], -) { - thread_pool.install(|| { - dest_buffer - .par_chunks_exact_mut(bpp) - .take(w * h) - .zip(src_buffer.par_chunks_exact(bpp).take(w * h)) - .for_each(|(dst, src)| { - if dst[bpp - 1] == 0 { - dst[0..bpp - 1].copy_from_slice(&src[0..bpp - 1]); - } - }); - }); -} - -pub fn dilate_image_sub( - thread_pool: &Arc, - img_buff: &mut [u8], - w: usize, - _h: usize, - bpp: usize, - x: usize, - y: usize, - sw: usize, - sh: usize, -) { - let [mut buffer_data1, mut buffer_data2] = [ - vec![0; sw * sh * std::mem::size_of::() * bpp], - vec![0; sw * sh * std::mem::size_of::() * bpp], - ]; - - let mut buffer_data_original = vec![0; sw * sh * std::mem::size_of::() * bpp]; - - let pixel_buffer_data = img_buff; - - thread_pool.install(|| { - // fill buffer_data_original completely - buffer_data_original - .chunks_exact_mut(sw * bpp) - .enumerate() - .for_each(|(yh, chunk)| { - let src_img_offset = ((y + yh) * w * bpp) + (x * bpp); - - chunk.copy_from_slice( - &pixel_buffer_data[src_img_offset..src_img_offset + chunk.len()], - ); - }); - }); - - dilate( - thread_pool, - sw, - sh, - bpp, - buffer_data_original.as_slice(), - buffer_data1.as_mut_slice(), - TW_DILATE_ALPHA_THRESHOLD, - ); - - for _i in 0..5 { - dilate( - thread_pool, - sw, - sh, - bpp, - buffer_data1.as_slice(), - buffer_data2.as_mut_slice(), - TW_DILATE_ALPHA_THRESHOLD, - ); - dilate( - thread_pool, - sw, - sh, - bpp, - buffer_data2.as_slice(), - buffer_data1.as_mut_slice(), - TW_DILATE_ALPHA_THRESHOLD, - ); - } - - copy_color_values( - thread_pool, - sw, - sh, - bpp, - buffer_data1.as_slice(), - buffer_data_original.as_mut_slice(), - ); - - thread_pool.install(|| { - pixel_buffer_data - .chunks_exact_mut(w * bpp) - .skip(y) - .take(sh) - .enumerate() - .for_each(|(yh, chunk)| { - let src_img_offset = x * bpp; - let dst_img_offset = yh * sw * bpp; - let copy_size = sw * bpp; - chunk[src_img_offset..src_img_offset + copy_size].copy_from_slice( - &buffer_data_original[dst_img_offset..dst_img_offset + copy_size], - ); - }); - }); -} - -pub fn dilate_image( - thread_pool: &Arc, - img_buff: &mut [u8], - w: usize, - h: usize, - bpp: usize, -) { - dilate_image_sub(thread_pool, img_buff, w, h, bpp, 0, 0, w, h); -} - -fn cubic_hermite(a: f32, b: f32, c: f32, d: f32, t: f32) -> f32 { - let a = -a / 2.0 + (3.0 * b) / 2.0 - (3.0 * c) / 2.0 + d / 2.0; - let b = a - (5.0 * b) / 2.0 + 2.0 * c - d / 2.0; - let c = -a / 2.0 + c / 2.0; - let d = b; - - (a * t * t * t) + (b * t * t) + (c * t) + d -} - -fn get_pixel_clamped( - src_img_buff: &[u8], - x_param: i32, - y_param: i32, - w: usize, - h: usize, - bpp: usize, - tmp_buff: &mut [u8], -) { - let x = x_param.clamp(0, w as i32 - 1); - let y = y_param.clamp(0, h as i32 - 1); - - for i in 0..bpp { - tmp_buff[i] = src_img_buff[x as usize * bpp + (w * bpp * y as usize) + i]; - } -} - -fn sample_bicubic( - src_image_buff: &[u8], - u: f32, - v: f32, - w: usize, - h: usize, - bpp: usize, - samples: &mut [u8], -) { - let x = (u * w as f32) - 0.5; - let x_int = x as i32; - let x_fract = x - (x).floor(); - - let y = (v * h as f32) - 0.5; - let y_int = y as i32; - let y_fract = y - (y).floor(); - - let mut pxs_00: [u8; 4] = Default::default(); - let mut pxs_10: [u8; 4] = Default::default(); - let mut pxs_20: [u8; 4] = Default::default(); - let mut pxs_30: [u8; 4] = Default::default(); - - let mut pxs_01: [u8; 4] = Default::default(); - let mut pxs_11: [u8; 4] = Default::default(); - let mut pxs_21: [u8; 4] = Default::default(); - let mut pxs_31: [u8; 4] = Default::default(); - - let mut pxs_02: [u8; 4] = Default::default(); - let mut pxs_12: [u8; 4] = Default::default(); - let mut pxs_22: [u8; 4] = Default::default(); - let mut pxs_32: [u8; 4] = Default::default(); - - let mut pxs_03: [u8; 4] = Default::default(); - let mut pxs_13: [u8; 4] = Default::default(); - let mut pxs_23: [u8; 4] = Default::default(); - let mut pxs_33: [u8; 4] = Default::default(); - - get_pixel_clamped( - src_image_buff, - x_int - 1, - y_int - 1, - w, - h, - bpp, - pxs_00.as_mut_slice(), - ); - get_pixel_clamped( - src_image_buff, - x_int, - y_int - 1, - w, - h, - bpp, - pxs_10.as_mut_slice(), - ); - get_pixel_clamped( - src_image_buff, - x_int + 1, - y_int - 1, - w, - h, - bpp, - pxs_20.as_mut_slice(), - ); - get_pixel_clamped( - src_image_buff, - x_int + 2, - y_int - 1, - w, - h, - bpp, - pxs_30.as_mut_slice(), - ); - - get_pixel_clamped( - src_image_buff, - x_int - 1, - y_int, - w, - h, - bpp, - pxs_01.as_mut_slice(), - ); - get_pixel_clamped( - src_image_buff, - x_int, - y_int, - w, - h, - bpp, - pxs_11.as_mut_slice(), - ); - get_pixel_clamped( - src_image_buff, - x_int + 1, - y_int, - w, - h, - bpp, - pxs_21.as_mut_slice(), - ); - get_pixel_clamped( - src_image_buff, - x_int + 2, - y_int, - w, - h, - bpp, - pxs_31.as_mut_slice(), - ); - - get_pixel_clamped( - src_image_buff, - x_int - 1, - y_int + 1, - w, - h, - bpp, - pxs_02.as_mut_slice(), - ); - get_pixel_clamped( - src_image_buff, - x_int, - y_int + 1, - w, - h, - bpp, - pxs_12.as_mut_slice(), - ); - get_pixel_clamped( - src_image_buff, - x_int + 1, - y_int + 1, - w, - h, - bpp, - pxs_22.as_mut_slice(), - ); - get_pixel_clamped( - src_image_buff, - x_int + 2, - y_int + 1, - w, - h, - bpp, - pxs_32.as_mut_slice(), - ); - - get_pixel_clamped( - src_image_buff, - x_int - 1, - y_int + 2, - w, - h, - bpp, - pxs_03.as_mut_slice(), - ); - get_pixel_clamped( - src_image_buff, - x_int, - y_int + 2, - w, - h, - bpp, - pxs_13.as_mut_slice(), - ); - get_pixel_clamped( - src_image_buff, - x_int + 1, - y_int + 2, - w, - h, - bpp, - pxs_23.as_mut_slice(), - ); - get_pixel_clamped( - src_image_buff, - x_int + 2, - y_int + 2, - w, - h, - bpp, - pxs_33.as_mut_slice(), - ); - - for i in 0..bpp { - let column_0 = cubic_hermite( - pxs_00[i] as f32, - pxs_10[i] as f32, - pxs_20[i] as f32, - pxs_30[i] as f32, - x_fract, - ); - let column_1 = cubic_hermite( - pxs_01[i] as f32, - pxs_11[i] as f32, - pxs_21[i] as f32, - pxs_31[i] as f32, - x_fract, - ); - let column_2 = cubic_hermite( - pxs_02[i] as f32, - pxs_12[i] as f32, - pxs_22[i] as f32, - pxs_32[i] as f32, - x_fract, - ); - let column_3 = cubic_hermite( - pxs_03[i] as f32, - pxs_13[i] as f32, - pxs_23[i] as f32, - pxs_33[i] as f32, - x_fract, - ); - - let mut value = cubic_hermite(column_0, column_1, column_2, column_3, y_fract); - - value = value.clamp(0.0, 255.0); - - samples[i] = value as u8; - } -} - -fn resize_image_inner( - thread_pool: &Arc, - src_image_buff: &[u8], - sw: usize, - sh: usize, - dst_image_buff: &mut [u8], - w: usize, - h: usize, - bpp: usize, -) { - thread_pool.install(|| { - dst_image_buff - .par_chunks_exact_mut(w * bpp) - .enumerate() - .for_each(|(y, write_chunk)| { - let v = y as f32 / (h - 1) as f32; - let mut samples: [u8; 4] = Default::default(); - - for x in 0..w as i32 { - let u = x as f32 / (w - 1) as f32; - sample_bicubic(src_image_buff, u, v, sw, sh, bpp, samples.as_mut_slice()); - - for i in 0..bpp { - write_chunk[x as usize * bpp + i] = samples[i]; - } - } - }); - }); -} - -pub fn resize_image( - thread_pool: &Arc, - img_data_buff: &[u8], - width: usize, - height: usize, - new_width: usize, - new_height: usize, - bpp: usize, -) -> Vec { - let mut img_data = Vec::::new(); - img_data.resize(new_width * new_height * bpp, Default::default()); - - resize_image_inner( - thread_pool, - img_data_buff, - width, - height, - &mut img_data, - new_width, - new_height, - bpp, - ); - - img_data -} - -pub fn resize( - thread_pool: &Arc, - data_buff: &[u8], - width: usize, - height: usize, - new_width: usize, - new_height: usize, - bpp: usize, -) -> Vec { - resize_image( - thread_pool, - data_buff, - width, - height, - new_width, - new_height, - bpp, - ) -} - -pub fn texture_2d_to_3d( - thread_pool: &Arc, - img_buff: &[u8], - image_width: usize, - image_height: usize, - image_color_channel_count: usize, - split_count_width: usize, - split_count_height: usize, - target_3d_img_buff_data: &mut [u8], - target_3d_img_width: &mut usize, - target_3d_img_height: &mut usize, -) -> bool { - *target_3d_img_width = image_width / split_count_width; - *target_3d_img_height = image_height / split_count_height; - - let full_image_width = image_width * image_color_channel_count; - - let target_image_full_width = { *target_3d_img_width } * image_color_channel_count; - thread_pool.install(|| { - target_3d_img_buff_data - .par_chunks_exact_mut(target_image_full_width) - .enumerate() - .for_each(|(index, write_chunk)| { - let x_src = (index / *target_3d_img_height) % split_count_width; - let y_src = index % *target_3d_img_height - + ((index / (split_count_width * *target_3d_img_height)) - * *target_3d_img_height); - let src_off = y_src * full_image_width + (x_src * target_image_full_width); - - write_chunk.copy_from_slice(&img_buff[src_off..src_off + target_image_full_width]); - }); - }); - - true -} - -pub fn highest_bit(of_var_param: u32) -> u32 { - let mut of_var = of_var_param; - if of_var == 0 { - return 0; - } - - let mut ret_v = 1; - - loop { - of_var >>= 1; - if of_var == 0 { - break; - } - ret_v <<= 1; - } - - ret_v -} diff --git a/lib/graphics/src/lib.rs b/lib/graphics/src/lib.rs index e8489eb..6d7eec8 100644 --- a/lib/graphics/src/lib.rs +++ b/lib/graphics/src/lib.rs @@ -4,7 +4,6 @@ pub mod graphics; pub mod graphics_mt; pub mod handles; -pub mod image; pub mod quad_container; pub mod streaming; pub mod utils; diff --git a/lib/image/Cargo.toml b/lib/image/Cargo.toml index 051fdf3..2ebf964 100644 --- a/lib/image/Cargo.toml +++ b/lib/image/Cargo.toml @@ -6,3 +6,5 @@ edition = "2021" [dependencies] anyhow = { version = "1.0.95", features = ["backtrace"] } png = "0.17.16" +image = { version = "0.25.5", default-features = false } +rayon = "1.10.0" diff --git a/lib/image/src/lib.rs b/lib/image/src/lib.rs index 6717f3e..a5c08e8 100644 --- a/lib/image/src/lib.rs +++ b/lib/image/src/lib.rs @@ -1 +1,2 @@ pub mod png; +pub mod utils; diff --git a/lib/image/src/png.rs b/lib/image/src/png.rs index 7de3f67..c84264f 100644 --- a/lib/image/src/png.rs +++ b/lib/image/src/png.rs @@ -1,4 +1,6 @@ -use std::io; +use std::{borrow::Cow, io, num::NonZeroU32}; + +use image::RgbaImage; #[derive(Debug)] pub struct PngResultPersistentFast { @@ -47,7 +49,7 @@ impl PngResult<'_> { } /// takes a closure of (width, height, color_channel_count) -pub fn load_png_image<'a, T>(file: &[u8], alloc_mem: T) -> io::Result> +pub fn load_png_image_as_rgba<'a, T>(file: &[u8], alloc_mem: T) -> io::Result> where T: FnOnce(usize, usize, usize) -> &'a mut [u8], { @@ -97,7 +99,7 @@ where } img_data } - _ => unreachable!("uncovered color type"), + _ => return Err(io::Error::new(io::ErrorKind::Other, "uncovered color type")), }; Ok(PngResult { @@ -107,30 +109,30 @@ where }) } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct PngValidatorOptions { - pub max_width: u32, - pub max_height: u32, + pub max_width: NonZeroU32, + pub max_height: NonZeroU32, } impl Default for PngValidatorOptions { fn default() -> Self { // 2048x2048 should be a safe limit for games Self { - max_width: 2048, - max_height: 2048, + max_width: 2048.try_into().unwrap(), + max_height: 2048.try_into().unwrap(), } } } pub fn is_png_image_valid(file: &[u8], options: PngValidatorOptions) -> anyhow::Result<()> { let mut mem = Vec::new(); - let img = load_png_image(file, |w, h, ppp| { + let img = load_png_image_as_rgba(file, |w, h, ppp| { mem.resize(w * h * ppp, 0); &mut mem })?; anyhow::ensure!( - img.width <= options.max_width && img.height <= options.max_height, + img.width <= options.max_width.get() && img.height <= options.max_height.get(), "the maximum allowed width and height \ for an image are currently: {} x {}", options.max_width, @@ -169,3 +171,19 @@ pub fn save_png_image_ex( pub fn save_png_image(raw_bytes: &[u8], width: u32, height: u32) -> anyhow::Result> { save_png_image_ex(raw_bytes, width, height, false) } + +pub fn resize_rgba( + img: Cow<[u8]>, + width: u32, + height: u32, + new_width: u32, + new_height: u32, +) -> Vec { + image::imageops::resize( + &RgbaImage::from_raw(width, height, img.into_owned()).unwrap(), + new_width, + new_height, + image::imageops::FilterType::Lanczos3, + ) + .to_vec() +} diff --git a/lib/image/src/utils.rs b/lib/image/src/utils.rs new file mode 100644 index 0000000..7fb663e --- /dev/null +++ b/lib/image/src/utils.rs @@ -0,0 +1,246 @@ +use rayon::{ + prelude::{IndexedParallelIterator, ParallelIterator}, + slice::{ParallelSlice, ParallelSliceMut}, +}; + +const TW_DILATE_ALPHA_THRESHOLD: u8 = 10; + +pub fn dilate( + thread_pool: &rayon::ThreadPool, + w: usize, + h: usize, + bpp: usize, + src_buff: &[u8], + dest_buff: &mut [u8], + alpha_threshold: u8, +) { + let dirs_x = [0, -1, 1, 0]; + let dirs_y = [-1, 0, 0, 1]; + + let alpha_comp_index = bpp - 1; + + thread_pool.install(|| { + dest_buff + .par_chunks_exact_mut(bpp) + .enumerate() + .take(w * h) + .for_each(|(i, dst)| { + let x = i % w; + let y = i / w; + + let m = y * w * bpp + x * bpp; + dst.copy_from_slice(&src_buff[m..(bpp + m)]); + if src_buff[m + alpha_comp_index] > alpha_threshold { + return; + } + + // clear pixels that are considered transparent + // this allows the image to always be black where no dilate is needed + dst[0..(bpp - 1)].fill(0); + + let mut sums_of_opaque = [0, 0, 0]; + let mut counter = 0; + for c in 0..4 { + let ix = (x as i64 + dirs_x[c]).clamp(0, w as i64 - 1) as usize; + let iy = (y as i64 + dirs_y[c]).clamp(0, h as i64 - 1) as usize; + let k = iy * w * bpp + ix * bpp; + if src_buff[k + alpha_comp_index] > alpha_threshold { + for p in 0..bpp - 1 { + // Seems safe for BPP = 3, 4 which we use. + sums_of_opaque[p] += src_buff[k + p] as u32; + } + counter += 1; + break; + } + } + + if counter > 0 { + for i in 0..bpp - 1 { + sums_of_opaque[i] /= counter; + dst[i] = sums_of_opaque[i] as u8; + } + + dst[alpha_comp_index] = 255; + } + }); + }); +} + +fn copy_color_values( + thread_pool: &rayon::ThreadPool, + w: usize, + h: usize, + bpp: usize, + src_buffer: &[u8], + dest_buffer: &mut [u8], +) { + thread_pool.install(|| { + dest_buffer + .par_chunks_exact_mut(bpp) + .take(w * h) + .zip(src_buffer.par_chunks_exact(bpp).take(w * h)) + .for_each(|(dst, src)| { + if dst[bpp - 1] == 0 { + dst[0..bpp - 1].copy_from_slice(&src[0..bpp - 1]); + } + }); + }); +} + +#[allow(clippy::too_many_arguments)] +pub fn dilate_image_sub( + thread_pool: &rayon::ThreadPool, + img_buff: &mut [u8], + w: usize, + _h: usize, + bpp: usize, + x: usize, + y: usize, + sw: usize, + sh: usize, +) { + let [mut buffer_data1, mut buffer_data2] = [ + vec![0; sw * sh * std::mem::size_of::() * bpp], + vec![0; sw * sh * std::mem::size_of::() * bpp], + ]; + + let mut buffer_data_original = vec![0; sw * sh * std::mem::size_of::() * bpp]; + + let pixel_buffer_data = img_buff; + + thread_pool.install(|| { + // fill buffer_data_original completely + buffer_data_original + .chunks_exact_mut(sw * bpp) + .enumerate() + .for_each(|(yh, chunk)| { + let src_img_offset = ((y + yh) * w * bpp) + (x * bpp); + + chunk.copy_from_slice( + &pixel_buffer_data[src_img_offset..src_img_offset + chunk.len()], + ); + }); + }); + + dilate( + thread_pool, + sw, + sh, + bpp, + buffer_data_original.as_slice(), + buffer_data1.as_mut_slice(), + TW_DILATE_ALPHA_THRESHOLD, + ); + + for _i in 0..5 { + dilate( + thread_pool, + sw, + sh, + bpp, + buffer_data1.as_slice(), + buffer_data2.as_mut_slice(), + TW_DILATE_ALPHA_THRESHOLD, + ); + dilate( + thread_pool, + sw, + sh, + bpp, + buffer_data2.as_slice(), + buffer_data1.as_mut_slice(), + TW_DILATE_ALPHA_THRESHOLD, + ); + } + + copy_color_values( + thread_pool, + sw, + sh, + bpp, + buffer_data1.as_slice(), + buffer_data_original.as_mut_slice(), + ); + + thread_pool.install(|| { + pixel_buffer_data + .chunks_exact_mut(w * bpp) + .skip(y) + .take(sh) + .enumerate() + .for_each(|(yh, chunk)| { + let src_img_offset = x * bpp; + let dst_img_offset = yh * sw * bpp; + let copy_size = sw * bpp; + chunk[src_img_offset..src_img_offset + copy_size].copy_from_slice( + &buffer_data_original[dst_img_offset..dst_img_offset + copy_size], + ); + }); + }); +} + +pub fn dilate_image( + thread_pool: &rayon::ThreadPool, + img_buff: &mut [u8], + w: usize, + h: usize, + bpp: usize, +) { + dilate_image_sub(thread_pool, img_buff, w, h, bpp, 0, 0, w, h); +} + +#[allow(clippy::too_many_arguments)] +pub fn texture_2d_to_3d( + thread_pool: &rayon::ThreadPool, + img_buff: &[u8], + image_width: usize, + image_height: usize, + image_color_channel_count: usize, + split_count_width: usize, + split_count_height: usize, + target_3d_img_buff_data: &mut [u8], + target_3d_img_width: &mut usize, + target_3d_img_height: &mut usize, +) -> bool { + *target_3d_img_width = image_width / split_count_width; + *target_3d_img_height = image_height / split_count_height; + + let full_image_width = image_width * image_color_channel_count; + + let target_image_full_width = { *target_3d_img_width } * image_color_channel_count; + thread_pool.install(|| { + target_3d_img_buff_data + .par_chunks_exact_mut(target_image_full_width) + .enumerate() + .for_each(|(index, write_chunk)| { + let x_src = (index / *target_3d_img_height) % split_count_width; + let y_src = index % *target_3d_img_height + + ((index / (split_count_width * *target_3d_img_height)) + * *target_3d_img_height); + let src_off = y_src * full_image_width + (x_src * target_image_full_width); + + write_chunk.copy_from_slice(&img_buff[src_off..src_off + target_image_full_width]); + }); + }); + + true +} + +pub fn highest_bit(of_var_param: u32) -> u32 { + let mut of_var = of_var_param; + if of_var == 0 { + return 0; + } + + let mut ret_v = 1; + + loop { + of_var >>= 1; + if of_var == 0 { + break; + } + ret_v <<= 1; + } + + ret_v +} diff --git a/src/dilate/Cargo.toml b/src/dilate/Cargo.toml index 1145ea3..ffe7bce 100644 --- a/src/dilate/Cargo.toml +++ b/src/dilate/Cargo.toml @@ -4,7 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] -graphics = { path = "../../lib/graphics" } image = { path = "../../lib/image" } tokio = { version = "1.42.0", features = ["rt-multi-thread", "sync", "fs", "time", "macros"] } diff --git a/src/dilate/src/main.rs b/src/dilate/src/main.rs index e091037..b113745 100644 --- a/src/dilate/src/main.rs +++ b/src/dilate/src/main.rs @@ -1,7 +1,7 @@ use std::{num::NonZeroUsize, path::PathBuf, sync::Arc}; use clap::Parser; -use graphics::image::dilate_image; +use image::utils::dilate_image; use oxipng::optimize_from_memory; #[derive(Parser, Debug)] @@ -34,7 +34,7 @@ async fn main() { let file = tokio::fs::read(&args.file).await.unwrap(); let mut mem = Vec::new(); - let img = image::png::load_png_image(&file, |w, h, bpp| { + let img = image::png::load_png_image_as_rgba(&file, |w, h, bpp| { assert!(bpp == 4, "png must be RGBA."); mem = vec![0; w * h * bpp]; &mut mem diff --git a/src/emoticon-convert/src/main.rs b/src/emoticon-convert/src/main.rs index 7f45493..3e9cc6c 100644 --- a/src/emoticon-convert/src/main.rs +++ b/src/emoticon-convert/src/main.rs @@ -68,7 +68,7 @@ fn main() { let file = std::fs::read(args.file).unwrap(); let mut mem: Vec = Default::default(); let img: image::png::PngResult<'_> = - image::png::load_png_image(&file, |width, height, bytes_per_pixel| { + image::png::load_png_image_as_rgba(&file, |width, height, bytes_per_pixel| { mem.resize(width * height * bytes_per_pixel, Default::default()); &mut mem }) diff --git a/src/game-convert/src/main.rs b/src/game-convert/src/main.rs index b43e27c..950ede0 100644 --- a/src/game-convert/src/main.rs +++ b/src/game-convert/src/main.rs @@ -73,7 +73,7 @@ fn main() { let file = std::fs::read(args.file).unwrap(); let mut mem: Vec = Default::default(); let img: image::png::PngResult<'_> = - image::png::load_png_image(&file, |width, height, bytes_per_pixel| { + image::png::load_png_image_as_rgba(&file, |width, height, bytes_per_pixel| { mem.resize(width * height * bytes_per_pixel, Default::default()); &mut mem }) diff --git a/src/hud-convert/src/main.rs b/src/hud-convert/src/main.rs index 5113c6b..92f95f9 100644 --- a/src/hud-convert/src/main.rs +++ b/src/hud-convert/src/main.rs @@ -68,7 +68,7 @@ fn main() { let file = std::fs::read(args.file).unwrap(); let mut mem: Vec = Default::default(); let img: image::png::PngResult<'_> = - image::png::load_png_image(&file, |width, height, bytes_per_pixel| { + image::png::load_png_image_as_rgba(&file, |width, height, bytes_per_pixel| { mem.resize(width * height * bytes_per_pixel, Default::default()); &mut mem }) diff --git a/src/part-convert/src/main.rs b/src/part-convert/src/main.rs index 824211b..21bd0e6 100644 --- a/src/part-convert/src/main.rs +++ b/src/part-convert/src/main.rs @@ -68,7 +68,7 @@ fn main() { let file = std::fs::read(args.file).unwrap(); let mut mem: Vec = Default::default(); let img: image::png::PngResult<'_> = - image::png::load_png_image(&file, |width, height, bytes_per_pixel| { + image::png::load_png_image_as_rgba(&file, |width, height, bytes_per_pixel| { mem.resize(width * height * bytes_per_pixel, Default::default()); &mut mem }) diff --git a/src/skin-convert/src/main.rs b/src/skin-convert/src/main.rs index ada055b..b2bdd9f 100644 --- a/src/skin-convert/src/main.rs +++ b/src/skin-convert/src/main.rs @@ -21,7 +21,7 @@ fn main() { let file = std::fs::read(args.file).unwrap(); let mut mem: Vec = Default::default(); let img: image::png::PngResult<'_> = - image::png::load_png_image(&file, |width, height, bytes_per_pixel| { + image::png::load_png_image_as_rgba(&file, |width, height, bytes_per_pixel| { mem.resize(width * height * bytes_per_pixel, Default::default()); &mut mem })