use std::io::Cursor;
use image::{GrayImage, ImageFormat, Luma};
const LEN_PREFIX: usize = 4;
pub fn bytes_to_png(data: &[u8]) -> Vec<u8> {
let mut payload = Vec::with_capacity(LEN_PREFIX + data.len());
payload.extend_from_slice(&(data.len() as u32).to_le_bytes());
payload.extend_from_slice(data);
let total = payload.len().max(1);
let width = (total as f64).sqrt().ceil() as u32;
let width = width.max(1);
let height = (total as u32).div_ceil(width);
let mut img = GrayImage::new(width, height);
let mut bytes = payload.iter();
for y in 0..height {
for x in 0..width {
let v = bytes.next().copied().unwrap_or(0);
img.put_pixel(x, y, Luma([v]));
}
}
let mut out = Vec::new();
img.write_to(&mut Cursor::new(&mut out), ImageFormat::Png)
.expect("codificación PNG");
out
}
pub fn png_to_bytes(png: &[u8]) -> Option<Vec<u8>> {
let img = image::load_from_memory_with_format(png, ImageFormat::Png)
.ok()?
.to_luma8();
let payload: Vec<u8> = img.pixels().map(|p| p.0[0]).collect();
if payload.len() < LEN_PREFIX {
return None;
}
let len = u32::from_le_bytes(payload[0..LEN_PREFIX].try_into().ok()?) as usize;
let end = LEN_PREFIX.checked_add(len)?;
if end > payload.len() {
return None;
}
Some(payload[LEN_PREFIX..end].to_vec())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_bytes_through_png() {
let data = b"datos arbitrarios para pintar como imagen";
let png = bytes_to_png(data);
assert_eq!(png_to_bytes(&png).unwrap(), data);
}
#[test]
fn round_trips_empty() {
let png = bytes_to_png(b"");
assert_eq!(png_to_bytes(&png).unwrap(), b"");
}
#[test]
fn output_is_a_valid_png() {
let png = bytes_to_png(b"hola");
assert_eq!(&png[0..8], &[0x89, b'P', b'N', b'G', 0x0d, 0x0a, 0x1a, 0x0a]);
}
#[test]
fn rejects_garbage() {
assert!(png_to_bytes(b"no soy un png").is_none());
}
}