use std::str::FromStr;
use image::codecs::jpeg::JpegEncoder;
use image::io::Reader;
use image::{imageops::FilterType, Pixel, Rgba};
use image::{DynamicImage, ExtendedColorType};
use crate::info::{ColourOrder, Mirroring, Rotation};
use crate::{rgb_to_bgr, Error};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "structopt", derive(structopt::StructOpt))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Colour {
#[cfg_attr(feature = "structopt", structopt(long))]
pub r: u8,
#[cfg_attr(feature = "structopt", structopt(long))]
pub g: u8,
#[cfg_attr(feature = "structopt", structopt(long))]
pub b: u8,
}
impl FromStr for Colour {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != 6 && s.len() != 8 {
return Err(format!("Expected colour in the hex form: RRGGBB"));
}
let r =
u8::from_str_radix(&s[0..2], 16).map_err(|e| format!("int parsing error: {}", e))?;
let g =
u8::from_str_radix(&s[2..4], 16).map_err(|e| format!("int parsing error: {}", e))?;
let b =
u8::from_str_radix(&s[4..6], 16).map_err(|e| format!("int parsing error: {}", e))?;
Ok(Self { r, g, b })
}
}
#[derive(Debug)]
#[cfg_attr(feature = "structopt", derive(structopt::StructOpt))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct ImageOptions {
#[cfg_attr(feature = "structopt", structopt(long = "bg"))]
background: Option<Colour>,
#[cfg_attr(feature = "structopt", structopt(long))]
invert: bool,
}
impl ImageOptions {
pub fn new(background: Option<Colour>, invert: bool) -> Self {
ImageOptions { background, invert }
}
}
impl Default for ImageOptions {
fn default() -> Self {
Self {
background: None,
invert: false,
}
}
}
pub(crate) fn apply_transform(
image: DynamicImage,
rotation: Rotation,
mirroring: Mirroring,
) -> DynamicImage {
let image = match rotation {
Rotation::Rot0 => image,
Rotation::Rot90 => image.rotate90(),
Rotation::Rot180 => image.rotate180(),
Rotation::Rot270 => image.rotate270(),
};
let image = match mirroring {
Mirroring::None => image,
Mirroring::X => image.flipv(),
Mirroring::Y => image.fliph(),
Mirroring::Both => image.flipv().fliph(),
};
image
}
pub(crate) fn load_image(
path: &str,
x: usize,
y: usize,
rotate: Rotation,
mirror: Mirroring,
opts: &ImageOptions,
colour_order: ColourOrder,
) -> Result<Vec<u8>, Error> {
let reader = match Reader::open(path) {
Ok(v) => v,
Err(e) => {
error!("error loading file '{}': {:?}", path, e);
return Err(Error::Io(e));
}
};
let mut image = reader.decode().map_err(Error::Image)?;
if let Some(c) = &opts.background {
let rgba = image.as_mut_rgba8().unwrap();
let mut r = Rgba([c.r, c.g, c.b, 0]);
if opts.invert {
r.invert();
}
for p in rgba.pixels_mut() {
r.0[3] = 255 - p.0[3];
p.blend(&r);
}
}
let mut image = image.resize(x as u32, y as u32, FilterType::Gaussian);
image = apply_transform(image, rotate, mirror);
if opts.invert {
image.invert();
}
let mut v = image.to_rgb8().into_vec();
if matches!(colour_order, ColourOrder::BGR) {
rgb_to_bgr(&mut v);
}
if v.len() != x * y * 3 {
return Err(Error::InvalidImageSize);
}
Ok(v)
}
pub(crate) fn encode_jpeg(image: &[u8], width: usize, height: usize) -> Result<Vec<u8>, Error> {
let mut buf = Vec::new();
let mut encoder = JpegEncoder::new_with_quality(&mut buf, 100);
encoder.encode(image, width as u32, height as u32, ExtendedColorType::Rgb8)?;
Ok(buf)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn load_images() {
let _image = load_image(
"./icons/power.png",
72,
72,
Rotation::Rot180,
Mirroring::Both,
&ImageOptions::default(),
ColourOrder::BGR,
)
.expect("error loading image");
}
}