summaryrefslogtreecommitdiff
path: root/vendor/png/examples
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
committerValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
commit1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch)
tree7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/png/examples
parent5ecd8cf2cba827454317368b68571df0d13d7842 (diff)
downloadfparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz
fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/png/examples')
-rw-r--r--vendor/png/examples/corpus-bench.rs198
-rw-r--r--vendor/png/examples/png-generate.rs55
-rw-r--r--vendor/png/examples/pngcheck.rs381
-rw-r--r--vendor/png/examples/show.rs198
4 files changed, 832 insertions, 0 deletions
diff --git a/vendor/png/examples/corpus-bench.rs b/vendor/png/examples/corpus-bench.rs
new file mode 100644
index 0000000..b030d6d
--- /dev/null
+++ b/vendor/png/examples/corpus-bench.rs
@@ -0,0 +1,198 @@
+use std::{fs, path::PathBuf};
+
+use clap::Parser;
+use png::Decoder;
+
+#[derive(clap::ValueEnum, Clone)]
+enum Speed {
+ Fast,
+ Default,
+ Best,
+}
+
+#[derive(clap::ValueEnum, Clone)]
+enum Filter {
+ None,
+ Sub,
+ Up,
+ Average,
+ Paeth,
+ Adaptive,
+}
+
+#[derive(clap::Parser)]
+struct Args {
+ directory: Option<PathBuf>,
+ #[clap(short, long, value_enum, default_value_t = Speed::Fast)]
+ speed: Speed,
+ #[clap(short, long, value_enum, default_value_t = Filter::Adaptive)]
+ filter: Filter,
+}
+
+#[inline(never)]
+fn run_encode(
+ args: &Args,
+ dimensions: (u32, u32),
+ color_type: png::ColorType,
+ bit_depth: png::BitDepth,
+ image: &[u8],
+) -> Vec<u8> {
+ let mut reencoded = Vec::new();
+ let mut encoder = png::Encoder::new(&mut reencoded, dimensions.0, dimensions.1);
+ encoder.set_color(color_type);
+ encoder.set_depth(bit_depth);
+ encoder.set_compression(match args.speed {
+ Speed::Fast => png::Compression::Fast,
+ Speed::Default => png::Compression::Default,
+ Speed::Best => png::Compression::Best,
+ });
+ encoder.set_filter(match args.filter {
+ Filter::None => png::FilterType::NoFilter,
+ Filter::Sub => png::FilterType::Sub,
+ Filter::Up => png::FilterType::Up,
+ Filter::Average => png::FilterType::Avg,
+ Filter::Paeth => png::FilterType::Paeth,
+ Filter::Adaptive => png::FilterType::Paeth,
+ });
+ encoder.set_adaptive_filter(match args.filter {
+ Filter::Adaptive => png::AdaptiveFilterType::Adaptive,
+ _ => png::AdaptiveFilterType::NonAdaptive,
+ });
+ let mut encoder = encoder.write_header().unwrap();
+ encoder.write_image_data(&image).unwrap();
+ encoder.finish().unwrap();
+ reencoded
+}
+
+#[inline(never)]
+fn run_decode(image: &[u8], output: &mut [u8]) {
+ let mut reader = Decoder::new(image).read_info().unwrap();
+ reader.next_frame(output).unwrap();
+}
+
+fn main() {
+ let mut total_uncompressed = 0;
+ let mut total_compressed = 0;
+ let mut total_pixels = 0;
+ let mut total_encode_time = 0;
+ let mut total_decode_time = 0;
+
+ let args = Args::parse();
+
+ println!(
+ "{:45} Ratio Encode Decode",
+ "Directory"
+ );
+ println!(
+ "{:45}------- -------------------- --------------------",
+ "---------"
+ );
+
+ let mut image2 = Vec::new();
+
+ let mut pending = vec![args.directory.clone().unwrap_or(PathBuf::from("."))];
+ while let Some(directory) = pending.pop() {
+ let mut dir_uncompressed = 0;
+ let mut dir_compressed = 0;
+ let mut dir_pixels = 0;
+ let mut dir_encode_time = 0;
+ let mut dir_decode_time = 0;
+
+ for entry in fs::read_dir(&directory).unwrap().flatten() {
+ if entry.file_type().unwrap().is_dir() {
+ pending.push(entry.path());
+ continue;
+ }
+
+ match entry.path().extension() {
+ Some(st) if st == "png" => {}
+ _ => continue,
+ }
+
+ // Parse
+ let data = fs::read(entry.path()).unwrap();
+ let mut decoder = Decoder::new(&*data);
+ if decoder.read_header_info().ok().map(|h| h.color_type)
+ == Some(png::ColorType::Indexed)
+ {
+ decoder.set_transformations(
+ png::Transformations::EXPAND | png::Transformations::STRIP_16,
+ );
+ }
+ let mut reader = match decoder.read_info() {
+ Ok(reader) => reader,
+ Err(_) => continue,
+ };
+ let mut image = vec![0; reader.output_buffer_size()];
+ let info = match reader.next_frame(&mut image) {
+ Ok(info) => info,
+ Err(_) => continue,
+ };
+ let (width, height) = (info.width, info.height);
+ let bit_depth = info.bit_depth;
+ let mut color_type = info.color_type;
+
+ // qoibench expands grayscale to RGB, so we do the same.
+ if bit_depth == png::BitDepth::Eight {
+ if color_type == png::ColorType::Grayscale {
+ image = image.into_iter().flat_map(|v| [v, v, v, 255]).collect();
+ color_type = png::ColorType::Rgba;
+ } else if color_type == png::ColorType::GrayscaleAlpha {
+ image = image
+ .chunks_exact(2)
+ .flat_map(|v| [v[0], v[0], v[0], v[1]])
+ .collect();
+ color_type = png::ColorType::Rgba;
+ }
+ }
+
+ // Re-encode
+ let start = std::time::Instant::now();
+ let reencoded = run_encode(&args, (width, height), color_type, bit_depth, &image);
+ let elapsed = start.elapsed().as_nanos() as u64;
+
+ // And decode again
+ image2.resize(image.len(), 0);
+ let start2 = std::time::Instant::now();
+ run_decode(&reencoded, &mut image2);
+ let elapsed2 = start2.elapsed().as_nanos() as u64;
+
+ assert_eq!(image, image2);
+
+ // Stats
+ dir_uncompressed += image.len();
+ dir_compressed += reencoded.len();
+ dir_pixels += (width * height) as u64;
+ dir_encode_time += elapsed;
+ dir_decode_time += elapsed2;
+ }
+ if dir_uncompressed > 0 {
+ println!(
+ "{:45}{:6.2}%{:8} mps {:6.2} GiB/s {:8} mps {:6.2} GiB/s",
+ directory.display(),
+ 100.0 * dir_compressed as f64 / dir_uncompressed as f64,
+ dir_pixels * 1000 / dir_encode_time,
+ dir_uncompressed as f64 / (dir_encode_time as f64 * 1e-9 * (1 << 30) as f64),
+ dir_pixels * 1000 / dir_decode_time,
+ dir_uncompressed as f64 / (dir_decode_time as f64 * 1e-9 * (1 << 30) as f64)
+ );
+ }
+
+ total_uncompressed += dir_uncompressed;
+ total_compressed += dir_compressed;
+ total_pixels += dir_pixels;
+ total_encode_time += dir_encode_time;
+ total_decode_time += dir_decode_time;
+ }
+
+ println!();
+ println!(
+ "{:44}{:7.3}%{:8} mps {:6.3} GiB/s {:8} mps {:6.3} GiB/s",
+ "Total",
+ 100.0 * total_compressed as f64 / total_uncompressed as f64,
+ total_pixels * 1000 / total_encode_time,
+ total_uncompressed as f64 / (total_encode_time as f64 * 1e-9 * (1 << 30) as f64),
+ total_pixels * 1000 / total_decode_time,
+ total_uncompressed as f64 / (total_decode_time as f64 * 1e-9 * (1 << 30) as f64)
+ );
+}
diff --git a/vendor/png/examples/png-generate.rs b/vendor/png/examples/png-generate.rs
new file mode 100644
index 0000000..9036a04
--- /dev/null
+++ b/vendor/png/examples/png-generate.rs
@@ -0,0 +1,55 @@
+// For reading and opening files
+use png::text_metadata::{ITXtChunk, ZTXtChunk};
+use std::env;
+use std::fs::File;
+use std::io::BufWriter;
+
+fn main() {
+ let path = env::args()
+ .nth(1)
+ .expect("Expected a filename to output to.");
+ let file = File::create(path).unwrap();
+ let w = &mut BufWriter::new(file);
+
+ let mut encoder = png::Encoder::new(w, 2, 1); // Width is 2 pixels and height is 1.
+ encoder.set_color(png::ColorType::Rgba);
+ encoder.set_depth(png::BitDepth::Eight);
+ // Adding text chunks to the header
+ encoder
+ .add_text_chunk(
+ "Testing tEXt".to_string(),
+ "This is a tEXt chunk that will appear before the IDAT chunks.".to_string(),
+ )
+ .unwrap();
+ encoder
+ .add_ztxt_chunk(
+ "Testing zTXt".to_string(),
+ "This is a zTXt chunk that is compressed in the png file.".to_string(),
+ )
+ .unwrap();
+ encoder
+ .add_itxt_chunk(
+ "Testing iTXt".to_string(),
+ "iTXt chunks support all of UTF8. Example: हिंदी.".to_string(),
+ )
+ .unwrap();
+
+ let mut writer = encoder.write_header().unwrap();
+
+ let data = [255, 0, 0, 255, 0, 0, 0, 255]; // An array containing a RGBA sequence. First pixel is red and second pixel is black.
+ writer.write_image_data(&data).unwrap(); // Save
+
+ // We can add a tEXt/zTXt/iTXt at any point before the encoder is dropped from scope. These chunks will be at the end of the png file.
+ let tail_ztxt_chunk = ZTXtChunk::new(
+ "Comment".to_string(),
+ "A zTXt chunk after the image data.".to_string(),
+ );
+ writer.write_text_chunk(&tail_ztxt_chunk).unwrap();
+
+ // The fields of the text chunk are public, so they can be mutated before being written to the file.
+ let mut tail_itxt_chunk = ITXtChunk::new("Author".to_string(), "सायंतन खान".to_string());
+ tail_itxt_chunk.compressed = true;
+ tail_itxt_chunk.language_tag = "hi".to_string();
+ tail_itxt_chunk.translated_keyword = "लेखक".to_string();
+ writer.write_text_chunk(&tail_itxt_chunk).unwrap();
+}
diff --git a/vendor/png/examples/pngcheck.rs b/vendor/png/examples/pngcheck.rs
new file mode 100644
index 0000000..69e95e3
--- /dev/null
+++ b/vendor/png/examples/pngcheck.rs
@@ -0,0 +1,381 @@
+#![allow(non_upper_case_globals)]
+
+extern crate getopts;
+extern crate glob;
+extern crate png;
+
+use std::env;
+use std::fs::File;
+use std::io;
+use std::io::prelude::*;
+use std::path::Path;
+
+use getopts::{Matches, Options, ParsingStyle};
+use term::{color, Attr};
+
+fn parse_args() -> Matches {
+ let args: Vec<String> = env::args().collect();
+ let mut opts = Options::new();
+ opts.optflag("c", "", "colorize output (for ANSI terminals)")
+ .optflag("q", "", "test quietly (output only errors)")
+ .optflag(
+ "t",
+ "",
+ "print contents of tEXt/zTXt/iTXt chunks (can be used with -q)",
+ )
+ .optflag("v", "", "test verbosely (print most chunk data)")
+ .parsing_style(ParsingStyle::StopAtFirstFree);
+ if args.len() > 1 {
+ match opts.parse(&args[1..]) {
+ Ok(matches) => return matches,
+ Err(err) => println!("{}", err),
+ }
+ }
+ println!("{}", opts.usage("Usage: pngcheck [-cpt] [file ...]"));
+ std::process::exit(0);
+}
+
+#[derive(Clone, Copy)]
+struct Config {
+ quiet: bool,
+ verbose: bool,
+ color: bool,
+ text: bool,
+}
+
+fn display_interlaced(i: bool) -> &'static str {
+ if i {
+ "interlaced"
+ } else {
+ "non-interlaced"
+ }
+}
+
+fn display_image_type(bits: u8, color: png::ColorType) -> String {
+ use png::ColorType::*;
+ format!(
+ "{}-bit {}",
+ bits,
+ match color {
+ Grayscale => "grayscale",
+ Rgb => "RGB",
+ Indexed => "palette",
+ GrayscaleAlpha => "grayscale+alpha",
+ Rgba => "RGB+alpha",
+ }
+ )
+}
+// channels after expansion of tRNS
+fn final_channels(c: png::ColorType, trns: bool) -> u8 {
+ use png::ColorType::*;
+ match c {
+ Grayscale => 1 + u8::from(trns),
+ Rgb => 3,
+ Indexed => 3 + u8::from(trns),
+ GrayscaleAlpha => 2,
+ Rgba => 4,
+ }
+}
+fn check_image<P: AsRef<Path>>(c: Config, fname: P) -> io::Result<()> {
+ // TODO improve performance by resusing allocations from decoder
+ use png::Decoded::*;
+ let mut t = term::stdout()
+ .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "could not open terminal"))?;
+ let data = &mut vec![0; 10 * 1024][..];
+ let mut reader = io::BufReader::new(File::open(&fname)?);
+ let fname = fname.as_ref().to_string_lossy();
+ let n = reader.read(data)?;
+ let mut buf = &data[..n];
+ let mut pos = 0;
+ let mut decoder = png::StreamingDecoder::new();
+ // Image data
+ let mut width = 0;
+ let mut height = 0;
+ let mut color = png::ColorType::Grayscale;
+ let mut bits = 0;
+ let mut trns = false;
+ let mut interlaced = false;
+ let mut compressed_size = 0;
+ let mut n_chunks = 0;
+ let mut have_idat = false;
+ macro_rules! c_ratio(
+ // TODO add palette entries to compressed_size
+ () => ({
+ compressed_size as f32/(
+ height as u64 *
+ (width as u64 * final_channels(color, trns) as u64 * bits as u64 + 7)>>3
+ ) as f32
+ });
+ );
+ let display_error = |err| -> Result<_, io::Error> {
+ let mut t = term::stdout()
+ .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "could not open terminal"))?;
+ if c.verbose {
+ if c.color {
+ print!(": ");
+ t.fg(color::RED)?;
+ writeln!(t, "{}", err)?;
+ t.attr(Attr::Bold)?;
+ write!(t, "ERRORS DETECTED")?;
+ t.reset()?;
+ } else {
+ println!(": {}", err);
+ print!("ERRORS DETECTED")
+ }
+ println!(" in {}", fname);
+ } else {
+ if !c.quiet {
+ if c.color {
+ t.fg(color::RED)?;
+ t.attr(Attr::Bold)?;
+ write!(t, "ERROR")?;
+ t.reset()?;
+ write!(t, ": ")?;
+ t.fg(color::YELLOW)?;
+ writeln!(t, "{}", fname)?;
+ t.reset()?;
+ } else {
+ println!("ERROR: {}", fname)
+ }
+ }
+ print!("{}: ", fname);
+ if c.color {
+ t.fg(color::RED)?;
+ writeln!(t, "{}", err)?;
+ t.reset()?;
+ } else {
+ println!("{}", err);
+ }
+ }
+ Ok(())
+ };
+
+ if c.verbose {
+ print!("File: ");
+ if c.color {
+ t.attr(Attr::Bold)?;
+ write!(t, "{}", fname)?;
+ t.reset()?;
+ } else {
+ print!("{}", fname);
+ }
+ print!(" ({}) bytes", data.len())
+ }
+ loop {
+ if buf.is_empty() {
+ // circumvent borrow checker
+ assert!(!data.is_empty());
+ let n = reader.read(data)?;
+
+ // EOF
+ if n == 0 {
+ println!("ERROR: premature end of file {}", fname);
+ break;
+ }
+ buf = &data[..n];
+ }
+ match decoder.update(buf, &mut Vec::new()) {
+ Ok((_, ImageEnd)) => {
+ if !have_idat {
+ // This isn't beautiful. But it works.
+ display_error(png::DecodingError::IoError(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "IDAT chunk missing",
+ )))?;
+ break;
+ }
+ if !c.verbose && !c.quiet {
+ if c.color {
+ t.fg(color::GREEN)?;
+ t.attr(Attr::Bold)?;
+ write!(t, "OK")?;
+ t.reset()?;
+ write!(t, ": ")?;
+ t.fg(color::YELLOW)?;
+ write!(t, "{}", fname)?;
+ t.reset()?;
+ } else {
+ print!("OK: {}", fname)
+ }
+ println!(
+ " ({}x{}, {}{}, {}, {:.1}%)",
+ width,
+ height,
+ display_image_type(bits, color),
+ (if trns { "+trns" } else { "" }),
+ display_interlaced(interlaced),
+ 100.0 * (1.0 - c_ratio!())
+ )
+ } else if !c.quiet {
+ println!();
+ if c.color {
+ t.fg(color::GREEN)?;
+ t.attr(Attr::Bold)?;
+ write!(t, "No errors detected ")?;
+ t.reset()?;
+ } else {
+ print!("No errors detected ");
+ }
+ println!(
+ "in {} ({} chunks, {:.1}% compression)",
+ fname,
+ n_chunks,
+ 100.0 * (1.0 - c_ratio!()),
+ )
+ }
+ break;
+ }
+ Ok((n, res)) => {
+ buf = &buf[n..];
+ pos += n;
+ match res {
+ Header(w, h, b, c, i) => {
+ width = w;
+ height = h;
+ bits = b as u8;
+ color = c;
+ interlaced = i;
+ }
+ ChunkBegin(len, type_str) => {
+ use png::chunk;
+ n_chunks += 1;
+ if c.verbose {
+ let chunk = type_str;
+ println!();
+ print!(" chunk ");
+ if c.color {
+ t.fg(color::YELLOW)?;
+ write!(t, "{:?}", chunk)?;
+ t.reset()?;
+ } else {
+ print!("{:?}", chunk)
+ }
+ print!(
+ " at offset {:#07x}, length {}",
+ pos - 4, // substract chunk name length
+ len
+ )
+ }
+ match type_str {
+ chunk::IDAT => {
+ have_idat = true;
+ compressed_size += len
+ }
+ chunk::tRNS => {
+ trns = true;
+ }
+ _ => (),
+ }
+ }
+ ImageData => {
+ //println!("got {} bytes of image data", data.len())
+ }
+ ChunkComplete(_, type_str) if c.verbose => {
+ use png::chunk::*;
+ if type_str == IHDR {
+ println!();
+ print!(
+ " {} x {} image, {}{}, {}",
+ width,
+ height,
+ display_image_type(bits, color),
+ (if trns { "+trns" } else { "" }),
+ display_interlaced(interlaced),
+ );
+ }
+ }
+ AnimationControl(actl) => {
+ println!();
+ print!(" {} frames, {} plays", actl.num_frames, actl.num_plays,);
+ }
+ FrameControl(fctl) => {
+ println!();
+ println!(
+ " sequence #{}, {} x {} pixels @ ({}, {})",
+ fctl.sequence_number,
+ fctl.width,
+ fctl.height,
+ fctl.x_offset,
+ fctl.y_offset,
+ /*fctl.delay_num,
+ fctl.delay_den,
+ fctl.dispose_op,
+ fctl.blend_op,*/
+ );
+ print!(
+ " {}/{} s delay, dispose: {}, blend: {}",
+ fctl.delay_num,
+ if fctl.delay_den == 0 {
+ 100
+ } else {
+ fctl.delay_den
+ },
+ fctl.dispose_op,
+ fctl.blend_op,
+ );
+ }
+ _ => (),
+ }
+ //println!("{} {:?}", n, res)
+ }
+ Err(err) => {
+ let _ = display_error(err);
+ break;
+ }
+ }
+ }
+ if c.text {
+ println!("Parsed tEXt chunks:");
+ for text_chunk in &decoder.info().unwrap().uncompressed_latin1_text {
+ println!("{:#?}", text_chunk);
+ }
+
+ println!("Parsed zTXt chunks:");
+ for text_chunk in &decoder.info().unwrap().compressed_latin1_text {
+ let mut cloned_text_chunk = text_chunk.clone();
+ cloned_text_chunk.decompress_text()?;
+ println!("{:#?}", cloned_text_chunk);
+ }
+
+ println!("Parsed iTXt chunks:");
+ for text_chunk in &decoder.info().unwrap().utf8_text {
+ let mut cloned_text_chunk = text_chunk.clone();
+ cloned_text_chunk.decompress_text()?;
+ println!("{:#?}", cloned_text_chunk);
+ }
+ }
+
+ Ok(())
+}
+
+fn main() {
+ let m = parse_args();
+
+ let config = Config {
+ quiet: m.opt_present("q"),
+ verbose: m.opt_present("v"),
+ color: m.opt_present("c"),
+ text: m.opt_present("t"),
+ };
+
+ for file in m.free {
+ let result = if file.contains('*') {
+ glob::glob(&file)
+ .map_err(|err| io::Error::new(io::ErrorKind::Other, err))
+ .and_then(|mut glob| {
+ glob.try_for_each(|entry| {
+ entry
+ .map_err(|err| io::Error::new(io::ErrorKind::Other, err))
+ .and_then(|file| check_image(config, file))
+ })
+ })
+ } else {
+ check_image(config, &file)
+ };
+
+ result.unwrap_or_else(|err| {
+ println!("{}: {}", file, err);
+ std::process::exit(1)
+ });
+ }
+}
diff --git a/vendor/png/examples/show.rs b/vendor/png/examples/show.rs
new file mode 100644
index 0000000..d8ddf75
--- /dev/null
+++ b/vendor/png/examples/show.rs
@@ -0,0 +1,198 @@
+use glium::{
+ backend::glutin::Display,
+ glutin::{
+ self, dpi,
+ event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
+ event_loop::ControlFlow,
+ },
+ texture::{ClientFormat, RawImage2d},
+ BlitTarget, Rect, Surface,
+};
+use std::{borrow::Cow, env, fs::File, io, path};
+
+/// Load the image using `png`
+fn load_image(path: &path::PathBuf) -> io::Result<RawImage2d<'static, u8>> {
+ use png::ColorType::*;
+ let mut decoder = png::Decoder::new(File::open(path)?);
+ decoder.set_transformations(png::Transformations::normalize_to_color8());
+ let mut reader = decoder.read_info()?;
+ let mut img_data = vec![0; reader.output_buffer_size()];
+ let info = reader.next_frame(&mut img_data)?;
+
+ let (data, format) = match info.color_type {
+ Rgb => (img_data, ClientFormat::U8U8U8),
+ Rgba => (img_data, ClientFormat::U8U8U8U8),
+ Grayscale => (
+ {
+ let mut vec = Vec::with_capacity(img_data.len() * 3);
+ for g in img_data {
+ vec.extend([g, g, g].iter().cloned())
+ }
+ vec
+ },
+ ClientFormat::U8U8U8,
+ ),
+ GrayscaleAlpha => (
+ {
+ let mut vec = Vec::with_capacity(img_data.len() * 3);
+ for ga in img_data.chunks(2) {
+ let g = ga[0];
+ let a = ga[1];
+ vec.extend([g, g, g, a].iter().cloned())
+ }
+ vec
+ },
+ ClientFormat::U8U8U8U8,
+ ),
+ _ => unreachable!("uncovered color type"),
+ };
+
+ Ok(RawImage2d {
+ data: Cow::Owned(data),
+ width: info.width,
+ height: info.height,
+ format,
+ })
+}
+
+fn main_loop(files: Vec<path::PathBuf>) -> io::Result<()> {
+ let mut files = files.into_iter();
+ let image = load_image(&files.next().unwrap())?;
+
+ let event_loop = glutin::event_loop::EventLoop::new();
+ let window_builder = glutin::window::WindowBuilder::new().with_title("Show Example");
+ let context_builder = glutin::ContextBuilder::new().with_vsync(true);
+ let display = glium::Display::new(window_builder, context_builder, &event_loop)
+ .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
+ resize_window(&display, &image);
+ let mut texture = glium::Texture2d::new(&display, image).unwrap();
+ draw(&display, &texture);
+
+ event_loop.run(move |event, _, control_flow| match event {
+ Event::WindowEvent {
+ event: WindowEvent::CloseRequested,
+ ..
+ } => exit(control_flow),
+ Event::WindowEvent {
+ event:
+ WindowEvent::KeyboardInput {
+ input:
+ KeyboardInput {
+ state: ElementState::Pressed,
+ virtual_keycode: code,
+ ..
+ },
+ ..
+ },
+ ..
+ } => match code {
+ Some(VirtualKeyCode::Escape) => exit(control_flow),
+ Some(VirtualKeyCode::Right) => match &files.next() {
+ Some(path) => {
+ match load_image(path) {
+ Ok(image) => {
+ resize_window(&display, &image);
+ texture = glium::Texture2d::new(&display, image).unwrap();
+ draw(&display, &texture);
+ }
+ Err(err) => {
+ println!("Error: {}", err);
+ exit(control_flow);
+ }
+ };
+ }
+ None => exit(control_flow),
+ },
+ _ => {}
+ },
+ Event::RedrawRequested(_) => draw(&display, &texture),
+ _ => {}
+ });
+}
+
+fn draw(display: &glium::Display, texture: &glium::Texture2d) {
+ let frame = display.draw();
+ fill_v_flipped(
+ &texture.as_surface(),
+ &frame,
+ glium::uniforms::MagnifySamplerFilter::Linear,
+ );
+ frame.finish().unwrap();
+}
+
+fn exit(control_flow: &mut ControlFlow) {
+ *control_flow = ControlFlow::Exit;
+}
+
+fn fill_v_flipped<S1, S2>(src: &S1, target: &S2, filter: glium::uniforms::MagnifySamplerFilter)
+where
+ S1: Surface,
+ S2: Surface,
+{
+ let src_dim = src.get_dimensions();
+ let src_rect = Rect {
+ left: 0,
+ bottom: 0,
+ width: src_dim.0 as u32,
+ height: src_dim.1 as u32,
+ };
+ let target_dim = target.get_dimensions();
+ let target_rect = BlitTarget {
+ left: 0,
+ bottom: target_dim.1,
+ width: target_dim.0 as i32,
+ height: -(target_dim.1 as i32),
+ };
+ src.blit_color(&src_rect, target, &target_rect, filter);
+}
+
+fn resize_window(display: &Display, image: &RawImage2d<'static, u8>) {
+ let mut width = image.width;
+ let mut height = image.height;
+ if width < 50 && height < 50 {
+ width *= 10;
+ height *= 10;
+ }
+ display
+ .gl_window()
+ .window()
+ .set_inner_size(dpi::LogicalSize::new(f64::from(width), f64::from(height)));
+}
+
+fn main() {
+ let args: Vec<String> = env::args().collect();
+ if args.len() < 2 {
+ println!("Usage: show files [...]");
+ } else {
+ let mut files = vec![];
+ for file in args.iter().skip(1) {
+ match if file.contains('*') {
+ (|| -> io::Result<_> {
+ for entry in glob::glob(file)
+ .map_err(|err| io::Error::new(io::ErrorKind::Other, err.msg))?
+ {
+ files.push(
+ entry
+ .map_err(|_| io::Error::new(io::ErrorKind::Other, "glob error"))?,
+ )
+ }
+ Ok(())
+ })()
+ } else {
+ files.push(path::PathBuf::from(file));
+ Ok(())
+ } {
+ Ok(_) => (),
+ Err(err) => {
+ println!("{}: {}", file, err);
+ break;
+ }
+ }
+ }
+ // "tests/pngsuite/pngsuite.png"
+ match main_loop(files) {
+ Ok(_) => (),
+ Err(err) => println!("Error: {}", err),
+ }
+ }
+}