mirror of
https://github.com/TheFunny/TelegramTwitterMediaBot.git
synced 2026-09-24 23:42:18 +00:00
refactor(photo): read the PNG header with the png crate
parse_png_header hand-decoded the IHDR — byte offsets, the depth byte's five legal values, the color byte's five — roughly thirty lines the png crate already implements (and validates properly: CRC included). It is now Decoder::new + read_info, which is all the header a plan needs; no pixels are decoded. The synthetic test fixture gained the IHDR CRC and an IDAT header (read_info stops at the first IDAT; the hand parser stopped four bytes earlier and checked no CRC), with a nine-line reflected CRC-32 alongside it; the assertions on width/height/depth/color are unchanged, as are the real-file cases.
This commit is contained in:
@@ -200,31 +200,15 @@ pub fn prepare_photo(file: NamedTempFile, bytes: &[u8]) -> Result<PhotoPrep, Str
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the PNG IHDR (bytes 8..26: signature + length + "IHDR" + width +
|
||||
/// height + bit depth + color type).
|
||||
/// The PNG's IHDR as the crate reads it (signature through the first IDAT):
|
||||
/// width/height/depth/color decide the plan and the decode channels, without
|
||||
/// decoding any pixels.
|
||||
fn parse_png_header(bytes: &[u8]) -> Option<(u32, u32, png::BitDepth, png::ColorType)> {
|
||||
if !bytes.starts_with(b"\x89PNG\r\n\x1a\n") || bytes.len() < 26 {
|
||||
return None;
|
||||
}
|
||||
let w = u32::from_be_bytes(bytes.get(16..20)?.try_into().ok()?);
|
||||
let h = u32::from_be_bytes(bytes.get(20..24)?.try_into().ok()?);
|
||||
let depth = match *bytes.get(24)? {
|
||||
1 => png::BitDepth::One,
|
||||
2 => png::BitDepth::Two,
|
||||
4 => png::BitDepth::Four,
|
||||
8 => png::BitDepth::Eight,
|
||||
16 => png::BitDepth::Sixteen,
|
||||
_ => return None,
|
||||
};
|
||||
let color = match *bytes.get(25)? {
|
||||
0 => png::ColorType::Grayscale,
|
||||
2 => png::ColorType::Rgb,
|
||||
3 => png::ColorType::Indexed,
|
||||
4 => png::ColorType::GrayscaleAlpha,
|
||||
6 => png::ColorType::Rgba,
|
||||
_ => return None,
|
||||
};
|
||||
Some((w, h, depth, color))
|
||||
let reader = png::Decoder::new(std::io::Cursor::new(bytes))
|
||||
.read_info()
|
||||
.ok()?;
|
||||
let info = reader.info();
|
||||
Some((info.width, info.height, info.bit_depth, info.color_type))
|
||||
}
|
||||
|
||||
/// Output channels of a decoded frame for the given color type (post
|
||||
@@ -453,9 +437,28 @@ mod tests {
|
||||
bytes.extend(w.to_be_bytes());
|
||||
bytes.extend(h.to_be_bytes());
|
||||
bytes.extend([depth, color, 0, 0, 0]);
|
||||
// A correct IHDR CRC plus an IDAT chunk header: `png::Decoder` verifies
|
||||
// the CRC and `read_info` stops at the first IDAT — all the header
|
||||
// read needs. The hand-rolled parser this fixture used to feed stopped
|
||||
// four bytes earlier and checked neither.
|
||||
bytes.extend(crc32(&bytes[12..]).to_be_bytes());
|
||||
bytes.extend(0u32.to_be_bytes()); // IDAT payload length (never read)
|
||||
bytes.extend(b"IDAT");
|
||||
bytes
|
||||
}
|
||||
|
||||
/// CRC-32 as PNG chunks use it (IEEE, reflected).
|
||||
fn crc32(bytes: &[u8]) -> u32 {
|
||||
let mut crc = !0u32;
|
||||
for &b in bytes {
|
||||
crc ^= b as u32;
|
||||
for _ in 0..8 {
|
||||
crc = (crc >> 1) ^ (0xEDB8_8320 & (crc & 1).wrapping_neg());
|
||||
}
|
||||
}
|
||||
!crc
|
||||
}
|
||||
|
||||
/// The budget is a *process-wide* memory bound: `PREP_SLOTS` (6) caps how
|
||||
/// many photos are prepared at once, but six max-size photos would still
|
||||
/// hold six decode buffers of up to 512 MiB each.
|
||||
|
||||
Reference in New Issue
Block a user