use otf_pixels_core::{
Codec, DecodeCapability, Decoder, Format, ImageDescriptor, Limits, Orientation, PixelFormat,
PixelsError, Result, Source,
};
use crate::format::{
ChunkReader, ChunkStream, ColorType, Filter, Header, SIGNATURE, adam7_pass_size,
adam7_position, unfilter,
};
use otf_pixels_compress::{ZlibStream, zlib_decompress};
const MAX_PLTE: usize = 256 * 3;
const MAX_TRNS: usize = 256;
const READ_CHUNK: usize = 64 * 1024;
const EXIF_PREFIX: usize = 64 * 1024;
const MAX_ICC: usize = 4 << 20;
#[derive(Debug, Clone)]
enum Transparency {
Gray(u16),
Rgb(u16, u16, u16),
Palette(Vec<u8>),
}
fn parse_iccp(data: &[u8]) -> Option<Vec<u8>> {
let nul = data
.iter()
.position(|&b| b == 0)
.filter(|&n| (1..=79).contains(&n))?;
let (&method, compressed) = data.get(nul + 1..)?.split_first()?;
if method != 0 {
return None;
}
zlib_decompress(compressed, MAX_ICC).ok()
}
#[derive(Debug)]
pub struct PngDecoder<S: Source> {
header: Header,
descriptor: ImageDescriptor,
prelude: Option<Prelude<S>>,
orientation: Orientation,
icc: Option<Vec<u8>>,
raster: Option<Vec<u8>>,
stream: Option<Box<Streaming<S>>>,
row: u32,
}
#[derive(Debug)]
struct Prelude<S: Source> {
chunks: ChunkStream<S>,
palette: Option<Vec<[u8; 3]>>,
transparency: Option<Transparency>,
}
#[derive(Debug)]
struct Streaming<S: Source> {
chunks: ChunkStream<S>,
zlib: ZlibStream,
filtered: Vec<u8>,
at: usize,
previous: Vec<u8>,
palette: Option<Vec<[u8; 3]>>,
transparency: Option<Transparency>,
input_done: bool,
}
impl<S: Source> PngDecoder<S> {
pub fn new(mut source: S, limits: Limits) -> Result<Self> {
let mut prefix = vec![0_u8; 33];
source.read_exact(&mut prefix)?;
let mut reader = ChunkReader::new(&prefix)?;
let chunk = reader.next_chunk()?;
if !chunk.is(b"IHDR") {
return Err(PixelsError::malformed(
"png",
format!("first chunk must be IHDR, got `{}`", chunk.name()),
));
}
let header = Header::parse(&chunk.data, &limits)?;
let mut chunks = ChunkStream::new(source);
let mut palette: Option<Vec<[u8; 3]>> = None;
let mut transparency: Option<Transparency> = None;
let mut orientation = None;
let mut icc = None;
loop {
let kind = chunks.open_next()?;
match &kind {
b"IHDR" => {
return Err(PixelsError::malformed("png", "more than one IHDR"));
}
b"PLTE" => {
let data = chunks.read_payload_to_end(MAX_PLTE)?;
palette = Some(parse_plte(&data)?);
chunks.close()?;
}
b"tRNS" => {
let data = chunks.read_payload_to_end(MAX_TRNS)?;
transparency = Some(parse_trns(&data, header.color_type)?);
chunks.close()?;
}
b"eXIf" => {
let mut exif = vec![0_u8; EXIF_PREFIX];
let mut filled = 0;
while let Some(rest) = exif.get_mut(filled..) {
match chunks.read_payload(rest)? {
0 => break,
n => filled += n,
}
}
exif.truncate(filled);
chunks.skip_payload()?;
chunks.close()?;
orientation = orientation.or_else(|| Orientation::from_exif_block(&exif));
}
b"iCCP" => {
let mut data = vec![0_u8; MAX_ICC + 1];
let mut filled = 0;
while let Some(rest) = data.get_mut(filled..).filter(|r| !r.is_empty()) {
match chunks.read_payload(rest)? {
0 => break,
n => filled += n,
}
}
data.truncate(filled);
chunks.skip_payload()?;
chunks.close()?;
if filled <= MAX_ICC {
icc = icc.or_else(|| parse_iccp(&data));
}
}
b"IDAT" => break,
b"IEND" => {
return Err(PixelsError::malformed("png", "no IDAT data"));
}
_ => {
if !chunks.is_ancillary() {
return Err(PixelsError::malformed(
"png",
format!("unknown critical chunk `{}`", chunks.name()),
));
}
chunks.skip_payload()?;
chunks.close()?;
}
}
}
if header.color_type == ColorType::Palette && palette.is_none() {
return Err(PixelsError::malformed(
"png",
"palette image has no PLTE chunk",
));
}
let descriptor = header.descriptor(transparency.is_some(), &limits)?;
Ok(Self {
header,
descriptor,
prelude: Some(Prelude {
chunks,
palette,
transparency,
}),
orientation: orientation.unwrap_or_default(),
icc,
raster: None,
stream: None,
row: 0,
})
}
#[must_use]
pub const fn header(&self) -> Header {
self.header
}
fn begin_streaming(&mut self) -> Result<Streaming<S>> {
let Some(Prelude {
chunks,
palette,
transparency,
}) = self.prelude.take()
else {
return Err(PixelsError::graph("png source was already consumed"));
};
let row_bytes = self.header.row_bytes(self.header.width);
Ok(Streaming {
chunks,
zlib: ZlibStream::new(self.header.filtered_size()),
filtered: Vec::new(),
at: 0,
previous: vec![0_u8; row_bytes],
palette,
transparency,
input_done: false,
})
}
fn decode_image(&mut self) -> Result<Vec<u8>> {
let Some(Prelude {
mut chunks,
palette,
transparency,
}) = self.prelude.take()
else {
return Err(PixelsError::graph("png source was already consumed"));
};
let mut compressed: Vec<u8> = Vec::new();
let mut buffer = vec![0_u8; READ_CHUNK];
loop {
loop {
let read = chunks.read_payload(&mut buffer)?;
if read == 0 {
break;
}
compressed.extend_from_slice(buffer.get(..read).unwrap_or(&[]));
}
chunks.close()?;
match &chunks.open_next()? {
b"IDAT" => {}
b"IEND" => {
chunks.skip_payload()?;
chunks.close()?;
break;
}
_ => {
if !chunks.is_ancillary() {
return Err(PixelsError::malformed(
"png",
format!("unknown critical chunk `{}`", chunks.name()),
));
}
chunks.skip_payload()?;
}
}
}
let filtered = zlib_decompress(&compressed, self.header.filtered_size())
.map_err(crate::compress_error)?;
let samples = self.unfilter_all(&filtered)?;
self.expand(&samples, palette.as_deref(), transparency.as_ref())
}
fn unfilter_all(&self, filtered: &[u8]) -> Result<Vec<u8>> {
let stride = self.header.filter_stride();
let full_row = self.header.row_bytes(self.header.width);
if !self.header.interlaced {
let mut out = vec![0_u8; self.header.height as usize * full_row];
let mut previous = vec![0_u8; full_row];
let mut at = 0;
for y in 0..self.header.height as usize {
let filter_byte = filtered
.get(at)
.copied()
.ok_or_else(|| PixelsError::malformed("png", "raster ends early"))?;
let filter = Filter::from_byte(filter_byte)?;
at += 1;
let row = filtered
.get(at..at + full_row)
.ok_or_else(|| PixelsError::malformed("png", "scanline ends early"))?;
at += full_row;
let mut current = row.to_vec();
unfilter(filter, &mut current, &previous, stride)?;
let start = y * full_row;
if let Some(slot) = out.get_mut(start..start + full_row) {
slot.copy_from_slice(¤t);
}
previous = current;
}
return Ok(out);
}
let mut out = vec![0_u8; self.header.height as usize * full_row];
let mut at = 0;
for pass in 0..7 {
let (pass_width, pass_height) =
adam7_pass_size(pass, self.header.width, self.header.height);
if pass_width == 0 || pass_height == 0 {
continue;
}
let pass_row = self.header.row_bytes(pass_width);
let mut previous = vec![0_u8; pass_row];
for y in 0..pass_height {
let filter_byte = filtered.get(at).copied().ok_or_else(|| {
PixelsError::malformed("png", format!("pass {pass} ends early"))
})?;
let filter = Filter::from_byte(filter_byte)?;
at += 1;
let row = filtered.get(at..at + pass_row).ok_or_else(|| {
PixelsError::malformed("png", format!("pass {pass} scanline ends early"))
})?;
at += pass_row;
let mut current = row.to_vec();
unfilter(filter, &mut current, &previous, stride)?;
for x in 0..pass_width {
let (image_x, image_y) = adam7_position(pass, x, y);
copy_pixel_bits(
¤t,
x as usize,
&mut out,
image_y as usize * full_row,
image_x as usize,
self.header.bits_per_pixel(),
);
}
previous = current;
}
}
Ok(out)
}
fn expand(
&self,
samples: &[u8],
palette: Option<&[[u8; 3]]>,
transparency: Option<&Transparency>,
) -> Result<Vec<u8>> {
let height = self.header.height as usize;
let row_bytes = self.header.row_bytes(self.header.width);
let out_row = self.descriptor.row_bytes();
let mut out = vec![0_u8; height * out_row];
for y in 0..height {
let row = samples
.get(y * row_bytes..(y + 1) * row_bytes)
.ok_or_else(|| PixelsError::malformed("png", "sample row missing"))?;
let Some(slot) = out.get_mut(y * out_row..(y + 1) * out_row) else {
return Err(PixelsError::graph("output row is missing"));
};
self.expand_row(row, palette, transparency, slot)?;
}
Ok(out)
}
fn expand_row(
&self,
samples: &[u8],
palette: Option<&[[u8; 3]]>,
transparency: Option<&Transparency>,
out: &mut [u8],
) -> Result<()> {
let format = self.descriptor.pixel;
let width = self.header.width as usize;
let depth = self.header.bit_depth;
let channels = self.header.color_type.channels();
let max = ((1_u32 << depth) - 1) as u16;
if depth == 8 {
let same_layout = matches!(
(self.header.color_type, format),
(ColorType::Rgb, PixelFormat::Rgb8)
| (ColorType::Rgba, PixelFormat::Rgba8)
| (ColorType::Grayscale, PixelFormat::Gray8)
| (ColorType::GrayscaleAlpha, PixelFormat::GrayA8)
);
if same_layout && samples.len() == out.len() {
out.copy_from_slice(samples);
return Ok(());
}
if matches!(self.header.color_type, ColorType::Palette) {
return expand_palette_row(samples, format, palette, transparency, out);
}
}
let mut at = 0;
for x in 0..width {
let mut channel = [0_u16; 4];
for (c, slot) in channel.iter_mut().take(channels).enumerate() {
*slot = read_sample(samples, x * channels + c, depth);
}
write_pixel(
out,
&mut at,
format,
self.header.color_type,
&channel,
depth,
max,
palette,
transparency,
)?;
}
Ok(())
}
fn read_row_buffered(&mut self, out: &mut [u8]) -> Result<()> {
if self.raster.is_none() {
self.raster = Some(self.decode_image()?);
}
let Some(raster) = self.raster.as_ref() else {
return Err(PixelsError::graph("raster vanished after decoding"));
};
if self.row >= self.descriptor.height {
return Err(PixelsError::invalid_argument(
"out",
format!("all {} rows have already been read", self.descriptor.height),
));
}
let row_bytes = self.descriptor.row_bytes();
if out.len() != row_bytes {
return Err(PixelsError::invalid_argument(
"out",
format!("row buffer is {} bytes, expected {row_bytes}", out.len()),
));
}
let start = self.row as usize * row_bytes;
let row = raster
.get(start..start + row_bytes)
.ok_or_else(|| PixelsError::malformed("png", "decoded raster is short"))?;
out.copy_from_slice(row);
self.row += 1;
Ok(())
}
}
impl<S: Source> Streaming<S> {
fn refill(&mut self) -> Result<bool> {
if self.input_done {
return Ok(false);
}
let mut buffer = vec![0_u8; READ_CHUNK];
loop {
if self.chunks.payload_done() {
self.chunks.close()?;
loop {
let kind = self.chunks.open_next()?;
match &kind {
b"IDAT" => break,
b"IEND" => {
self.chunks.skip_payload()?;
self.chunks.close()?;
self.input_done = true;
let tail = self.zlib.finish().map_err(crate::compress_error)?;
self.filtered.extend_from_slice(&tail);
return Ok(!tail.is_empty());
}
_ => {
if !self.chunks.is_ancillary() {
return Err(PixelsError::malformed(
"png",
format!("unknown critical chunk `{}`", self.chunks.name()),
));
}
self.chunks.skip_payload()?;
self.chunks.close()?;
}
}
}
}
let read = self.chunks.read_payload(&mut buffer)?;
if read == 0 {
continue;
}
let produced = self
.zlib
.push(buffer.get(..read).unwrap_or(&[]))
.map_err(crate::compress_error)?;
if !produced.is_empty() {
self.filtered.extend_from_slice(&produced);
return Ok(true);
}
}
}
fn finalize(&mut self) -> Result<()> {
while self.refill()? {}
if !self.input_done {
return Err(PixelsError::malformed(
"png",
"stream ends without an IEND chunk",
));
}
Ok(())
}
fn next_scanline(&mut self, row_bytes: usize, stride: usize) -> Result<Vec<u8>> {
let want = row_bytes + 1;
while self.filtered.len() - self.at < want {
if !self.refill()? {
return Err(PixelsError::malformed(
"png",
"image data ends before the last scanline",
));
}
}
let filter_byte = self
.filtered
.get(self.at)
.copied()
.ok_or_else(|| PixelsError::malformed("png", "scanline ends early"))?;
let filter = Filter::from_byte(filter_byte)?;
let start = self.at + 1;
let mut current = self
.filtered
.get(start..start + row_bytes)
.ok_or_else(|| PixelsError::malformed("png", "scanline ends early"))?
.to_vec();
self.at = start + row_bytes;
if self.at >= self.filtered.len() {
self.filtered.clear();
self.at = 0;
} else if self.at > READ_CHUNK {
self.filtered.drain(..self.at);
self.at = 0;
}
unfilter(filter, &mut current, &self.previous, stride)?;
self.previous.clear();
self.previous.extend_from_slice(¤t);
Ok(current)
}
}
fn parse_plte(data: &[u8]) -> Result<Vec<[u8; 3]>> {
if data.len() % 3 != 0 || data.is_empty() {
return Err(PixelsError::malformed(
"png",
format!("PLTE length {} is not a positive multiple of 3", data.len()),
));
}
if data.len() / 3 > 256 {
return Err(PixelsError::malformed(
"png",
"PLTE has more than 256 entries",
));
}
Ok(data
.chunks_exact(3)
.map(|rgb| {
[
rgb.first().copied().unwrap_or(0),
rgb.get(1).copied().unwrap_or(0),
rgb.get(2).copied().unwrap_or(0),
]
})
.collect())
}
fn copy_pixel_bits(
source_row: &[u8],
source_x: usize,
out: &mut [u8],
row_start: usize,
dest_x: usize,
bits_per_pixel: usize,
) {
if bits_per_pixel >= 8 {
let bytes = bits_per_pixel / 8;
for byte in 0..bytes {
let value = source_row
.get(source_x * bytes + byte)
.copied()
.unwrap_or(0);
if let Some(slot) = out.get_mut(row_start + dest_x * bytes + byte) {
*slot = value;
}
}
return;
}
let value = read_bits(source_row, source_x, bits_per_pixel);
write_bits(out, row_start, dest_x, bits_per_pixel, value);
}
fn read_bits(row: &[u8], index: usize, bits: usize) -> u8 {
let per_byte = 8 / bits;
let byte = row.get(index / per_byte).copied().unwrap_or(0);
let shift = 8 - bits * (index % per_byte + 1);
(byte >> shift) & ((1 << bits) - 1) as u8
}
fn write_bits(out: &mut [u8], row_start: usize, index: usize, bits: usize, value: u8) {
let per_byte = 8 / bits;
let offset = row_start + index / per_byte;
let shift = 8 - bits * (index % per_byte + 1);
let mask = ((1 << bits) - 1) as u8;
if let Some(slot) = out.get_mut(offset) {
*slot = (*slot & !(mask << shift)) | ((value & mask) << shift);
}
}
fn read_sample(row: &[u8], index: usize, depth: u8) -> u16 {
match depth {
16 => {
let high = row.get(index * 2).copied().unwrap_or(0);
let low = row.get(index * 2 + 1).copied().unwrap_or(0);
u16::from_be_bytes([high, low])
}
8 => u16::from(row.get(index).copied().unwrap_or(0)),
bits => u16::from(read_bits(row, index, bits as usize)),
}
}
const fn scale8(value: u16, max: u16) -> u8 {
if max == 0 {
return 0;
}
((value as u32 * 255 + max as u32 / 2) / max as u32) as u8
}
fn expand_palette_row(
indices: &[u8],
format: PixelFormat,
palette: Option<&[[u8; 3]]>,
transparency: Option<&Transparency>,
out: &mut [u8],
) -> Result<()> {
let entries =
palette.ok_or_else(|| PixelsError::malformed("png", "palette image without a palette"))?;
let alphas = match transparency {
Some(Transparency::Palette(alphas)) => alphas.as_slice(),
_ => &[],
};
let channels = if format == PixelFormat::Rgba8 { 4 } else { 3 };
for (&index, pixel) in indices.iter().zip(out.chunks_exact_mut(channels)) {
let index = index as usize;
let rgb = entries.get(index).ok_or_else(|| {
PixelsError::malformed(
"png",
format!(
"palette index {index} is beyond the {}-entry palette",
entries.len()
),
)
})?;
for (slot, &value) in pixel.iter_mut().zip(rgb) {
*slot = value;
}
if let Some(alpha) = pixel.get_mut(3) {
*alpha = alphas.get(index).copied().unwrap_or(255);
}
}
Ok(())
}
#[allow(
clippy::too_many_arguments,
reason = "one pixel conversion needs all of it"
)]
fn write_pixel(
out: &mut [u8],
at: &mut usize,
format: PixelFormat,
color_type: ColorType,
channel: &[u16; 4],
depth: u8,
max: u16,
palette: Option<&[[u8; 3]]>,
transparency: Option<&Transparency>,
) -> Result<()> {
fn push(out: &mut [u8], at: &mut usize, value: u8) {
if let Some(slot) = out.get_mut(*at) {
*slot = value;
}
*at += 1;
}
fn push16(out: &mut [u8], at: &mut usize, value: u16) {
for byte in value.to_ne_bytes() {
push(out, at, byte);
}
}
match color_type {
ColorType::Palette => {
let index = channel[0] as usize;
let entries = palette
.ok_or_else(|| PixelsError::malformed("png", "palette image without a palette"))?;
let rgb = entries.get(index).copied().ok_or_else(|| {
PixelsError::malformed(
"png",
format!(
"palette index {index} is beyond the {}-entry palette",
entries.len()
),
)
})?;
push(out, at, rgb[0]);
push(out, at, rgb[1]);
push(out, at, rgb[2]);
if format == PixelFormat::Rgba8 {
let alpha = match transparency {
Some(Transparency::Palette(alphas)) => {
alphas.get(index).copied().unwrap_or(255)
}
_ => 255,
};
push(out, at, alpha);
}
}
ColorType::Grayscale => {
let transparent =
matches!(transparency, Some(Transparency::Gray(key)) if *key == channel[0]);
match format {
PixelFormat::Gray8 => push(out, at, scale8(channel[0], max)),
PixelFormat::Gray16 => push16(out, at, channel[0]),
PixelFormat::GrayA8 => {
push(out, at, scale8(channel[0], max));
push(out, at, if transparent { 0 } else { 255 });
}
PixelFormat::Rgba16 => {
let value = if depth == 16 {
channel[0]
} else {
channel[0] * 257
};
push16(out, at, value);
push16(out, at, value);
push16(out, at, value);
push16(out, at, if transparent { 0 } else { u16::MAX });
}
other => {
return Err(PixelsError::unsupported(format!(
"greyscale cannot be written as {other}"
)));
}
}
}
ColorType::GrayscaleAlpha => match format {
PixelFormat::GrayA8 => {
push(out, at, scale8(channel[0], max));
push(out, at, scale8(channel[1], max));
}
PixelFormat::Rgba16 => {
push16(out, at, channel[0]);
push16(out, at, channel[0]);
push16(out, at, channel[0]);
push16(out, at, channel[1]);
}
other => {
return Err(PixelsError::unsupported(format!(
"grey+alpha cannot be written as {other}"
)));
}
},
ColorType::Rgb => {
let transparent = matches!(
transparency,
Some(Transparency::Rgb(r, g, b))
if *r == channel[0] && *g == channel[1] && *b == channel[2]
);
match format {
PixelFormat::Rgb8 => {
for &value in channel.iter().take(3) {
push(out, at, scale8(value, max));
}
}
PixelFormat::Rgb16 => {
for &value in channel.iter().take(3) {
push16(out, at, value);
}
}
PixelFormat::Rgba8 => {
for &value in channel.iter().take(3) {
push(out, at, scale8(value, max));
}
push(out, at, if transparent { 0 } else { 255 });
}
PixelFormat::Rgba16 => {
for &value in channel.iter().take(3) {
push16(out, at, value);
}
push16(out, at, if transparent { 0 } else { u16::MAX });
}
other => {
return Err(PixelsError::unsupported(format!(
"RGB cannot be written as {other}"
)));
}
}
}
ColorType::Rgba => match format {
PixelFormat::Rgba8 => {
for &value in channel {
push(out, at, scale8(value, max));
}
}
PixelFormat::Rgba16 => {
for &value in channel {
push16(out, at, value);
}
}
other => {
return Err(PixelsError::unsupported(format!(
"RGBA cannot be written as {other}"
)));
}
},
}
Ok(())
}
fn parse_trns(data: &[u8], color_type: ColorType) -> Result<Transparency> {
fn be16(data: &[u8], offset: usize) -> u16 {
u16::from_be_bytes([
data.get(offset).copied().unwrap_or(0),
data.get(offset + 1).copied().unwrap_or(0),
])
}
match color_type {
ColorType::Grayscale => {
if data.len() != 2 {
return Err(PixelsError::malformed(
"png",
format!("greyscale tRNS must be 2 bytes, got {}", data.len()),
));
}
Ok(Transparency::Gray(be16(data, 0)))
}
ColorType::Rgb => {
if data.len() != 6 {
return Err(PixelsError::malformed(
"png",
format!("RGB tRNS must be 6 bytes, got {}", data.len()),
));
}
Ok(Transparency::Rgb(
be16(data, 0),
be16(data, 2),
be16(data, 4),
))
}
ColorType::Palette => {
if data.len() > 256 {
return Err(PixelsError::malformed(
"png",
format!(
"palette tRNS has {} entries, over the 256 maximum",
data.len()
),
));
}
Ok(Transparency::Palette(data.to_vec()))
}
ColorType::GrayscaleAlpha | ColorType::Rgba => Err(PixelsError::malformed(
"png",
"tRNS is not allowed for colour types that already carry alpha",
)),
}
}
impl<S: Source + std::fmt::Debug> Decoder for PngDecoder<S> {
fn descriptor(&self) -> ImageDescriptor {
self.descriptor
}
fn orientation(&self) -> Orientation {
self.orientation
}
fn icc_profile(&self) -> Option<&[u8]> {
self.icc.as_deref()
}
fn capability(&self) -> DecodeCapability {
DecodeCapability::Sequential
}
fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
if self.header.interlaced {
return self.read_row_buffered(out);
}
if self.stream.is_none() {
let started = self.begin_streaming()?;
self.stream = Some(Box::new(started));
}
if self.row >= self.descriptor.height {
return Err(PixelsError::invalid_argument(
"out",
format!("all {} rows have already been read", self.descriptor.height),
));
}
let row_bytes = self.descriptor.row_bytes();
if out.len() != row_bytes {
return Err(PixelsError::invalid_argument(
"out",
format!("row buffer is {} bytes, expected {row_bytes}", out.len()),
));
}
let sample_bytes = self.header.row_bytes(self.header.width);
let stride = self.header.filter_stride();
let Some(stream) = self.stream.as_mut() else {
return Err(PixelsError::graph("png stream vanished after starting"));
};
let samples = stream.next_scanline(sample_bytes, stride)?;
let Some(stream) = self.stream.as_deref() else {
return Err(PixelsError::graph("png stream vanished after starting"));
};
self.expand_row(
&samples,
stream.palette.as_deref(),
stream.transparency.as_ref(),
out,
)?;
self.row += 1;
if self.row == self.descriptor.height {
if let Some(stream) = self.stream.as_mut() {
stream.finalize()?;
}
}
Ok(())
}
}
#[must_use]
pub fn probe(prefix: &[u8]) -> bool {
prefix.get(..8) == Some(&SIGNATURE[..])
}
#[derive(Debug, Clone, Copy, Default)]
pub struct PngCodec;
impl Codec for PngCodec {
fn format(&self) -> Format {
Format::Png
}
fn magic_len(&self) -> usize {
SIGNATURE.len()
}
fn probe(&self, prefix: &[u8]) -> bool {
probe(prefix)
}
}