use crate::error::{Error, Result};
#[derive(Debug, Clone)]
pub struct JpegSegment {
pub marker: u8,
pub data: Vec<u8>,
}
#[allow(clippy::too_many_arguments)]
pub fn write_jpeg(
source: &[u8],
new_exif: Option<&[u8]>,
new_xmp: Option<&[u8]>,
new_iptc: Option<&[u8]>,
new_comment: Option<&str>,
remove_exif: bool,
remove_xmp: bool,
remove_iptc: bool,
remove_comment: bool,
) -> Result<Vec<u8>> {
if source.len() < 2 || source[0] != 0xFF || source[1] != 0xD8 {
return Err(Error::InvalidData("not a JPEG file".into()));
}
let mut output = Vec::with_capacity(source.len());
output.extend_from_slice(&[0xFF, 0xD8]);
let mut pos = 2;
let mut wrote_exif = false;
let mut wrote_xmp = false;
let mut wrote_iptc = false;
let mut wrote_comment = false;
let mut past_first_segment = false;
while pos + 4 <= source.len() {
if source[pos] != 0xFF {
pos += 1;
continue;
}
let marker = source[pos + 1];
pos += 2;
if marker == 0xFF || marker == 0x00 {
continue;
}
if marker == 0xDA {
if !wrote_exif {
if let Some(exif) = new_exif {
write_app1_exif(&mut output, exif);
}
}
if !wrote_xmp {
if let Some(xmp) = new_xmp {
write_app1_xmp(&mut output, xmp);
}
}
if !wrote_iptc {
if let Some(iptc) = new_iptc {
write_app13_iptc(&mut output, iptc);
}
}
if !wrote_comment {
if let Some(comment) = new_comment {
write_com(&mut output, comment);
}
}
output.extend_from_slice(&[0xFF, marker]);
output.extend_from_slice(&source[pos..]);
return Ok(output);
}
if marker == 0xD8 || (0xD0..=0xD7).contains(&marker) {
output.extend_from_slice(&[0xFF, marker]);
continue;
}
if pos + 2 > source.len() {
break;
}
let seg_len = u16::from_be_bytes([source[pos], source[pos + 1]]) as usize;
if seg_len < 2 || pos + seg_len > source.len() {
break;
}
let seg_data = &source[pos + 2..pos + seg_len];
if !past_first_segment {
past_first_segment = true;
if new_exif.is_some() && !is_exif_segment(marker, seg_data) {
if let Some(exif) = new_exif {
write_app1_exif(&mut output, exif);
wrote_exif = true;
}
}
}
match marker {
0xE1 => {
if is_exif_segment(marker, seg_data) {
if remove_exif {
} else if let Some(exif) = new_exif {
if !wrote_exif {
write_app1_exif(&mut output, exif);
wrote_exif = true;
}
} else {
write_segment(&mut output, marker, &source[pos..pos + seg_len]);
}
} else if is_xmp_segment(seg_data) {
if remove_xmp {
} else if let Some(xmp) = new_xmp {
if !wrote_xmp {
write_app1_xmp(&mut output, xmp);
wrote_xmp = true;
}
} else {
write_segment(&mut output, marker, &source[pos..pos + seg_len]);
}
} else {
write_segment(&mut output, marker, &source[pos..pos + seg_len]);
}
}
0xED => {
if remove_iptc {
} else if let Some(iptc) = new_iptc {
if !wrote_iptc {
write_app13_iptc(&mut output, iptc);
wrote_iptc = true;
}
} else {
write_segment(&mut output, marker, &source[pos..pos + seg_len]);
}
}
0xFE => {
if remove_comment {
} else if let Some(comment) = new_comment {
if !wrote_comment {
write_com(&mut output, comment);
wrote_comment = true;
}
} else {
write_segment(&mut output, marker, &source[pos..pos + seg_len]);
}
}
_ => {
write_segment(&mut output, marker, &source[pos..pos + seg_len]);
}
}
pos += seg_len;
}
Ok(output)
}
fn is_exif_segment(_marker: u8, data: &[u8]) -> bool {
data.len() > 6 && data.starts_with(b"Exif\0\0")
}
fn is_xmp_segment(data: &[u8]) -> bool {
data.len() > 29 && data.starts_with(b"http://ns.adobe.com/xap/1.0/\0")
}
fn write_segment(output: &mut Vec<u8>, marker: u8, data: &[u8]) {
output.push(0xFF);
output.push(marker);
output.extend_from_slice(data);
}
fn write_app1_exif(output: &mut Vec<u8>, exif_data: &[u8]) {
let mut segment = Vec::new();
segment.extend_from_slice(b"Exif\0\0");
segment.extend_from_slice(exif_data);
let total_len = (segment.len() + 2) as u16; output.push(0xFF);
output.push(0xE1);
output.extend_from_slice(&total_len.to_be_bytes());
output.extend_from_slice(&segment);
}
fn write_app1_xmp(output: &mut Vec<u8>, xmp_data: &[u8]) {
let mut segment = Vec::new();
segment.extend_from_slice(b"http://ns.adobe.com/xap/1.0/\0");
segment.extend_from_slice(xmp_data);
if segment.len() + 2 > 65535 {
let max = 65533;
let total_len = (max + 2) as u16;
output.push(0xFF);
output.push(0xE1);
output.extend_from_slice(&total_len.to_be_bytes());
output.extend_from_slice(&segment[..max]);
} else {
let total_len = (segment.len() + 2) as u16;
output.push(0xFF);
output.push(0xE1);
output.extend_from_slice(&total_len.to_be_bytes());
output.extend_from_slice(&segment);
}
}
pub fn extract_jpeg_iptc_iim(jpeg: &[u8]) -> Option<Vec<u8>> {
const PS_HEADER: &[u8] = b"Photoshop 3.0\0";
let mut pos = 2; while pos + 4 <= jpeg.len() {
if jpeg[pos] != 0xFF {
break;
}
let marker = jpeg[pos + 1];
if marker == 0x01 || (0xD0..=0xD9).contains(&marker) {
pos += 2;
continue;
}
if marker == 0xDA {
break; }
let len = u16::from_be_bytes([jpeg[pos + 2], jpeg[pos + 3]]) as usize;
if len < 2 || pos + 2 + len > jpeg.len() {
break;
}
let seg = &jpeg[pos + 4..pos + 2 + len];
if marker == 0xED && seg.starts_with(PS_HEADER) {
return crate::formats::jpeg::extract_photoshop_irbs(&seg[PS_HEADER.len()..]).0;
}
pos += 2 + len;
}
None
}
fn write_app13_iptc(output: &mut Vec<u8>, iptc_data: &[u8]) {
let mut segment = Vec::new();
segment.extend_from_slice(b"Photoshop 3.0\0");
segment.extend_from_slice(b"8BIM");
segment.extend_from_slice(&0x0404u16.to_be_bytes()); segment.push(0); segment.push(0); segment.extend_from_slice(&(iptc_data.len() as u32).to_be_bytes());
segment.extend_from_slice(iptc_data);
if iptc_data.len() % 2 != 0 {
segment.push(0); }
let total_len = (segment.len() + 2) as u16;
output.push(0xFF);
output.push(0xED);
output.extend_from_slice(&total_len.to_be_bytes());
output.extend_from_slice(&segment);
}
fn write_com(output: &mut Vec<u8>, comment: &str) {
let data = comment.as_bytes();
let total_len = (data.len() + 2) as u16;
output.push(0xFF);
output.push(0xFE);
output.extend_from_slice(&total_len.to_be_bytes());
output.extend_from_slice(data);
}