extern crate serde;
extern crate base64;
extern crate image;
extern crate log;
use log::warn;
use super::{Justification};
use crate::{Error, command::{Command}};
use image::{DynamicImage, GenericImageView, Pixel};
use serde::{Serialize, Deserialize, ser::Serializer, de::Deserializer};
use std::collections::{HashMap, HashSet};
use serde::ser::SerializeTuple;
#[derive(Clone, Debug)]
pub struct EscposImage {
source: String,
dynamic_image: DynamicImage,
cached_widths: HashSet<u16>,
pub(crate) cache: HashMap<u16, 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 = base64::encode(&encoded);
Ok(EscposImage {
source,
dynamic_image,
cached_widths: HashSet::new(),
cache: HashMap::new()
})
}
fn build_scaled(&self, 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 new_height = ((printer_width as f64)/(aspect_ratio*3.0)).floor() as u32;
let b = image::imageops::resize(&self.dynamic_image, printer_width as u32, new_height, image::imageops::FilterType::Nearest);
for (y, pixel_row) in b.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;
}
}
for (_idx, printer_row) in printer_rows.iter().enumerate() {
feed.extend_from_slice(&Command::Bitmap.as_bytes());
let m = 0x01;
feed.push(m);
feed.push((printer_width % 256) as u8); feed.push((printer_width / 256) as u8); feed.extend_from_slice(printer_row);
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, width: u16) {
self.cache.insert(width, self.build_scaled(width));
self.cached_widths.insert(width);
}
pub fn feed(&self, width: u16) -> Vec<u8> {
if let Some(feed) = self.cache.get(&width) {
feed.clone()
} else {
warn!("Building an image on the fly in non-mutable mode. Consider caching the width.");
self.build_scaled(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.cached_widths)?;
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 base64::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: HashSet<u16> = seq.next_element()?.ok_or_else(|| serde::de::Error::custom("second element of tuple missing"))?;
for width in cached_widths {
escpos_image.cache_for(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)
}
}