include_pnm/
lib.rs

1//! Embed PNM images directly into Rust code.
2//!
3//! This supports *most* PNM files in a mostly reasonable way. The idea here is that PNM is a very
4//! simple format that is also compressible enough to embed directly into version control
5//! repositories, and so, very suited to loading in at compile time.
6//!
7//! Most PNM images are supported, with the primary exception applying to PPM images and 3-channel
8//! PAM images: these must have a `MAXVAL` of 255 or 65535 in order to be loaded at all.
9//!
10//! # Conversions
11//!
12//! Similar to `netpbm`, all formats of PNM images are supported, and then converted to the result
13//! depending on the mode requested. Color space is completely ignored when converting between
14//! formats, and conversion is only allowed in the forward direction, where e.g. grayscale images
15//! can be converted to RGB by duplicating values three times.
16//!
17//! Similarly, conversion from 8-bit color to 16-bit color is done under the assumption that the
18//! scaling does not change, and all values get scaled by 65535/255.
19//!
20//! # "Indexed" images
21//!
22//! While PGM and 1-channel PAM images may have any allowed `MAXVAL`, images without a `MAXVAL` of
23//! 1, 255, or 65535 may not be converted into any other color format, under the assumption that
24//! they were intended to reference palette indices instead of colors.
25//!
26//! # Tracking included images
27//!
28//! On stable Rust, you must add a build script and output `cargo:rerun-if-changed=path` for each
29//! included image to ensure that your crate recompiles whenever the image files change. However,
30//! on nightly Rust with the `nightly` feature enabled, the unstable `track_path` feature will be
31//! used so this happens automatically.
32#![cfg_attr(feature = "nightly", feature(track_path))]
33use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
34
35mod args;
36mod cold;
37mod formats;
38mod metadata;
39mod parse;
40use self::{
41    metadata::DepthChannels,
42    parse::{Magic, Reader},
43};
44use crate::formats::Nesting;
45
46/// Load PNM file and output it as an array.
47fn include_file(depth_channels: DepthChannels, nesting: Nesting, path: String) -> TokenStream {
48    let span = Span::call_site();
49
50    // rust-analyzer does not implement `Span::local_file` at all, so, detect this and just
51    // return something that will compile instead.
52    if span.file().is_empty() && span.local_file().is_none() {
53        eprintln!("warning: rust-analyzer does not implement `Span::local_file`");
54        eprintln!(
55            "warning: see this issue: https://github.com/rust-lang/rust-analyzer/issues/1595"
56        );
57
58        // unsafe { ::core::mem::zeroed() }
59        let mut result = TokenStream::new();
60        result.extend(Some(TokenTree::Ident(Ident::new("unsafe", span))));
61        {
62            let mut block = TokenStream::new();
63            block.extend(Some(TokenTree::Punct(Punct::new(':', Spacing::Alone))));
64            block.extend(Some(TokenTree::Punct(Punct::new(':', Spacing::Joint))));
65            block.extend(Some(TokenTree::Ident(Ident::new("core", span))));
66            block.extend(Some(TokenTree::Punct(Punct::new(':', Spacing::Alone))));
67            block.extend(Some(TokenTree::Punct(Punct::new(':', Spacing::Joint))));
68            block.extend(Some(TokenTree::Ident(Ident::new("mem", span))));
69            block.extend(Some(TokenTree::Punct(Punct::new(':', Spacing::Alone))));
70            block.extend(Some(TokenTree::Punct(Punct::new(':', Spacing::Joint))));
71            block.extend(Some(TokenTree::Ident(Ident::new("zeroed", span))));
72            block.extend(Some(TokenTree::Group(Group::new(
73                Delimiter::Parenthesis,
74                TokenStream::new(),
75            ))));
76            result.extend(Some(TokenTree::Group(Group::new(Delimiter::Brace, block))));
77        }
78        result
79    } else {
80        #[cfg(feature = "nightly")]
81        {
82            proc_macro::tracked_path::path(&path);
83        }
84        let mut reader = Reader::open(&path);
85        match reader.read_magic() {
86            Magic::P1 => formats::load_plain_pbm(&mut reader)
87                .convert(depth_channels)
88                .into_tokens(nesting),
89            Magic::P2 => formats::load_plain_pgm(&mut reader)
90                .convert(depth_channels)
91                .into_tokens(nesting),
92            Magic::P3 => formats::load_plain_ppm(&mut reader)
93                .convert(depth_channels)
94                .into_tokens(nesting),
95            Magic::P4 => formats::load_pbm(&mut reader)
96                .convert(depth_channels)
97                .into_tokens(nesting),
98            Magic::P5 => formats::load_pgm(&mut reader)
99                .convert(depth_channels)
100                .into_tokens(nesting),
101            Magic::P6 => formats::load_ppm(&mut reader)
102                .convert(depth_channels)
103                .into_tokens(nesting),
104            Magic::P7 => formats::load_pam(&mut reader)
105                .convert(depth_channels)
106                .into_tokens(nesting),
107        }
108    }
109}
110
111/// Include a PAM image as a 2D array of the specified type and number of channels.
112///
113/// Should be called as `include_pnm!(ty, filename)`.
114///
115/// Supported types are:
116///
117/// * `[bool; 1]` (`include_pbm`)
118/// * `[bool; 2]` (`include_pbma`)
119/// * `[u8; 1]` (`include_pgm`)
120/// * `[u8; 2]` (`include_pgma`)
121/// * `[u8; 3]` (`include_ppm`)
122/// * `[u8; 4]` (`include_ppma`)
123/// * `[u16; 1]` (`include_pgm16`)
124/// * `[u16; 2]` (`include_pgma16`)
125/// * `[u16; 3]` (`include_ppm16`)
126/// * `[u16; 4]` (`include_ppma16`)
127#[proc_macro]
128pub fn include_pnm(input: TokenStream) -> TokenStream {
129    let caller = "include_pnm";
130    let mut input = input.into_iter();
131    let depth_channels = args::get_depth_channels(caller, &mut input);
132    let file = args::get_file(caller, &mut input, Some(args::LeadingComma));
133    include_file(depth_channels, Nesting::Arrays, file)
134}
135
136/// Include a PBM image as a 2D array of `bool`.
137///
138/// Requires PAM images which can be converted to `BLACKANDWHITE` format.
139#[proc_macro]
140pub fn include_pbm(input: TokenStream) -> TokenStream {
141    let file = args::get_file("include_pbm", &mut input.into_iter(), None);
142    include_file(DepthChannels::BlackAndWhite, Nesting::Inline, file)
143}
144
145/// Include a "PBMA" image as a 2D array of `[bool; 2]`.
146///
147/// Requires PAM images which can be converted to `BLACKANDWHITE_ALPHA` format.
148#[proc_macro]
149pub fn include_pbma(input: TokenStream) -> TokenStream {
150    let file = args::get_file("include_pbma", &mut input.into_iter(), None);
151    include_file(DepthChannels::BlackAndWhiteAlpha, Nesting::Inline, file)
152}
153
154/// Include a PGM image as a 2D array of `u8`.
155///
156/// Requires PAM images which can be converted to `GRAYSCALE` format.
157#[proc_macro]
158pub fn include_pgm(input: TokenStream) -> TokenStream {
159    let file = args::get_file("include_pgm", &mut input.into_iter(), None);
160    include_file(DepthChannels::Grayscale, Nesting::Inline, file)
161}
162
163/// Include a "PGMA" image as a 2D array of `[u8; 2]`.
164///
165/// Requires PAM images which can be converted to `GRAYSCALE_ALPHA` format.
166#[proc_macro]
167pub fn include_pgma(input: TokenStream) -> TokenStream {
168    let file = args::get_file("include_pgma", &mut input.into_iter(), None);
169    include_file(DepthChannels::GrayscaleAlpha, Nesting::Inline, file)
170}
171
172/// Include a PGM image as a 2D array of `u16`.
173///
174/// Requires PAM images which can be converted to `GRAYSCALE` format.
175#[proc_macro]
176pub fn include_pgm16(input: TokenStream) -> TokenStream {
177    let file = args::get_file("include_pgm16", &mut input.into_iter(), None);
178    include_file(DepthChannels::Grayscale16, Nesting::Inline, file)
179}
180
181/// Include a "PGMA" image as a 2D array of `[u16; 2]`.
182///
183/// Requires PAM images which can be converted to `GRAYSCALE_ALPHA` format.
184#[proc_macro]
185pub fn include_pgma16(input: TokenStream) -> TokenStream {
186    let file = args::get_file("include_pgma", &mut input.into_iter(), None);
187    include_file(DepthChannels::GrayscaleAlpha16, Nesting::Inline, file)
188}
189
190/// Include a PPM image as a 2D array of `[u8; 3]`.
191///
192/// Requires PAM images which can be converted to `RGB` format.
193#[proc_macro]
194pub fn include_ppm(input: TokenStream) -> TokenStream {
195    let file = args::get_file("include_ppm", &mut input.into_iter(), None);
196    include_file(DepthChannels::Rgb, Nesting::Inline, file)
197}
198
199/// Include a "PPMA" image as a 2D array of `[u8; 4]`.
200///
201/// Requires PAM images which can be converted to `RGB_ALPHA` format.
202#[proc_macro]
203pub fn include_ppma(input: TokenStream) -> TokenStream {
204    let file = args::get_file("include_ppm", &mut input.into_iter(), None);
205    include_file(DepthChannels::RgbAlpha, Nesting::Inline, file)
206}
207
208/// Include a PPM image as a 2D array of `[u16; 3]`.
209///
210/// Requires PAM images which can be converted to `RGB` format.
211#[proc_macro]
212pub fn include_ppm16(input: TokenStream) -> TokenStream {
213    let file = args::get_file("include_ppm16", &mut input.into_iter(), None);
214    include_file(DepthChannels::Rgb16, Nesting::Inline, file)
215}
216
217/// Include a "PPMA" image as a 2D array of `[u16; 4]`.
218///
219/// Requires PAM images which can be converted to `RGB_ALPHA` format.
220#[proc_macro]
221pub fn include_ppma16(input: TokenStream) -> TokenStream {
222    let file = args::get_file("include_ppm16", &mut input.into_iter(), None);
223    include_file(DepthChannels::RgbAlpha16, Nesting::Inline, file)
224}