oxideav_gif/lib.rs
1//! Pure-Rust GIF87a / GIF89a codec.
2//!
3//! Decode a byte stream with [`decode`] and produce a [`GifImage`].
4//! Use [`decode_first_frame`] when you only need the cover frame —
5//! it short-circuits at the first image-bearing block and skips the
6//! per-block dispatch overhead for everything that follows.
7//! Construct or modify a [`GifImage`] and serialise it with [`encode`].
8//!
9//! ## Implemented (per CompuServe specifications)
10//!
11//! * Header, Logical Screen Descriptor, Global / Local Color Tables
12//! (§17–§19, §21).
13//! * Image Descriptor + Table-Based Image Data, including the LZW
14//! variable-length-code compression scheme of Appendix F (§20, §22).
15//! * Graphic Control Extension — disposal method, user-input flag,
16//! transparent colour index, delay time (§23).
17//! * Comment, Plain Text, and Application Extensions (§24, §25, §26).
18//! * Trailer (§27).
19//! * Four-pass interlace transform (Appendix E) on both decode and
20//! encode.
21//! * Multi-frame compositing onto the §18 Logical Screen using the
22//! §23 Disposal Method state machine — see [`compose`] for the
23//! eager `Vec<ComposedFrame>` form, [`playback::Playback`] for the
24//! lazy iterator form (one canvas at a time, plus a NETSCAPE2.0
25//! loop-count-aware [`playback::LoopingFrameIter`]).
26//!
27//! ## Application-extension structured views
28//!
29//! The CompuServe spec defines no concrete §26 Application
30//! Extensions. The [`app_ext`] module layers typed parsers on top of
31//! the raw [`Application`] block for the five ecosystem-defined
32//! shapes that achieved cross-decoder de-facto interoperability:
33//!
34//! * NETSCAPE2.0 looping + buffering sub-blocks
35//! ([`app_ext::LoopControl`]) — see also [`GifImage::loop_count`]
36//! and [`GifImage::netscape_buffer_hint`].
37//! * ANIMEXTS1.0 looping ([`app_ext::AnimextsLoopControl`]) — older
38//! Aldus-era variant that predates NETSCAPE2.0 and reuses the same
39//! *Looping* sub-block layout under a different identifier+auth.
40//! [`GifImage::loop_count`] falls back to it when NETSCAPE2.0 is
41//! absent.
42//! * XMP packet ([`app_ext::XmpPacket`]) — see also
43//! [`GifImage::xmp_packet`].
44//! * ICC colour profile ([`app_ext::IccProfile`]) — see also
45//! [`GifImage::icc_profile`].
46//! * EXIF metadata ([`app_ext::ExifMetadata`]) — see also
47//! [`GifImage::exif`]. Carries a TIFF EXIF blob per Exif 2.3
48//! §4.7.2; the spec defines this for JPEG/TIFF and the GIF binding
49//! is an ecosystem convention.
50//!
51//! These accessors layer on top of the raw block list — the
52//! [`Application`] block stays in [`GifImage::blocks`] regardless,
53//! preserving byte-stable round-trip.
54//!
55//! ## §7 Required Version enforcement on encode
56//!
57//! The CompuServe spec's §7 ("Version Numbers") requires encoders to
58//! label a stream with the earliest version that covers every block in
59//! it. Per-block "Required Version" entries are 87a for §20 Image
60//! Descriptor / §21 Local Color Table / §22 Table-Based Image Data and
61//! 89a for §23 Graphic Control / §24 Comment / §25 Plain Text / §26
62//! Application Extensions.
63//!
64//! [`encode`] enforces this on the encoder side: a [`GifImage`] declared
65//! [`Version::Gif87a`] that contains any 89a-only block is rejected with
66//! [`Error::InvalidInput`] before any bytes go to the wire — the
67//! alternative ("a `GIF87a` header followed by 89a-only block payloads")
68//! would be structurally invalid.
69//!
70//! Two recovery helpers ride on top: [`GifImage::required_version`]
71//! returns the minimum version that covers the current block list, and
72//! [`GifImage::upgrade_version_if_needed`] bumps the declared version up
73//! to that minimum in one call. The upgrade helper never *down*grades:
74//! a caller's explicit choice of [`Version::Gif89a`] for an
75//! 87a-compatible payload is preserved.
76//!
77//! ## §24 Comment Extension accessors
78//!
79//! The CompuServe spec (§24.a) allows any number of Comment
80//! Extensions in the Data Stream and recommends (§24.e.i) that they
81//! contain 7-bit ASCII text and (§24.e.ii) that they appear at the
82//! beginning or end of the stream. [`GifImage::comments`] iterates the
83//! payloads in source order; [`GifImage::concatenated_comment`]
84//! returns every payload joined with a single LF (or `None` when no
85//! Comment Extension is present). [`GifImage::comments_are_7bit_ascii`]
86//! and [`GifImage::comments_in_recommended_position`] surface the
87//! §24.e *recommendations* as boolean queries so a caller that wants
88//! to honour them strictly can gate on them; the encoder itself never
89//! enforces a recommendation.
90//!
91//! ## Plain Text rendering
92//!
93//! The §25 Plain Text Extension leaves font choice to the decoder
94//! (§25.e). [`compose`] / [`playback`] render Plain Text blocks
95//! against a crate-local clean-room 8×8 monospace bitmap font (see
96//! [`font`]) using the §23.c.viii transparent-index handling for the
97//! background colour. Out-of-range characters fall back to space per
98//! §25.e.
99//!
100//! [`Application`]: crate::Application
101//!
102//! ## Standalone vs registry-integrated
103//!
104//! With the default `registry` Cargo feature on, the crate exposes
105//! [`oxideav_core::Decoder`] / [`oxideav_core::Encoder`] trait impls
106//! plus a [`registry::register`] entry point against `oxideav-core`.
107//! With the feature off the crate ships only the standalone
108//! [`decode`] / [`encode`] / [`compose`] API plus the local
109//! [`GifImage`] / [`Error`] types, with no `oxideav-core` dep in the
110//! tree. Image-library consumers should depend on `oxideav-gif` with
111//! `default-features = false`.
112
113pub mod app_ext;
114pub mod builder;
115pub mod compose;
116pub mod decoder;
117pub mod encoder;
118pub mod error;
119pub mod font;
120pub mod image;
121pub mod interlace;
122pub mod lzw;
123pub mod playback;
124#[cfg(feature = "registry")]
125pub mod registry;
126
127pub use builder::AnimationBuilder;
128pub use compose::{compose, ComposedFrame, RgbaCanvas};
129pub use decoder::{decode, decode_first_frame, decode_lenient};
130pub use encoder::encode;
131pub use error::{Error, Result};
132pub use image::{
133 Application, Block, DisposalMethod, Frame, GifImage, GraphicControl, PlainText, Rgb, Version,
134};
135pub use playback::{FrameIter, LoopingFrameIter, Playback, PlaybackFrame};
136
137// Registry-gated public surface. The `__oxideav_entry` re-export is
138// load-bearing: `oxideav-meta`'s build-script-generated `register_all`
139// looks up `oxideav_gif::__oxideav_entry`, which only exists at the
140// crate root via this re-export.
141#[cfg(feature = "registry")]
142pub use registry::{
143 __oxideav_entry, register, register_codecs, register_containers, GifDecoder, GifEncoder,
144 CODEC_ID_STR,
145};