#[cfg(feature = "multimodal")]
use std::io::Cursor;
#[cfg(feature = "multimodal")]
use base64::Engine;
#[cfg(feature = "multimodal")]
use image::ImageReader;
#[cfg(feature = "multimodal")]
const MAX_DECODE_EDGE: u32 = 8192;
#[cfg(feature = "multimodal")]
const MAX_DECODE_ALLOC: u64 = 64 * 1024 * 1024;
#[cfg(feature = "multimodal")]
const JPEG_QUALITY: u8 = 90;
#[cfg(feature = "multimodal")]
fn decode_limits() -> image::Limits {
let mut limits = image::Limits::default();
limits.max_image_width = Some(MAX_DECODE_EDGE);
limits.max_image_height = Some(MAX_DECODE_EDGE);
limits.max_alloc = Some(MAX_DECODE_ALLOC);
limits
}
#[cfg(feature = "multimodal")]
fn jpeg_exif_orientation(bytes: &[u8]) -> Option<u16> {
if bytes.len() < 2 || bytes[0] != 0xFF || bytes[1] != 0xD8 {
return None;
}
let mut i = 2;
while i + 4 <= bytes.len() {
if bytes[i] != 0xFF {
return None; }
let marker = bytes[i + 1];
if marker == 0xDA || marker == 0xD9 {
return None; }
let seg_len = u16::from_be_bytes([bytes[i + 2], bytes[i + 3]]) as usize;
if seg_len < 2 {
return None;
}
let body_start = i + 4;
let body_end = i + 2 + seg_len;
if body_end > bytes.len() {
return None;
}
if marker == 0xE1 {
let body = &bytes[body_start..body_end];
return exif_orientation_from_app1(body);
}
i = body_end;
}
None
}
#[cfg(feature = "multimodal")]
fn exif_orientation_from_app1(body: &[u8]) -> Option<u16> {
if body.len() < 14 || &body[0..6] != b"Exif\0\0" {
return None;
}
let tiff = &body[6..];
let le = match &tiff[0..2] {
b"II" => true,
b"MM" => false,
_ => return None,
};
let u16_at = |buf: &[u8], off: usize| -> Option<u16> {
let b = buf.get(off..off + 2)?;
Some(if le {
u16::from_le_bytes([b[0], b[1]])
} else {
u16::from_be_bytes([b[0], b[1]])
})
};
let u32_at = |buf: &[u8], off: usize| -> Option<u32> {
let b = buf.get(off..off + 4)?;
Some(if le {
u32::from_le_bytes([b[0], b[1], b[2], b[3]])
} else {
u32::from_be_bytes([b[0], b[1], b[2], b[3]])
})
};
let ifd0 = u32_at(tiff, 4)? as usize;
let count = u16_at(tiff, ifd0)? as usize;
for e in 0..count {
let entry = ifd0 + 2 + e * 12;
if tiff.get(entry..entry + 12).is_none() {
break;
}
if u16_at(tiff, entry)? == 0x0112 {
return u16_at(tiff, entry + 8);
}
}
None
}
#[derive(Clone, Copy)]
pub struct ImageCap {
pub max_long: u32,
pub max_short: u32,
pub max_pixels: u64,
pub tile: u32,
}
pub const CAP_OPENAI: ImageCap = ImageCap {
max_long: 2048,
max_short: 768,
max_pixels: u64::MAX,
tile: 512,
};
pub const CAP_ANTHROPIC: ImageCap = ImageCap {
max_long: 1568,
max_short: u32::MAX,
max_pixels: 1_150_000,
tile: 0,
};
pub const CAP_GOOGLE: ImageCap = ImageCap {
max_long: 3072,
max_short: u32::MAX,
max_pixels: u64::MAX,
tile: 0,
};
#[cfg(feature = "multimodal")]
fn b64() -> base64::engine::general_purpose::GeneralPurpose {
base64::engine::general_purpose::STANDARD
}
#[cfg(feature = "multimodal")]
fn target_dims(w: u32, h: u32, cap: ImageCap) -> Option<(u32, u32)> {
let (wf, hf) = (f64::from(w), f64::from(h));
let long = wf.max(hf);
let short = wf.min(hf);
let mut scale = 1.0_f64;
if long > f64::from(cap.max_long) {
scale = scale.min(f64::from(cap.max_long) / long);
}
if short > f64::from(cap.max_short) {
scale = scale.min(f64::from(cap.max_short) / short);
}
let pixels = wf * hf;
if pixels > cap.max_pixels as f64 {
scale = scale.min((cap.max_pixels as f64 / pixels).sqrt());
}
if scale >= 1.0 {
return None;
}
let nw = (wf * scale).floor().max(1.0) as u32;
let nh = (hf * scale).floor().max(1.0) as u32;
Some((nw, nh))
}
#[cfg(feature = "multimodal")]
fn snap_tile(dim: u32, tile: u32) -> u32 {
if tile == 0 || dim <= tile {
return dim;
}
let rem = dim % tile;
if rem != 0 && rem < tile / 10 {
dim - rem
} else {
dim
}
}
#[cfg(feature = "multimodal")]
pub fn fit_to_cap(data: &str, cap: ImageCap) -> Option<String> {
let bytes = b64().decode(data.trim()).ok()?;
let format = image::guess_format(&bytes).ok()?;
let is_jpeg = format == image::ImageFormat::Jpeg;
if is_jpeg && jpeg_exif_orientation(&bytes).is_some_and(|o| o != 1) {
return None;
}
let mut reader = ImageReader::with_format(Cursor::new(&bytes), format);
reader.limits(decode_limits());
let img = reader.decode().ok()?;
let (w, h) = (img.width(), img.height());
let (cw, ch) = target_dims(w, h, cap).unwrap_or((w, h));
let (nw, nh) = (snap_tile(cw, cap.tile), snap_tile(ch, cap.tile));
if nw == w && nh == h {
return None; }
let resized = img.resize(nw, nh, image::imageops::FilterType::Lanczos3);
let mut buf = Cursor::new(Vec::new());
if is_jpeg {
let encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, JPEG_QUALITY);
resized.write_with_encoder(encoder).ok()?;
} else {
resized.write_to(&mut buf, format).ok()?;
}
if buf.get_ref().len() >= bytes.len() {
return None;
}
Some(b64().encode(buf.get_ref()))
}
#[cfg(not(feature = "multimodal"))]
pub fn fit_to_cap(_data: &str, _cap: ImageCap) -> Option<String> {
None
}
pub fn fit_data_uri(uri: &str, cap: ImageCap) -> Option<String> {
let (header, data) = uri.split_once(',')?;
if !header.contains("base64") {
return None;
}
Some(format!("{header},{}", fit_to_cap(data, cap)?))
}
#[cfg(all(test, feature = "multimodal"))]
mod tests {
use super::*;
fn png_b64(w: u32, h: u32) -> String {
let img = image::DynamicImage::ImageRgb8(image::RgbImage::new(w, h));
let mut buf = std::io::Cursor::new(Vec::new());
img.write_to(&mut buf, image::ImageFormat::Png).unwrap();
b64().encode(buf.get_ref())
}
fn dims(b64data: &str) -> (u32, u32) {
let bytes = b64().decode(b64data).unwrap();
let img = image::load_from_memory(&bytes).unwrap();
(img.width(), img.height())
}
#[test]
fn openai_caps_short_side_to_768() {
let big = png_b64(1000, 900); let out = fit_to_cap(&big, CAP_OPENAI).expect("resized");
let (w, h) = dims(&out);
assert!(
w.min(h) <= 768,
"short side capped at 768 (got {}x{})",
w,
h
);
assert!(w.max(h) <= 2048);
}
#[test]
fn anthropic_caps_megapixels() {
let big = png_b64(1200, 1100); let out = fit_to_cap(&big, CAP_ANTHROPIC).expect("resized");
let (w, h) = dims(&out);
assert!(u64::from(w) * u64::from(h) <= 1_150_000, "within 1.15 MP");
assert!(w.max(h) <= 1568);
}
#[test]
fn within_cap_is_untouched() {
let small = png_b64(640, 480); assert!(fit_to_cap(&small, CAP_OPENAI).is_none());
assert!(fit_to_cap(&small, CAP_ANTHROPIC).is_none());
}
#[test]
fn data_uri_preserves_header() {
let uri = format!("data:image/png;base64,{}", png_b64(1000, 1000));
let out = fit_data_uri(&uri, CAP_OPENAI).expect("resized");
assert!(out.starts_with("data:image/png;base64,"));
}
#[test]
fn non_image_is_skipped() {
let txt = b64().encode(b"not an image at all");
assert!(fit_to_cap(&txt, CAP_OPENAI).is_none());
}
#[test]
fn openai_tile_snap_trims_sliver_tile() {
let img = png_b64(1025, 700);
let out = fit_to_cap(&img, CAP_OPENAI).expect("tile-snapped");
let (w, _h) = dims(&out);
assert!(w <= 1024, "snapped under the 512 tile boundary (got {w})");
}
#[test]
fn tile_snap_leaves_well_filled_tiles_alone() {
assert!(fit_to_cap(&png_b64(640, 480), CAP_OPENAI).is_none());
}
#[test]
fn oversized_image_skipped_under_decode_limit() {
let huge = png_b64(MAX_DECODE_EDGE + 100, 16);
assert!(
fit_to_cap(&huge, CAP_OPENAI).is_none(),
"image over the decode dimension cap is skipped, not resized"
);
}
#[test]
fn decode_limits_are_bounded() {
let l = decode_limits();
assert_eq!(l.max_image_width, Some(MAX_DECODE_EDGE));
assert_eq!(l.max_image_height, Some(MAX_DECODE_EDGE));
assert_eq!(l.max_alloc, Some(MAX_DECODE_ALLOC));
}
fn jpeg_with_orientation_dims(orient: u16, w: u32, h: u32) -> Vec<u8> {
let img = image::DynamicImage::ImageRgb8(image::RgbImage::new(w, h));
let mut body = Vec::new();
let enc = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut body, 90);
img.write_with_encoder(enc).unwrap();
let mut tiff = vec![b'M', b'M', 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08];
tiff.extend_from_slice(&[0x00, 0x01]); tiff.extend_from_slice(&[0x01, 0x12]); tiff.extend_from_slice(&[0x00, 0x03]); tiff.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]); tiff.extend_from_slice(&orient.to_be_bytes()); tiff.extend_from_slice(&[0x00, 0x00]); tiff.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); let mut app1_body = b"Exif\0\0".to_vec();
app1_body.extend_from_slice(&tiff);
let seg_len = (app1_body.len() + 2) as u16;
let mut app1 = vec![0xFF, 0xE1];
app1.extend_from_slice(&seg_len.to_be_bytes());
app1.extend_from_slice(&app1_body);
let mut out = body[0..2].to_vec(); out.extend_from_slice(&app1);
out.extend_from_slice(&body[2..]);
out
}
#[test]
fn exif_orientation_is_parsed() {
assert_eq!(
jpeg_exif_orientation(&jpeg_with_orientation_dims(6, 1, 1)),
Some(6)
);
assert_eq!(
jpeg_exif_orientation(&jpeg_with_orientation_dims(1, 1, 1)),
Some(1)
);
let img = image::DynamicImage::ImageRgb8(image::RgbImage::new(2, 2));
let mut plain = Vec::new();
let enc = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut plain, 90);
img.write_with_encoder(enc).unwrap();
assert_eq!(jpeg_exif_orientation(&plain), None);
assert_eq!(jpeg_exif_orientation(b"not a jpeg"), None);
}
#[test]
fn rotated_jpeg_is_passed_through() {
let rotated = jpeg_with_orientation_dims(6, 1000, 900);
let b64data = b64().encode(&rotated);
assert!(
fit_to_cap(&b64data, CAP_OPENAI).is_none(),
"non-upright JPEG is passed through unchanged, not re-encoded"
);
let upright = jpeg_with_orientation_dims(1, 1000, 900);
assert!(
fit_to_cap(&b64().encode(&upright), CAP_OPENAI).is_some(),
"upright JPEG over the cap is still resized"
);
}
#[test]
fn jpeg_downscale_round_trips_and_decodes() {
let mut buf = image::RgbImage::new(2000, 1500);
for (x, y, px) in buf.enumerate_pixels_mut() {
*px = image::Rgb([(x % 256) as u8, (y % 256) as u8, ((x + y) % 256) as u8]);
}
let img = image::DynamicImage::ImageRgb8(buf);
let mut bytes = Vec::new();
let enc = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut bytes, 90);
img.write_with_encoder(enc).unwrap();
let data = b64().encode(&bytes);
let out = fit_to_cap(&data, CAP_OPENAI).expect("jpeg downscaled");
let (w, h) = dims(&out);
assert!(w.min(h) <= 768 && w.max(h) <= 2048, "within OpenAI cap");
}
}