Skip to main content

oximedia_codec/png/
mod.rs

1//! PNG (Portable Network Graphics) codec implementation.
2//!
3//! This module provides a complete PNG 1.2 specification implementation with:
4//!
5//! ## Features
6//!
7//! - **Decoding**: Full support for all PNG color types and bit depths
8//! - **Encoding**: Optimized encoding with adaptive filtering
9//! - **Color Types**: Grayscale, RGB, Palette, GrayscaleAlpha, RGBA
10//! - **Bit Depths**: 1, 2, 4, 8, 16 bits per sample
11//! - **Interlacing**: Adam7 interlacing support
12//! - **Filtering**: All five PNG filter types (None, Sub, Up, Average, Paeth)
13//! - **Compression**: DEFLATE compression via oxiarc-deflate (patent-free, pure Rust)
14//! - **Validation**: CRC32 chunk validation
15//! - **Metadata**: Gamma, transparency, and other ancillary chunks
16//!
17//! ## Examples
18//!
19//! ### Decoding
20//!
21//! ```ignore
22//! use oximedia_codec::png::PngDecoder;
23//!
24//! let png_data = std::fs::read("image.png")?;
25//! let decoder = PngDecoder::new(&png_data)?;
26//! let image = decoder.decode()?;
27//!
28//! println!("Decoded {}x{} image", image.width, image.height);
29//! ```
30//!
31//! ### Encoding
32//!
33//! ```ignore
34//! use oximedia_codec::png::{PngEncoder, EncoderConfig, FilterStrategy};
35//!
36//! let config = EncoderConfig::new()
37//!     .with_compression(9)
38//!     .with_filter_strategy(FilterStrategy::Best);
39//!
40//! let encoder = PngEncoder::with_config(config);
41//! let png_data = encoder.encode_rgba(width, height, &rgba_pixels)?;
42//! std::fs::write("output.png", png_data)?;
43//! ```
44//!
45//! ### Fast Encoding
46//!
47//! ```ignore
48//! use oximedia_codec::png::fast_encoder;
49//!
50//! let encoder = fast_encoder();
51//! let png_data = encoder.encode_rgb(width, height, &rgb_pixels)?;
52//! ```
53//!
54//! ## Performance
55//!
56//! The encoder provides different strategies for balancing speed and compression:
57//!
58//! - **Fast**: No filtering, fast compression (~10x faster, larger files)
59//! - **Default**: Fast filter selection, medium compression (good balance)
60//! - **Best**: All filters tested, maximum compression (slowest, smallest files)
61//!
62//! ## Safety
63//!
64//! This implementation uses no unsafe code and is fully memory-safe.
65//! All input data is validated, including CRC checks on all chunks.
66
67pub mod apng;
68mod decoder;
69mod encoder;
70mod filter;
71
72// Re-export public API
73pub use decoder::{
74    Chromaticity, ColorType, DecodedImage, ImageHeader, PhysicalDimensions, PngDecoder,
75    PngDecoderExtended, PngMetadata, SignificantBits, TextChunk,
76};
77pub use encoder::{
78    batch_encode, best_encoder, encoder_from_profile, fast_encoder, CompressionLevel,
79    EncoderBuilder, EncoderConfig, EncodingProfile, EncodingStats, PaletteEntry, PaletteOptimizer,
80    ParallelPngEncoder, PngEncoder, PngEncoderExtended,
81};
82pub use filter::{FilterStrategy, FilterType};
83
84use crate::error::{CodecError, CodecResult};
85use bytes::Bytes;
86
87/// Decode PNG data and return RGBA image.
88///
89/// This is a convenience function that creates a decoder and decodes the image.
90///
91/// # Arguments
92///
93/// * `data` - PNG file data
94///
95/// # Errors
96///
97/// Returns error if PNG data is invalid or decoding fails.
98///
99/// # Examples
100///
101/// ```ignore
102/// let png_data = std::fs::read("image.png")?;
103/// let image = oximedia_codec::png::decode(&png_data)?;
104/// ```
105pub fn decode(data: &[u8]) -> CodecResult<DecodedImage> {
106    let decoder = PngDecoder::new(data)?;
107    decoder.decode()
108}
109
110/// Encode RGBA image data to PNG format.
111///
112/// This is a convenience function that creates a default encoder and encodes the image.
113///
114/// # Arguments
115///
116/// * `width` - Image width in pixels
117/// * `height` - Image height in pixels
118/// * `rgba_data` - RGBA pixel data (width * height * 4 bytes)
119///
120/// # Errors
121///
122/// Returns error if encoding fails or data is invalid.
123///
124/// # Examples
125///
126/// ```ignore
127/// let png_data = oximedia_codec::png::encode_rgba(width, height, &rgba_pixels)?;
128/// std::fs::write("output.png", png_data)?;
129/// ```
130pub fn encode_rgba(width: u32, height: u32, rgba_data: &[u8]) -> CodecResult<Vec<u8>> {
131    let encoder = PngEncoder::new();
132    encoder.encode_rgba(width, height, rgba_data)
133}
134
135/// Encode RGB image data to PNG format.
136///
137/// This is a convenience function that creates a default encoder and encodes the image.
138///
139/// # Arguments
140///
141/// * `width` - Image width in pixels
142/// * `height` - Image height in pixels
143/// * `rgb_data` - RGB pixel data (width * height * 3 bytes)
144///
145/// # Errors
146///
147/// Returns error if encoding fails or data is invalid.
148///
149/// # Examples
150///
151/// ```ignore
152/// let png_data = oximedia_codec::png::encode_rgb(width, height, &rgb_pixels)?;
153/// std::fs::write("output.png", png_data)?;
154/// ```
155pub fn encode_rgb(width: u32, height: u32, rgb_data: &[u8]) -> CodecResult<Vec<u8>> {
156    let encoder = PngEncoder::new();
157    encoder.encode_rgb(width, height, rgb_data)
158}
159
160/// Encode grayscale image data to PNG format.
161///
162/// # Arguments
163///
164/// * `width` - Image width in pixels
165/// * `height` - Image height in pixels
166/// * `gray_data` - Grayscale pixel data
167/// * `bit_depth` - Bit depth (1, 2, 4, 8, or 16)
168///
169/// # Errors
170///
171/// Returns error if encoding fails or data is invalid.
172///
173/// # Examples
174///
175/// ```ignore
176/// let png_data = oximedia_codec::png::encode_grayscale(width, height, &gray_pixels, 8)?;
177/// std::fs::write("output.png", png_data)?;
178/// ```
179pub fn encode_grayscale(
180    width: u32,
181    height: u32,
182    gray_data: &[u8],
183    bit_depth: u8,
184) -> CodecResult<Vec<u8>> {
185    let encoder = PngEncoder::new();
186    encoder.encode_grayscale(width, height, gray_data, bit_depth)
187}
188
189/// PNG image information without decoding pixel data.
190#[derive(Debug, Clone)]
191pub struct PngInfo {
192    /// Image width in pixels.
193    pub width: u32,
194    /// Image height in pixels.
195    pub height: u32,
196    /// Color type.
197    pub color_type: ColorType,
198    /// Bit depth.
199    pub bit_depth: u8,
200    /// Whether image is interlaced.
201    pub interlaced: bool,
202}
203
204/// Get PNG image information without decoding pixel data.
205///
206/// This is faster than full decoding when you only need metadata.
207///
208/// # Arguments
209///
210/// * `data` - PNG file data
211///
212/// # Errors
213///
214/// Returns error if PNG data is invalid.
215///
216/// # Examples
217///
218/// ```ignore
219/// let png_data = std::fs::read("image.png")?;
220/// let info = oximedia_codec::png::get_info(&png_data)?;
221/// println!("Image size: {}x{}", info.width, info.height);
222/// ```
223pub fn get_info(data: &[u8]) -> CodecResult<PngInfo> {
224    let decoder = PngDecoder::new(data)?;
225    Ok(PngInfo {
226        width: decoder.width(),
227        height: decoder.height(),
228        color_type: decoder.color_type(),
229        bit_depth: decoder.bit_depth(),
230        interlaced: decoder.is_interlaced(),
231    })
232}
233
234/// Validate PNG file format.
235///
236/// Checks PNG signature and performs basic validation without full decode.
237///
238/// # Arguments
239///
240/// * `data` - PNG file data
241///
242/// # Errors
243///
244/// Returns error if PNG data is invalid.
245///
246/// # Examples
247///
248/// ```ignore
249/// let png_data = std::fs::read("image.png")?;
250/// if oximedia_codec::png::validate(&png_data).is_ok() {
251///     println!("Valid PNG file");
252/// }
253/// ```
254pub fn validate(data: &[u8]) -> CodecResult<()> {
255    PngDecoder::new(data)?;
256    Ok(())
257}
258
259/// Check if data appears to be PNG format.
260///
261/// Only checks the PNG signature without full validation.
262///
263/// # Examples
264///
265/// ```ignore
266/// let data = std::fs::read("file.png")?;
267/// if oximedia_codec::png::is_png(&data) {
268///     println!("Appears to be PNG format");
269/// }
270/// ```
271#[must_use]
272pub fn is_png(data: &[u8]) -> bool {
273    const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10];
274    data.len() >= 8 && &data[0..8] == PNG_SIGNATURE
275}
276
277/// Transcode PNG to different encoding settings.
278///
279/// Decodes and re-encodes PNG with different settings.
280///
281/// # Arguments
282///
283/// * `data` - Input PNG data
284/// * `config` - Encoder configuration
285///
286/// # Errors
287///
288/// Returns error if decoding or encoding fails.
289///
290/// # Examples
291///
292/// ```ignore
293/// let input = std::fs::read("input.png")?;
294/// let config = EncoderConfig::new()
295///     .with_compression(9)
296///     .with_filter_strategy(FilterStrategy::Best);
297/// let output = oximedia_codec::png::transcode(&input, config)?;
298/// std::fs::write("output.png", output)?;
299/// ```
300pub fn transcode(data: &[u8], config: EncoderConfig) -> CodecResult<Vec<u8>> {
301    let decoder = PngDecoder::new(data)?;
302    let image = decoder.decode()?;
303    let encoder = PngEncoder::with_config(config);
304    encoder.encode_rgba(image.width, image.height, &image.data)
305}
306
307/// Optimize PNG file for smaller size.
308///
309/// Re-encodes PNG with maximum compression settings.
310///
311/// # Arguments
312///
313/// * `data` - Input PNG data
314///
315/// # Errors
316///
317/// Returns error if optimization fails.
318///
319/// # Examples
320///
321/// ```ignore
322/// let input = std::fs::read("input.png")?;
323/// let optimized = oximedia_codec::png::optimize(&input)?;
324/// std::fs::write("output.png", optimized)?;
325/// println!("Size reduced from {} to {} bytes", input.len(), optimized.len());
326/// ```
327pub fn optimize(data: &[u8]) -> CodecResult<Vec<u8>> {
328    let config = EncoderConfig::new()
329        .with_compression(9)
330        .with_filter_strategy(FilterStrategy::Best)
331        .with_palette_optimization(true);
332    transcode(data, config)
333}