812 lines
28 KiB
Rust
812 lines
28 KiB
Rust
//! Common types shared between the encoder and decoder
|
||
use crate::text_metadata::{EncodableTextChunk, ITXtChunk, TEXtChunk, ZTXtChunk};
|
||
use crate::{chunk, encoder};
|
||
use io::Write;
|
||
use std::{borrow::Cow, convert::TryFrom, fmt, io};
|
||
|
||
/// Describes how a pixel is encoded.
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
#[repr(u8)]
|
||
pub enum ColorType {
|
||
/// 1 grayscale sample.
|
||
Grayscale = 0,
|
||
/// 1 red sample, 1 green sample, 1 blue sample.
|
||
Rgb = 2,
|
||
/// 1 sample for the palette index.
|
||
Indexed = 3,
|
||
/// 1 grayscale sample, then 1 alpha sample.
|
||
GrayscaleAlpha = 4,
|
||
/// 1 red sample, 1 green sample, 1 blue sample, and finally, 1 alpha sample.
|
||
Rgba = 6,
|
||
}
|
||
|
||
impl ColorType {
|
||
/// Returns the number of samples used per pixel encoded in this way.
|
||
pub fn samples(self) -> usize {
|
||
self.samples_u8().into()
|
||
}
|
||
|
||
pub(crate) fn samples_u8(self) -> u8 {
|
||
use self::ColorType::*;
|
||
match self {
|
||
Grayscale | Indexed => 1,
|
||
Rgb => 3,
|
||
GrayscaleAlpha => 2,
|
||
Rgba => 4,
|
||
}
|
||
}
|
||
|
||
/// u8 -> Self. Temporary solution until Rust provides a canonical one.
|
||
pub fn from_u8(n: u8) -> Option<ColorType> {
|
||
match n {
|
||
0 => Some(ColorType::Grayscale),
|
||
2 => Some(ColorType::Rgb),
|
||
3 => Some(ColorType::Indexed),
|
||
4 => Some(ColorType::GrayscaleAlpha),
|
||
6 => Some(ColorType::Rgba),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
pub(crate) fn checked_raw_row_length(self, depth: BitDepth, width: u32) -> Option<usize> {
|
||
// No overflow can occur in 64 bits, we multiply 32-bit with 5 more bits.
|
||
let bits = u64::from(width) * u64::from(self.samples_u8()) * u64::from(depth.into_u8());
|
||
TryFrom::try_from(1 + (bits + 7) / 8).ok()
|
||
}
|
||
|
||
pub(crate) fn raw_row_length_from_width(self, depth: BitDepth, width: u32) -> usize {
|
||
let samples = width as usize * self.samples();
|
||
1 + match depth {
|
||
BitDepth::Sixteen => samples * 2,
|
||
BitDepth::Eight => samples,
|
||
subbyte => {
|
||
let samples_per_byte = 8 / subbyte as usize;
|
||
let whole = samples / samples_per_byte;
|
||
let fract = usize::from(samples % samples_per_byte > 0);
|
||
whole + fract
|
||
}
|
||
}
|
||
}
|
||
|
||
pub(crate) fn is_combination_invalid(self, bit_depth: BitDepth) -> bool {
|
||
// Section 11.2.2 of the PNG standard disallows several combinations
|
||
// of bit depth and color type
|
||
((bit_depth == BitDepth::One || bit_depth == BitDepth::Two || bit_depth == BitDepth::Four)
|
||
&& (self == ColorType::Rgb
|
||
|| self == ColorType::GrayscaleAlpha
|
||
|| self == ColorType::Rgba))
|
||
|| (bit_depth == BitDepth::Sixteen && self == ColorType::Indexed)
|
||
}
|
||
}
|
||
|
||
/// Bit depth of the PNG file.
|
||
/// Specifies the number of bits per sample.
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
#[repr(u8)]
|
||
pub enum BitDepth {
|
||
One = 1,
|
||
Two = 2,
|
||
Four = 4,
|
||
Eight = 8,
|
||
Sixteen = 16,
|
||
}
|
||
|
||
/// Internal count of bytes per pixel.
|
||
/// This is used for filtering which never uses sub-byte units. This essentially reduces the number
|
||
/// of possible byte chunk lengths to a very small set of values appropriate to be defined as an
|
||
/// enum.
|
||
#[derive(Debug, Clone, Copy)]
|
||
#[repr(u8)]
|
||
pub(crate) enum BytesPerPixel {
|
||
One = 1,
|
||
Two = 2,
|
||
Three = 3,
|
||
Four = 4,
|
||
Six = 6,
|
||
Eight = 8,
|
||
}
|
||
|
||
impl BitDepth {
|
||
/// u8 -> Self. Temporary solution until Rust provides a canonical one.
|
||
pub fn from_u8(n: u8) -> Option<BitDepth> {
|
||
match n {
|
||
1 => Some(BitDepth::One),
|
||
2 => Some(BitDepth::Two),
|
||
4 => Some(BitDepth::Four),
|
||
8 => Some(BitDepth::Eight),
|
||
16 => Some(BitDepth::Sixteen),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
pub(crate) fn into_u8(self) -> u8 {
|
||
self as u8
|
||
}
|
||
}
|
||
|
||
/// Pixel dimensions information
|
||
#[derive(Clone, Copy, Debug)]
|
||
pub struct PixelDimensions {
|
||
/// Pixels per unit, X axis
|
||
pub xppu: u32,
|
||
/// Pixels per unit, Y axis
|
||
pub yppu: u32,
|
||
/// Either *Meter* or *Unspecified*
|
||
pub unit: Unit,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
#[repr(u8)]
|
||
/// Physical unit of the pixel dimensions
|
||
pub enum Unit {
|
||
Unspecified = 0,
|
||
Meter = 1,
|
||
}
|
||
|
||
impl Unit {
|
||
/// u8 -> Self. Temporary solution until Rust provides a canonical one.
|
||
pub fn from_u8(n: u8) -> Option<Unit> {
|
||
match n {
|
||
0 => Some(Unit::Unspecified),
|
||
1 => Some(Unit::Meter),
|
||
_ => None,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// How to reset buffer of an animated png (APNG) at the end of a frame.
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
#[repr(u8)]
|
||
pub enum DisposeOp {
|
||
/// Leave the buffer unchanged.
|
||
None = 0,
|
||
/// Clear buffer with the background color.
|
||
Background = 1,
|
||
/// Reset the buffer to the state before the current frame.
|
||
Previous = 2,
|
||
}
|
||
|
||
impl DisposeOp {
|
||
/// u8 -> Self. Using enum_primitive or transmute is probably the right thing but this will do for now.
|
||
pub fn from_u8(n: u8) -> Option<DisposeOp> {
|
||
match n {
|
||
0 => Some(DisposeOp::None),
|
||
1 => Some(DisposeOp::Background),
|
||
2 => Some(DisposeOp::Previous),
|
||
_ => None,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl fmt::Display for DisposeOp {
|
||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
let name = match *self {
|
||
DisposeOp::None => "DISPOSE_OP_NONE",
|
||
DisposeOp::Background => "DISPOSE_OP_BACKGROUND",
|
||
DisposeOp::Previous => "DISPOSE_OP_PREVIOUS",
|
||
};
|
||
write!(f, "{}", name)
|
||
}
|
||
}
|
||
|
||
/// How pixels are written into the buffer.
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
#[repr(u8)]
|
||
pub enum BlendOp {
|
||
/// Pixels overwrite the value at their position.
|
||
Source = 0,
|
||
/// The new pixels are blended into the current state based on alpha.
|
||
Over = 1,
|
||
}
|
||
|
||
impl BlendOp {
|
||
/// u8 -> Self. Using enum_primitive or transmute is probably the right thing but this will do for now.
|
||
pub fn from_u8(n: u8) -> Option<BlendOp> {
|
||
match n {
|
||
0 => Some(BlendOp::Source),
|
||
1 => Some(BlendOp::Over),
|
||
_ => None,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl fmt::Display for BlendOp {
|
||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
let name = match *self {
|
||
BlendOp::Source => "BLEND_OP_SOURCE",
|
||
BlendOp::Over => "BLEND_OP_OVER",
|
||
};
|
||
write!(f, "{}", name)
|
||
}
|
||
}
|
||
|
||
/// Frame control information
|
||
#[derive(Clone, Copy, Debug)]
|
||
pub struct FrameControl {
|
||
/// Sequence number of the animation chunk, starting from 0
|
||
pub sequence_number: u32,
|
||
/// Width of the following frame
|
||
pub width: u32,
|
||
/// Height of the following frame
|
||
pub height: u32,
|
||
/// X position at which to render the following frame
|
||
pub x_offset: u32,
|
||
/// Y position at which to render the following frame
|
||
pub y_offset: u32,
|
||
/// Frame delay fraction numerator
|
||
pub delay_num: u16,
|
||
/// Frame delay fraction denominator
|
||
pub delay_den: u16,
|
||
/// Type of frame area disposal to be done after rendering this frame
|
||
pub dispose_op: DisposeOp,
|
||
/// Type of frame area rendering for this frame
|
||
pub blend_op: BlendOp,
|
||
}
|
||
|
||
impl Default for FrameControl {
|
||
fn default() -> FrameControl {
|
||
FrameControl {
|
||
sequence_number: 0,
|
||
width: 0,
|
||
height: 0,
|
||
x_offset: 0,
|
||
y_offset: 0,
|
||
delay_num: 1,
|
||
delay_den: 30,
|
||
dispose_op: DisposeOp::None,
|
||
blend_op: BlendOp::Source,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl FrameControl {
|
||
pub fn set_seq_num(&mut self, s: u32) {
|
||
self.sequence_number = s;
|
||
}
|
||
|
||
pub fn inc_seq_num(&mut self, i: u32) {
|
||
self.sequence_number += i;
|
||
}
|
||
|
||
pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> {
|
||
let mut data = [0u8; 26];
|
||
data[..4].copy_from_slice(&self.sequence_number.to_be_bytes());
|
||
data[4..8].copy_from_slice(&self.width.to_be_bytes());
|
||
data[8..12].copy_from_slice(&self.height.to_be_bytes());
|
||
data[12..16].copy_from_slice(&self.x_offset.to_be_bytes());
|
||
data[16..20].copy_from_slice(&self.y_offset.to_be_bytes());
|
||
data[20..22].copy_from_slice(&self.delay_num.to_be_bytes());
|
||
data[22..24].copy_from_slice(&self.delay_den.to_be_bytes());
|
||
data[24] = self.dispose_op as u8;
|
||
data[25] = self.blend_op as u8;
|
||
|
||
encoder::write_chunk(w, chunk::fcTL, &data)
|
||
}
|
||
}
|
||
|
||
/// Animation control information
|
||
#[derive(Clone, Copy, Debug)]
|
||
pub struct AnimationControl {
|
||
/// Number of frames
|
||
pub num_frames: u32,
|
||
/// Number of times to loop this APNG. 0 indicates infinite looping.
|
||
pub num_plays: u32,
|
||
}
|
||
|
||
impl AnimationControl {
|
||
pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> {
|
||
let mut data = [0; 8];
|
||
data[..4].copy_from_slice(&self.num_frames.to_be_bytes());
|
||
data[4..].copy_from_slice(&self.num_plays.to_be_bytes());
|
||
encoder::write_chunk(w, chunk::acTL, &data)
|
||
}
|
||
}
|
||
|
||
/// The type and strength of applied compression.
|
||
#[derive(Debug, Clone, Copy)]
|
||
pub enum Compression {
|
||
/// Default level
|
||
Default,
|
||
/// Fast minimal compression
|
||
Fast,
|
||
/// Higher compression level
|
||
///
|
||
/// Best in this context isn't actually the highest possible level
|
||
/// the encoder can do, but is meant to emulate the `Best` setting in the `Flate2`
|
||
/// library.
|
||
Best,
|
||
#[deprecated(
|
||
since = "0.17.6",
|
||
note = "use one of the other compression levels instead, such as 'fast'"
|
||
)]
|
||
Huffman,
|
||
#[deprecated(
|
||
since = "0.17.6",
|
||
note = "use one of the other compression levels instead, such as 'fast'"
|
||
)]
|
||
Rle,
|
||
}
|
||
|
||
impl Default for Compression {
|
||
fn default() -> Self {
|
||
Self::Default
|
||
}
|
||
}
|
||
|
||
/// An unsigned integer scaled version of a floating point value,
|
||
/// equivalent to an integer quotient with fixed denominator (100_000)).
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||
pub struct ScaledFloat(u32);
|
||
|
||
impl ScaledFloat {
|
||
const SCALING: f32 = 100_000.0;
|
||
|
||
/// Gets whether the value is within the clamped range of this type.
|
||
pub fn in_range(value: f32) -> bool {
|
||
value >= 0.0 && (value * Self::SCALING).floor() <= std::u32::MAX as f32
|
||
}
|
||
|
||
/// Gets whether the value can be exactly converted in round-trip.
|
||
#[allow(clippy::float_cmp)] // Stupid tool, the exact float compare is _the entire point_.
|
||
pub fn exact(value: f32) -> bool {
|
||
let there = Self::forward(value);
|
||
let back = Self::reverse(there);
|
||
value == back
|
||
}
|
||
|
||
fn forward(value: f32) -> u32 {
|
||
(value.max(0.0) * Self::SCALING).floor() as u32
|
||
}
|
||
|
||
fn reverse(encoded: u32) -> f32 {
|
||
encoded as f32 / Self::SCALING
|
||
}
|
||
|
||
/// Slightly inaccurate scaling and quantization.
|
||
/// Clamps the value into the representable range if it is negative or too large.
|
||
pub fn new(value: f32) -> Self {
|
||
Self(Self::forward(value))
|
||
}
|
||
|
||
/// Fully accurate construction from a value scaled as per specification.
|
||
pub fn from_scaled(val: u32) -> Self {
|
||
Self(val)
|
||
}
|
||
|
||
/// Get the accurate encoded value.
|
||
pub fn into_scaled(self) -> u32 {
|
||
self.0
|
||
}
|
||
|
||
/// Get the unscaled value as a floating point.
|
||
pub fn into_value(self) -> f32 {
|
||
Self::reverse(self.0)
|
||
}
|
||
|
||
pub(crate) fn encode_gama<W: Write>(self, w: &mut W) -> encoder::Result<()> {
|
||
encoder::write_chunk(w, chunk::gAMA, &self.into_scaled().to_be_bytes())
|
||
}
|
||
}
|
||
|
||
/// Chromaticities of the color space primaries
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||
pub struct SourceChromaticities {
|
||
pub white: (ScaledFloat, ScaledFloat),
|
||
pub red: (ScaledFloat, ScaledFloat),
|
||
pub green: (ScaledFloat, ScaledFloat),
|
||
pub blue: (ScaledFloat, ScaledFloat),
|
||
}
|
||
|
||
impl SourceChromaticities {
|
||
pub fn new(white: (f32, f32), red: (f32, f32), green: (f32, f32), blue: (f32, f32)) -> Self {
|
||
SourceChromaticities {
|
||
white: (ScaledFloat::new(white.0), ScaledFloat::new(white.1)),
|
||
red: (ScaledFloat::new(red.0), ScaledFloat::new(red.1)),
|
||
green: (ScaledFloat::new(green.0), ScaledFloat::new(green.1)),
|
||
blue: (ScaledFloat::new(blue.0), ScaledFloat::new(blue.1)),
|
||
}
|
||
}
|
||
|
||
#[rustfmt::skip]
|
||
pub fn to_be_bytes(self) -> [u8; 32] {
|
||
let white_x = self.white.0.into_scaled().to_be_bytes();
|
||
let white_y = self.white.1.into_scaled().to_be_bytes();
|
||
let red_x = self.red.0.into_scaled().to_be_bytes();
|
||
let red_y = self.red.1.into_scaled().to_be_bytes();
|
||
let green_x = self.green.0.into_scaled().to_be_bytes();
|
||
let green_y = self.green.1.into_scaled().to_be_bytes();
|
||
let blue_x = self.blue.0.into_scaled().to_be_bytes();
|
||
let blue_y = self.blue.1.into_scaled().to_be_bytes();
|
||
[
|
||
white_x[0], white_x[1], white_x[2], white_x[3],
|
||
white_y[0], white_y[1], white_y[2], white_y[3],
|
||
red_x[0], red_x[1], red_x[2], red_x[3],
|
||
red_y[0], red_y[1], red_y[2], red_y[3],
|
||
green_x[0], green_x[1], green_x[2], green_x[3],
|
||
green_y[0], green_y[1], green_y[2], green_y[3],
|
||
blue_x[0], blue_x[1], blue_x[2], blue_x[3],
|
||
blue_y[0], blue_y[1], blue_y[2], blue_y[3],
|
||
]
|
||
}
|
||
|
||
pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> {
|
||
encoder::write_chunk(w, chunk::cHRM, &self.to_be_bytes())
|
||
}
|
||
}
|
||
|
||
/// The rendering intent for an sRGB image.
|
||
///
|
||
/// Presence of this data also indicates that the image conforms to the sRGB color space.
|
||
#[repr(u8)]
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||
pub enum SrgbRenderingIntent {
|
||
/// For images preferring good adaptation to the output device gamut at the expense of colorimetric accuracy, such as photographs.
|
||
Perceptual = 0,
|
||
/// For images requiring colour appearance matching (relative to the output device white point), such as logos.
|
||
RelativeColorimetric = 1,
|
||
/// For images preferring preservation of saturation at the expense of hue and lightness, such as charts and graphs.
|
||
Saturation = 2,
|
||
/// For images requiring preservation of absolute colorimetry, such as previews of images destined for a different output device (proofs).
|
||
AbsoluteColorimetric = 3,
|
||
}
|
||
|
||
impl SrgbRenderingIntent {
|
||
pub(crate) fn into_raw(self) -> u8 {
|
||
self as u8
|
||
}
|
||
|
||
pub(crate) fn from_raw(raw: u8) -> Option<Self> {
|
||
match raw {
|
||
0 => Some(SrgbRenderingIntent::Perceptual),
|
||
1 => Some(SrgbRenderingIntent::RelativeColorimetric),
|
||
2 => Some(SrgbRenderingIntent::Saturation),
|
||
3 => Some(SrgbRenderingIntent::AbsoluteColorimetric),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
pub fn encode<W: Write>(self, w: &mut W) -> encoder::Result<()> {
|
||
encoder::write_chunk(w, chunk::sRGB, &[self.into_raw()])
|
||
}
|
||
}
|
||
|
||
/// PNG info struct
|
||
#[derive(Clone, Debug)]
|
||
#[non_exhaustive]
|
||
pub struct Info<'a> {
|
||
pub width: u32,
|
||
pub height: u32,
|
||
pub bit_depth: BitDepth,
|
||
/// How colors are stored in the image.
|
||
pub color_type: ColorType,
|
||
pub interlaced: bool,
|
||
/// The image's `tRNS` chunk, if present; contains the alpha channel of the image's palette, 1 byte per entry.
|
||
pub trns: Option<Cow<'a, [u8]>>,
|
||
pub pixel_dims: Option<PixelDimensions>,
|
||
/// The image's `PLTE` chunk, if present; contains the RGB channels (in that order) of the image's palettes, 3 bytes per entry (1 per channel).
|
||
pub palette: Option<Cow<'a, [u8]>>,
|
||
/// The contents of the image's gAMA chunk, if present.
|
||
/// Prefer `source_gamma` to also get the derived replacement gamma from sRGB chunks.
|
||
pub gama_chunk: Option<ScaledFloat>,
|
||
/// The contents of the image's `cHRM` chunk, if present.
|
||
/// Prefer `source_chromaticities` to also get the derived replacements from sRGB chunks.
|
||
pub chrm_chunk: Option<SourceChromaticities>,
|
||
|
||
pub frame_control: Option<FrameControl>,
|
||
pub animation_control: Option<AnimationControl>,
|
||
pub compression: Compression,
|
||
/// Gamma of the source system.
|
||
/// Set by both `gAMA` as well as to a replacement by `sRGB` chunk.
|
||
pub source_gamma: Option<ScaledFloat>,
|
||
/// Chromaticities of the source system.
|
||
/// Set by both `cHRM` as well as to a replacement by `sRGB` chunk.
|
||
pub source_chromaticities: Option<SourceChromaticities>,
|
||
/// The rendering intent of an SRGB image.
|
||
///
|
||
/// Presence of this value also indicates that the image conforms to the SRGB color space.
|
||
pub srgb: Option<SrgbRenderingIntent>,
|
||
/// The ICC profile for the image.
|
||
pub icc_profile: Option<Cow<'a, [u8]>>,
|
||
/// tEXt field
|
||
pub uncompressed_latin1_text: Vec<TEXtChunk>,
|
||
/// zTXt field
|
||
pub compressed_latin1_text: Vec<ZTXtChunk>,
|
||
/// iTXt field
|
||
pub utf8_text: Vec<ITXtChunk>,
|
||
}
|
||
|
||
impl Default for Info<'_> {
|
||
fn default() -> Info<'static> {
|
||
Info {
|
||
width: 0,
|
||
height: 0,
|
||
bit_depth: BitDepth::Eight,
|
||
color_type: ColorType::Grayscale,
|
||
interlaced: false,
|
||
palette: None,
|
||
trns: None,
|
||
gama_chunk: None,
|
||
chrm_chunk: None,
|
||
pixel_dims: None,
|
||
frame_control: None,
|
||
animation_control: None,
|
||
// Default to `deflate::Compression::Fast` and `filter::FilterType::Sub`
|
||
// to maintain backward compatible output.
|
||
compression: Compression::Fast,
|
||
source_gamma: None,
|
||
source_chromaticities: None,
|
||
srgb: None,
|
||
icc_profile: None,
|
||
uncompressed_latin1_text: Vec::new(),
|
||
compressed_latin1_text: Vec::new(),
|
||
utf8_text: Vec::new(),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Info<'_> {
|
||
/// A utility constructor for a default info with width and height.
|
||
pub fn with_size(width: u32, height: u32) -> Self {
|
||
Info {
|
||
width,
|
||
height,
|
||
..Default::default()
|
||
}
|
||
}
|
||
|
||
/// Size of the image, width then height.
|
||
pub fn size(&self) -> (u32, u32) {
|
||
(self.width, self.height)
|
||
}
|
||
|
||
/// Returns true if the image is an APNG image.
|
||
pub fn is_animated(&self) -> bool {
|
||
self.frame_control.is_some() && self.animation_control.is_some()
|
||
}
|
||
|
||
/// Returns the frame control information of the image.
|
||
pub fn animation_control(&self) -> Option<&AnimationControl> {
|
||
self.animation_control.as_ref()
|
||
}
|
||
|
||
/// Returns the frame control information of the current frame
|
||
pub fn frame_control(&self) -> Option<&FrameControl> {
|
||
self.frame_control.as_ref()
|
||
}
|
||
|
||
/// Returns the number of bits per pixel.
|
||
pub fn bits_per_pixel(&self) -> usize {
|
||
self.color_type.samples() * self.bit_depth as usize
|
||
}
|
||
|
||
/// Returns the number of bytes per pixel.
|
||
pub fn bytes_per_pixel(&self) -> usize {
|
||
// If adjusting this for expansion or other transformation passes, remember to keep the old
|
||
// implementation for bpp_in_prediction, which is internal to the png specification.
|
||
self.color_type.samples() * ((self.bit_depth as usize + 7) >> 3)
|
||
}
|
||
|
||
/// Return the number of bytes for this pixel used in prediction.
|
||
///
|
||
/// Some filters use prediction, over the raw bytes of a scanline. Where a previous pixel is
|
||
/// require for such forms the specification instead references previous bytes. That is, for
|
||
/// a gray pixel of bit depth 2, the pixel used in prediction is actually 4 pixels prior. This
|
||
/// has the consequence that the number of possible values is rather small. To make this fact
|
||
/// more obvious in the type system and the optimizer we use an explicit enum here.
|
||
pub(crate) fn bpp_in_prediction(&self) -> BytesPerPixel {
|
||
BytesPerPixel::from_usize(self.bytes_per_pixel())
|
||
}
|
||
|
||
/// Returns the number of bytes needed for one deinterlaced image.
|
||
pub fn raw_bytes(&self) -> usize {
|
||
self.height as usize * self.raw_row_length()
|
||
}
|
||
|
||
/// Returns the number of bytes needed for one deinterlaced row.
|
||
pub fn raw_row_length(&self) -> usize {
|
||
self.raw_row_length_from_width(self.width)
|
||
}
|
||
|
||
pub(crate) fn checked_raw_row_length(&self) -> Option<usize> {
|
||
self.color_type
|
||
.checked_raw_row_length(self.bit_depth, self.width)
|
||
}
|
||
|
||
/// Returns the number of bytes needed for one deinterlaced row of width `width`.
|
||
pub fn raw_row_length_from_width(&self, width: u32) -> usize {
|
||
self.color_type
|
||
.raw_row_length_from_width(self.bit_depth, width)
|
||
}
|
||
|
||
/// Encode this header to the writer.
|
||
///
|
||
/// Note that this does _not_ include the PNG signature, it starts with the IHDR chunk and then
|
||
/// includes other chunks that were added to the header.
|
||
pub fn encode<W: Write>(&self, mut w: W) -> encoder::Result<()> {
|
||
// Encode the IHDR chunk
|
||
let mut data = [0; 13];
|
||
data[..4].copy_from_slice(&self.width.to_be_bytes());
|
||
data[4..8].copy_from_slice(&self.height.to_be_bytes());
|
||
data[8] = self.bit_depth as u8;
|
||
data[9] = self.color_type as u8;
|
||
data[12] = self.interlaced as u8;
|
||
encoder::write_chunk(&mut w, chunk::IHDR, &data)?;
|
||
// Encode the pHYs chunk
|
||
if let Some(pd) = self.pixel_dims {
|
||
let mut phys_data = [0; 9];
|
||
phys_data[0..4].copy_from_slice(&pd.xppu.to_be_bytes());
|
||
phys_data[4..8].copy_from_slice(&pd.yppu.to_be_bytes());
|
||
match pd.unit {
|
||
Unit::Meter => phys_data[8] = 1,
|
||
Unit::Unspecified => phys_data[8] = 0,
|
||
}
|
||
encoder::write_chunk(&mut w, chunk::pHYs, &phys_data)?;
|
||
}
|
||
|
||
if let Some(p) = &self.palette {
|
||
encoder::write_chunk(&mut w, chunk::PLTE, p)?;
|
||
};
|
||
|
||
if let Some(t) = &self.trns {
|
||
encoder::write_chunk(&mut w, chunk::tRNS, t)?;
|
||
}
|
||
|
||
// If specified, the sRGB information overrides the source gamma and chromaticities.
|
||
if let Some(srgb) = &self.srgb {
|
||
let gamma = crate::srgb::substitute_gamma();
|
||
let chromaticities = crate::srgb::substitute_chromaticities();
|
||
srgb.encode(&mut w)?;
|
||
gamma.encode_gama(&mut w)?;
|
||
chromaticities.encode(&mut w)?;
|
||
} else {
|
||
if let Some(gma) = self.source_gamma {
|
||
gma.encode_gama(&mut w)?
|
||
}
|
||
if let Some(chrms) = self.source_chromaticities {
|
||
chrms.encode(&mut w)?;
|
||
}
|
||
}
|
||
if let Some(actl) = self.animation_control {
|
||
actl.encode(&mut w)?;
|
||
}
|
||
|
||
for text_chunk in &self.uncompressed_latin1_text {
|
||
text_chunk.encode(&mut w)?;
|
||
}
|
||
|
||
for text_chunk in &self.compressed_latin1_text {
|
||
text_chunk.encode(&mut w)?;
|
||
}
|
||
|
||
for text_chunk in &self.utf8_text {
|
||
text_chunk.encode(&mut w)?;
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
impl BytesPerPixel {
|
||
pub(crate) fn from_usize(bpp: usize) -> Self {
|
||
match bpp {
|
||
1 => BytesPerPixel::One,
|
||
2 => BytesPerPixel::Two,
|
||
3 => BytesPerPixel::Three,
|
||
4 => BytesPerPixel::Four,
|
||
6 => BytesPerPixel::Six, // Only rgb×16bit
|
||
8 => BytesPerPixel::Eight, // Only rgba×16bit
|
||
_ => unreachable!("Not a possible byte rounded pixel width"),
|
||
}
|
||
}
|
||
|
||
pub(crate) fn into_usize(self) -> usize {
|
||
self as usize
|
||
}
|
||
}
|
||
|
||
bitflags! {
|
||
/// Output transformations
|
||
///
|
||
/// Many flags from libpng are not yet supported. A PR discussing/adding them would be nice.
|
||
///
|
||
#[doc = "
|
||
```c
|
||
/// Discard the alpha channel
|
||
const STRIP_ALPHA = 0x0002; // read only
|
||
/// Expand 1; 2 and 4-bit samples to bytes
|
||
const PACKING = 0x0004; // read and write
|
||
/// Change order of packed pixels to LSB first
|
||
const PACKSWAP = 0x0008; // read and write
|
||
/// Invert monochrome images
|
||
const INVERT_MONO = 0x0020; // read and write
|
||
/// Normalize pixels to the sBIT depth
|
||
const SHIFT = 0x0040; // read and write
|
||
/// Flip RGB to BGR; RGBA to BGRA
|
||
const BGR = 0x0080; // read and write
|
||
/// Flip RGBA to ARGB or GA to AG
|
||
const SWAP_ALPHA = 0x0100; // read and write
|
||
/// Byte-swap 16-bit samples
|
||
const SWAP_ENDIAN = 0x0200; // read and write
|
||
/// Change alpha from opacity to transparency
|
||
const INVERT_ALPHA = 0x0400; // read and write
|
||
const STRIP_FILLER = 0x0800; // write only
|
||
const STRIP_FILLER_BEFORE = 0x0800; // write only
|
||
const STRIP_FILLER_AFTER = 0x1000; // write only
|
||
const GRAY_TO_RGB = 0x2000; // read only
|
||
const EXPAND_16 = 0x4000; // read only
|
||
/// Similar to STRIP_16 but in libpng considering gamma?
|
||
/// Not entirely sure the documentation says it is more
|
||
/// accurate but doesn't say precisely how.
|
||
const SCALE_16 = 0x8000; // read only
|
||
```
|
||
"]
|
||
pub struct Transformations: u32 {
|
||
/// No transformation
|
||
const IDENTITY = 0x00000; // read and write */
|
||
/// Strip 16-bit samples to 8 bits
|
||
const STRIP_16 = 0x00001; // read only */
|
||
/// Expand paletted images to RGB; expand grayscale images of
|
||
/// less than 8-bit depth to 8-bit depth; and expand tRNS chunks
|
||
/// to alpha channels.
|
||
const EXPAND = 0x00010; // read only */
|
||
/// Expand paletted images to include an alpha channel. Implies `EXPAND`.
|
||
const ALPHA = 0x10000; // read only */
|
||
}
|
||
}
|
||
|
||
impl Transformations {
|
||
/// Transform every input to 8bit grayscale or color.
|
||
///
|
||
/// This sets `EXPAND` and `STRIP_16` which is similar to the default transformation used by
|
||
/// this library prior to `0.17`.
|
||
pub fn normalize_to_color8() -> Transformations {
|
||
Transformations::EXPAND | Transformations::STRIP_16
|
||
}
|
||
}
|
||
|
||
/// Instantiate the default transformations, the identity transform.
|
||
impl Default for Transformations {
|
||
fn default() -> Transformations {
|
||
Transformations::IDENTITY
|
||
}
|
||
}
|
||
|
||
#[derive(Debug)]
|
||
pub struct ParameterError {
|
||
inner: ParameterErrorKind,
|
||
}
|
||
|
||
#[derive(Debug)]
|
||
pub(crate) enum ParameterErrorKind {
|
||
/// A provided buffer must be have the exact size to hold the image data. Where the buffer can
|
||
/// be allocated by the caller, they must ensure that it has a minimum size as hinted previously.
|
||
/// Even though the size is calculated from image data, this does counts as a parameter error
|
||
/// because they must react to a value produced by this library, which can have been subjected
|
||
/// to limits.
|
||
ImageBufferSize { expected: usize, actual: usize },
|
||
/// A bit like return `None` from an iterator.
|
||
/// We use it to differentiate between failing to seek to the next image in a sequence and the
|
||
/// absence of a next image. This is an error of the caller because they should have checked
|
||
/// the number of images by inspecting the header data returned when opening the image. This
|
||
/// library will perform the checks necessary to ensure that data was accurate or error with a
|
||
/// format error otherwise.
|
||
PolledAfterEndOfImage,
|
||
}
|
||
|
||
impl From<ParameterErrorKind> for ParameterError {
|
||
fn from(inner: ParameterErrorKind) -> Self {
|
||
ParameterError { inner }
|
||
}
|
||
}
|
||
|
||
impl fmt::Display for ParameterError {
|
||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||
use ParameterErrorKind::*;
|
||
match self.inner {
|
||
ImageBufferSize { expected, actual } => {
|
||
write!(fmt, "wrong data size, expected {} got {}", expected, actual)
|
||
}
|
||
PolledAfterEndOfImage => write!(fmt, "End of image has been reached"),
|
||
}
|
||
}
|
||
}
|