escpos_md/instruction/
escpos_image.rs1use 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 let mut printer_rows: Vec<Vec<u8>> = Vec::new();
71
72 let (im_width, im_height) = self.img.dimensions();
73 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 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 for (y, pixel_row) in img.enumerate_rows() {
122 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 for (x, y, pixel) in pixel_row {
129 let ps = pixel.channels();
130 let mut color = if ps[0] == 0 { 0x01 } else { 0x00 };
132 color <<= 7 - y % 8;
134 row[x as usize] |= color;
136 }
137 }
138
139 for (_idx, printer_row) in printer_rows.iter().enumerate() {
141 feed.extend_from_slice(&Command::Bitmap.as_bytes());
143 let m = 0x01;
145 feed.push(m);
146 feed.push((printer_width % 256) as u8); feed.push((printer_width / 256) as u8); feed.extend_from_slice(printer_row);
150 feed.push(b'\n'); }
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}