use crate::lossless::{EdgeHandling, LosslessTransform, TransformConfig};
use zenlayout::{Command, Constraint, Orientation};
pub(crate) fn detect_lossless(commands: &[Command]) -> Option<LosslessTransform> {
let mut has_resize = false;
let mut has_crop = false;
let mut has_pad = false;
let mut has_region = false;
let mut orientation = Orientation::Identity;
for cmd in commands {
match cmd {
Command::AutoOrient(exif) => {
if let Some(o) = Orientation::from_exif(*exif) {
orientation = orientation.compose(o);
}
}
Command::Rotate(r) => {
let o = match r {
zenlayout::Rotation::Rotate90 => Orientation::Rotate90,
zenlayout::Rotation::Rotate180 => Orientation::Rotate180,
zenlayout::Rotation::Rotate270 => Orientation::Rotate270,
_ => return None,
};
orientation = orientation.compose(o);
}
Command::Flip(axis) => {
let o = match axis {
zenlayout::FlipAxis::Horizontal => Orientation::FlipH,
zenlayout::FlipAxis::Vertical => Orientation::FlipV,
_ => return None,
};
orientation = orientation.compose(o);
}
Command::Constrain(c) => {
has_resize = constraint_may_resize(c);
}
Command::Crop(_) => has_crop = true,
Command::Region(_) => has_region = true,
Command::Pad(_) => has_pad = true,
_ => return None, }
}
if has_resize || has_crop || has_pad || has_region {
return None;
}
if orientation.is_identity() {
return Some(LosslessTransform::None);
}
orientation_to_lossless(orientation)
}
fn orientation_to_lossless(o: Orientation) -> Option<LosslessTransform> {
Some(match o {
Orientation::Identity => LosslessTransform::None,
Orientation::FlipH => LosslessTransform::FlipHorizontal,
Orientation::FlipV => LosslessTransform::FlipVertical,
Orientation::Rotate180 => LosslessTransform::Rotate180,
Orientation::Rotate90 => LosslessTransform::Rotate90,
Orientation::Rotate270 => LosslessTransform::Rotate270,
Orientation::Transpose => LosslessTransform::Transpose,
Orientation::Transverse => LosslessTransform::Transverse,
_ => return None, })
}
pub(crate) fn execute_lossless(
jpeg_data: &[u8],
transform: LosslessTransform,
edge_handling: EdgeHandling,
stop: &dyn enough::Stop,
) -> crate::error::Result<Vec<u8>> {
if transform == LosslessTransform::None {
return Ok(jpeg_data.to_vec());
}
let config = TransformConfig {
transform,
edge_handling,
};
crate::lossless::transform(jpeg_data, &config, stop)
}
pub(crate) fn execute_restructure(
jpeg_data: &[u8],
transform: LosslessTransform,
edge_handling: EdgeHandling,
stop: &dyn enough::Stop,
) -> crate::error::Result<Vec<u8>> {
use crate::lossless::{OutputMode, RestartInterval, RestructureConfig};
let transform_config = if transform == LosslessTransform::None {
None
} else {
Some(TransformConfig {
transform,
edge_handling,
})
};
let config = RestructureConfig {
output_mode: OutputMode::Sequential,
restart_interval: RestartInterval::EveryMcuRows(4),
transform: transform_config,
};
crate::lossless::restructure(jpeg_data, &config, stop)
}
pub(crate) fn safe_auto_orient(info: &crate::decode::JpegInfo) -> Option<u8> {
let exif_orient = info
.exif
.as_ref()
.and_then(|e| crate::lossless::parse_exif_orientation(e))?;
if exif_orient == 1 {
return None; }
let transform = LosslessTransform::from_exif_orientation(exif_orient)?;
let (mcu_w, mcu_h) = mcu_dimensions(info.subsampling);
let w = info.dimensions.width;
let h = info.dimensions.height;
let w_aligned = w % mcu_w == 0;
let h_aligned = h % mcu_h == 0;
let would_trim = match transform {
LosslessTransform::None => false,
LosslessTransform::FlipHorizontal => !w_aligned,
LosslessTransform::FlipVertical => !h_aligned,
LosslessTransform::Rotate180 => !w_aligned || !h_aligned,
LosslessTransform::Rotate90 => !h_aligned,
LosslessTransform::Rotate270 => !w_aligned,
LosslessTransform::Transpose => false, LosslessTransform::Transverse => !w_aligned || !h_aligned,
};
if would_trim { None } else { Some(exif_orient) }
}
fn mcu_dimensions(subsampling: crate::types::Subsampling) -> (u32, u32) {
use crate::types::Subsampling;
match subsampling {
Subsampling::S444 => (8, 8),
Subsampling::S422 => (16, 8),
Subsampling::S420 => (16, 16),
Subsampling::S440 => (8, 16),
}
}
pub(crate) fn reset_exif_orientation_in_jpeg(jpeg_data: &mut [u8]) -> bool {
let mut i = 0;
while i + 1 < jpeg_data.len() {
if jpeg_data[i] == 0xFF && jpeg_data[i + 1] == 0xE1 {
if i + 3 >= jpeg_data.len() {
break;
}
let seg_len = u16::from_be_bytes([jpeg_data[i + 2], jpeg_data[i + 3]]) as usize;
let seg_start = i + 2; let seg_end = seg_start + seg_len;
if seg_end > jpeg_data.len() {
break;
}
let data_start = i + 4; if data_start + 6 <= jpeg_data.len()
&& jpeg_data[data_start..data_start + 6] == *b"Exif\0\0"
{
crate::lossless::set_exif_orientation(&mut jpeg_data[data_start..seg_end], 1);
return true;
}
}
i += 1;
}
false
}
pub(crate) fn is_decode_ready(jpeg_data: &[u8], info: &crate::decode::JpegInfo) -> bool {
let w = info.dimensions.width;
let h = info.dimensions.height;
if w < 512 && h < 512 {
return true;
}
let ri = match parse_dri(jpeg_data) {
Some(ri) if ri > 0 => ri as usize,
_ => return false,
};
let (mcu_w, _) = mcu_dimensions(info.subsampling);
let mcu_cols = (w as usize + mcu_w as usize - 1) / mcu_w as usize;
if mcu_cols == 0 {
return false;
}
if ri % mcu_cols != 0 {
return false;
}
let mcu_rows_per_segment = ri / mcu_cols;
(1..=8).contains(&mcu_rows_per_segment)
}
fn parse_dri(jpeg_data: &[u8]) -> Option<u16> {
let mut i = 0;
while i + 1 < jpeg_data.len() {
if jpeg_data[i] != 0xFF {
i += 1;
continue;
}
let marker = jpeg_data[i + 1];
if marker == 0xDD {
if i + 5 < jpeg_data.len() {
return Some(u16::from_be_bytes([jpeg_data[i + 4], jpeg_data[i + 5]]));
}
return None;
}
if marker == 0xDA {
break;
}
i += 1;
}
None
}
fn constraint_may_resize(c: &Constraint) -> bool {
if c.width.is_none() && c.height.is_none() {
return false;
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detect_identity() {
let commands = vec![];
assert_eq!(detect_lossless(&commands), Some(LosslessTransform::None));
}
#[test]
fn detect_exif_rotate90() {
let commands = vec![Command::AutoOrient(6)]; assert_eq!(
detect_lossless(&commands),
Some(LosslessTransform::Rotate90)
);
}
#[test]
fn detect_composed_orientation() {
use zenlayout::{FlipAxis, Rotation};
let commands = vec![
Command::Rotate(Rotation::Rotate90),
Command::Flip(FlipAxis::Horizontal),
];
let result = detect_lossless(&commands);
assert!(result.is_some());
}
#[test]
fn detect_resize_is_lossy() {
use zenlayout::ConstraintMode;
let commands = vec![
Command::AutoOrient(6),
Command::Constrain(Constraint::new(ConstraintMode::Fit, 800, 600)),
];
assert_eq!(detect_lossless(&commands), None);
}
#[test]
fn detect_crop_is_lossy() {
let commands = vec![Command::Crop(zenlayout::SourceCrop::pixels(
10, 10, 100, 100,
))];
assert_eq!(detect_lossless(&commands), None);
}
#[test]
fn parse_dri_absent() {
let data = [0xFF, 0xD8, 0xFF, 0xDA];
assert_eq!(super::parse_dri(&data), None);
}
#[test]
fn parse_dri_present() {
let data = [
0xFF, 0xD8, 0xFF, 0xDD, 0x00, 0x04, 0x00, 0x10, 0xFF, 0xDA, ];
assert_eq!(super::parse_dri(&data), Some(16));
}
#[test]
fn parse_dri_zero() {
let data = [0xFF, 0xD8, 0xFF, 0xDD, 0x00, 0x04, 0x00, 0x00, 0xFF, 0xDA];
assert_eq!(super::parse_dri(&data), Some(0));
}
}