diff --git a/bingus/Cargo.toml b/bingus/Cargo.toml index f09e84b..f2f32ff 100644 --- a/bingus/Cargo.toml +++ b/bingus/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bingus" -version = "0.8.0" +version = "0.9.0" edition.workspace = true license.workspace = true description.workspace = true diff --git a/bingus/src/doc.rs b/bingus/src/doc.rs index 57af55a..e6c0fe7 100644 --- a/bingus/src/doc.rs +++ b/bingus/src/doc.rs @@ -1,4 +1,6 @@ +#[cfg(feature = "printpdf")] pub use printpdf; +#[cfg(feature = "printpdf")] mod pdf; #[cfg(feature = "shiva")] mod shiva; diff --git a/bingus/src/fnt.rs b/bingus/src/fnt.rs index 800fe2e..e86a3b3 100644 --- a/bingus/src/fnt.rs +++ b/bingus/src/fnt.rs @@ -1,2 +1,4 @@ +#[cfg(feature = "font-kit")] mod fontkit; +#[cfg(feature = "font-kit")] pub use fontkit::Font; diff --git a/bingus/src/img.rs b/bingus/src/img.rs index bb13e63..627154b 100644 --- a/bingus/src/img.rs +++ b/bingus/src/img.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "image")] mod image; +#[cfg(feature = "image")] pub use image::ImageBuffer as Image; +#[cfg(feature = "image")] pub use image::*; diff --git a/bingus/src/lib.rs b/bingus/src/lib.rs index 30c7bfc..f924fe3 100644 --- a/bingus/src/lib.rs +++ b/bingus/src/lib.rs @@ -1,3 +1,5 @@ +#![deny(unused_crate_dependencies)] + #[cfg(feature = "binary")] pub mod bin; #[cfg(feature = "documents")] @@ -23,13 +25,13 @@ pub mod dynamic { path::Path, }; - #[cfg(feature = "documents")] + #[cfg(all(feature = "documents", feature = "shiva"))] use super::doc::ShivaDocument; - #[cfg(feature = "fonts")] + #[cfg(all(feature = "fonts", feature = "font-kit"))] use super::fnt::Font as FontKitFont; - #[cfg(feature = "pictures")] + #[cfg(all(feature = "pictures", feature = "image"))] use super::img::{self, DynamicImage}; - #[cfg(feature = "music")] + #[cfg(all(feature = "music", feature = "symphonia"))] use super::snd::{self, Audio}; #[cfg(feature = "text")] use super::txt::Text; @@ -38,32 +40,32 @@ pub mod dynamic { use std::marker::PhantomData; use cfg_if::cfg_if; - #[cfg(feature = "fonts")] + #[cfg(all(feature = "fonts", feature = "font-kit"))] use font_kit::error::FontLoadingError; pub use infer::*; - #[cfg(feature = "documents")] + #[cfg(all(feature = "documents", feature = "printpdf"))] use printpdf::PdfDocument; - #[cfg(feature = "documents")] + #[cfg(all(feature = "documents", feature = "shiva"))] use shiva::core::{bytes, Document, DocumentType}; use thiserror::Error; pub enum DynamicBendable<'a> { - #[cfg(feature = "pictures")] + #[cfg(all(feature = "pictures", feature = "image"))] Image(DynamicImage), #[cfg(feature = "binary")] Binary(Bytes), - #[cfg(feature = "music")] + #[cfg(all(feature = "music", feature = "symphonia"))] Sound(Audio), #[cfg(feature = "text")] Text(Text<'a>), #[cfg(not(feature = "text"))] Phantom(PhantomData<&'a ()>), - #[cfg(feature = "documents")] + #[cfg(all(feature = "documents", feature = "shiva"))] Doc(ShivaDocument), - #[cfg(feature = "documents")] + #[cfg(all(feature = "documents", feature = "printpdf"))] Archive(PdfDocument), Meta, - #[cfg(feature = "fonts")] + #[cfg(all(feature = "fonts", feature = "font-kit"))] Font(FontKitFont), } @@ -85,22 +87,22 @@ pub mod dynamic { pub enum OpenError { #[error("io: {0}")] Io(#[from] io::Error), - #[cfg(feature = "pictures")] + #[cfg(all(feature = "pictures", feature = "image"))] #[error("image: {0}")] Image(#[from] img::ImageError), - #[cfg(feature = "music")] + #[cfg(all(feature = "music", feature = "symphonia"))] #[error("audio: {0}")] Audio(#[from] snd::AudioOpenError), - #[cfg(feature = "documents")] + #[cfg(all(feature = "documents", feature = "printpdf"))] #[error("pdf: {0}")] Pdf(String), #[cfg(feature = "text")] #[error("text: {0}")] Text(#[from] FromUtf8Error), - #[cfg(feature = "documents")] + #[cfg(all(feature = "documents", feature = "shiva"))] #[error("document: {0}")] Document(#[from] ShivaError), - #[cfg(feature = "fonts")] + #[cfg(all(feature = "fonts", feature = "font-kit"))] #[error("font: {0:?}")] Font(#[from] FontLoadingError), } @@ -155,11 +157,11 @@ pub mod dynamic { .map( |(matcher, extension)| -> Result { Ok(match matcher { - #[cfg(feature = "pictures")] + #[cfg(all(feature = "pictures", feature = "image"))] Image => DynamicBendable::Image(img::load_from_memory(&bytes)?), - #[cfg(feature = "music")] + #[cfg(all(feature = "music", feature = "symphonia"))] Audio => DynamicBendable::Sound(crate::snd::Audio::open(Cursor::new(bytes), None)?), - #[cfg(feature = "documents")] + #[cfg(all(feature = "documents", feature = "printpdf"))] Archive if extension == "pdf" => DynamicBendable::Archive( PdfDocument::try_from_data_bytes( bytes, @@ -168,7 +170,7 @@ pub mod dynamic { ) .map_err(OpenError::Pdf)?, ), - #[cfg(feature = "documents")] + #[cfg(all(feature = "documents", feature = "shiva"))] Archive | Doc => { let document_type = DocumentType::from_extension(extension) .ok_or(ShivaUnknownExtensionError) @@ -182,7 +184,7 @@ pub mod dynamic { document_type, )) } - #[cfg(feature = "fonts")] + #[cfg(all(feature = "fonts", feature = "font-kit"))] Font => DynamicBendable::Font(FontKitFont::try_from_data_bytes( bytes, (), diff --git a/bingus/src/snd.rs b/bingus/src/snd.rs index dda4d48..b6c1951 100644 --- a/bingus/src/snd.rs +++ b/bingus/src/snd.rs @@ -1,5 +1,8 @@ +#[cfg(feature = "symphonia")] pub use symphonia::core::*; mod raw; pub use raw::*; +#[cfg(feature = "symphonia")] mod simphonia; +#[cfg(feature = "symphonia")] pub use simphonia::*; diff --git a/bong/Cargo.toml b/bong/Cargo.toml index c6aef48..001655a 100644 --- a/bong/Cargo.toml +++ b/bong/Cargo.toml @@ -12,7 +12,7 @@ keywords.workspace = true [dependencies] anyhow = "1" -bingus = "0.6" +bingus = "0.8" strum = { version = "0", features = ["derive"] } derive-new = "0" clap = { version = "4.5.40", features = ["derive"] } diff --git a/bong/src/cli.rs b/bong/src/cli.rs index 774f621..4c1492a 100644 --- a/bong/src/cli.rs +++ b/bong/src/cli.rs @@ -18,17 +18,18 @@ use strum::{Display, EnumString}; pub(crate) struct Cli { /// Input file or standard input ("-"). pub(crate) input: FileOrStd, - #[command(subcommand)] - pub(crate) output_format: OutputFormat, /// Output file or standard output ("-"). pub(crate) output: FileOrStd, + /// Output format + #[command(subcommand)] + pub(crate) output_format: Option, } #[derive(Clone, Copy, Parser)] #[cfg_attr(debug_assertions, derive(Debug))] pub struct Dimensions { - pub width: u32, - pub height: u32, + pub width: Option, + pub height: Option, } #[derive(Clone, Copy, Parser)] @@ -40,16 +41,23 @@ pub struct ShivaFormat { #[derive(Clone, Copy, Parser)] pub struct Mp3Format { - #[arg(default_value = "44100")] pub sample_rate: u32, - #[arg(default_value = "128")] pub bitrate: u16, - #[arg(default_value = "5")] pub quality: u8, - #[arg(default_value_t)] pub sample_format: Mp3SampleFormat, } +impl Default for Mp3Format { + fn default() -> Self { + Self { + sample_rate: 44100, + bitrate: 128, + quality: 5, + sample_format: Default::default(), + } + } +} + #[derive(Clone, Copy, EnumString, Default, Display)] #[strum(ascii_case_insensitive)] pub enum Mp3SampleFormat { @@ -63,12 +71,19 @@ pub enum Mp3SampleFormat { #[derive(Clone, Copy, Parser)] pub struct WavFormat { - #[arg(default_value = "48000")] pub sample_rate: u32, - #[arg(default_value_t)] pub sample_format: WavSampleFormat, } +impl Default for WavFormat { + fn default() -> Self { + Self { + sample_rate: 48000, + sample_format: Default::default(), + } + } +} + #[derive(Clone, Copy, EnumString, Display, Default)] #[strum(ascii_case_insensitive)] pub enum WavSampleFormat { @@ -81,12 +96,20 @@ pub enum WavSampleFormat { #[derive(Clone, Copy, Parser)] pub struct FlacFormat { - #[arg(default_value = "48000")] pub sample_rate: usize, - #[arg(value_parser = flac_bps_value_parser, default_value = "16")] + #[arg(value_parser = flac_bps_value_parser)] pub bps: usize, } +impl Default for FlacFormat { + fn default() -> Self { + Self { + sample_rate: 48000, + bps: 16, + } + } +} + fn flac_bps_value_parser(input: &str) -> anyhow::Result { input.parse().context("not a number").and_then(|n| match n { 8 | 16 | 24 => Ok(n), @@ -106,8 +129,8 @@ impl From for hound::SampleFormat { impl From for bingus::img::Dimensions { fn from(value: Dimensions) -> Self { Self { - width: value.width, - height: value.height, + width: value.width.unwrap_or_default(), + height: value.height.unwrap_or_default(), } } } @@ -173,8 +196,10 @@ where } } -#[derive(Clone, Parser)] +#[derive(Clone, Parser, Default)] pub(crate) enum OutputFormat { + #[default] + Auto, Bytes, Pdf, ShivaDocument(ShivaFormat), diff --git a/bong/src/main.rs b/bong/src/main.rs index 5292fe7..dbac40a 100644 --- a/bong/src/main.rs +++ b/bong/src/main.rs @@ -1,3 +1,5 @@ +#![deny(unused_crate_dependencies)] + use std::{ borrow::Cow, fs::File, @@ -6,12 +8,12 @@ use std::{ use anyhow::{anyhow, bail, Context}; use bingus::{ - doc::{printpdf::PdfDocument, ShivaDocument}, + doc::{printpdf::PdfDocument, DocumentType, ShivaDocument, ShivaFormat}, dynamic::{self, DynamicBendable}, fnt::Font, img::{ - GrayAlphaImage, GrayImage, Image, Luma, LumaA, Rgb, Rgb32FImage, RgbImage, Rgba, - Rgba32FImage, RgbaImage, + Dimensions, GrayAlphaImage, GrayImage, Image, ImageFormat, Luma, LumaA, Rgb, Rgb32FImage, + RgbImage, Rgba, Rgba32FImage, RgbaImage, }, snd::{conv::IntoSample, sample::i24, RawSamples}, txt::Text, @@ -42,7 +44,7 @@ fn main() -> anyhow::Result<()> { .context("reading input")?; buf }; - let mut bytes = match dynamic::open(&mut Cursor::new(input_bytes.clone())) + let bytes = match dynamic::open(&mut Cursor::new(input_bytes.clone())) .context("guessing media type / loading media")? .or(String::from_utf8(input_bytes.clone()) .ok() @@ -70,10 +72,483 @@ fn main() -> anyhow::Result<()> { .context("bending input into bytes")?; match output { cli::FileOrStd::File(file) => { - match output_format { - cli::OutputFormat::Bytes => { - File::bend_from(bytes, Box::new(file), Default::default()) - .context("writing to file")?; + match output_format.unwrap_or_default() { + cli::OutputFormat::Auto => { + let extension = file + .extension() + .and_then(|os_str| os_str.to_str()) + .map(|s| s.to_ascii_lowercase()); + if let Some(ext) = &extension { + match ext.as_str() { + "pdf" => { + File::bend_from( + PdfDocument::bend_from(bytes, (), Default::default()) + .map_err(|s| anyhow!("{}", s)) + .context("bending into PDF document")? + .into_data_bytes(), + Box::new(file), + Default::default(), + ) + .context("writing PDF to output file")?; + } + _valid_shiva_extension + if DocumentType::from_extension(ext).is_some() => + { + let document_type = DocumentType::from_extension(ext).unwrap(); + File::bend_from( + ShivaDocument::bend_from( + bytes, + ShivaFormat::new(document_type, document_type), + Default::default(), + ) + .context("bending into a Shiva Document")?, + Box::new(file), + Default::default(), + ) + .context("writing Shiva Document to output file")?; + } + "ttf" | "ttc" | "otf" | "otc" | "cff" | "pfb" | "pfa" | "pfm" + | "afm" | "bdf" | "pcf" | "fon" | "fnt" | "svg" | "dfont" | "woff" + | "woff2" | "eot" => { + File::bend_from( + Font::bend_from(bytes, (), Default::default()) + .context("bending to font")? + .into_data_bytes(), + Box::new(file), + Default::default(), + ) + .context("writing font to output file")?; + } + "mp3" => { + eprintln!("warning: Rust MP3 lame encoder is freaky!"); + let mp3_format = Mp3Format::default(); + let Mp3Format { + bitrate, + quality, + sample_rate, + sample_format, + } = mp3_format; + let buff = { + use mp3lame_encoder::{Bitrate::*, Quality::*}; + let mut encoder = Mp3EncoderBuilder::new() + .context("Failed to create MP3 encoder builder")?; + encoder + .set_num_channels(1) + .context("Failed to set MP3 encoder channels")?; + encoder + .set_sample_rate(sample_rate) + .context("Failed to set MP3 encoder sample rate")?; + encoder + .set_brate(match bitrate { + 8 => Kbps8, + 16 => Kbps16, + 24 => Kbps24, + 32 => Kbps32, + 40 => Kbps40, + 48 => Kbps48, + 64 => Kbps64, + 80 => Kbps80, + 96 => Kbps96, + 112 => Kbps112, + 128 => Kbps128, + 160 => Kbps160, + 192 => Kbps192, + 224 => Kbps224, + 256 => Kbps256, + 320 => Kbps320, + _ => bail!("invalid MP3 bitrate"), + }) + .context("Failed to set MP3 encoder bitrate")?; + encoder + .set_quality(match quality { + 0 => Best, + 1 => SecondBest, + 2 => NearBest, + 3 => VeryNice, + 4 => Nice, + 5 => Good, + 6 => Decent, + 7 => Ok, + 8 => SecondWorst, + 9 => Worst, + _ => bail!("invalid MP3 quality"), + }) + .context("Failed to set MP3 encoder quality")?; + + let mut encoder = encoder + .build() + .context("Failed to initialize MP3 encoder")?; + + let (encoded_size, mut output) = { + match sample_format { + cli::Mp3SampleFormat::U16 => { + let (left, right): (Vec<_>, Vec<_>) = + RawSamples::::bend_from( + bytes, + (), + Default::default(), + ) + .unwrap() + .into_inner() + .chunks_exact(2) + .map(|c| (c[0], c[1])) + .unzip(); + let input = DualPcm { + left: &left, + right: &right, + }; + let mut output = Vec::with_capacity( + mp3lame_encoder::max_required_buffer_size( + left.len() + right.len(), + ), + ); + ( + encoder + .encode(input, output.spare_capacity_mut()) + .context("Failed MP3 encoding")?, + output, + ) + } + cli::Mp3SampleFormat::I16 => { + let (left, right): (Vec<_>, Vec<_>) = + RawSamples::::bend_from( + bytes, + (), + Default::default(), + ) + .unwrap() + .into_inner() + .chunks_exact(2) + .map(|c| (c[0], c[1])) + .unzip(); + let input = DualPcm { + left: &left, + right: &right, + }; + let mut output = Vec::with_capacity( + mp3lame_encoder::max_required_buffer_size( + left.len() + right.len(), + ), + ); + ( + encoder + .encode(input, output.spare_capacity_mut()) + .context("Failed MP3 encoding")?, + output, + ) + } + cli::Mp3SampleFormat::I32 => { + let (left, right): (Vec<_>, Vec<_>) = + RawSamples::::bend_from( + bytes, + (), + Default::default(), + ) + .unwrap() + .into_inner() + .chunks_exact(2) + .map(|c| (c[0], c[1])) + .unzip(); + let input = DualPcm { + left: &left, + right: &right, + }; + let mut output = Vec::with_capacity( + mp3lame_encoder::max_required_buffer_size( + left.len() + right.len(), + ), + ); + ( + encoder + .encode(input, output.spare_capacity_mut()) + .context("Failed MP3 encoding")?, + output, + ) + } + cli::Mp3SampleFormat::F32 => { + let (left, right): (Vec<_>, Vec<_>) = + RawSamples::::bend_from( + bytes, + (), + Default::default(), + ) + .unwrap() + .into_inner() + .chunks_exact(2) + .map(|c| (c[0], c[1])) + .unzip(); + let input = DualPcm { + left: &left, + right: &right, + }; + let mut output = Vec::with_capacity( + mp3lame_encoder::max_required_buffer_size( + left.len() + right.len(), + ), + ); + ( + encoder + .encode(input, output.spare_capacity_mut()) + .context("Failed MP3 encoding")?, + output, + ) + } + cli::Mp3SampleFormat::F64 => { + let (left, right): (Vec<_>, Vec<_>) = + RawSamples::::bend_from( + bytes, + (), + Default::default(), + ) + .unwrap() + .into_inner() + .chunks_exact(2) + .map(|c| (c[0], c[1])) + .unzip(); + let input = DualPcm { + left: &left, + right: &right, + }; + let mut output = Vec::with_capacity( + mp3lame_encoder::max_required_buffer_size( + left.len() + right.len(), + ), + ); + ( + encoder + .encode(input, output.spare_capacity_mut()) + .context("Failed MP3 encoding")?, + output, + ) + } + } + }; + unsafe { + output.set_len(output.len().wrapping_add(encoded_size)); + } + let encoded_size = encoder + .flush::(output.spare_capacity_mut()) + .context( + "Failed MP3 flushing (don't know what that means)", + )?; + unsafe { + output.set_len(output.len().wrapping_add(encoded_size)); + } + output + }; + File::bend_from(buff, Box::new(file), Default::default()) + .context("writing MP3 to output file")?; + } + "wav" => { + let wav_format = Default::default(); + let WavFormat { + sample_rate, + sample_format, + } = wav_format; + match sample_format { + cli::WavSampleFormat::I8 => { + let samples = RawSamples::::bend_from( + bytes, + (), + Default::default(), + ) + .unwrap_infallible(); + let mut buff = Cursor::new(Vec::with_capacity( + samples.as_ref().len() * 8, + )); + { + let mut writer = WavWriter::new( + &mut buff, + WavSpec { + channels: 1, + sample_rate, + bits_per_sample: i8::BITS as u16, + sample_format: sample_format.into(), + }, + ) + .context("Failed to create WAV writer")?; + for sample in samples.as_ref() { + writer.write_sample(*sample)?; + } + } + File::try_from_data_bytes( + buff.into_inner(), + Box::new(file), + Default::default(), + ) + .context("writing WAV data to output file")? + } + cli::WavSampleFormat::I16 => { + let samples = RawSamples::::bend_from( + bytes, + (), + Default::default(), + ) + .unwrap_infallible(); + let mut buff = Cursor::new(Vec::with_capacity( + samples.as_ref().len() * 8, + )); + { + let mut writer = WavWriter::new( + &mut buff, + WavSpec { + channels: 1, + sample_rate, + bits_per_sample: i16::BITS as u16, + sample_format: sample_format.into(), + }, + ) + .context("Failed to create WAV writer")?; + for sample in samples.as_ref() { + writer.write_sample(*sample)?; + } + } + File::try_from_data_bytes( + buff.into_inner(), + Box::new(file), + Default::default(), + ) + .context("writing WAV data to output file")? + } + cli::WavSampleFormat::I32 => { + let samples = RawSamples::::bend_from( + bytes, + (), + Default::default(), + ) + .unwrap_infallible(); + let mut buff = Cursor::new(Vec::with_capacity( + samples.as_ref().len() * 8, + )); + { + let mut writer = WavWriter::new( + &mut buff, + WavSpec { + channels: 1, + sample_rate, + bits_per_sample: i32::BITS as u16, + sample_format: sample_format.into(), + }, + ) + .context("Failed to create WAV writer")?; + for sample in samples.as_ref() { + writer.write_sample(*sample)?; + } + } + File::try_from_data_bytes( + buff.into_inner(), + Box::new(file), + Default::default(), + ) + .context("writing WAV data to output file")? + } + cli::WavSampleFormat::F32 => { + let samples = RawSamples::::bend_from( + bytes, + (), + Default::default(), + ) + .unwrap_infallible(); + let mut buff = Cursor::new(Vec::with_capacity( + samples.as_ref().len() * 8, + )); + { + let mut writer = WavWriter::new( + &mut buff, + WavSpec { + channels: 1, + sample_rate, + bits_per_sample: 32, + sample_format: sample_format.into(), + }, + ) + .context("Failed to create WAV writer")?; + for sample in samples.as_ref() { + writer.write_sample(*sample)?; + } + } + File::try_from_data_bytes( + buff.into_inner(), + Box::new(file), + Default::default(), + ) + .context("writing WAV data to output file")? + } + }; + } + "flac" => { + let flac_format = Default::default(); + let FlacFormat { sample_rate, bps } = flac_format; + let config = flacenc::config::Encoder::default() + .into_verified() + .expect("Config data error."); + let samples = + RawSamples::::bend_from(bytes, (), Default::default()) + .unwrap_infallible(); + let source = flacenc::source::MemSource::from_samples( + samples.as_ref().iter().copied() + .map(|sample| match bps { + 8 => IntoSample::::into_sample(sample) as i32, + 16 => IntoSample::::into_sample(sample) as i32, + 24 => IntoSample::::into_sample(sample).inner(), + _=> unimplemented!("sorry, the current implementation for the flac encoder doesn't support any other bitrate than 8, 16 or 24.") + }) + .collect::>() + .as_slice(), + 1, + bps, + sample_rate, + ); + let flac_stream = flacenc::encode_with_fixed_block_size( + &config, + source, + config.block_size, + ) + .expect("Encode failed."); + + // `Stream` imlpements `BitRepr` so you can obtain the encoded stream via + // `ByteSink` struct that implements `BitSink`. + let mut sink = flacenc::bitsink::ByteSink::new(); + flac_stream + .write(&mut sink) + .context("Failed to write samples to FLAC byte sink")?; + File::try_from_data_bytes( + sink.into_inner(), + Box::new(file), + Default::default(), + ) + .context("writing FLAC data to output file")?; + } + "txt" => { + File::bend_from( + Text::try_from_data_bytes(bytes, (), Default::default()) + .context("invalid UTF-8")?, + Box::new(file), + Default::default(), + ) + .context("writing text to output file")?; + } + _valid_image_extension + if ImageFormat::from_extension(ext).is_some() => + { + let len = bytes.len(); + RgbImage::from_data_bytes( + bytes, + Dimensions::square((len / 3).isqrt() as u32), + Default::default(), + ) + .save(file) + .context("writing image data to output file")? + } + "bin" => { + File::bend_from(bytes, Box::new(file), Default::default()) + .context("writing to file")?; + } + _ => bail!("Unknown file extension! Can't guess output file type. Please use one of the available types instead (see --help)."), + } + } else { + bail!("Can't guess the output file type without a file extension. Please specify one."); + } } cli::OutputFormat::Pdf => { File::bend_from( @@ -471,76 +946,126 @@ fn main() -> anyhow::Result<()> { .context("writing text to output file")?; } cli::OutputFormat::ImageLuma8(dimensions) => { - GrayImage::from_data_bytes(bytes, dimensions.into(), Default::default()) - .save(file) - .context("writing image data to output file")? + let len = bytes.len(); + GrayImage::from_data_bytes( + bytes, + if_zero_adjust(dimensions.into(), len), + Default::default(), + ) + .save(file) + .context("writing image data to output file")? } cli::OutputFormat::ImageLumaA8(dimensions) => { - GrayAlphaImage::from_data_bytes(bytes, dimensions.into(), Default::default()) - .save(file) - .context("writing image data to output file")? + let len = bytes.len(); + GrayAlphaImage::from_data_bytes( + bytes, + if_zero_adjust(dimensions.into(), len), + Default::default(), + ) + .save(file) + .context("writing image data to output file")? } cli::OutputFormat::ImageRgb8(dimensions) => { - RgbImage::from_data_bytes(bytes, dimensions.into(), Default::default()) - .save(file) - .context("writing image data to output file")? + let len = bytes.len(); + RgbImage::from_data_bytes( + bytes, + if_zero_adjust(dimensions.into(), len), + Default::default(), + ) + .save(file) + .context("writing image data to output file")? } cli::OutputFormat::ImageRgba8(dimensions) => { - RgbaImage::from_data_bytes(bytes, dimensions.into(), Default::default()) - .save(file) - .context("writing image data to output file")? + let len = bytes.len(); + RgbaImage::from_data_bytes( + bytes, + if_zero_adjust(dimensions.into(), len), + Default::default(), + ) + .save(file) + .context("writing image data to output file")? } cli::OutputFormat::ImageLuma16(dimensions) => { + let len = bytes.len(); Image::, Vec>::from_data_bytes( bytes, - dimensions.into(), + if_zero_adjust(dimensions.into(), len), Default::default(), ) .save(file) .context("writing image data to output file")? } cli::OutputFormat::ImageLumaA16(dimensions) => { + let len = bytes.len(); Image::, Vec>::from_data_bytes( bytes, - dimensions.into(), + if_zero_adjust(dimensions.into(), len), Default::default(), ) .save(file) .context("writing image data to output file")? } cli::OutputFormat::ImageRgb16(dimensions) => { + let len = bytes.len(); Image::, Vec>::from_data_bytes( bytes, - dimensions.into(), + if_zero_adjust(dimensions.into(), len), Default::default(), ) .save(file) .context("writing image data to output file")? } cli::OutputFormat::ImageRgba16(dimensions) => { + let len = bytes.len(); Image::, Vec>::from_data_bytes( bytes, - dimensions.into(), + if_zero_adjust(dimensions.into(), len), Default::default(), ) .save(file) .context("writing image data to output file")? } cli::OutputFormat::ImageRgb32F(dimensions) => { - Rgb32FImage::from_data_bytes(bytes, dimensions.into(), Default::default()) - .save(file) - .context("writing image data to output file")? + let len = bytes.len(); + Rgb32FImage::from_data_bytes( + bytes, + if_zero_adjust(dimensions.into(), len), + Default::default(), + ) + .save(file) + .context("writing image data to output file")? } cli::OutputFormat::ImageRgba32F(dimensions) => { - Rgba32FImage::from_data_bytes(bytes, dimensions.into(), Default::default()) - .save(file) - .context("writing image data to output file")? + let len = bytes.len(); + Rgba32FImage::from_data_bytes( + bytes, + if_zero_adjust(dimensions.into(), len), + Default::default(), + ) + .save(file) + .context("writing image data to output file")? + } + cli::OutputFormat::Bytes => { + File::bend_from(bytes, Box::new(file), Default::default()) + .context("writing to file")?; } }; } cli::FileOrStd::Std(_) => Stdout::new() - .write_all(&mut bytes) + .write_all(&bytes) .context("writing to stdout")?, }; Ok(()) } + +fn if_zero_adjust(dimensions: Dimensions, bytes_len: usize) -> Dimensions { + if dimensions == Dimensions::square(0) { + Dimensions::square((bytes_len / 3).isqrt() as u32) + } else if dimensions.width == 0 { + Dimensions::square(dimensions.height) + } else if dimensions.height == 0 { + Dimensions::square(dimensions.width) + } else { + dimensions + } +}