use alloc::vec::Vec;
use crate::encode::extras::{EncoderSegments, MpfImageType, inject_encoder_segments};
pub(crate) fn is_ultrahdr_xmp(xmp: &str) -> bool {
xmp.contains("hdrgm:Version") || xmp.contains("hdrgm:GainMapMax")
}
pub(crate) fn find_secondary_jpeg(data: &[u8]) -> Option<Vec<u8>> {
const SOI: [u8; 2] = [0xFF, 0xD8];
const EOI: [u8; 2] = [0xFF, 0xD9];
let primary_end = find_primary_eoi(data)?;
let remaining = &data[primary_end..];
if remaining.len() < 4 {
return None;
}
for i in 0..remaining.len().saturating_sub(1) {
if remaining[i] == SOI[0] && remaining[i + 1] == SOI[1] {
let gm_start = primary_end + i;
let mut pos = i + 2;
while pos < remaining.len().saturating_sub(1) {
if remaining[pos] != 0xFF {
pos += 1;
continue;
}
let marker = remaining[pos + 1];
match marker {
0xD9 => {
let gm_end = primary_end + pos + 2;
return Some(data[gm_start..gm_end].to_vec());
}
0x00 => pos += 2, 0xFF => pos += 1, 0xDA => {
if pos + 4 > remaining.len() {
break;
}
let len =
u16::from_be_bytes([remaining[pos + 2], remaining[pos + 3]]) as usize;
pos += 2 + len;
while pos < remaining.len().saturating_sub(1) {
if remaining[pos] == 0xFF {
let next = remaining[pos + 1];
if next == 0x00 || next == 0xFF {
pos += if next == 0x00 { 2 } else { 1 };
} else if (0xD0..=0xD7).contains(&next) {
pos += 2;
} else {
break; }
} else {
pos += 1;
}
}
}
m if (0xD0..=0xD7).contains(&m) => pos += 2,
_ => {
if pos + 4 > remaining.len() {
break;
}
let len =
u16::from_be_bytes([remaining[pos + 2], remaining[pos + 3]]) as usize;
pos += 2 + len;
}
}
}
return Some(data[gm_start..].to_vec());
}
}
None
}
fn find_primary_eoi(data: &[u8]) -> Option<usize> {
if data.len() < 4 || data[0] != 0xFF || data[1] != 0xD8 {
return None; }
let mut pos = 2;
while pos < data.len().saturating_sub(1) {
if data[pos] != 0xFF {
pos += 1;
continue;
}
let marker = data[pos + 1];
match marker {
0xD9 => {
return Some(pos + 2);
}
0x00 => {
pos += 2;
}
0xFF => {
pos += 1;
}
0xDA => {
if pos + 4 > data.len() {
return None;
}
let len = u16::from_be_bytes([data[pos + 2], data[pos + 3]]) as usize;
pos += 2 + len;
while pos < data.len().saturating_sub(1) {
if data[pos] == 0xFF {
let next = data[pos + 1];
if next == 0x00 {
pos += 2;
} else if next == 0xFF {
pos += 1;
} else if (0xD0..=0xD7).contains(&next) {
pos += 2;
} else {
break;
}
} else {
pos += 1;
}
}
}
m if (0xD0..=0xD7).contains(&m) => {
pos += 2;
}
_ => {
if pos + 4 > data.len() {
return None;
}
let len = u16::from_be_bytes([data[pos + 2], data[pos + 3]]) as usize;
pos += 2 + len;
}
}
}
None
}
pub(crate) fn assemble_ultrahdr(primary: Vec<u8>, gain_map: Vec<u8>) -> Vec<u8> {
let segments = EncoderSegments::new().add_mpf_image(gain_map, MpfImageType::Undefined);
inject_encoder_segments(primary, &segments)
}
pub(crate) fn compute_gainmap_target(
primary_src_w: u32,
primary_src_h: u32,
primary_dst_w: u32,
primary_dst_h: u32,
gm_src_w: u32,
gm_src_h: u32,
) -> (u32, u32) {
if primary_src_w == 0 || primary_src_h == 0 {
return (gm_src_w.max(1), gm_src_h.max(1));
}
let scale_x = primary_dst_w as f64 / primary_src_w as f64;
let scale_y = primary_dst_h as f64 / primary_src_h as f64;
let gm_dst_w = (gm_src_w as f64 * scale_x).round().max(1.0) as u32;
let gm_dst_h = (gm_src_h as f64 * scale_y).round().max(1.0) as u32;
(gm_dst_w, gm_dst_h)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detect_ultrahdr_xmp() {
assert!(is_ultrahdr_xmp(r#"<rdf:Description hdrgm:Version="1.0"/>"#));
assert!(is_ultrahdr_xmp(
r#"<rdf:Description hdrgm:GainMapMax="4.0"/>"#
));
assert!(!is_ultrahdr_xmp(r#"<rdf:Description dc:creator="Test"/>"#));
assert!(!is_ultrahdr_xmp(""));
}
#[test]
fn find_secondary_in_multi_jpeg() {
let primary = vec![0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x02, 0xFF, 0xD9];
let secondary = vec![0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x02, 0xFF, 0xD9];
let mut combined = primary.clone();
combined.extend_from_slice(&secondary);
let found = find_secondary_jpeg(&combined);
assert!(found.is_some());
assert_eq!(found.unwrap(), secondary);
}
#[test]
fn no_secondary_in_single_jpeg() {
let single = vec![0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x02, 0xFF, 0xD9];
assert!(find_secondary_jpeg(&single).is_none());
}
#[test]
fn gainmap_target_proportional() {
let (w, h) = compute_gainmap_target(2048, 2048, 512, 512, 512, 512);
assert_eq!(w, 128);
assert_eq!(h, 128);
}
#[test]
fn gainmap_target_asymmetric() {
let (w, h) = compute_gainmap_target(1000, 500, 500, 250, 250, 125);
assert_eq!(w, 125);
assert_eq!(h, 63);
}
#[test]
fn gainmap_target_minimum() {
let (w, h) = compute_gainmap_target(1000, 1000, 1, 1, 2, 2);
assert!(w >= 1);
assert!(h >= 1);
}
}