use super::{Justification};
use crate::{Error, command::{Command, ImageMode}};
use image::{DynamicImage, GenericImageView, Pixel};
use serde::{Serialize, Deserialize, ser::Serializer, de::Deserializer};
use base64::{Engine, engine::general_purpose::STANDARD};
use std::collections::{HashMap};
use serde::ser::SerializeTuple;
#[derive(Clone, Debug)]
pub struct EscposImage {
source: String,
dynamic_image: DynamicImage,
pub(crate) cache: HashMap<u16, HashMap<ImageMode, Vec<u8>>>
}
impl EscposImage {
pub fn new(mut dynamic_image: DynamicImage, scale: u8, justification: Justification) -> Result<EscposImage, Error> {
let (im_width, im_height) = dynamic_image.dimensions();
let aspect_ratio = (im_width as f64)/(im_height as f64);
let sc_width = (im_width as f64) * (scale as f64)/255.0;
let sc_height = ((sc_width)/aspect_ratio).floor() as u32;
let sc_width = sc_width.floor() as u32;
let mut back = DynamicImage::new_rgba8(im_width, sc_height);
let x_offset = match justification {
Justification::Left => 0,
Justification::Center => (im_width - sc_width)/2,
Justification::Right => im_width - sc_width
};
image::imageops::overlay(
&mut back,
&image::imageops::resize(&dynamic_image, sc_width, sc_height, image::imageops::FilterType::Nearest),
x_offset, 0 );
dynamic_image = DynamicImage::ImageRgba8(image::imageops::crop(&mut back, 0, 0, im_width, sc_height).to_image());
let mut encoded = Vec::new();
dynamic_image.write_to(&mut encoded, image::ImageFormat::Png).map_err(Error::ImageError)?;
let source = STANDARD.encode(&encoded);
Ok(EscposImage {
source,
dynamic_image,
cache: HashMap::new()
})
}
fn build_scaled(&self, image_mode: ImageMode, printer_width: u16) -> Vec<u8> {
let mut feed = Vec::new();
feed.extend_from_slice(&Command::NoLine.as_bytes());
let (im_width, im_height) = self.dynamic_image.dimensions();
let aspect_ratio = (im_width as f64)/(im_height as f64);
let mut printer_rows: Vec<Vec<u8>> = Vec::new();
let vertical_scale = match image_mode {
ImageMode::EightDotSingleDensity => 1.0,
ImageMode::EightDotDoubleDensity => 0.5,
ImageMode::TwentyfourDotSingleDensity => 3.0,
ImageMode::TwentyfourDotDoubleDensity => 1.5
};
let intrinsic_scaling = 2.0/3.0;
let new_height = ((((printer_width as f64) * intrinsic_scaling /(aspect_ratio)).floor()) * vertical_scale) as u32;
let resized_image = image::imageops::resize(&self.dynamic_image, printer_width as u32, new_height, image::imageops::FilterType::Nearest);
for (y, pixel_row) in resized_image.enumerate_rows() {
if y%8 == 0 {
printer_rows.push(vec![0; printer_width as usize]);
}
let row = printer_rows.get_mut((y/8) as usize).unwrap();
for (x, y, pixel) in pixel_row {
let ps = pixel.channels();
let mut color = if ps.len() == 3 || ps[3] > 64 {
let grayscale = 0.2126*(ps[0] as f64) + 0.7152*(ps[1] as f64) + 0.0722*(ps[2] as f64);
if grayscale < 78.0 {
0x01
} else {
0x00
}
} else {
0x00
};
color <<= 7 - y%8;
row[x as usize] |= color;
}
}
match image_mode {
ImageMode::EightDotSingleDensity | ImageMode::EightDotDoubleDensity => {
for (_idx, printer_row) in printer_rows.iter().enumerate() {
feed.extend_from_slice(&Command::Bitmap{image_mode: image_mode.clone()}.as_bytes());
feed.push((printer_width % 256) as u8); feed.push((printer_width / 256) as u8); feed.extend_from_slice(printer_row);
feed.push(b'\n'); }
},
ImageMode::TwentyfourDotSingleDensity | ImageMode::TwentyfourDotDoubleDensity => {
for counter in 0..(printer_rows.len() / 3) {
let first_row = &printer_rows[3*counter + 0];
let second_row = &printer_rows[3*counter + 1];
let third_row = &printer_rows[3*counter + 2];
let mut buffer = Vec::new();
for width_idx in 0..(printer_width as usize) {
buffer.push(first_row[width_idx]);
buffer.push(second_row[width_idx]);
buffer.push(third_row[width_idx]);
}
feed.extend_from_slice(&Command::Bitmap{image_mode: image_mode.clone()}.as_bytes());
feed.push((printer_width % 256) as u8); feed.push((printer_width / 256) as u8); feed.extend_from_slice(&buffer);
feed.push(b'\n'); }
}
}
feed.extend_from_slice(&Command::ResetLine.as_bytes());
feed.extend_from_slice(&Command::Reset.as_bytes());
feed
}
pub fn cache_for(&mut self, image_mode: ImageMode, width: u16) {
let cache = self.build_scaled(image_mode.clone(), width);
let image_modes = self.cache.entry(width).or_insert_with(|| HashMap::new());
image_modes.insert(image_mode, cache);
}
pub fn feed(&self, image_mode: ImageMode, width: u16) -> Vec<u8> {
if let Some(feed) = self.cache.get(&width).map(|image_modes| image_modes.get(&image_mode)).flatten() {
feed.clone()
} else {
log::warn!("Building an image on the fly in non-mutable mode. Consider caching the width.");
self.build_scaled(image_mode, width)
}
}
}
impl Serialize for EscposImage {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer {
let mut tup = serializer.serialize_tuple(2)?;
tup.serialize_element(&self.source)?;
tup.serialize_element(&self.cache.iter().map(|(width, image_modes)| {
(*width, image_modes.keys().cloned().collect())
}).collect::<HashMap<u16, Vec<ImageMode>>>())?;
tup.end()
}
}
struct EscposImageVisitor;
impl<'de> serde::de::Visitor<'de> for EscposImageVisitor {
type Value = EscposImage;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a tuple containing as first element a base64 encoded image, as second a list of cached widths")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> where A: serde::de::SeqAccess<'de> {
let value: Option<&[u8]> = seq.next_element()?;
let value = value.ok_or_else(|| serde::de::Error::custom("first element of tuple missing"))?;
let content = match STANDARD.decode(value) {
Ok(v) => v,
Err(_) => return Err(serde::de::Error::custom("string is not a valid base64 sequence"))
};
let dynamic_image = image::load_from_memory(&content).map_err(|_| serde::de::Error::custom("first element of tuple not an image"))?;
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)))?;
let cached_widths: HashMap<u16, Vec<ImageMode>> = seq.next_element()?.ok_or_else(|| serde::de::Error::custom("second element of tuple missing"))?;
for (width, image_modes) in cached_widths {
for image_mode in image_modes {
escpos_image.cache_for(image_mode, width);
}
}
Ok(escpos_image)
}
}
impl<'de> Deserialize<'de> for EscposImage {
fn deserialize<D>(deserializer: D) -> Result<EscposImage, D::Error>
where D: Deserializer<'de> {
deserializer.deserialize_seq(EscposImageVisitor)
}
}