escpos_md/instruction/
escpos_image.rs

1use crate::command::{Command, Justification};
2use crate::error::{Error, Result};
3use image::Pixel;
4
5#[derive(Debug, Clone, Copy)]
6pub enum BitMapAlgorithm {
7    Threshold(u8),
8    Dithering,
9}
10
11#[derive(Debug, Clone)]
12pub struct ImageOptions {
13    bit_map_algorithm: BitMapAlgorithm,
14    scale: f64,
15    filter_type: image::imageops::FilterType,
16}
17
18impl ImageOptions {
19    pub fn bit_map_algorithm(&mut self, bit_map_algorithm: BitMapAlgorithm) -> &mut Self {
20        self.bit_map_algorithm = bit_map_algorithm;
21        self
22    }
23    pub fn scale(&mut self, scale: f64) -> Result<&mut Self> {
24        if scale <= 0. || scale > 1. {
25            Err(Error::InvalidImageScale)
26        } else {
27            self.scale = scale;
28            Ok(self)
29        }
30    }
31    pub fn filter_type(&mut self, filter_type: image::imageops::FilterType) -> &mut Self {
32        self.filter_type = filter_type;
33        self
34    }
35}
36
37impl Default for ImageOptions {
38    fn default() -> Self {
39        Self {
40            bit_map_algorithm: BitMapAlgorithm::Dithering,
41            scale: 1.,
42            filter_type: image::imageops::FilterType::Gaussian,
43        }
44    }
45}
46
47pub struct EscposImage {
48    img: image::GrayImage,
49    opts: ImageOptions,
50}
51
52impl EscposImage {
53    pub fn new(img: &image::DynamicImage, opts: &ImageOptions) -> Self {
54        Self {
55            img: img.to_luma8(),
56            opts: opts.clone(),
57        }
58    }
59
60    pub fn as_bytes(
61        &self,
62        printer_width: usize,
63        justification: Justification,
64        line_spacing: Option<u8>,
65    ) -> Vec<u8> {
66        let mut feed = Vec::new();
67        feed.extend_from_slice(&Command::LineSpacing(0).as_bytes());
68
69        // Each row will contain the information of 8 rows from the picture
70        let mut printer_rows: Vec<Vec<u8>> = Vec::new();
71
72        let (im_width, im_height) = self.img.dimensions();
73        // We redefine the aspect ratio
74        let aspect_ratio = (im_width as f64) / (im_height as f64);
75
76        let sc_width = (im_width as f64) * self.opts.scale;
77        let sc_height = ((sc_width) / aspect_ratio).floor() as u32;
78        let sc_width = sc_width.floor() as u32;
79        let x_offset = match justification {
80            Justification::Left => 0,
81            Justification::Center => (im_width - sc_width) / 2,
82            Justification::Right => im_width - sc_width,
83        };
84
85        let mut composite = image::GrayImage::from_pixel(im_width, sc_height, [255].into());
86        image::imageops::overlay(
87            &mut composite,
88            &image::imageops::resize(&self.img, sc_width, sc_height, self.opts.filter_type),
89            x_offset,
90            0,
91        );
92        let mut img = image::imageops::crop(&mut composite, 0, 0, im_width, sc_height).to_image();
93
94        // Multiplied by 3 to account for the reduced vertical density
95        let new_height =
96            ((printer_width as f64 * self.opts.scale) / (aspect_ratio * 3.0)).floor() as u32;
97
98        img = image::imageops::resize(
99            &img,
100            printer_width as u32,
101            new_height,
102            self.opts.filter_type,
103        );
104        img = match self.opts.bit_map_algorithm {
105            BitMapAlgorithm::Dithering => {
106                image::imageops::dither(&mut img, &image::imageops::BiLevel);
107                img
108            }
109            BitMapAlgorithm::Threshold(threshold) => image::GrayImage::from_raw(
110                img.width(),
111                img.height(),
112                img.into_raw()
113                    .into_iter()
114                    .map(|intensity| if intensity > threshold { 255 } else { 0 })
115                    .collect(),
116            )
117            .unwrap(),
118        };
119
120        // We will turn the image into a grayscale boolean matrix
121        for (y, pixel_row) in img.enumerate_rows() {
122            // Here we iterate over each row of the image.
123            if y % 8 == 0 {
124                printer_rows.push(vec![0; printer_width as usize]);
125            }
126            let row = printer_rows.get_mut((y / 8) as usize).unwrap();
127            // Here, we iterate horizontally this time
128            for (x, y, pixel) in pixel_row {
129                let ps = pixel.channels();
130                // We get the color as a boolean
131                let mut color = if ps[0] == 0 { 0x01 } else { 0x00 };
132                // We shift the boolean by 7 - y%8 positions in the register
133                color <<= 7 - y % 8;
134                // An or operation preserves the previous pixels in the rows
135                row[x as usize] |= color;
136            }
137        }
138
139        // Finally, we push each row to the feed vector
140        for (_idx, printer_row) in printer_rows.iter().enumerate() {
141            // We first, declare a bitmap mode
142            feed.extend_from_slice(&Command::Bitmap.as_bytes());
143            // Now, we pass m
144            let m = 0x01;
145            feed.push(m);
146            // The formula on how many pixels we will do, is nL + nH * 256
147            feed.push((printer_width % 256) as u8); // nL
148            feed.push((printer_width / 256) as u8); // nH
149            feed.extend_from_slice(printer_row);
150            feed.push(b'\n'); // Line feed and print
151        }
152
153        if let Some(line_spacing) = line_spacing {
154            feed.extend_from_slice(&Command::LineSpacing(line_spacing).as_bytes());
155        } else {
156            feed.extend_from_slice(&Command::DefaultLineSpacing.as_bytes());
157        }
158
159        feed
160    }
161}