lunar_png/
lib.rs

1//! # lunar-png
2//!
3//! A simple png decoding and encoding library
4//!
5//!
6//!# Crate Features
7//!
8//!The crate is split into 2 features:
9//!- Encoding
10//!- Decoding
11//!
12//!# Usage
13//!
14//! ```no_run
15//!use std::io::Read;
16//!use lunar_png::*;
17//!
18//!let mut file = std::fs::File::open("something.png").unwrap();
19//!let mut data = Vec::new();
20//!
21//!file.read_to_end(&mut data);
22//!
23//!//Decode a png image
24//!let image = decode_png(&mut data.into_iter()).unwrap();
25//!
26//!//Re-encode that image
27//!let png = encode_png(&image, &PngEncodingOptions::default());
28//! ```
29#![deny(missing_docs)]
30#![allow(
31    clippy::cast_lossless,
32    clippy::cast_possible_truncation,
33    clippy::cast_sign_loss
34)]
35use std::fmt::Debug;
36
37#[cfg(feature = "decoding")]
38mod decoding;
39#[cfg(feature = "encoding")]
40mod encoding;
41
42#[cfg(any(feature = "decoding", feature = "encoding"))]
43mod helpers;
44#[cfg(test)]
45mod tests;
46
47#[cfg(feature = "decoding")]
48pub use decoding::{Error, decode_png};
49#[cfg(feature = "encoding")]
50pub use encoding::{CompressionLevel, PngEncodingOptions, encode_png};
51
52#[derive(Debug, PartialEq, Eq, Clone, Copy)]
53///Image type of a loaded image
54pub enum ImageType {
55    ///A grayscale image with bit depth of 8
56    R8,
57    ///A grayscale image with bit depth of 16
58    R16,
59    ///A grayscale image  with an alpha channel and bit depth of 8
60    Ra8,
61    ///A grayscale image  with an alpha channel and bit depth of 16
62    Ra16,
63    ///An rgb image with the bit depth of 8
64    Rgb8,
65    ///An rgba image with the bit depth of 8
66    Rgba8,
67    ///An rgb image with the bit depth of 16
68    Rgb16,
69    ///An rgba image with the bit depth of 16
70    Rgba16,
71}
72
73impl ImageType {
74    ///Returns whether the image format is 16 bit or not
75    #[must_use]
76    pub const fn is_16_bit(&self) -> bool {
77        !matches!(self, Self::R8 | Self::Ra8 | Self::Rgb8 | Self::Rgba8)
78    }
79}
80
81#[derive(PartialEq, Eq, Clone)]
82///A loaded png image
83pub struct Image {
84    ///Width of the image
85    pub width: u32,
86    ///Height of the image
87    pub height: u32,
88    ///Type of the image
89    pub img_type: ImageType,
90    ///Actual data. Data in an image is stored in scanlines, going left to right, top to bottom
91    pub data: Vec<u8>,
92}
93
94#[allow(clippy::missing_fields_in_debug)]
95impl Debug for Image {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        f.debug_struct("Image")
98            .field("width", &self.width)
99            .field("height", &self.height)
100            .field("img_type", &self.img_type)
101            .finish()
102    }
103}
104
105impl Image {
106    ///Adds an alpha channel to the image, does nothing if the image already contains an alpha channel
107    pub fn add_alpha(&mut self) {
108        match self.img_type {
109            ImageType::R8 => {
110                self.img_type = ImageType::Ra8;
111                self.data = self.data.iter().flat_map(|i| [*i, 0xff]).collect();
112            }
113            ImageType::R16 => {
114                self.img_type = ImageType::Ra16;
115                self.data = self
116                    .data
117                    .chunks(2)
118                    .flat_map(|i| [i[0], i[1], 0xff, 0xff])
119                    .collect();
120            }
121            ImageType::Rgb8 => {
122                self.img_type = ImageType::Rgba8;
123                self.data = self
124                    .data
125                    .chunks(3)
126                    .flat_map(|c| [c[0], c[1], c[2], 0xff])
127                    .collect();
128            }
129            ImageType::Rgb16 => {
130                self.img_type = ImageType::Rgba16;
131                self.data = self
132                    .data
133                    .chunks(6)
134                    .flat_map(|c| [c[0], c[1], c[2], c[3], c[4], c[5], 0xff, 0xff])
135                    .collect();
136            }
137            _ => {}
138        }
139    }
140
141    ///Adds channels to a grayscale image. Does nothing if the image is not grayscale
142    pub fn add_channels(&mut self) {
143        match self.img_type {
144            ImageType::R8 => {
145                self.img_type = ImageType::Rgb8;
146                self.data = self.data.iter().copied().flat_map(|i| [i, i, i]).collect();
147            }
148            ImageType::R16 => {
149                self.img_type = ImageType::Rgb16;
150                self.data = self
151                    .data
152                    .chunks(2)
153                    .flat_map(|i| [i[0], i[1], i[0], i[1], i[0], i[1]])
154                    .collect();
155            }
156            ImageType::Ra8 => {
157                self.img_type = ImageType::Rgba8;
158                self.data = self
159                    .data
160                    .chunks(2)
161                    .flat_map(|i| [i[0], i[0], i[0], i[1]])
162                    .collect();
163            }
164            ImageType::Ra16 => {
165                self.img_type = ImageType::Rgba16;
166                self.data = self
167                    .data
168                    .chunks(4)
169                    .flat_map(|i| [i[0], i[1], i[0], i[1], i[0], i[1], i[2], i[3]])
170                    .collect();
171            }
172            _ => {}
173        }
174    }
175}