escpos_rust/instruction/
escpos_image.rs1extern crate serde;
2extern crate base64;
3extern crate image;
4extern crate log;
5
6use log::warn;
7use super::{Justification};
8use crate::{Error, command::{Command}};
9use image::{DynamicImage, GenericImageView, Pixel};
10use serde::{Serialize, Deserialize, ser::Serializer, de::Deserializer};
11
12use std::collections::{HashMap, HashSet};
13use serde::ser::SerializeTuple;
14
15#[derive(Clone, Debug)]
19pub struct EscposImage {
20 source: String,
21 dynamic_image: DynamicImage,
23 cached_widths: HashSet<u16>,
25 pub(crate) cache: HashMap<u16, Vec<u8>>
27}
28
29impl EscposImage {
30 pub fn new(mut dynamic_image: DynamicImage, scale: u8, justification: Justification) -> Result<EscposImage, Error> {
34 let (im_width, im_height) = dynamic_image.dimensions();
36 let aspect_ratio = (im_width as f64)/(im_height as f64);
37
38 let sc_width = (im_width as f64) * (scale as f64)/255.0;
42 let sc_height = ((sc_width)/aspect_ratio).floor() as u32;
44 let sc_width = sc_width.floor() as u32;
46
47 let mut back = DynamicImage::new_rgba8(im_width, sc_height);
49
50 let x_offset = match justification {
52 Justification::Left => 0,
53 Justification::Center => (im_width - sc_width)/2,
54 Justification::Right => im_width - sc_width
55 };
56
57 image::imageops::overlay(
59 &mut back,
60 &image::imageops::resize(&dynamic_image, sc_width, sc_height, image::imageops::FilterType::Nearest),
61 x_offset, 0 );
63
64 dynamic_image = DynamicImage::ImageRgba8(image::imageops::crop(&mut back, 0, 0, im_width, sc_height).to_image());
66
67 let mut encoded = Vec::new();
68 dynamic_image.write_to(&mut encoded, image::ImageFormat::Png).map_err(Error::ImageError)?;
70
71 let source = base64::encode(&encoded);
72
73 Ok(EscposImage {
74 source,
75 dynamic_image,
76 cached_widths: HashSet::new(),
77 cache: HashMap::new()
78 })
79 }
80
81 fn build_scaled(&self, printer_width: u16) -> Vec<u8> {
82 let mut feed = Vec::new();
83 feed.extend_from_slice(&Command::NoLine.as_bytes());
84
85 let (im_width, im_height) = self.dynamic_image.dimensions();
86 let aspect_ratio = (im_width as f64)/(im_height as f64);
88
89 let mut printer_rows: Vec<Vec<u8>> = Vec::new();
94
95 let new_height = ((printer_width as f64)/(aspect_ratio*3.0)).floor() as u32;
97
98 let b = image::imageops::resize(&self.dynamic_image, printer_width as u32, new_height, image::imageops::FilterType::Nearest);
99
100 for (y, pixel_row) in b.enumerate_rows() {
102 if y%8 == 0 {
104 printer_rows.push(vec![0; printer_width as usize]);
105 }
106 let row = printer_rows.get_mut((y/8) as usize).unwrap();
107 for (x, y, pixel) in pixel_row {
109 let ps = pixel.channels();
110 let mut color = if ps.len() == 3 || ps[3] > 64 {
112 let grayscale = 0.2126*(ps[0] as f64) + 0.7152*(ps[1] as f64) + 0.0722*(ps[2] as f64);
113 if grayscale < 78.0 {
114 0x01
115 } else {
116 0x00
117 }
118 } else {
119 0x00
121 };
122 color <<= 7 - y%8;
124 row[x as usize] |= color;
126 }
127 }
128
129 for (_idx, printer_row) in printer_rows.iter().enumerate() {
131 feed.extend_from_slice(&Command::Bitmap.as_bytes());
133 let m = 0x01;
135 feed.push(m);
136 feed.push((printer_width % 256) as u8); feed.push((printer_width / 256) as u8); feed.extend_from_slice(printer_row);
142 feed.push(b'\n'); }
144 feed.extend_from_slice(&Command::ResetLine.as_bytes());
145 feed.extend_from_slice(&Command::Reset.as_bytes());
146
147 feed
148 }
149
150 pub fn cache_for(&mut self, width: u16) {
154 self.cache.insert(width, self.build_scaled(width));
155 self.cached_widths.insert(width);
156 }
157
158 pub fn feed(&self, width: u16) -> Vec<u8> {
159 if let Some(feed) = self.cache.get(&width) {
160 feed.clone()
161 } else {
162 warn!("Building an image on the fly in non-mutable mode. Consider caching the width.");
164 self.build_scaled(width)
165 }
166 }
167}
168
169impl Serialize for EscposImage {
171 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
172 where S: Serializer {
173 let mut tup = serializer.serialize_tuple(2)?;
174 tup.serialize_element(&self.source)?;
175 tup.serialize_element(&self.cached_widths)?;
176 tup.end()
177 }
178}
179
180struct EscposImageVisitor;
181
182impl<'de> serde::de::Visitor<'de> for EscposImageVisitor {
183 type Value = EscposImage;
184
185 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
186 formatter.write_str("a tuple containing as first element a base64 encoded image, as second a list of cached widths")
187 }
188
189 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> where A: serde::de::SeqAccess<'de> {
190 let value: Option<&[u8]> = seq.next_element()?;
191 let value = value.ok_or_else(|| serde::de::Error::custom("first element of tuple missing"))?;
192 let content = match base64::decode(value) {
193 Ok(v) => v,
194 Err(_) => return Err(serde::de::Error::custom("string is not a valid base64 sequence"))
195 };
196 let dynamic_image = image::load_from_memory(&content).map_err(|_| serde::de::Error::custom("first element of tuple not an image"))?;
197 let mut escpos_image = EscposImage::new(dynamic_image, 255, Justification::Left).map_err(|e| serde::de::Error::custom(format!("failed to create the image, {}", e)))?;
199 let cached_widths: HashSet<u16> = seq.next_element()?.ok_or_else(|| serde::de::Error::custom("second element of tuple missing"))?;
200
201 for width in cached_widths {
202 escpos_image.cache_for(width);
203 }
204
205 Ok(escpos_image)
206 }
207}
208
209impl<'de> Deserialize<'de> for EscposImage {
211 fn deserialize<D>(deserializer: D) -> Result<EscposImage, D::Error>
212 where D: Deserializer<'de> {
213 deserializer.deserialize_seq(EscposImageVisitor)
214 }
215}