#![cfg(feature = "graphics")]
use crate::errors::{PrinterError, Result};
use image::{DynamicImage, GenericImageView, Rgba};
use std::fmt;
#[derive(Debug, Clone, Copy)]
pub enum GraphicDensity {
Low,
High,
}
impl From<GraphicDensity> for u8 {
fn from(value: GraphicDensity) -> Self {
match value {
GraphicDensity::Low => 50,
GraphicDensity::High => 51,
}
}
}
impl fmt::Display for GraphicDensity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GraphicDensity::Low => write!(f, "180dpi"),
GraphicDensity::High => write!(f, "360dpi"),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum GraphicTone {
Monochrome,
Multiple,
}
impl From<GraphicTone> for u8 {
fn from(value: GraphicTone) -> Self {
match value {
GraphicTone::Monochrome => 48,
GraphicTone::Multiple => 52,
}
}
}
impl fmt::Display for GraphicTone {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GraphicTone::Monochrome => write!(f, "Monochrome"),
GraphicTone::Multiple => write!(f, "Multiple tone"),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum GraphicColor {
Color1,
Color2,
Color3,
Color4,
}
impl From<GraphicColor> for u8 {
fn from(value: GraphicColor) -> Self {
match value {
GraphicColor::Color1 => 49,
GraphicColor::Color2 => 50,
GraphicColor::Color3 => 51,
GraphicColor::Color4 => 52,
}
}
}
impl fmt::Display for GraphicColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GraphicColor::Color1 => write!(f, "Color 1"),
GraphicColor::Color2 => write!(f, "Color 2"),
GraphicColor::Color3 => write!(f, "Color 3"),
GraphicColor::Color4 => write!(f, "Color 4"),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum GraphicSize {
Normal,
Double,
}
impl From<GraphicSize> for u8 {
fn from(value: GraphicSize) -> Self {
match value {
GraphicSize::Normal => 1,
GraphicSize::Double => 2,
}
}
}
impl fmt::Display for GraphicSize {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GraphicSize::Normal => write!(f, "Normal"),
GraphicSize::Double => write!(f, "Double"),
}
}
}
#[derive(Debug)]
pub struct GraphicOption {
pub max_width: Option<u32>,
pub max_height: Option<u32>,
pub density: GraphicDensity,
pub tone: GraphicTone,
pub color: GraphicColor,
pub width_size: GraphicSize,
pub height_size: GraphicSize,
}
impl Default for GraphicOption {
fn default() -> Self {
Self {
max_width: None,
max_height: None,
density: GraphicDensity::Low,
tone: GraphicTone::Monochrome,
color: GraphicColor::Color1,
width_size: GraphicSize::Normal,
height_size: GraphicSize::Normal,
}
}
}
impl GraphicOption {
pub fn new(
density: GraphicDensity,
tone: GraphicTone,
color: GraphicColor,
width_size: GraphicSize,
height_size: GraphicSize,
max_width: Option<u32>,
max_height: Option<u32>,
) -> Self {
Self {
max_width,
max_height,
density,
tone,
color,
width_size,
height_size,
}
}
}
#[derive(Debug)]
pub struct Graphic {
path: String,
option: GraphicOption,
image: DynamicImage,
}
impl Graphic {
pub fn new(path: &str, option: Option<GraphicOption>) -> Result<Self> {
let img = image::open(path)?;
let option = option.unwrap_or_default();
let img = match (option.max_width, option.max_height) {
(Some(max_width), None) => {
let resized = img.resize(max_width, max_width, image::imageops::Nearest);
resized.grayscale()
}
(None, Some(max_height)) => {
let resized = img.resize(max_height, max_height, image::imageops::Nearest);
resized.grayscale()
}
(Some(max_width), Some(max_height)) => {
let resized = img.resize(max_width, max_height, image::imageops::Nearest);
resized.grayscale()
}
_ => img.grayscale(),
};
Ok(Self {
path: path.to_string(),
option,
image: img,
})
}
pub fn width(&self) -> u32 {
self.image.width()
}
pub fn height(&self) -> u32 {
self.image.height()
}
pub fn dimensions(&self) -> (u32, u32) {
(self.width(), self.height())
}
pub fn width_bytes(&self) -> u32 {
self.width().div_ceil(8)
}
pub fn path(&self) -> &str {
&self.path
}
pub fn image(&self) -> &DynamicImage {
&self.image
}
pub fn pixel(&self, x: u32, y: u32) -> Rgba<u8> {
self.image.get_pixel(x, y)
}
pub fn is_blank_pixel(&self, x: u32, y: u32) -> bool {
let pixel = self.pixel(x, y);
pixel[3] == 0 || (pixel[0] & pixel[1] & pixel[2]) == 0xFF
}
pub fn density(&self) -> u8 {
self.option.density.into()
}
pub fn tone(&self) -> u8 {
self.option.tone.into()
}
pub fn color(&self) -> u8 {
self.option.color.into()
}
pub fn width_size(&self) -> u8 {
self.option.width_size.into()
}
pub fn height_size(&self) -> u8 {
self.option.height_size.into()
}
pub fn data_size(&self) -> Result<(u8, u8, u8, u8)> {
let length = self.image.as_bytes().len() - 11;
let p4 = length / 16_777_216;
let p3 = length
.checked_add_signed(-16_777_216 * isize::try_from(p4)?)
.ok_or(PrinterError::Input("graphics invalid (pL, pH)".to_owned()))?
/ 65_536;
let p2 = length
.checked_add_signed(-16_777_216 * isize::try_from(p4)?)
.ok_or(PrinterError::Input("graphics invalid (pL, pH)".to_owned()))?
.checked_add_signed(-65_536 * isize::try_from(p3)?)
.ok_or(PrinterError::Input("graphics invalid (pL, pH)".to_owned()))?
/ 256;
let p1 = length
.checked_add_signed(-256 * isize::try_from(p2)?)
.ok_or(PrinterError::Input("graphics invalid (pL, pH)".to_owned()))?
.checked_add_signed(-65_536 * isize::try_from(p3)?)
.ok_or(PrinterError::Input("graphics invalid (pL, pH)".to_owned()))?
.checked_add_signed(-16_777_216 * isize::try_from(p4)?)
.ok_or(PrinterError::Input("graphics invalid (pL, pH)".to_owned()))?;
Ok((
u8::try_from(p1)?,
u8::try_from(p2)?,
u8::try_from(p3)?,
u8::try_from(p4)?,
))
}
pub fn dots_per_direction(&self, length: usize) -> Result<(u8, u8)> {
let ph = length / 256;
let pl = length
.checked_add_signed(-256 * isize::try_from(ph)?)
.ok_or(PrinterError::Input("graphics invalid dots per direction".to_owned()))?;
Ok((u8::try_from(pl)?, u8::try_from(ph)?))
}
pub fn data(&self) -> Result<Vec<u8>> {
let width = self.width_bytes();
let height = self.height();
let mut data = vec![0; (width * height) as usize];
for y in 0..height {
for x in 0..width {
for b in 0..8 {
let i = x * 8 + b;
if i < self.width() && !self.is_blank_pixel(i, y) {
data[(y * width + x) as usize] += 0x80 >> (b & 0x7);
}
}
}
}
Ok(data)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_graphic_width() {
let graphic = Graphic::new("./resources/images/rust-logo-small.png", None).unwrap();
assert_eq!(graphic.width(), 200);
}
#[test]
fn test_graphic_height() {
let graphic = Graphic::new("./resources/images/rust-logo.png", None).unwrap();
assert_eq!(graphic.height(), 1_000);
}
}