app
Adom Shotlog
Public Made by Adomby adom
See the shots your AI never showed you, and know it actually SAW them. adom-shotlog streams every screenshot the AI gathers into a live gallery (webview/pup/browser/phone), reports whether a human is
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
use base64::Engine;
use image::imageops::FilterType;
use image::ImageFormat;
use std::io::Cursor;
/// Read width and height from a PNG file's IHDR chunk (bytes 16-23).
/// Returns (0, 0) if the buffer is too short or not a valid PNG.
pub fn read_png_dimensions(bytes: &[u8]) -> (u32, u32) {
if bytes.len() < 24 {
return (0, 0);
}
// PNG signature check
if &bytes[0..4] != b"\x89PNG" {
return (0, 0);
}
let w = u32::from_be_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]);
let h = u32::from_be_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]);
(w, h)
}
/// Resize a PNG image if its width exceeds `max_width`.
/// Resize so neither dimension exceeds `max_edge`, preserving aspect ratio.
/// Returns the (possibly resized) PNG bytes and final (w, h). Caps BOTH width
/// and height — a tall narrow screenshot is constrained by height, not just width.
pub fn resize_png(bytes: Vec<u8>, max_edge: u32) -> Result<(Vec<u8>, u32, u32), String> {
let (orig_w, orig_h) = read_png_dimensions(&bytes);
if orig_w == 0 || (orig_w <= max_edge && orig_h <= max_edge) {
return Ok((bytes, orig_w, orig_h));
}
let img = image::load_from_memory_with_format(&bytes, ImageFormat::Png)
.map_err(|e| format!("Failed to decode PNG: {e}"))?;
// Scale by whichever edge is over the limit (the larger ratio wins).
let scale = (max_edge as f64 / orig_w as f64).min(max_edge as f64 / orig_h as f64);
let new_w = (orig_w as f64 * scale).round().max(1.0) as u32;
let new_h = (orig_h as f64 * scale).round().max(1.0) as u32;
let resized = img.resize_exact(new_w, new_h, FilterType::Lanczos3);
let mut buf = Cursor::new(Vec::new());
resized
.write_to(&mut buf, ImageFormat::Png)
.map_err(|e| format!("Failed to encode resized PNG: {e}"))?;
Ok((buf.into_inner(), new_w, new_h))
}
/// PNG metadata from the IHDR chunk.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PngMeta {
pub bit_depth: u8,
pub color_type: String,
pub channels: u8,
pub raw_bytes: u64,
pub file_bytes: u64,
pub compression_ratio: f64,
pub compression_pct: u8,
}
/// Read extended PNG metadata from IHDR chunk.
pub fn read_png_meta(bytes: &[u8]) -> Option<PngMeta> {
if bytes.len() < 26 || &bytes[0..4] != b"\x89PNG" {
return None;
}
let w = u32::from_be_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]);
let h = u32::from_be_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]);
let bit_depth = bytes[24];
let color_type_byte = bytes[25];
let (color_type, channels) = match color_type_byte {
0 => ("Grayscale", 1u8),
2 => ("RGB", 3),
3 => ("Indexed", 1),
4 => ("Grayscale+Alpha", 2),
6 => ("RGBA", 4),
_ => ("Unknown", 1),
};
let bytes_per_pixel = (channels as u64) * (bit_depth as u64) / 8;
let raw = (w as u64) * (h as u64) * bytes_per_pixel.max(1);
let file = bytes.len() as u64;
let ratio = if file > 0 { raw as f64 / file as f64 } else { 1.0 };
let pct = if raw > 0 { ((1.0 - (file as f64 / raw as f64)) * 100.0).max(0.0) as u8 } else { 0 };
Some(PngMeta {
bit_depth,
color_type: color_type.to_string(),
channels,
raw_bytes: raw,
file_bytes: file,
compression_ratio: (ratio * 10.0).round() / 10.0,
compression_pct: pct,
})
}
/// Parse a data URL like `data:image/png;base64,iVBOR...` into (mime_type, raw_bytes).
pub fn parse_data_url(data_url: &str) -> Result<(String, Vec<u8>), String> {
let rest = data_url
.strip_prefix("data:")
.ok_or("Not a data URL")?;
let (meta, b64) = rest
.split_once(",")
.ok_or("No comma in data URL")?;
let mime = meta
.split(';')
.next()
.unwrap_or("application/octet-stream")
.to_string();
let bytes = base64::engine::general_purpose::STANDARD
.decode(b64)
.map_err(|e| format!("Base64 decode error: {e}"))?;
Ok((mime, bytes))
}