buffer_graphics_lib/
lib.rs

1//! Buffer Graphics Lib
2//!
3//! This is a simple graphics library for drawing to a buffer, mainly designed to be used with [Rust Graphics Library](https://github.com/raybritton/rust-graphics-lib) or [Pixels](https://github.com/parasyte/pixels)
4//!
5//! It has basic shape drawing, bitmap text and image rendering.
6//!
7//! Using the library is as simple as:
8//!```
9//! # use graphics_shapes::rect::Rect;
10//! # use buffer_graphics_lib::prelude::*;
11//! let mut buffer = Graphics::create_buffer_u8(800, 600); //800 x 600 RGBA
12//! let mut graphics = Graphics::new_u8_rgba(&mut buffer, 800, 600).unwrap();
13//! let text = Text::new("Some text", TextPos::cr((1,1)), (LIGHT_GRAY, PixelFont::Standard6x7));
14//! graphics.draw(&text);
15//! graphics.draw_rect(Rect::new((40, 50), (100, 100)), stroke(BLUE));
16//! ```
17
18extern crate core;
19
20pub mod clipping;
21pub mod drawable;
22pub mod drawing;
23pub mod image;
24#[cfg(feature = "image_loading")]
25pub mod image_loading;
26pub mod indexed;
27pub mod integration;
28pub mod renderable_image;
29pub mod renderable_macros;
30pub mod scaling;
31pub mod shapes;
32pub mod text;
33
34use crate::prelude::*;
35use crate::GraphicsError::InvalidBufferLength;
36use fnv::FnvHashMap;
37use thiserror::Error;
38
39pub mod prelude {
40    pub use crate::clipping::*;
41    pub use crate::drawable::*;
42    pub use crate::drawing::*;
43    pub use crate::image::*;
44    #[cfg(feature = "image_loading")]
45    pub use crate::image_loading::*;
46    pub use crate::indexed::*;
47    #[allow(unused_imports)]
48    pub use crate::integration::*;
49    pub use crate::shapes::collection::*;
50    pub use crate::shapes::polyline::*;
51    pub use crate::shapes::*;
52    pub use crate::text::format::*;
53    pub use crate::text::pos::*;
54    pub use crate::text::wrapping::*;
55    pub use crate::text::*;
56    pub use crate::CustomLetter;
57    pub use crate::Graphics;
58    pub use crate::GraphicsError;
59    pub use graphics_shapes::prelude::*;
60    pub use ici_files::prelude::*;
61    #[cfg(feature = "image_loading")]
62    pub use image_lib::ImageError;
63    #[cfg(feature = "image_loading")]
64    pub use image_lib::ImageFormat;
65}
66
67#[derive(Error, Debug)]
68pub enum GraphicsError {
69    #[error("Invalid buffer length, expected: {0}, found: {1}")]
70    InvalidBufferLength(usize, usize),
71    #[error("Invalid pixel array length, expected: {0}, found: {1}")]
72    ImageInitSize(usize, usize),
73    #[error("Both images must be the same size, expected: {0}x{1}, found: {2}x{3}")]
74    ImageBlendSize(usize, usize, usize, usize),
75    #[error("Over 255 colours have been drawn")]
76    TooManyColors,
77    #[error("Size is greater than 255x255: {0}x{1}")]
78    TooBig(usize, usize),
79    #[error("Creating image")]
80    ImageError(IndexedImageError),
81}
82
83pub enum GraphicsBuffer<'a> {
84    RgbaU8(&'a mut [u8]),
85    RgbaU32(&'a mut [u32]),
86    ArgbU32(&'a mut [u32]),
87}
88
89impl GraphicsBuffer<'_> {
90    pub fn to_pixels(&self) -> Vec<Color> {
91        match self {
92            GraphicsBuffer::RgbaU8(buf) => buf
93                .chunks_exact(4)
94                .map(|p| Color::new(p[0], p[1], p[2], p[3]))
95                .collect(),
96            GraphicsBuffer::RgbaU32(buf) => buf.iter().copied().map(Color::from_rgba).collect(),
97            GraphicsBuffer::ArgbU32(buf) => buf.iter().copied().map(Color::from_argb).collect(),
98        }
99    }
100
101    pub const fn pixel_size(&self) -> usize {
102        match self {
103            GraphicsBuffer::RgbaU8(_) => 4,
104            GraphicsBuffer::RgbaU32(_) => 1,
105            GraphicsBuffer::ArgbU32(_) => 1,
106        }
107    }
108
109    pub fn get_color(&self, idx: usize) -> Color {
110        match self {
111            GraphicsBuffer::RgbaU8(buf) => {
112                Color::new(buf[idx], buf[idx + 1], buf[idx + 2], buf[idx + 3])
113            }
114            GraphicsBuffer::RgbaU32(buf) => Color::from_rgba(buf[idx]),
115            GraphicsBuffer::ArgbU32(buf) => Color::from_argb(buf[idx]),
116        }
117    }
118}
119
120pub struct Graphics<'buffer> {
121    buffer: GraphicsBuffer<'buffer>,
122    width: usize,
123    height: usize,
124    ///Offsets all drawing commands
125    translate: Coord,
126    ///Used to restrict drawing to inside this region, initially includes the whole screen
127    clip: Clip,
128    /// Allows you to replace any supported ASCII with a custom glyph
129    /// To replace 'a' with '█' for 4x5 fonts (such as `Standard4x5`) write
130    ///
131    /// `graphics.custom_font.insert(chr_to_code('a'), CustomLetter { _4x5: [true; 20], ..CustomLetter::default });`
132    ///
133    /// Characters are replaced on a size basis, so if `_4x4` is provided then all _4x4 fonts will draw this custom character
134    ///
135    /// Whitespace isn't supported and is skipped when drawing
136    ///
137    /// Note: `A-Za-z0-9!@$%^&*(),./;'\\[]<>?:\"{}_+~#…¤£¥¢✓|€` are valid for [text::chr_to_code]
138    pub custom_font: FnvHashMap<u8, CustomLetter>,
139    index_method: fn(usize, usize, usize) -> usize,
140    clear_method: fn(&mut GraphicsBuffer, Color),
141}
142
143impl Graphics<'_> {
144    /// Create a buffer of the correct size
145    #[inline]
146    pub fn create_buffer_u32(width: usize, height: usize) -> Vec<u32> {
147        vec![0; width * height]
148    }
149
150    /// Create a buffer of the correct size
151    #[inline]
152    pub fn create_buffer_u8(width: usize, height: usize) -> Vec<u8> {
153        vec![0; width * height * 4]
154    }
155}
156
157/// Only the letter sizes you'll use need to be set
158#[derive(Debug, Clone, Eq, PartialEq, Hash)]
159pub struct CustomLetter {
160    pub _4x4: [bool; text::font::standard_4x4::LETTER_PX_COUNT],
161    pub _4x5: [bool; text::font::standard_4x5::LETTER_PX_COUNT],
162    pub _6x7: [bool; text::font::standard_6x7::LETTER_PX_COUNT],
163    pub _7x9: [bool; text::font::outline_7x9::LETTER_PX_COUNT],
164    pub _8x8: [bool; text::font::script_8x8::LETTER_PX_COUNT],
165    pub _8x10: [bool; text::font::standard_8x10::LETTER_PX_COUNT],
166    pub _3x5: [bool; text::font::limited_3x5::LETTER_PX_COUNT],
167}
168
169impl Default for CustomLetter {
170    fn default() -> Self {
171        Self {
172            _4x4: [false; text::font::standard_4x4::LETTER_PX_COUNT],
173            _4x5: [false; text::font::standard_4x5::LETTER_PX_COUNT],
174            _6x7: [false; text::font::standard_6x7::LETTER_PX_COUNT],
175            _7x9: [false; text::font::outline_7x9::LETTER_PX_COUNT],
176            _8x8: [false; text::font::script_8x8::LETTER_PX_COUNT],
177            _8x10: [false; text::font::standard_8x10::LETTER_PX_COUNT],
178            _3x5: [false; text::font::limited_3x5::LETTER_PX_COUNT],
179        }
180    }
181}
182
183/// Create an image `width`x`height` px and then run drawing commands on it
184///
185/// # Usage
186/// ```
187///# use buffer_graphics_lib::make_image;
188///# use buffer_graphics_lib::prelude::*;
189///let image: Result<Image, GraphicsError> = make_image(30, 30, |g| {
190///    g.clear(BLACK);
191///    g.draw_rect(Rect::new(coord!(0,0), coord!(29,29)), stroke(WHITE))
192/// });
193/// ```
194pub fn make_image<F: FnOnce(&mut Graphics)>(
195    width: usize,
196    height: usize,
197    method: F,
198) -> Result<Image, GraphicsError> {
199    let mut buffer = Graphics::create_buffer_u8(width, height);
200    let mut graphics = Graphics::new_u8_rgba(&mut buffer, width, height)?;
201    method(&mut graphics);
202    Ok(graphics.copy_to_image())
203}
204
205/// Create an indexed image `width`x`height` px and then run drawing commands on it
206///
207/// # Params
208/// * `simplify_palette` if true and there's more than 255 colours, this will simplify/merge the palette until there are under 255 colours
209///
210/// # Usage
211/// ```
212///# use buffer_graphics_lib::make_indexed_image;
213///# use buffer_graphics_lib::prelude::*;
214///let image: Result<IndexedImage, GraphicsError> = make_indexed_image(30, 30, true, |g| {
215///    g.clear(BLACK);
216///    g.draw_rect(Rect::new(coord!(0,0), coord!(29,29)), stroke(WHITE))
217/// });
218/// ```
219///
220/// # Errors
221/// * `GraphicsError::TooManyColors` Over 255 colors have been used and `simplify_palette` was false
222/// * `GraphicsError::TooBig` Image is bigger than 255x255
223pub fn make_indexed_image<F: FnOnce(&mut Graphics)>(
224    width: usize,
225    height: usize,
226    simplify_palette: bool,
227    method: F,
228) -> Result<IndexedImage, GraphicsError> {
229    let mut buffer = Graphics::create_buffer_u8(width, height);
230    let mut graphics = Graphics::new_u8_rgba(&mut buffer, width, height)?;
231    method(&mut graphics);
232    graphics.copy_to_indexed_image(simplify_palette)
233}
234
235impl<'buffer> Graphics<'_> {
236    /// `buffer` needs to be `width * height * 4` long
237    ///
238    /// You can use [Graphics::create_buffer_u8] to guarantee the correct size
239    pub fn new_u8_rgba(
240        buffer: &'buffer mut [u8],
241        width: usize,
242        height: usize,
243    ) -> Result<Graphics<'buffer>, GraphicsError> {
244        let count = width * height * 4;
245        if count != buffer.len() {
246            return Err(InvalidBufferLength(count, buffer.len()));
247        }
248        let buffer = GraphicsBuffer::RgbaU8(buffer);
249        Ok(Graphics {
250            buffer,
251            width,
252            height,
253            translate: Coord::default(),
254            clip: Clip::new(width, height),
255            custom_font: FnvHashMap::default(),
256            clear_method: clear_u8,
257            index_method: index_u8,
258        })
259    }
260
261    /// `buffer` needs to be `width * height` long
262    ///
263    /// You can use [Graphics::create_buffer_u32] to guarantee the correct size
264    pub fn new_u32_rgba(
265        buffer: &'buffer mut [u32],
266        width: usize,
267        height: usize,
268    ) -> Result<Graphics<'buffer>, GraphicsError> {
269        let count = width * height;
270        if count != buffer.len() {
271            return Err(InvalidBufferLength(count, buffer.len()));
272        }
273        let buffer = GraphicsBuffer::RgbaU32(buffer);
274        Ok(Graphics {
275            buffer,
276            width,
277            height,
278            translate: Coord::default(),
279            clip: Clip::new(width, height),
280            custom_font: FnvHashMap::default(),
281            clear_method: clear_u32,
282            index_method: index_u32,
283        })
284    }
285
286    /// `buffer` needs to be `width * height` long
287    ///
288    /// You can use [Graphics::create_buffer_u32] to guarantee the correct size
289    pub fn new_u32_argb(
290        buffer: &'buffer mut [u32],
291        width: usize,
292        height: usize,
293    ) -> Result<Graphics<'buffer>, GraphicsError> {
294        let count = width * height;
295        if count != buffer.len() {
296            return Err(InvalidBufferLength(count, buffer.len()));
297        }
298        let buffer = GraphicsBuffer::ArgbU32(buffer);
299        Ok(Graphics {
300            buffer,
301            width,
302            height,
303            translate: Coord::default(),
304            clip: Clip::new(width, height),
305            custom_font: FnvHashMap::default(),
306            clear_method: clear_u32,
307            index_method: index_u32,
308        })
309    }
310}
311
312impl Graphics<'_> {
313    /// Replace the clip with `clip`
314    pub fn set_clip(&mut self, clip: Clip) {
315        self.clip = clip;
316    }
317
318    /// Get ref to [Graphic]'s [Clip]
319    pub fn clip(&self) -> &Clip {
320        &self.clip
321    }
322
323    /// Get mut ref to [Graphic]'s [Clip]
324    ///
325    /// Changes are effective immediately
326    pub fn clip_mut(&mut self) -> &mut Clip {
327        &mut self.clip
328    }
329}