changeset 704:84af5bedbde4

anmrenderer: also load the alpha PNG.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Fri, 23 Aug 2019 19:09:37 +0200
parents 81232dac8136
children ed65f9412bc0
files examples/anmrenderer.rs src/th06/anm0.rs
diffstat 2 files changed, 79 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/examples/anmrenderer.rs
+++ b/examples/anmrenderer.rs
@@ -1,9 +1,9 @@
-use image::GenericImageView;
+use image::{GenericImageView, DynamicImage};
 use luminance::blending::{Equation, Factor};
 use luminance::context::GraphicsContext;
 use luminance::framebuffer::Framebuffer;
 use luminance::pipeline::BoundTexture;
-use luminance::pixel::{NormRGB8UI, Floating};
+use luminance::pixel::{NormRGB8UI, NormRGBA8UI, Floating};
 use luminance::render_state::RenderState;
 use luminance::shader::program::{Program, Uniform};
 use luminance::tess::{Mode, TessBuilder};
@@ -87,7 +87,7 @@ struct ShaderInterface {
     mvp: Uniform<[[f32; 4]; 4]>,
 }
 
-fn load_file_into_vec(filename: &str) -> Vec<u8> {
+fn load_file_into_vec(filename: &Path) -> Vec<u8> {
     let file = File::open(filename).unwrap();
     let mut file = BufReader::new(file);
     let mut buf = vec![];
@@ -95,16 +95,20 @@ fn load_file_into_vec(filename: &str) ->
     buf
 }
 
+enum LoadedTexture {
+    Rgba(Texture<Flat, Dim2, NormRGBA8UI>),
+    Rgb(Texture<Flat, Dim2, NormRGB8UI>),
+}
+
 fn main() {
     // Parse arguments.
     let args: Vec<_> = env::args().collect();
-    if args.len() != 4 {
-        eprintln!("Usage: {} <ANM file> <PNG file> <script number>", args[0]);
+    if args.len() != 3 {
+        eprintln!("Usage: {} <ANM file> <script number>", args[0]);
         return;
     }
-    let anm_filename = &args[1];
-    let png_filename = &args[2];
-    let script: u8 = args[3].parse().expect("number");
+    let anm_filename = Path::new(&args[1]);
+    let script: u8 = args[2].parse().expect("number");
 
     // Open the ANM file.
     let buf = load_file_into_vec(anm_filename);
@@ -132,8 +136,16 @@ fn main() {
     let mut surface = GlfwSurface::new(WindowDim::Windowed(384, 448), "Touhou", WindowOpt::default()).unwrap();
 
     // Open the image atlas matching this ANM.
-    println!("{} {}", anm0.first_name, png_filename);
-    let tex = load_from_disk(&mut surface, Path::new(png_filename)).expect("texture loading");
+    let png_filename = anm_filename.with_file_name(Path::new(&anm0.png_filename).file_name().unwrap());
+    let tex = match anm0.alpha_filename {
+        Some(ref filename) => {
+            let alpha_filename = anm_filename.with_file_name(Path::new(filename).file_name().unwrap());
+            LoadedTexture::Rgba(png_alpha(&mut surface, &png_filename, &alpha_filename).expect("texture loading"))
+        },
+        None => {
+            LoadedTexture::Rgb(load_from_disk(&mut surface, &png_filename).expect("texture loading"))
+        }
+    };
 
     // set the uniform interface to our type so that we can read textures from the shader
     let (program, _) =
@@ -175,7 +187,10 @@ fn main() {
             .pipeline_builder()
             .pipeline(&back_buffer, [0., 0., 0., 0.], |pipeline, shd_gate| {
                 // bind our fancy texture to the GPU: it gives us a bound texture we can use with the shader
-                let bound_tex = pipeline.bind_texture(&tex);
+                let bound_tex = match &tex {
+                    LoadedTexture::Rgb(tex) => pipeline.bind_texture(tex),
+                    LoadedTexture::Rgba(tex) => pipeline.bind_texture(tex),
+                };
 
                 shd_gate.shade(&program, |rdr_gate, iface| {
                     // update the texture; strictly speaking, this update doesn’t do much: it just tells the GPU
@@ -221,7 +236,7 @@ fn load_from_disk(surface: &mut GlfwSurf
             let (width, height) = img.dimensions();
             let texels = img
                 .pixels()
-                .map(|(x, y, rgb)| (rgb[0], rgb[1], rgb[2]))
+                .map(|(_x, _y, rgb)| (rgb[0], rgb[1], rgb[2]))
                 .collect::<Vec<_>>();
 
             // create the luminance texture; the third argument is the number of mipmaps we want (leave it
@@ -242,3 +257,47 @@ fn load_from_disk(surface: &mut GlfwSurf
         }
     }
 }
+
+fn png_alpha(surface: &mut GlfwSurface, rgb: &Path, alpha: &Path) -> Option<Texture<Flat, Dim2, NormRGBA8UI>> {
+    // load the texture into memory as a whole bloc (i.e. no streaming)
+    match image::open(&alpha) {
+        Ok(img) => {
+            let (width, height) = img.dimensions();
+            let alpha = match img.grayscale() {
+                DynamicImage::ImageLuma8(img) => img,
+                _ => {
+                    eprintln!("cannot convert alpha image {} to grayscale", alpha.display());
+                    return None;
+                }
+            };
+            let img = match image::open(&rgb) {
+                Ok(img) => img,
+                Err(e) => {
+                    eprintln!("cannot open rgb image {}: {}", rgb.display(), e);
+                    return None;
+                },
+            };
+            let texels = img
+                .pixels()
+                .zip(alpha.pixels())
+                .map(|((_x, _y, rgb), luma)| (rgb[0], rgb[1], rgb[1], luma[0]))
+                .collect::<Vec<_>>();
+
+            // create the luminance texture; the third argument is the number of mipmaps we want (leave it
+            // to 0 for now) and the latest is a the sampler to use when sampling the texels in the
+            // shader (we’ll just use the default one)
+            let tex =
+                Texture::new(surface, [width, height], 0, &Sampler::default()).expect("luminance texture creation");
+
+            // the first argument disables mipmap generation (we don’t care so far)
+            tex.upload(GenMipmaps::No, &texels);
+
+            Some(tex)
+        }
+
+        Err(e) => {
+            eprintln!("cannot open alpha image {}: {}", alpha.display(), e);
+            None
+        }
+    }
+}
--- a/src/th06/anm0.rs
+++ b/src/th06/anm0.rs
@@ -58,10 +58,10 @@ pub struct Anm0 {
     pub format: u32,
 
     /// File name of the main image.
-    pub first_name: String,
+    pub png_filename: String,
 
     /// File name of an alpha channel image.
-    pub second_name: String,
+    pub alpha_filename: Option<String>,
 
     /// A list of sprites, coordinates into the attached image.
     pub sprites: Vec<Sprite>,
@@ -187,7 +187,7 @@ fn parse_anm0(input: &[u8]) -> IResult<&
         let (i, sprite_offsets) = many_m_n(num_sprites, num_sprites, le_u32)(i)?;
         let (_, script_offsets) = many_m_n(num_scripts, num_scripts, tuple((le_u32, le_u32)))(i)?;
 
-        let first_name = if first_name_offset > 0 {
+        let png_filename = if first_name_offset > 0 {
             if input.len() < start_offset + first_name_offset as usize {
                 return Err(nom::Err::Failure((input, nom::error::ErrorKind::Eof)));
             }
@@ -198,15 +198,15 @@ fn parse_anm0(input: &[u8]) -> IResult<&
             String::new()
         };
 
-        let second_name = if second_name_offset > 0 {
+        let alpha_filename = if second_name_offset > 0 {
             if input.len() < start_offset + second_name_offset as usize {
                 return Err(nom::Err::Failure((input, nom::error::ErrorKind::Eof)));
             }
             let i = &input[start_offset + second_name_offset as usize..];
             let (_, name) = parse_name(i)?;
-            name
+            Some(name)
         } else {
-            String::new()
+            None
         };
 
         let mut sprites = vec![];
@@ -272,8 +272,8 @@ fn parse_anm0(input: &[u8]) -> IResult<&
         let anm0 = Anm0 {
             size: (width, height),
             format,
-            first_name,
-            second_name,
+            png_filename,
+            alpha_filename,
             sprites,
             scripts,
         };