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}