diff --git a/Cargo.lock b/Cargo.lock index 95cb141..e350c09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,24 @@ version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +[[package]] +name = "arboard" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4" +dependencies = [ + "clipboard-win", + "core-graphics", + "image", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "parking_lot", + "windows-sys 0.48.0", + "x11rb", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -38,6 +56,21 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -71,6 +104,55 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + [[package]] name = "crc32fast" version = "1.3.2" @@ -117,6 +199,22 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "error-code" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" + [[package]] name = "fastrand" version = "2.1.1" @@ -138,7 +236,7 @@ version = "24.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8add37afff2d4ffa83bc748a70b4b1370984f6980768554182424ef71447c35f" dependencies = [ - "bitflags", + "bitflags 1.3.2", "rustc_version", ] @@ -152,6 +250,33 @@ dependencies = [ "miniz_oxide 0.7.1", ] +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -161,6 +286,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + [[package]] name = "getrandom" version = "0.2.11" @@ -199,27 +334,28 @@ dependencies = [ [[package]] name = "image" -version = "0.25.2" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" +checksum = "bc144d44a31d753b02ce64093d532f55ff8dc4ebf2ffb8a63c0dda691385acae" dependencies = [ "bytemuck", "byteorder-lite", "image-webp", "num-traits", "png", + "tiff", "zune-core", "zune-jpeg", ] [[package]] name = "image-webp" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d730b085583c4d789dfd07fdcf185be59501666a90c97c40162b37e4fdad272d" +checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f" dependencies = [ "byteorder-lite", - "thiserror", + "quick-error", ] [[package]] @@ -228,6 +364,12 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "lexopt" version = "0.3.0" @@ -240,6 +382,22 @@ version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.20" @@ -273,9 +431,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -290,6 +448,105 @@ dependencies = [ "libc", ] +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.6.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.6.0", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + [[package]] name = "ocrs" version = "0.9.0" @@ -311,6 +568,7 @@ name = "ocrs-cli" version = "0.9.0" dependencies = [ "anyhow", + "arboard", "home", "image", "lexopt", @@ -330,6 +588,29 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.0", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -342,7 +623,7 @@ version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", @@ -358,6 +639,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.35" @@ -387,6 +674,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "ring" version = "0.17.7" @@ -466,6 +762,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustls" version = "0.23.12" @@ -504,6 +813,12 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "1.0.20" @@ -597,6 +912,17 @@ dependencies = [ "syn", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -736,6 +1062,12 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "windows-sys" version = "0.48.0" @@ -868,6 +1200,23 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "gethostname", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + [[package]] name = "zeroize" version = "1.7.0" @@ -882,9 +1231,9 @@ checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" [[package]] name = "zune-jpeg" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" dependencies = [ "zune-core", ] diff --git a/ocrs-cli/Cargo.toml b/ocrs-cli/Cargo.toml index 079c9f3..0a1f15c 100644 --- a/ocrs-cli/Cargo.toml +++ b/ocrs-cli/Cargo.toml @@ -9,7 +9,7 @@ homepage = "https://github.com/robertknight/ocrs" repository = "https://github.com/robertknight/ocrs" [dependencies] -image = { version = "0.25.2", default-features = false, features = ["png", "jpeg", "webp"] } +image = { version = "0.25.4", default-features = false, features = ["png", "jpeg", "webp"] } png = "0.17.14" serde_json = "1.0.127" rten = { workspace = true } @@ -19,6 +19,7 @@ ocrs = { path = "../ocrs", version = "0.9.0" } lexopt = "0.3.0" url = "2.5.2" anyhow = "1.0.89" +arboard = { version = "3.4.1", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] ureq = "2.10.1" @@ -27,6 +28,7 @@ home = "0.5.9" [features] # Use AVX-512 instructions if available. Requires nightly Rust. avx512 = ["rten/avx512"] +clipboard = ["dep:arboard"] [[bin]] name = "ocrs" diff --git a/ocrs-cli/src/main.rs b/ocrs-cli/src/main.rs index fe0b1b3..1258bff 100644 --- a/ocrs-cli/src/main.rs +++ b/ocrs-cli/src/main.rs @@ -1,9 +1,12 @@ use std::collections::VecDeque; -use std::error::Error; use std::fs; use std::io::BufWriter; use anyhow::{anyhow, Context}; +#[cfg(feature = "clipboard")] +use std::borrow::Cow; +// use clipboard_rs::{common::RustImage, Clipboard, RustImageData}; +use image::{DynamicImage, ImageBuffer, Rgb, Rgba}; use ocrs::{DecodeMethod, DimOrder, ImageSource, OcrEngine, OcrEngineParams, OcrInput}; use rten_imageproc::RotatedRect; use rten_tensor::prelude::*; @@ -82,8 +85,8 @@ struct Args { /// Path to text recognition model. recognition_model: Option, - /// Path to image to process. - image: String, + /// Path to image to process, None if `clip` is true. + image: Option, /// Enable debug output. debug: bool, @@ -126,6 +129,7 @@ fn parse_args() -> Result { let mut output_format = OutputFormat::Text; let mut output_path = None; let mut recognition_model = None; + let mut clip = false; let mut text_line_images = false; let mut text_map = false; let mut text_mask = false; @@ -149,6 +153,10 @@ fn parse_args() -> Result { Long("detect-model") => { detection_model = Some(parser.value()?.string()?); } + #[cfg(feature = "clipboard")] + Short('c') | Long("clip") => { + clip = true; + } Short('j') | Long("json") => { output_format = OutputFormat::Json; } @@ -185,6 +193,7 @@ Options: -a, --alphabet Specify the alphabet used by the recognition model + {} --detect-model @@ -234,7 +243,15 @@ Advanced options: Generate a binary text mask for the input image ", - bin_name = parser.bin_name().unwrap_or("ocrs") + if cfg!(feature = "clipboard") { + " + -c, --clip + + Use image from clipboard instead of path" + } else { + "" + }, + bin_name = parser.bin_name().unwrap_or("ocrs"), ); std::process::exit(0); } @@ -246,6 +263,23 @@ Advanced options: } } + #[cfg(feature = "clipboard")] + let image = match (clip, values.pop_front()) { + (true, Some(_)) => { + println!("Warning: Ignoring image path, using clipboard instead"); + None + } + (true, None) => None, + (false, Some(val)) => Some(val), + (false, None) => { + println!("Error: No image path or clipboard option provided"); + std::process::exit(1); + } + }; + + #[cfg(not(feature = "clipboard"))] + let image = Some(values.pop_front().ok_or("missing argument")?); + Ok(Args { alphabet, beam_search, @@ -253,7 +287,7 @@ Advanced options: detection_model, output_format, output_path, - image: values.pop_front().ok_or("missing `` arg")?, + image, recognition_model, text_map, text_mask, @@ -269,7 +303,31 @@ const DETECTION_MODEL: &str = "https://ocrs-models.s3-accelerate.amazonaws.com/t const RECOGNITION_MODEL: &str = "https://ocrs-models.s3-accelerate.amazonaws.com/text-recognition.rten"; -fn main() -> Result<(), Box> { +fn open_image(src: &str) -> anyhow::Result, Vec>> { + image::open(src) + .map(DynamicImage::into_rgba8) + .with_context(|| format!("Failed to read image from {src}")) +} + +#[cfg(feature = "clipboard")] +fn get_image_from_clipboard() -> anyhow::Result, Vec>> { + let Ok(mut clipboard) = arboard::Clipboard::new() else { + anyhow::bail!("Failed to initialize clipboard"); + }; + let Ok(data) = clipboard.get_image() else { + anyhow::bail!("No clipboard image data!"); + }; + let Some(img) = ImageBuffer::, _>::from_raw( + data.width as u32, + data.height as u32, + data.bytes.into_owned(), + ) else { + anyhow::bail!("Failed to create image from clipboard data"); + }; + Ok(img) +} + +fn main() -> anyhow::Result<()> { let args = parse_args()?; // Fetch and load ML models. @@ -316,17 +374,21 @@ fn main() -> Result<(), Box> { })?; // Read image into HWC tensor. - let color_img: NdTensor = image::open(&args.image) - .map(|image| { - let image = image.into_rgb8(); - let (width, height) = image.dimensions(); - let in_chans = 3; - NdTensor::from_data( - [height as usize, width as usize, in_chans], - image.into_vec(), - ) - }) - .with_context(|| format!("Failed to read image from {}", &args.image))?; + + #[cfg(feature = "clipboard")] + let image = match &args.image { + Some(path) => open_image(path), + None => get_image_from_clipboard(), + }?; + #[cfg(not(feature = "clipboard"))] + let image = open_image(args.image.as_deref().expect("Always set if clipboard disabled"))?; + + let (width, height) = image.dimensions(); + let in_chans = 4; + let color_img = NdTensor::from_data( + [height as usize, width as usize, in_chans], + image.into_raw(), + ); // Preprocess image for use with OCR engine. let color_img_source = ImageSource::from_tensor(color_img.view(), DimOrder::Hwc)?; @@ -357,7 +419,7 @@ fn main() -> Result<(), Box> { let line_texts = engine.recognize_text(&ocr_input, &line_rects)?; - let write_output_str = |content: String| -> Result<(), Box> { + let write_output_str = |content: String| -> Result<(), anyhow::Error> { if let Some(output_path) = &args.output_path { std::fs::write(output_path, content.into_bytes()) .with_context(|| format!("Failed to write output to {}", output_path))?; @@ -374,7 +436,7 @@ fn main() -> Result<(), Box> { } OutputFormat::Json => { let content = format_json_output(FormatJsonArgs { - input_path: &args.image, + input_path: args.image.as_deref().unwrap_or("Clipboard image"), input_hw: color_img.shape()[1..].try_into()?, text_lines: &line_texts, }); @@ -388,7 +450,7 @@ fn main() -> Result<(), Box> { }; let annotated_img = generate_annotated_png(png_args); let Some(output_path) = args.output_path else { - return Err("Output path must be specified when generating annotated PNG".into()); + anyhow::bail!("Output path must be specified when generating annotated PNG"); }; write_image(&output_path, annotated_img.view()) .with_context(|| format!("Failed to write output to {}", &output_path))?;