1use std::error::Error;
2use std::ffi::OsStr;
3use std::fs;
4use std::path::Path;
5
6use image::{DynamicImage, Rgba};
7use qrcode::render::{svg, unicode, Renderer};
8use qrcode::QrCode;
9use resvg::{
10 tiny_skia::{Pixmap, Transform},
11 usvg,
12};
13use rqrr::PreparedImage;
14
15use crate::cli::args::{Arguments, OutputFormat};
16use crate::errors::BoxResult;
17
18pub fn make_code(data: &str) -> BoxResult<QrCode> {
19 Ok(QrCode::new(data.as_bytes())?)
20}
21
22pub fn is_svg_path(path: &Path) -> bool {
23 matches!(
24 path.extension().and_then(OsStr::to_str),
25 Some("svg" | "svgz")
26 )
27}
28
29fn convert_svg_to_image_data(data: &[u8]) -> BoxResult<Vec<u8>> {
30 let options = usvg::Options::default();
31 let tree = usvg::Tree::from_data(data, &options)?;
32
33 let pixmap_size = tree.size().to_int_size();
34 let mut pixmap = Pixmap::new(pixmap_size.width(), pixmap_size.height()).unwrap();
35 resvg::render(&tree, Transform::default(), &mut pixmap.as_mut());
36 let png_data = pixmap.encode_png()?;
37
38 Ok(png_data)
39}
40
41fn convert_svg_to_image(input: &[u8]) -> BoxResult<DynamicImage> {
43 let svg_img = convert_svg_to_image_data(input)?;
44 image::load_from_memory_with_format(&svg_img, image::ImageFormat::Png).map_err(Into::into)
45}
46
47pub fn to_svg(code: &QrCode, view_arguments: QrCodeViewArguments) -> String {
49 let (dark_color, light_color) = match view_arguments.invert_colors {
50 true => (svg::Color("white"), svg::Color("black")),
51 false => (svg::Color("black"), svg::Color("white")),
52 };
53
54 Renderer::<svg::Color<'_>>::new(&code.to_colors(), code.width(), view_arguments.margin)
55 .dark_color(dark_color)
56 .light_color(light_color)
57 .build()
58}
59
60pub fn to_unicode(code: &QrCode, view_arguments: QrCodeViewArguments) -> String {
62 let (dark_color, light_color) = match view_arguments.invert_colors {
63 true => (unicode::Dense1x2::Dark, unicode::Dense1x2::Light),
64 false => (unicode::Dense1x2::Light, unicode::Dense1x2::Dark),
65 };
66
67 Renderer::<unicode::Dense1x2>::new(&code.to_colors(), code.width(), view_arguments.margin)
68 .dark_color(dark_color)
69 .light_color(light_color)
70 .build()
71}
72
73pub fn to_image(code: &QrCode, view_arguments: QrCodeViewArguments) -> DynamicImage {
75 let mut image = DynamicImage::ImageRgba8(
76 Renderer::<Rgba<u8>>::new(&code.to_colors(), code.width(), view_arguments.margin).build(),
77 );
78
79 if view_arguments.invert_colors {
80 image.invert()
81 }
82
83 image
84}
85
86pub fn print_code_to_term(code: &QrCode, view_arguments: QrCodeViewArguments) {
87 println!("\n{}", to_unicode(code, view_arguments));
88}
89
90pub fn extract_contents_from_image(img: DynamicImage) -> Vec<String> {
92 let mut prepared_img = PreparedImage::prepare(img.to_luma8());
93
94 prepared_img
95 .detect_grids()
96 .into_iter()
97 .map(|grid| {
98 grid.decode()
99 .map(|(_, content)| content)
100 .unwrap_or_else(|err| {
101 eprintln!("\nERROR reading data from qr code: {}", err);
102 panic!();
103 })
104 })
105 .collect()
106}
107
108pub fn read_data_from_image(file: &Path) -> BoxResult<Vec<String>> {
109 let img = if is_svg_path(file) {
110 let input = fs::read(file)?;
111 convert_svg_to_image(&input)?
112 } else {
113 image::open(file)?
114 };
115
116 Ok(extract_contents_from_image(img))
117}
118
119pub fn save(file: &Path, code: &QrCode, image_save_args: ImageSaveArguments) -> BoxResult<()> {
120 let save_fn: fn(&Path, &QrCode, QrCodeViewArguments) -> BoxResult<()> =
121 match image_save_args.output_format {
122 OutputFormat::Image => save_image,
123 OutputFormat::Svg => save_svg,
124 OutputFormat::Unicode => save_unicode,
125 };
126 save_fn(file, code, image_save_args.view_arguments)
127}
128
129fn save_unicode(file: &Path, code: &QrCode, view_arguments: QrCodeViewArguments) -> BoxResult<()> {
130 let unicode_image = to_unicode(code, view_arguments);
131 fs::write(file, unicode_image).map_err(|err| handle_save_error(file, err))
132}
133
134fn save_image(file: &Path, code: &QrCode, view_arguments: QrCodeViewArguments) -> BoxResult<()> {
135 let image = to_image(code, view_arguments);
136
137 let image = match file.extension().and_then(OsStr::to_str) {
138 Some("jpg") | Some("jpeg") | Some("pbm") | Some("pgm") | Some("ppm") => {
139 DynamicImage::ImageRgb8(image.into_rgb8())
140 }
141 _ => image,
142 };
143
144 image.save(file).map_err(|err| handle_save_error(file, err))
145}
146
147fn save_svg(file: &Path, code: &QrCode, view_arguments: QrCodeViewArguments) -> BoxResult<()> {
148 let svg_image = to_svg(code, view_arguments);
149 fs::write(file, svg_image).map_err(|err| handle_save_error(file, err))
150}
151
152fn handle_save_error<E: Error + 'static>(file: &Path, error: E) -> Box<dyn Error> {
153 eprintln!("\nERROR saving the file: {}", error);
154 fs::remove_file(file).unwrap();
155 Box::new(error)
156}
157
158pub struct ImageSaveArguments<'a> {
159 pub output_format: &'a OutputFormat,
160 pub view_arguments: QrCodeViewArguments,
161}
162
163pub struct QrCodeViewArguments {
164 pub margin: u32,
165 pub invert_colors: bool,
166}
167
168impl<'a> From<&'a Arguments> for ImageSaveArguments<'a> {
169 fn from(args: &'a Arguments) -> Self {
170 Self {
171 output_format: &args.output_format,
172 view_arguments: args.into(),
173 }
174 }
175}
176
177impl From<&Arguments> for QrCodeViewArguments {
178 fn from(args: &Arguments) -> Self {
179 Self {
180 margin: args.margin,
181 invert_colors: args.invert_colors,
182 }
183 }
184}