ez_pixmap/
lib.rs

1//! # ez-pixmap
2//!
3//! A naive and easy inline pixmap (xpm-like) image decoder. 
4//! This is non-compliant with xpm image format, however it's close enough.
5//! - Doesn't support monochrome nor symbolics.
6//! - Supports only 1 character per pixel.
7//!
8//! Main use case: Simple icon art.
9//!
10//! ## Usage
11//! ```ignored
12//! [dependencies]
13//! ez-pixmap = "0.2"
14//! ```
15//!
16//! ```no_run
17//! extern crate ez_pixmap;
18//!
19//! const PXM: &[&str] = &[
20//!     "50 34 4 1", // <width> <height> <num of colors> <chars/pixels>
21//!     "  c black", // <char> c <color>
22//!     "o c #ff9900",
23//!     "@ c white",
24//!     "# c None",
25//!     // pixels
26//!     "##################################################",
27//!     "###      ##############################       ####",
28//!     "### ooooo  ###########################  ooooo ####",
29//!     "### oo  oo  #########################  oo  oo ####",
30//!     "### oo   oo  #######################  oo   oo ####",
31//!     "### oo    oo  #####################  oo    oo ####",
32//!     "### oo     oo  ###################  oo     oo ####",
33//!     "### oo      oo                     oo      oo ####",
34//!     "### oo       oo  ooooooooooooooo  oo       oo ####",
35//!     "### oo        ooooooooooooooooooooo        oo ####",
36//!     "### oo     ooooooooooooooooooooooooooo    ooo ####",
37//!     "#### oo   ooooooo ooooooooooooo ooooooo   oo #####",
38//!     "####  oo oooooooo ooooooooooooo oooooooo oo  #####",
39//!     "##### oo oooooooo ooooooooooooo oooooooo oo ######",
40//!     "#####  o ooooooooooooooooooooooooooooooo o  ######",
41//!     "###### ooooooooooooooooooooooooooooooooooo #######",
42//!     "##### ooooooooo     ooooooooo     ooooooooo ######",
43//!     "##### oooooooo  @@@  ooooooo  @@@  oooooooo ######",
44//!     "##### oooooooo @@@@@ ooooooo @@@@@ oooooooo ######",
45//!     "##### oooooooo @@@@@ ooooooo @@@@@ oooooooo ######",
46//!     "##### oooooooo  @@@  ooooooo  @@@  oooooooo ######",
47//!     "##### ooooooooo     ooooooooo     ooooooooo ######",
48//!     "###### oooooooooooooo       oooooooooooooo #######",
49//!     "###### oooooooo@@@@@@@     @@@@@@@oooooooo #######",
50//!     "###### ooooooo@@@@@@@@@   @@@@@@@@@ooooooo #######",
51//!     "####### ooooo@@@@@@@@@@@ @@@@@@@@@@@ooooo ########",
52//!     "######### oo@@@@@@@@@@@@ @@@@@@@@@@@@oo ##########",
53//!     "########## o@@@@@@ @@@@@ @@@@@ @@@@@@o ###########",
54//!     "########### @@@@@@@     @     @@@@@@@ ############",
55//!     "############  @@@@@@@@@@@@@@@@@@@@@  #############",
56//!     "##############  @@@@@@@@@@@@@@@@@  ###############",
57//!     "################    @@@@@@@@@    #################",
58//!     "####################         #####################",
59//!     "##################################################",
60//! ];
61//!
62//! fn main() -> Result<(), Box<dyn std::error::Error>> {
63//!     let my_image = ez_pixmap::RgbaImage::from(PXM)?;
64//!     assert_eq!(my_image.width(), 50);
65//!     assert_eq!(my_image.height(), 34);
66//!     assert_eq!(my_image.data().len(), 50 * 34 * 4); // since it's rgba
67//!     Ok(())
68//! }
69//! ```
70//!
71//! The list of supported color names can be found [here](https://github.com/MoAlyousef/ez-pixmap/blob/main/src/colors.rs).
72
73#![warn(missing_docs)]
74
75/// EzPixmap Error types
76#[derive(Debug)]
77pub enum EzPixmapError {
78    /// Parse error
79    ParseError(std::num::ParseIntError),
80    /// Internal error
81    Internal(EzPixmapErrorKind),
82}
83
84/// EzPixmap error kinds
85#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
86pub enum EzPixmapErrorKind {
87    /// Invalid EzPixmap format
88    InvalidFormat,
89    /// Xpm feature not implemented
90    NotImplemented,
91}
92
93impl std::error::Error for EzPixmapError {
94    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
95        match self {
96            EzPixmapError::ParseError(err) => Some(err),
97            _ => None,
98        }
99    }
100}
101
102impl std::fmt::Display for EzPixmapError {
103    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
104        match *self {
105            EzPixmapError::ParseError(ref err) => err.fmt(f),
106            EzPixmapError::Internal(ref err) => write!(f, "An internal error occured {:?}", err),
107        }
108    }
109}
110
111impl From<std::num::ParseIntError> for EzPixmapError {
112    fn from(err: std::num::ParseIntError) -> EzPixmapError {
113        EzPixmapError::ParseError(err)
114    }
115}
116
117#[derive(Default, Clone)]
118struct Header {
119    w: u32,
120    h: u32,
121    num_colors: u32,
122    ppc: u32,
123}
124
125#[derive(Default, Clone, Copy)]
126struct ColorMap {
127    c: char,
128    col: (u8, u8, u8, u8),
129}
130
131/// Struct containing Rgba data
132#[derive(Debug, Clone)]
133pub struct RgbaImage {
134    width: u32,
135    height: u32,
136    data: Vec<u8>,
137}
138
139impl RgbaImage {
140    /// Generate RGBA data from a pixmap
141    pub fn from(pixmap: &[&str]) -> Result<RgbaImage, EzPixmapError> {
142        let mut header = Header::default();
143        let mut data = vec![];
144        let mut col_vec: Vec<ColorMap> = vec![];
145        for i in 0..pixmap.len() {
146            if i == 0 {
147                let line = pixmap[0];
148                let vals: Vec<&str> = line.split_ascii_whitespace().collect();
149                header.w = vals[0].parse()?;
150                header.h = vals[1].parse()?;
151                header.num_colors = vals[2].parse()?;
152                header.ppc = vals[3].parse()?;
153                if header.ppc != 1 {
154                    return Err(EzPixmapError::Internal(EzPixmapErrorKind::InvalidFormat));
155                }
156                continue;
157            }
158            if i <= header.num_colors as usize {
159                let mut col = ColorMap::default();
160                let line = pixmap[i];
161                let chars: Vec<char> = line.chars().collect();
162                col.c = chars[0];
163                if chars[2] != 'c' {
164                    return Err(EzPixmapError::Internal(EzPixmapErrorKind::InvalidFormat));
165                }
166                let color: String = chars[4..].iter().collect();
167                if color.starts_with('#') {
168                    // shouldn't fail
169                    let color = color.strip_prefix("#").unwrap();
170                    let r = u8::from_str_radix(&color[0..2], 16)?;
171                    let g = u8::from_str_radix(&color[2..4], 16)?;
172                    let b = u8::from_str_radix(&color[4..6], 16)?;
173                    let a = 255;
174                    col.col = (r, g, b, a);
175                } else {
176                    if color == "None" || color == "none" {
177                        col.col = (255, 255, 255, 0);
178                    } else {
179                        let rgb = *color_maps::x::X_MAP.get(color.as_str()).unwrap_or(&(0, 0, 0));
180                        col.col = (rgb.0, rgb.1, rgb.2, 255);
181                    }
182                }
183                col_vec.push(col);
184                continue;
185            }
186            let line = pixmap[i];
187            let chars: Vec<char> = line.chars().collect();
188            for c in chars {
189                for elem in &col_vec {
190                    if c == elem.c {
191                        data.push(elem.col.0);
192                        data.push(elem.col.1);
193                        data.push(elem.col.2);
194                        data.push(elem.col.3);
195                    }
196                }
197            }
198        }
199        if data.len() != (header.w * header.h * 4) as usize {
200            return Err(EzPixmapError::Internal(EzPixmapErrorKind::InvalidFormat));
201        }
202        Ok(RgbaImage {
203            data,
204            width: header.w,
205            height: header.h,
206        })
207    }
208
209    /// Get the data of the image
210    pub fn data(&self) -> &[u8] {
211        &self.data
212    }
213
214    /// Get the width of the RgbaImage
215    pub fn width(&self) -> u32 {
216        self.width
217    }
218
219    /// Get the height of the RgbaImage
220    pub fn height(&self) -> u32 {
221        self.height
222    }
223}