use crate::core::{Pix, PixMut, PixelDepth, pixel};
use crate::transform::{TransformError, TransformResult};
const MIN_DIFF_FROM_HALF_PI: f32 = 0.04;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ShearFill {
#[default]
White,
Black,
}
impl ShearFill {
pub fn to_value(self, depth: PixelDepth) -> u32 {
match self {
ShearFill::White => match depth {
PixelDepth::Bit1 => 0, PixelDepth::Bit2 => 3,
PixelDepth::Bit4 => 15,
PixelDepth::Bit8 => 255,
PixelDepth::Bit16 => 65535,
PixelDepth::Bit32 => 0xFFFFFF00,
},
ShearFill::Black => match depth {
PixelDepth::Bit1 => 1, PixelDepth::Bit32 => 0x00000000,
_ => 0,
},
}
}
}
fn normalize_angle_for_shear(mut radang: f32, mindif: f32) -> Option<f32> {
let pi2 = std::f32::consts::FRAC_PI_2;
if radang < -pi2 || radang > pi2 {
radang -= (radang / pi2).trunc() * pi2;
}
if radang > pi2 - mindif {
radang = pi2 - mindif;
} else if radang < -pi2 + mindif {
radang = -pi2 + mindif;
}
if radang.abs() < 1e-7 || radang.tan().abs() < 1e-7 {
return None;
}
Some(radang)
}
pub fn h_shear(pix: &Pix, yloc: i32, radang: f32, fill: ShearFill) -> TransformResult<Pix> {
let radang = match normalize_angle_for_shear(radang, MIN_DIFF_FROM_HALF_PI) {
Some(a) => a,
None => return Ok(pix.deep_clone()),
};
let w = pix.width();
let h = pix.height();
let depth = pix.depth();
let fill_value = fill.to_value(depth);
let out_pix = Pix::new(w, h, depth)?;
let mut out_mut = out_pix.try_into_mut().unwrap();
if let Some(cmap) = pix.colormap() {
let _ = out_mut.set_colormap(Some(cmap.clone()));
}
fill_image(&mut out_mut, fill_value);
let tan_angle = radang.tan();
let wi = w as i32;
let hi = h as i32;
for y in 0..hi {
let shift = ((yloc - y) as f32 * tan_angle).round() as i32;
for x in 0..wi {
let src_x = x - shift;
if src_x >= 0 && src_x < wi {
let val = pix.get_pixel_unchecked(src_x as u32, y as u32);
out_mut.set_pixel_unchecked(x as u32, y as u32, val);
}
}
}
Ok(out_mut.into())
}
pub fn h_shear_corner(pix: &Pix, radang: f32, fill: ShearFill) -> TransformResult<Pix> {
h_shear(pix, 0, radang, fill)
}
pub fn h_shear_center(pix: &Pix, radang: f32, fill: ShearFill) -> TransformResult<Pix> {
let yloc = (pix.height() / 2) as i32;
h_shear(pix, yloc, radang, fill)
}
pub fn v_shear(pix: &Pix, xloc: i32, radang: f32, fill: ShearFill) -> TransformResult<Pix> {
let radang = match normalize_angle_for_shear(radang, MIN_DIFF_FROM_HALF_PI) {
Some(a) => a,
None => return Ok(pix.deep_clone()),
};
let w = pix.width();
let h = pix.height();
let depth = pix.depth();
let fill_value = fill.to_value(depth);
let out_pix = Pix::new(w, h, depth)?;
let mut out_mut = out_pix.try_into_mut().unwrap();
if let Some(cmap) = pix.colormap() {
let _ = out_mut.set_colormap(Some(cmap.clone()));
}
fill_image(&mut out_mut, fill_value);
let tan_angle = radang.tan();
let wi = w as i32;
let hi = h as i32;
for x in 0..wi {
let shift = ((x - xloc) as f32 * tan_angle).round() as i32;
for y in 0..hi {
let src_y = y - shift;
if src_y >= 0 && src_y < hi {
let val = pix.get_pixel_unchecked(x as u32, src_y as u32);
out_mut.set_pixel_unchecked(x as u32, y as u32, val);
}
}
}
Ok(out_mut.into())
}
pub fn v_shear_corner(pix: &Pix, radang: f32, fill: ShearFill) -> TransformResult<Pix> {
v_shear(pix, 0, radang, fill)
}
pub fn v_shear_center(pix: &Pix, radang: f32, fill: ShearFill) -> TransformResult<Pix> {
let xloc = (pix.width() / 2) as i32;
v_shear(pix, xloc, radang, fill)
}
pub fn h_shear_ip(
pix: &mut PixMut,
yloc: i32,
radang: f32,
fill: ShearFill,
) -> TransformResult<()> {
let radang = match normalize_angle_for_shear(radang, MIN_DIFF_FROM_HALF_PI) {
Some(a) => a,
None => return Ok(()), };
let w = pix.width();
let h = pix.height();
let depth = pix.depth();
let fill_value = fill.to_value(depth);
let tan_angle = radang.tan();
let wi = w as i32;
let hi = h as i32;
let mut row_buffer = vec![fill_value; w as usize];
for y in 0..hi {
let shift = ((yloc - y) as f32 * tan_angle).round() as i32;
for x in 0..wi {
row_buffer[x as usize] = pix.get_pixel_unchecked(x as u32, y as u32);
}
for x in 0..wi {
let src_x = x - shift;
let val = if src_x >= 0 && src_x < wi {
row_buffer[src_x as usize]
} else {
fill_value
};
pix.set_pixel_unchecked(x as u32, y as u32, val);
}
}
Ok(())
}
pub fn v_shear_ip(
pix: &mut PixMut,
xloc: i32,
radang: f32,
fill: ShearFill,
) -> TransformResult<()> {
let radang = match normalize_angle_for_shear(radang, MIN_DIFF_FROM_HALF_PI) {
Some(a) => a,
None => return Ok(()), };
let w = pix.width();
let h = pix.height();
let depth = pix.depth();
let fill_value = fill.to_value(depth);
let tan_angle = radang.tan();
let wi = w as i32;
let hi = h as i32;
let mut col_buffer = vec![fill_value; h as usize];
for x in 0..wi {
let shift = ((x - xloc) as f32 * tan_angle).round() as i32;
for y in 0..hi {
col_buffer[y as usize] = pix.get_pixel_unchecked(x as u32, y as u32);
}
for y in 0..hi {
let src_y = y - shift;
let val = if src_y >= 0 && src_y < hi {
col_buffer[src_y as usize]
} else {
fill_value
};
pix.set_pixel_unchecked(x as u32, y as u32, val);
}
}
Ok(())
}
pub fn h_shear_li(pix: &Pix, yloc: i32, radang: f32, fill: ShearFill) -> TransformResult<Pix> {
let w = pix.width();
let h = pix.height();
let depth = pix.depth();
if yloc < 0 || yloc >= h as i32 {
return Err(TransformError::InvalidParameters(format!(
"yloc {} is out of bounds [0, {})",
yloc, h
)));
}
if depth != PixelDepth::Bit8 && depth != PixelDepth::Bit32 && pix.colormap().is_none() {
return Err(TransformError::UnsupportedDepth(
"h_shear_li requires 8bpp, 32bpp, or colormapped image".to_string(),
));
}
let src_pix = if pix.colormap().is_some() {
remove_colormap(pix)?
} else {
pix.deep_clone()
};
let radang = match normalize_angle_for_shear(radang, MIN_DIFF_FROM_HALF_PI) {
Some(a) => a,
None => return Ok(src_pix),
};
let src_depth = src_pix.depth();
let fill_value = fill.to_value(src_depth);
let out_pix = Pix::new(w, h, src_depth)?;
let mut out_mut = out_pix.try_into_mut().unwrap();
fill_image(&mut out_mut, fill_value);
let tan_angle = radang.tan();
let wi = w as i32;
let hi = h as i32;
let wm = wi - 1;
match src_depth {
PixelDepth::Bit8 => {
h_shear_li_gray(
&src_pix,
&mut out_mut,
yloc,
tan_angle,
wi,
hi,
wm,
fill_value as u8,
);
}
PixelDepth::Bit32 => {
h_shear_li_color(
&src_pix,
&mut out_mut,
yloc,
tan_angle,
wi,
hi,
wm,
fill_value,
);
}
_ => unreachable!(),
}
Ok(out_mut.into())
}
pub fn v_shear_li(pix: &Pix, xloc: i32, radang: f32, fill: ShearFill) -> TransformResult<Pix> {
let w = pix.width();
let h = pix.height();
let depth = pix.depth();
if xloc < 0 || xloc >= w as i32 {
return Err(TransformError::InvalidParameters(format!(
"xloc {} is out of bounds [0, {})",
xloc, w
)));
}
if depth != PixelDepth::Bit8 && depth != PixelDepth::Bit32 && pix.colormap().is_none() {
return Err(TransformError::UnsupportedDepth(
"v_shear_li requires 8bpp, 32bpp, or colormapped image".to_string(),
));
}
let src_pix = if pix.colormap().is_some() {
remove_colormap(pix)?
} else {
pix.deep_clone()
};
let radang = match normalize_angle_for_shear(radang, MIN_DIFF_FROM_HALF_PI) {
Some(a) => a,
None => return Ok(src_pix),
};
let src_depth = src_pix.depth();
let fill_value = fill.to_value(src_depth);
let out_pix = Pix::new(w, h, src_depth)?;
let mut out_mut = out_pix.try_into_mut().unwrap();
fill_image(&mut out_mut, fill_value);
let tan_angle = radang.tan();
let wi = w as i32;
let hi = h as i32;
let hm = hi - 1;
match src_depth {
PixelDepth::Bit8 => {
v_shear_li_gray(
&src_pix,
&mut out_mut,
xloc,
tan_angle,
wi,
hi,
hm,
fill_value as u8,
);
}
PixelDepth::Bit32 => {
v_shear_li_color(
&src_pix,
&mut out_mut,
xloc,
tan_angle,
wi,
hi,
hm,
fill_value,
);
}
_ => unreachable!(),
}
Ok(out_mut.into())
}
#[allow(clippy::too_many_arguments)]
fn h_shear_li_gray(
src: &Pix,
dst: &mut PixMut,
yloc: i32,
tan_angle: f32,
wi: i32,
hi: i32,
wm: i32,
_fill_value: u8,
) {
for y in 0..hi {
let xshift = (yloc - y) as f32 * tan_angle;
for jd in 0..wi {
let x = (64.0 * (-xshift + jd as f32) + 0.5) as i32;
let xp = x >> 6; let xf = x & 63;
if xp < 0 || xp > wm {
continue; }
let val = if xp < wm {
let v0 = src.get_pixel_unchecked(xp as u32, y as u32) as i32;
let v1 = src.get_pixel_unchecked((xp + 1) as u32, y as u32) as i32;
((63 - xf) * v0 + xf * v1 + 31) / 63
} else {
src.get_pixel_unchecked(xp as u32, y as u32) as i32
};
dst.set_pixel_unchecked(jd as u32, y as u32, val as u32);
}
}
}
#[allow(clippy::too_many_arguments)]
fn h_shear_li_color(
src: &Pix,
dst: &mut PixMut,
yloc: i32,
tan_angle: f32,
wi: i32,
hi: i32,
wm: i32,
_fill_value: u32,
) {
for y in 0..hi {
let xshift = (yloc - y) as f32 * tan_angle;
for jd in 0..wi {
let x = (64.0 * (-xshift + jd as f32) + 0.5) as i32;
let xp = x >> 6;
let xf = x & 63;
if xp < 0 || xp > wm {
continue;
}
let pixel = if xp < wm {
let word0 = src.get_pixel_unchecked(xp as u32, y as u32);
let word1 = src.get_pixel_unchecked((xp + 1) as u32, y as u32);
let (r0, g0, b0, a0) = pixel::extract_rgba(word0);
let (r1, g1, b1, a1) = pixel::extract_rgba(word1);
let r = interp_channel(r0, r1, xf);
let g = interp_channel(g0, g1, xf);
let b = interp_channel(b0, b1, xf);
let a = interp_channel(a0, a1, xf);
pixel::compose_rgba(r, g, b, a)
} else {
src.get_pixel_unchecked(xp as u32, y as u32)
};
dst.set_pixel_unchecked(jd as u32, y as u32, pixel);
}
}
}
#[allow(clippy::too_many_arguments)]
fn v_shear_li_gray(
src: &Pix,
dst: &mut PixMut,
xloc: i32,
tan_angle: f32,
wi: i32,
hi: i32,
hm: i32,
_fill_value: u8,
) {
for x in 0..wi {
let yshift = (x - xloc) as f32 * tan_angle;
for id in 0..hi {
let y = (64.0 * (-yshift + id as f32) + 0.5) as i32;
let yp = y >> 6;
let yf = y & 63;
if yp < 0 || yp > hm {
continue;
}
let val = if yp < hm {
let v0 = src.get_pixel_unchecked(x as u32, yp as u32) as i32;
let v1 = src.get_pixel_unchecked(x as u32, (yp + 1) as u32) as i32;
((63 - yf) * v0 + yf * v1 + 31) / 63
} else {
src.get_pixel_unchecked(x as u32, yp as u32) as i32
};
dst.set_pixel_unchecked(x as u32, id as u32, val as u32);
}
}
}
#[allow(clippy::too_many_arguments)]
fn v_shear_li_color(
src: &Pix,
dst: &mut PixMut,
xloc: i32,
tan_angle: f32,
wi: i32,
hi: i32,
hm: i32,
_fill_value: u32,
) {
for x in 0..wi {
let yshift = (x - xloc) as f32 * tan_angle;
for id in 0..hi {
let y = (64.0 * (-yshift + id as f32) + 0.5) as i32;
let yp = y >> 6;
let yf = y & 63;
if yp < 0 || yp > hm {
continue;
}
let pixel = if yp < hm {
let word0 = src.get_pixel_unchecked(x as u32, yp as u32);
let word1 = src.get_pixel_unchecked(x as u32, (yp + 1) as u32);
let (r0, g0, b0, a0) = pixel::extract_rgba(word0);
let (r1, g1, b1, a1) = pixel::extract_rgba(word1);
let r = interp_channel(r0, r1, yf);
let g = interp_channel(g0, g1, yf);
let b = interp_channel(b0, b1, yf);
let a = interp_channel(a0, a1, yf);
pixel::compose_rgba(r, g, b, a)
} else {
src.get_pixel_unchecked(x as u32, yp as u32)
};
dst.set_pixel_unchecked(x as u32, id as u32, pixel);
}
}
}
#[inline]
fn interp_channel(v0: u8, v1: u8, f: i32) -> u8 {
(((63 - f) * v0 as i32 + f * v1 as i32 + 31) / 63).clamp(0, 255) as u8
}
fn fill_image(pix: &mut PixMut, value: u32) {
let w = pix.width();
let h = pix.height();
for y in 0..h {
for x in 0..w {
pix.set_pixel_unchecked(x, y, value);
}
}
}
fn remove_colormap(pix: &Pix) -> TransformResult<Pix> {
let w = pix.width();
let h = pix.height();
let cmap = pix
.colormap()
.ok_or_else(|| TransformError::InvalidParameters("image has no colormap".to_string()))?;
let is_gray = cmap
.colors()
.iter()
.all(|c| c.red == c.green && c.green == c.blue);
if is_gray {
let out_pix = Pix::new(w, h, PixelDepth::Bit8)?;
let mut out_mut = out_pix.try_into_mut().unwrap();
for y in 0..h {
for x in 0..w {
let idx = pix.get_pixel_unchecked(x, y) as usize;
let gray = if idx < cmap.len() {
cmap.colors()[idx].red
} else {
0
};
out_mut.set_pixel_unchecked(x, y, gray as u32);
}
}
Ok(out_mut.into())
} else {
let out_pix = Pix::new(w, h, PixelDepth::Bit32)?;
let mut out_mut = out_pix.try_into_mut().unwrap();
for y in 0..h {
for x in 0..w {
let idx = pix.get_pixel_unchecked(x, y) as usize;
let pixel = if idx < cmap.len() {
let c = &cmap.colors()[idx];
pixel::compose_rgba(c.red, c.green, c.blue, 255)
} else {
0
};
out_mut.set_pixel_unchecked(x, y, pixel);
}
}
Ok(out_mut.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shear_fill_values() {
assert_eq!(ShearFill::White.to_value(PixelDepth::Bit1), 0);
assert_eq!(ShearFill::Black.to_value(PixelDepth::Bit1), 1);
assert_eq!(ShearFill::White.to_value(PixelDepth::Bit8), 255);
assert_eq!(ShearFill::Black.to_value(PixelDepth::Bit8), 0);
assert_eq!(ShearFill::White.to_value(PixelDepth::Bit32), 0xFFFFFF00);
assert_eq!(ShearFill::Black.to_value(PixelDepth::Bit32), 0);
}
#[test]
fn test_normalize_angle_zero() {
let result = normalize_angle_for_shear(0.0, MIN_DIFF_FROM_HALF_PI);
assert!(result.is_none());
}
#[test]
fn test_normalize_angle_small() {
let result = normalize_angle_for_shear(0.1, MIN_DIFF_FROM_HALF_PI);
assert!(result.is_some());
assert!((result.unwrap() - 0.1).abs() < 1e-5);
}
#[test]
fn test_normalize_angle_near_pi_half() {
let pi2 = std::f32::consts::FRAC_PI_2;
let result = normalize_angle_for_shear(pi2 - 0.01, MIN_DIFF_FROM_HALF_PI);
assert!(result.is_some());
assert!(result.unwrap() <= pi2 - MIN_DIFF_FROM_HALF_PI);
}
#[test]
fn test_h_shear_zero_angle() {
let pix = Pix::new(10, 10, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_pixel_unchecked(5, 5, 100);
let pix: Pix = pix_mut.into();
let result = h_shear(&pix, 5, 0.0, ShearFill::White).unwrap();
assert_eq!(result.get_pixel_unchecked(5, 5), 100);
}
#[test]
fn test_h_shear_positive_angle() {
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_pixel_unchecked(10, 5, 100);
let pix: Pix = pix_mut.into();
let result = h_shear(&pix, 10, 0.1, ShearFill::White).unwrap();
let mut found_marker = false;
for x in 10..20 {
if result.get_pixel_unchecked(x, 5) == 100 {
found_marker = true;
break;
}
}
assert!(found_marker, "marker should have shifted right");
}
#[test]
fn test_h_shear_center() {
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let result = h_shear_center(&pix, 0.1, ShearFill::White);
assert!(result.is_ok());
assert_eq!(result.unwrap().width(), 20);
}
#[test]
fn test_h_shear_corner() {
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let result = h_shear_corner(&pix, 0.1, ShearFill::White);
assert!(result.is_ok());
}
#[test]
fn test_v_shear_zero_angle() {
let pix = Pix::new(10, 10, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_pixel_unchecked(5, 5, 100);
let pix: Pix = pix_mut.into();
let result = v_shear(&pix, 5, 0.0, ShearFill::White).unwrap();
assert_eq!(result.get_pixel_unchecked(5, 5), 100);
}
#[test]
fn test_v_shear_positive_angle() {
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_pixel_unchecked(15, 10, 100);
let pix: Pix = pix_mut.into();
let result = v_shear(&pix, 10, 0.1, ShearFill::White).unwrap();
let mut found_marker = false;
for y in 10..20 {
if result.get_pixel_unchecked(15, y) == 100 {
found_marker = true;
break;
}
}
assert!(found_marker, "marker should have shifted down");
}
#[test]
fn test_v_shear_center() {
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let result = v_shear_center(&pix, 0.1, ShearFill::White);
assert!(result.is_ok());
}
#[test]
fn test_v_shear_corner() {
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let result = v_shear_corner(&pix, 0.1, ShearFill::White);
assert!(result.is_ok());
}
#[test]
fn test_h_shear_ip_zero_angle() {
let pix = Pix::new(10, 10, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_pixel_unchecked(5, 5, 100);
let result = h_shear_ip(&mut pix_mut, 5, 0.0, ShearFill::White);
assert!(result.is_ok());
assert_eq!(pix_mut.get_pixel_unchecked(5, 5), 100);
}
#[test]
fn test_h_shear_ip_basic() {
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_pixel_unchecked(10, 5, 100);
let result = h_shear_ip(&mut pix_mut, 10, 0.1, ShearFill::White);
assert!(result.is_ok());
}
#[test]
fn test_v_shear_ip_basic() {
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_pixel_unchecked(15, 10, 100);
let result = v_shear_ip(&mut pix_mut, 10, 0.1, ShearFill::White);
assert!(result.is_ok());
}
#[test]
fn test_shear_ip_without_colormap() {
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
pix_mut.set_pixel_unchecked(10, 5, 100);
let result = h_shear_ip(&mut pix_mut, 10, 0.1, ShearFill::White);
assert!(result.is_ok());
}
#[test]
fn test_h_shear_li_8bpp() {
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let result = h_shear_li(&pix, 10, 0.1, ShearFill::White);
assert!(result.is_ok());
}
#[test]
fn test_h_shear_li_32bpp() {
let pix = Pix::new(20, 20, PixelDepth::Bit32).unwrap();
let result = h_shear_li(&pix, 10, 0.1, ShearFill::White);
assert!(result.is_ok());
}
#[test]
fn test_h_shear_li_invalid_yloc() {
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let result = h_shear_li(&pix, 30, 0.1, ShearFill::White);
assert!(matches!(result, Err(TransformError::InvalidParameters(_))));
}
#[test]
fn test_h_shear_li_unsupported_depth() {
let pix = Pix::new(20, 20, PixelDepth::Bit1).unwrap();
let result = h_shear_li(&pix, 10, 0.1, ShearFill::White);
assert!(matches!(result, Err(TransformError::UnsupportedDepth(_))));
}
#[test]
fn test_v_shear_li_8bpp() {
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let result = v_shear_li(&pix, 10, 0.1, ShearFill::White);
assert!(result.is_ok());
}
#[test]
fn test_v_shear_li_32bpp() {
let pix = Pix::new(20, 20, PixelDepth::Bit32).unwrap();
let result = v_shear_li(&pix, 10, 0.1, ShearFill::White);
assert!(result.is_ok());
}
#[test]
fn test_v_shear_li_invalid_xloc() {
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let result = v_shear_li(&pix, 30, 0.1, ShearFill::White);
assert!(matches!(result, Err(TransformError::InvalidParameters(_))));
}
#[test]
fn test_h_shear_1bpp() {
let pix = Pix::new(20, 20, PixelDepth::Bit1).unwrap();
let result = h_shear(&pix, 10, 0.1, ShearFill::White);
assert!(result.is_ok());
}
#[test]
fn test_v_shear_1bpp() {
let pix = Pix::new(20, 20, PixelDepth::Bit1).unwrap();
let result = v_shear(&pix, 10, 0.1, ShearFill::Black);
assert!(result.is_ok());
}
#[test]
fn test_h_shear_32bpp() {
let pix = Pix::new(20, 20, PixelDepth::Bit32).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
let red = pixel::compose_rgb(255, 0, 0);
pix_mut.set_pixel_unchecked(10, 5, red);
let pix: Pix = pix_mut.into();
let result = h_shear(&pix, 10, 0.1, ShearFill::White);
assert!(result.is_ok());
}
#[test]
fn test_h_shear_fill_black() {
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let result = h_shear(&pix, 0, 0.3, ShearFill::Black).unwrap();
assert_eq!(result.get_pixel_unchecked(0, 19), 0);
}
#[test]
fn test_v_shear_fill_black() {
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let result = v_shear(&pix, 0, 0.3, ShearFill::Black).unwrap();
assert_eq!(result.get_pixel_unchecked(19, 0), 0);
}
#[test]
fn test_h_shear_preserves_colormap() {
use crate::core::PixColormap;
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
let mut cmap = PixColormap::new(8).unwrap();
cmap.add_rgb(255, 0, 0).unwrap();
cmap.add_rgb(0, 255, 0).unwrap();
let _ = pix_mut.set_colormap(Some(cmap));
let pix: Pix = pix_mut.into();
let result = h_shear(&pix, 10, 0.1, ShearFill::White).unwrap();
assert!(result.colormap().is_some());
}
#[test]
fn test_h_shear_li_removes_colormap() {
use crate::core::PixColormap;
let pix = Pix::new(20, 20, PixelDepth::Bit8).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
let mut cmap = PixColormap::new(8).unwrap();
cmap.add_rgb(128, 128, 128).unwrap(); let _ = pix_mut.set_colormap(Some(cmap));
let pix: Pix = pix_mut.into();
let result = h_shear_li(&pix, 10, 0.1, ShearFill::White).unwrap();
assert!(result.colormap().is_none());
}
}