use image::{
DynamicImage, GenericImageView, ImageBuffer, Luma, LumaA, Pixel, Primitive, Rgb, Rgba,
};
use num_traits::{NumCast, ToPrimitive};
use crate::{max, min};
pub trait ToGrayScale {
type SubPixel;
fn to_gray_scale(&self) -> LumaA<Self::SubPixel>;
}
impl<T> ToGrayScale for Rgb<T>
where
T: Primitive,
{
type SubPixel = T;
fn to_gray_scale(&self) -> LumaA<Self::SubPixel> {
LumaA([to_gray_scale(&self.0), NumCast::from(1).unwrap()])
}
}
impl<T> ToGrayScale for Rgba<T>
where
T: Primitive,
{
type SubPixel = T;
fn to_gray_scale(&self) -> LumaA<Self::SubPixel> {
LumaA([to_gray_scale(&self.0), self.0[3]])
}
}
fn to_gray_scale<T: Primitive>(rgb: &[T]) -> T {
const TO_LUMA: [f64; 3] = [0.299, 0.587, 0.114];
let r: f64 = NumCast::from(rgb[0]).unwrap();
let g: f64 = NumCast::from(rgb[1]).unwrap();
let b: f64 = NumCast::from(rgb[2]).unwrap();
let r = r * TO_LUMA[0];
let g = g * TO_LUMA[1];
let b = b * TO_LUMA[2];
NumCast::from(r + g + b).unwrap()
}
impl<T> ToGrayScale for Luma<T>
where
T: Primitive,
{
type SubPixel = T;
fn to_gray_scale(&self) -> LumaA<Self::SubPixel> {
LumaA([self.0[0], NumCast::from(1).unwrap()])
}
}
impl<T> ToGrayScale for LumaA<T>
where
T: Primitive,
{
type SubPixel = T;
fn to_gray_scale(&self) -> LumaA<Self::SubPixel> {
*self
}
}
pub fn grayscale<Sub, OldPixel, I: GenericImageView<Pixel = OldPixel>>(
image: &I,
) -> ImageBuffer<LumaA<Sub>, Vec<Sub>>
where
Sub: Primitive,
OldPixel: Pixel + ToGrayScale<SubPixel = Sub>,
{
let (width, height) = image.dimensions();
let mut out = ImageBuffer::new(width, height);
for (x, y, pixel) in image.pixels() {
let grayscale = pixel.to_gray_scale();
out.put_pixel(x, y, grayscale);
}
out
}
pub struct Hsl {
h: f64,
s: f64,
l: f64,
}
pub fn rgb2hsl<Sub: Primitive + ToPrimitive>(rgb: Rgb<Sub>) -> Hsl {
let mut h: f64;
let max: f64 = NumCast::from(Sub::DEFAULT_MAX_VALUE).unwrap();
let r: f64 = rgb.0[0].to_f64().unwrap() / max;
let g: f64 = rgb.0[1].to_f64().unwrap() / max;
let b: f64 = rgb.0[2].to_f64().unwrap() / max;
let max = max!(max!(r, g), b);
let min = min!(min!(r, g), b);
let l = (max + min) / 2_f64;
let delta: f64 = max - min;
if delta == 0_f64 {
return Hsl {
h: 0_f64,
s: 0_f64,
l,
};
}
let s = if l < 0.5_f64 {
delta / (max + min)
} else {
delta / (2_f64 - max - min)
};
let r2 = (((max - r) / 6_f64) + (delta / 2_f64)) / delta;
let g2 = (((max - g) / 6_f64) + (delta / 2_f64)) / delta;
let b2 = (((max - b) / 6_f64) + (delta / 2_f64)) / delta;
h = match max {
x if x == r => b2 - g2,
x if x == g => (1_f64 / 3_f64) + r2 - b2,
_ => (2_f64 / 3_f64) + g2 - r2,
};
if h < 0 as f64 {
h += 1_f64;
} else if h > 1_f64 {
h -= 1_f64;
}
let h_degrees = (h * 360_f64 * 100_f64).round() / 100_f64;
Hsl { h: h_degrees, s, l }
}
pub fn hsl2rgb<Sub: Primitive + ToPrimitive>(hsl: Hsl) -> Rgb<Sub> {
let to_sub = |pre: f64| -> Sub {
NumCast::from((pre * Sub::DEFAULT_MAX_VALUE.to_f64().unwrap()).round()).unwrap()
};
if hsl.s == 0.0 {
let l: Sub = to_sub(hsl.l);
return Rgb([l, l, l]);
}
let h = hsl.h / 360.0; let s = hsl.s;
let l = hsl.l;
let q = if l < 0.5 {
l * (1.0 + s)
} else {
l + s - (l * s)
};
let p = 2.0 * l - q;
Rgb([
to_sub(hue_to_rgb(p, q, h + 1.0 / 3.0)),
to_sub(hue_to_rgb(p, q, h)),
to_sub(hue_to_rgb(p, q, h - 1.0 / 3.0)),
])
}
fn hue_to_rgb(p: f64, q: f64, t: f64) -> f64 {
let t = if t < 0.0 {
t + 1.0
} else if t > 1.0 {
t - 1.0
} else {
t
};
if t < 1.0 / 6.0 {
p + (q - p) * 6.0 * t
} else if t < 1.0 / 2.0 {
q
} else if t < 2.0 / 3.0 {
p + (q - p) * (2.0 / 3.0 - t) * 6.0
} else {
p
}
}
#[allow(unused)]
fn luma2hsl<Sub: Primitive + ToPrimitive>(luma: Sub) -> Hsl {
rgb2hsl(Rgb([luma, luma, luma]))
}
pub fn adjust_saturation_in_place(image: &mut DynamicImage, saturation: f64) {
let (width, height) = image.dimensions();
match image {
DynamicImage::ImageRgb8(image) => {
for y in 0..height {
for x in 0..width {
let pixel = image.get_pixel(x, y);
let rgb = pixel.to_rgb();
let mut hsl = rgb2hsl(rgb);
let saturation = saturation / 100.0;
hsl.s = (hsl.s + saturation).clamp(0.0, 1.0);
let rgb: Rgb<u8> = hsl2rgb(hsl);
image.put_pixel(x, y, rgb);
}
}
}
DynamicImage::ImageRgba8(image) => {
for y in 0..height {
for x in 0..width {
let pixel = image.get_pixel(x, y);
let rgba = pixel.to_rgba();
let rgb = *Rgb::from_slice(&rgba.0[0..3]);
let mut hsl = rgb2hsl(rgb);
let saturation = saturation / 100.0;
hsl.s = (hsl.s + saturation).clamp(0.0, 1.0);
let rgb: Rgb<u8> = hsl2rgb(hsl);
image.put_pixel(x, y, Rgba([rgb[0], rgb[1], rgb[2], rgba[3]]));
}
}
}
DynamicImage::ImageRgb16(image) => {
for y in 0..height {
for x in 0..width {
let pixel = image.get_pixel(x, y);
let rgb = pixel.to_rgb();
let mut hsl = rgb2hsl(rgb);
let saturation = saturation / 100.0;
hsl.s = (hsl.s + saturation).clamp(0.0, 1.0);
let rgb: Rgb<u16> = hsl2rgb(hsl);
image.put_pixel(x, y, rgb);
}
}
}
DynamicImage::ImageRgba16(image) => {
for y in 0..height {
for x in 0..width {
let pixel = image.get_pixel(x, y);
let rgba = pixel.to_rgba();
let rgb = *Rgb::from_slice(&rgba.0[0..3]);
let mut hsl = rgb2hsl(rgb);
let saturation = saturation / 100.0;
hsl.s = (hsl.s + saturation).clamp(0.0, 1.0);
let rgb: Rgb<u16> = hsl2rgb(hsl);
image.put_pixel(x, y, Rgba([rgb[0], rgb[1], rgb[2], rgba[3]]));
}
}
}
DynamicImage::ImageRgb32F(image) => {
for y in 0..height {
for x in 0..width {
let pixel = image.get_pixel(x, y);
let rgb = pixel.to_rgb();
let mut hsl = rgb2hsl(rgb);
let saturation = saturation / 100.0;
hsl.s = (hsl.s + saturation).clamp(0.0, 1.0);
let rgb: Rgb<f32> = hsl2rgb(hsl);
image.put_pixel(x, y, rgb);
}
}
}
DynamicImage::ImageRgba32F(image) => {
for y in 0..height {
for x in 0..width {
let pixel = image.get_pixel(x, y);
let rgba = pixel.to_rgba();
let rgb = *Rgb::from_slice(&rgba.0[0..3]);
let mut hsl = rgb2hsl(rgb);
let saturation = saturation / 100.0;
hsl.s = (hsl.s + saturation).clamp(0.0, 1.0);
let rgb: Rgb<f32> = hsl2rgb(hsl);
image.put_pixel(x, y, Rgba([rgb[0], rgb[1], rgb[2], rgba[3]]));
}
}
}
_ => (),
}
}
pub fn brighten_in_place(image: &mut DynamicImage, value: f64) {
let value = (value / 100.0).clamp(0.0, 1.0);
let (width, height) = image.dimensions();
match image {
DynamicImage::ImageRgb8(image) => {
let max = u8::MAX as f64;
let value = value * max;
for y in 0..height {
for x in 0..width {
let e = image.get_pixel(x, y).map_with_alpha(
|b| {
let c: i32 = NumCast::from(b).unwrap();
let d = (c as f64 + value).clamp(0.0, max) as u8;
NumCast::from(d).unwrap()
},
|alpha| alpha,
);
image.put_pixel(x, y, e);
}
}
}
DynamicImage::ImageRgba8(image) => {
let max = u8::MAX as f64;
let value = value * max;
for y in 0..height {
for x in 0..width {
let e = image.get_pixel(x, y).map_with_alpha(
|b| {
let c: i32 = NumCast::from(b).unwrap();
let d = (c as f64 + value).clamp(0.0, max) as u8;
NumCast::from(d).unwrap()
},
|alpha| alpha,
);
image.put_pixel(x, y, e);
}
}
}
DynamicImage::ImageRgb16(image) => {
let max = u16::MAX as f64;
let value = value * max;
for y in 0..height {
for x in 0..width {
let e = image.get_pixel(x, y).map_with_alpha(
|b| {
let c: i32 = NumCast::from(b).unwrap();
let d = (c as f64 + value).clamp(0.0, max) as u8;
NumCast::from(d).unwrap()
},
|alpha| alpha,
);
image.put_pixel(x, y, e);
}
}
}
DynamicImage::ImageRgba16(image) => {
let max = u16::MAX as f64;
let value = value * max;
for y in 0..height {
for x in 0..width {
let e = image.get_pixel(x, y).map_with_alpha(
|b| {
let c: i32 = NumCast::from(b).unwrap();
let d = (c as f64 + value).clamp(0.0, max) as u8;
NumCast::from(d).unwrap()
},
|alpha| alpha,
);
image.put_pixel(x, y, e);
}
}
}
DynamicImage::ImageRgb32F(image) => {
let max = 1.0f64;
let value = value * max;
for y in 0..height {
for x in 0..width {
let e = image.get_pixel(x, y).map_with_alpha(
|b| {
let c: i32 = NumCast::from(b).unwrap();
let d = (c as f64 + value).clamp(0.0, max) as u8;
NumCast::from(d).unwrap()
},
|alpha| alpha,
);
image.put_pixel(x, y, e);
}
}
}
DynamicImage::ImageRgba32F(image) => {
let max = 1.0f64;
let value = value * max;
for y in 0..height {
for x in 0..width {
let e = image.get_pixel(x, y).map_with_alpha(
|b| {
let c: i32 = NumCast::from(b).unwrap();
let d = (c as f64 + value).clamp(0.0, max) as u8;
NumCast::from(d).unwrap()
},
|alpha| alpha,
);
image.put_pixel(x, y, e);
}
}
}
DynamicImage::ImageLuma8(image) => {
let max = u8::MAX as f64;
let value = value * max;
for y in 0..height {
for x in 0..width {
let e = image.get_pixel(x, y).map_with_alpha(
|b| {
let c: i32 = NumCast::from(b).unwrap();
let d = (c as f64 + value).clamp(0.0, max) as u8;
NumCast::from(d).unwrap()
},
|alpha| alpha,
);
image.put_pixel(x, y, e);
}
}
}
DynamicImage::ImageLumaA8(image) => {
let max = u8::MAX as f64;
let value = value * max;
for y in 0..height {
for x in 0..width {
let e = image.get_pixel(x, y).map_with_alpha(
|b| {
let c: i32 = NumCast::from(b).unwrap();
let d = (c as f64 + value).clamp(0.0, max) as u8;
NumCast::from(d).unwrap()
},
|alpha| alpha,
);
image.put_pixel(x, y, e);
}
}
}
DynamicImage::ImageLuma16(image) => {
let max = u16::MAX as f64;
let value = value * max;
for y in 0..height {
for x in 0..width {
let e = image.get_pixel(x, y).map_with_alpha(
|b| {
let c: i32 = NumCast::from(b).unwrap();
let d = (c as f64 + value).clamp(0.0, max) as u8;
NumCast::from(d).unwrap()
},
|alpha| alpha,
);
image.put_pixel(x, y, e);
}
}
}
DynamicImage::ImageLumaA16(image) => {
let max = u16::MAX as f64;
let value = value * max;
for y in 0..height {
for x in 0..width {
let e = image.get_pixel(x, y).map_with_alpha(
|b| {
let c: i32 = NumCast::from(b).unwrap();
let d = (c as f64 + value).clamp(0.0, max) as u8;
NumCast::from(d).unwrap()
},
|alpha| alpha,
);
image.put_pixel(x, y, e);
}
}
}
_ => (),
}
}