buffer_graphics_lib/
drawing.rs

1use crate::clipping::Clip;
2use crate::drawable::{DrawType, Drawable};
3use crate::image::Image;
4use crate::prelude::PixelFont;
5use crate::shapes::CreateDrawable;
6use crate::text::format::TextFormat;
7use crate::text::pos::TextPos;
8use crate::text::{chr_to_code, Text};
9use crate::GraphicsBuffer::RgbaU8;
10use crate::{Graphics, GraphicsBuffer, GraphicsError};
11use graphics_shapes::circle::Circle;
12use graphics_shapes::coord::Coord;
13use graphics_shapes::polygon::Polygon;
14use graphics_shapes::prelude::Ellipse;
15use graphics_shapes::rect::Rect;
16use graphics_shapes::triangle::Triangle;
17use ici_files::palette::simplify_palette_to_fit;
18use ici_files::prelude::*;
19use std::collections::HashSet;
20use std::mem::swap;
21
22/// Represents anything that [Graphics] can render
23pub trait Renderable<T> {
24    fn render(&self, graphics: &mut Graphics);
25}
26
27#[inline]
28pub(crate) fn index_u8(width: usize, x: usize, y: usize) -> usize {
29    (x + y * width) * 4
30}
31
32#[inline]
33pub(crate) fn index_u32(width: usize, x: usize, y: usize) -> usize {
34    x + y * width
35}
36
37pub(crate) fn clear_u8(buffer: &mut GraphicsBuffer, color: Color) {
38    if let RgbaU8(buffer) = buffer {
39        buffer.chunks_exact_mut(4).for_each(|px| {
40            px[0] = color.r;
41            px[1] = color.g;
42            px[2] = color.b;
43            px[3] = color.a;
44        });
45    } else {
46        panic!(
47            "clear_u8 called on non u8 buffer, please create GitHub issue for buffer-graphics-lib"
48        )
49    }
50}
51
52pub(crate) fn clear_u32(buffer: &mut GraphicsBuffer, color: Color) {
53    #[allow(clippy::type_complexity)] //it's internal to this method
54    let result: Option<(&mut &mut [u32], fn(Color) -> u32)> = match buffer {
55        RgbaU8(_) => None,
56        GraphicsBuffer::RgbaU32(buf) => Some((buf, Color::to_rgba)),
57        GraphicsBuffer::ArgbU32(buf) => Some((buf, Color::to_argb)),
58    };
59    if let Some((buffer, method)) = result {
60        let color = method(color);
61        buffer.iter_mut().for_each(|p| *p = color);
62    } else {
63        panic!("clear_u32 called on non u32 buffer, please create GitHub issue for buffer-graphics-lib")
64    }
65}
66
67impl Graphics<'_> {
68    /// Convert an x,y coord to idx for use with `self.pixels`
69    #[inline(always)]
70    pub fn index(&self, x: usize, y: usize) -> usize {
71        (self.index_method)(self.width, x, y)
72    }
73
74    #[inline(always)]
75    pub fn width(&self) -> usize {
76        self.width
77    }
78
79    #[inline(always)]
80    pub fn height(&self) -> usize {
81        self.height
82    }
83
84    pub fn is_on_screen(&self, point: Coord) -> bool {
85        let x = point.x - self.translate.x;
86        let y = point.y - self.translate.y;
87        x >= 0 && y >= 0 && x < self.width as isize && y < self.height as isize
88    }
89}
90
91impl Graphics<'_> {
92    /// Get the canvas offset in pixels
93    #[inline(always)]
94    pub fn get_translate(&self) -> Coord {
95        self.translate
96    }
97
98    /// Set the canvas offset in pixels
99    ///
100    /// All drawing commands will be offset by this value
101    ///
102    /// # Returns
103    /// The previous translation value
104    #[inline]
105    pub fn set_translate(&mut self, new_value: Coord) -> Coord {
106        let old = self.translate;
107        self.translate = new_value;
108        old
109    }
110
111    #[inline]
112    pub fn with_translate<F: Fn(&mut Graphics)>(&mut self, set: Coord, method: F) {
113        let old_trans = self.set_translate(set);
114        method(self);
115        self.set_translate(old_trans);
116    }
117
118    /// Adds `delta` to the current canvas offset
119    #[inline]
120    pub fn update_translate(&mut self, delta: Coord) {
121        self.translate.x += delta.x;
122        self.translate.y += delta.y;
123    }
124
125    /// Copy entire pixels array to an image
126    pub fn copy_to_image(&self) -> Image {
127        let pixels = self.buffer.to_pixels();
128        Image::new(pixels, self.width, self.height)
129            .expect("Copy to image failed, please create GitHub issue for buffer-graphics-lib")
130    }
131
132    /// Copy entire pixels array to an indexed image
133    /// `simplify_palette` if true and there's more than 255 colours, this will simplify/merge the palette until there are under 255 colours
134    ///
135    /// # Errors
136    ///
137    /// * `GraphicsError::TooManyColors` Over 255 colors have been used and `simplify_palette` was false
138    /// * `GraphicsError::TooBig` Image is bigger than 255x255
139    /// * `GraphicsError::ImageError` Something went wrong creating the IndexedImage
140    pub fn copy_to_indexed_image(
141        &self,
142        simplify_palette: bool,
143    ) -> Result<IndexedImage, GraphicsError> {
144        if self.width > 255 || self.height > 255 {
145            return Err(GraphicsError::TooBig(self.width, self.height));
146        }
147        let width = self.width as u8;
148        let height = self.height as u8;
149        let pixels = self.buffer.to_pixels();
150        let colors: HashSet<Color> = HashSet::from_iter(pixels.iter().copied());
151        let colors = if colors.len() > 255 {
152            if simplify_palette {
153                simplify_palette_to_fit(&colors.into_iter().collect::<Vec<Color>>(), 255)
154            } else {
155                return Err(GraphicsError::TooManyColors);
156            }
157        } else {
158            colors.into_iter().collect()
159        };
160
161        let pixels = pixels
162            .iter()
163            .map(|c| {
164                colors
165                    .iter()
166                    .position(|o| o == c)
167                    .unwrap_or_else(|| panic!()) as u8
168            })
169            .collect();
170
171        IndexedImage::new(width, height, colors, pixels).map_err(GraphicsError::ImageError)
172    }
173
174    /// Get top left pixel coord for letter col row
175    pub fn get_px_for_char(col: usize, row: usize, font: &PixelFont) -> (usize, usize) {
176        (col * font.char_width(), row * font.line_height())
177    }
178
179    /// Draw an image at `x`, `y`
180    /// If the image definitely will draw inside the window you can use [draw_image_unchecked] instead
181    pub fn draw_image<P: Into<Coord>>(&mut self, xy: P, image: &Image) {
182        let xy = xy.into();
183        let mut x = 0;
184        let mut y = 0;
185        for pixel in image.pixels() {
186            update_pixel(
187                &mut self.buffer,
188                &self.translate,
189                &self.clip,
190                (self.width, self.height),
191                xy.x + x as isize,
192                xy.y + y,
193                *pixel,
194            );
195            x += 1;
196            if x >= image.width() {
197                x = 0;
198                y += 1;
199            }
200        }
201    }
202
203    /// Draw an indexed image at `x`, `y`
204
205    pub fn draw_indexed_image<P: Into<Coord>>(&mut self, xy: P, image: &IndexedImage) {
206        let xy = xy.into();
207        let palette = image.get_palette();
208        let (width, height) = image.size();
209        for x in 0..width {
210            for y in 0..height {
211                let i = image.get_pixel_index(x, y).unwrap();
212                let color_idx = image.get_pixel(i).unwrap() as usize;
213                let color = palette[color_idx];
214                update_pixel(
215                    &mut self.buffer,
216                    &self.translate,
217                    &self.clip,
218                    (self.width, self.height),
219                    x as isize + xy.x,
220                    y as isize + xy.y,
221                    color,
222                );
223            }
224        }
225    }
226
227    pub fn draw_wrapped_image<P: Into<Coord>>(&mut self, xy: P, image: &IndexedWrapper) {
228        match image {
229            IndexedWrapper::Static(img) => self.draw_indexed_image(xy, img),
230            IndexedWrapper::Animated(img) => self.draw_animated_image(xy, img),
231        }
232    }
233
234    /// Draw an animated image at `x`, `y`
235
236    pub fn draw_animated_image<P: Into<Coord>>(&mut self, xy: P, image: &AnimatedIndexedImage) {
237        let xy = xy.into();
238        let palette = image.get_palette();
239        let (width, height) = image.size();
240        let current_frame = image.get_current_frame_pixels();
241        for x in 0..width {
242            for y in 0..height {
243                let i = image.get_pixel_index(x, y).unwrap();
244                let color_idx = current_frame[i] as usize;
245                let color = palette[color_idx];
246                update_pixel(
247                    &mut self.buffer,
248                    &self.translate,
249                    &self.clip,
250                    (self.width, self.height),
251                    x as isize + xy.x,
252                    y as isize + xy.y,
253                    color,
254                );
255            }
256        }
257    }
258
259    pub fn draw_arc(
260        &mut self,
261        center: Coord,
262        angle_start: isize,
263        angle_end: isize,
264        radius: usize,
265        close: bool,
266        color: Color,
267    ) {
268        for r in angle_start..=angle_end {
269            let px = Coord::from_angle(center, radius, r);
270            update_pixel(
271                &mut self.buffer,
272                &self.translate,
273                &self.clip,
274                (self.width, self.height),
275                px.x,
276                px.y,
277                color,
278            );
279        }
280        if close {
281            self.draw_line(
282                center,
283                Coord::from_angle(center, radius, angle_start),
284                color,
285            );
286            self.draw_line(center, Coord::from_angle(center, radius, angle_end), color);
287        }
288    }
289
290    pub fn draw_line<P1: Into<Coord>, P2: Into<Coord>>(
291        &mut self,
292        start: P1,
293        end: P2,
294        color: Color,
295    ) {
296        let mut start = start.into();
297        let mut end = end.into();
298        if start.x > end.x || start.y > end.y {
299            swap(&mut start, &mut end);
300        }
301        if start.x == end.x {
302            for y in start.y..=end.y {
303                update_pixel(
304                    &mut self.buffer,
305                    &self.translate,
306                    &self.clip,
307                    (self.width, self.height),
308                    start.x,
309                    y,
310                    color,
311                );
312            }
313        } else if start.y == end.y {
314            for x in start.x..=end.x {
315                update_pixel(
316                    &mut self.buffer,
317                    &self.translate,
318                    &self.clip,
319                    (self.width, self.height),
320                    x,
321                    start.y,
322                    color,
323                );
324            }
325        } else {
326            let mut delta = 0;
327            let x1 = start.x;
328            let y1 = start.y;
329            let x2 = end.x;
330            let y2 = end.y;
331            let dx = isize::abs(x2 - x1);
332            let dy = isize::abs(y2 - y1);
333            let dx2 = dx * 2;
334            let dy2 = dy * 2;
335            let ix: isize = if x1 < x2 { 1 } else { -1 };
336            let iy: isize = if y1 < y2 { 1 } else { -1 };
337            let mut x = x1;
338            let mut y = y1;
339            if dx >= dy {
340                loop {
341                    update_pixel(
342                        &mut self.buffer,
343                        &self.translate,
344                        &self.clip,
345                        (self.width, self.height),
346                        x,
347                        y,
348                        color,
349                    );
350                    if x == x2 {
351                        break;
352                    }
353                    x += ix;
354                    delta += dy2;
355                    if delta > dx {
356                        y += iy;
357                        delta -= dx2;
358                    }
359                }
360            } else {
361                loop {
362                    update_pixel(
363                        &mut self.buffer,
364                        &self.translate,
365                        &self.clip,
366                        (self.width, self.height),
367                        x,
368                        y,
369                        color,
370                    );
371                    if y == y2 {
372                        break;
373                    }
374                    y += iy;
375                    delta += dx2;
376                    if delta > dy {
377                        x += ix;
378                        delta -= dy2;
379                    }
380                }
381            }
382        }
383    }
384
385    /// Draw renderable offset by [xy]
386    #[inline(always)]
387    pub fn draw_offset<T, P: Into<Coord>>(&mut self, xy: P, renderable: &dyn Renderable<T>) {
388        self.with_translate(xy.into(), |g| renderable.render(g));
389    }
390
391    /// Draw renderable
392    #[inline(always)]
393    pub fn draw<T>(&mut self, renderable: &dyn Renderable<T>) {
394        renderable.render(self);
395    }
396
397    /// Get the RGB values for a pixel
398    /// Alpha will always be 255
399    ///
400    /// If `use_translate` is true then the x,y will be updated with `self.translate`
401    ///
402    /// # Returns
403    ///
404    /// `Some(Color)` if x,y is within bounds
405    #[inline]
406    pub fn get_pixel(&self, x: isize, y: isize, use_translate: bool) -> Option<Color> {
407        let (x, y) = if use_translate {
408            (x + self.translate.x, y + self.translate.y)
409        } else {
410            (x, y)
411        };
412
413        let len = self.width * self.height;
414        if x >= 0 && y >= 0 && x < self.width as isize {
415            let idx = self.index(x as usize, y as usize);
416            if idx < len {
417                return Some(self.buffer.get_color(idx));
418            }
419        }
420
421        None
422    }
423
424    /// Set every pixel to `color`, this ignores translate and clip
425    #[inline(always)]
426    pub fn clear(&mut self, color: Color) {
427        (self.clear_method)(&mut self.buffer, color);
428    }
429
430    /// Set/blend every pixel with `color`, same as [clear] but this follows translate and clip
431    pub fn clear_aware(&mut self, color: Color) {
432        for y in 0..self.height {
433            for x in 0..self.width {
434                self.set_pixel(x as isize, y as isize, color);
435            }
436        }
437    }
438
439    /// Draw `chr` at `pos`
440    ///
441    /// # Usage
442    /// ```
443    ///# use buffer_graphics_lib::prelude::*;
444    ///# fn doc(graphics: &mut Graphics) {
445    ///     graphics.draw_letter((20,20), 'A', PixelFont::Limited3x5, BLUE);
446    ///# }
447    /// ```
448    #[inline(always)]
449    pub fn draw_letter(&mut self, pos: (isize, isize), chr: char, font: PixelFont, color: Color) {
450        self.draw_ascii_letter(pos, chr_to_code(chr), font, color);
451    }
452
453    /// Shouldn't be called in normal usage
454    pub fn draw_ascii_letter(
455        &mut self,
456        pos: (isize, isize),
457        code: u8,
458        font: PixelFont,
459        color: Color,
460    ) {
461        if code == 32 || code == 9 {
462            return;
463        }
464        let (width, height) = font.size();
465
466        let px: &[bool] = if let Some(custom) = self.custom_font.get(&code) {
467            match font {
468                PixelFont::Standard4x4 => &custom._4x4,
469                PixelFont::Script8x8 => &custom._8x8,
470                PixelFont::Outline7x9 => &custom._7x9,
471                PixelFont::Standard4x5 => &custom._4x5,
472                PixelFont::Standard6x7 => &custom._6x7,
473                PixelFont::Standard8x10 => &custom._8x10,
474                PixelFont::Limited3x5 => &custom._3x5,
475            }
476        } else {
477            font.pixels(code)
478        };
479
480        for x in 0..width {
481            for y in 0..height {
482                let i = x + y * width;
483                if px[i] {
484                    update_pixel(
485                        &mut self.buffer,
486                        &self.translate,
487                        &self.clip,
488                        (self.width, self.height),
489                        x as isize + pos.0,
490                        y as isize + pos.1,
491                        color,
492                    );
493                }
494            }
495        }
496    }
497
498    /// Should only be used by Text::render
499    /// `text` param must already be corrected wrapped
500    pub fn draw_ascii<P: Into<TextPos>, F: Into<TextFormat>>(
501        &mut self,
502        text: &[Vec<u8>],
503        pos: P,
504        format: F,
505    ) {
506        let format = format.into();
507        let font = format.font();
508        let color = format.color();
509        let per_x = format.char_width();
510        let per_y = format.line_height();
511
512        if per_y == 0 || per_x == 0 {
513            return;
514        }
515
516        let (start_x, start_y) = format.positioning().calc(
517            pos.into().to_coord(font),
518            text.iter().map(|list| list.len()).max().unwrap() * per_x.unsigned_abs(),
519            text.len() * per_y.unsigned_abs(),
520        );
521
522        for (y, line) in text.iter().enumerate() {
523            let y = y as isize * per_y;
524            for (x, char) in line.iter().enumerate() {
525                let x = x as isize * per_x;
526                self.draw_ascii_letter((start_x + x, start_y + y), *char, font, color);
527            }
528        }
529    }
530
531    /// Draw `text` on screen at `pos` using `format`
532    ///
533    /// # Params
534    /// * `format` See [TextFormat], can be
535    ///     * `TextFormat`
536    ///     * `Color`
537    ///     * `(Color, PixelFont)`
538    ///     * `(Color, PixelFont, Positioning)`
539    ///     * `(Color, PixelFont, WrappingStrategy, Positioning)`
540    ///     * `(Color, PixelFont, WrappingStrategy)`
541    ///     * `(Color, PixelFont, WrappingStrategy, f32)` (f32 = line height)
542    ///     * `(Color, PixelFont, WrappingStrategy, f32, f32)` (1st f32 = line height, 2nd f32 = char width)
543    ///     * `(Color, PixelFont, WrappingStrategy, f32, f32, Positioning)` (1st f32 = line height, 2nd f32 = char width)
544    ///
545    /// # Usage
546    ///
547    /// ```
548    ///# use buffer_graphics_lib::prelude::*;
549    ///# fn doc(graphics: &mut Graphics) {
550    ///  //simple example
551    ///  graphics.draw_text("Test", TextPos::ColRow(1,1), RED);
552    ///# }
553    ///
554    /// //full example
555    /// fn draw_message(graphics: &mut Graphics, msg: String) {
556    ///     let width_in_columns = PixelFont::Standard6x7.get_max_characters(graphics.width(), graphics.height()).0;
557    ///     graphics.draw_text(&msg, TextPos::px(coord!(8,8)), (BLACK, PixelFont::Standard6x7, WrappingStrategy::AtCol(width_in_columns - 1)));
558    /// }
559    /// ```
560    #[inline]
561    pub fn draw_text<P: Into<TextPos>, F: Into<TextFormat>>(
562        &mut self,
563        text: &str,
564        pos: P,
565        format: F,
566    ) {
567        let text = Text::new(text, pos.into(), format.into());
568        text.render(self);
569    }
570
571    #[inline]
572    pub fn draw_rect<R: Into<Rect>>(&mut self, rect: R, draw_type: DrawType) {
573        Drawable::from_obj(rect.into(), draw_type).render(self)
574    }
575
576    #[inline]
577    pub fn draw_circle<C: Into<Circle>>(&mut self, circle: C, draw_type: DrawType) {
578        Drawable::from_obj(circle.into(), draw_type).render(self)
579    }
580
581    #[inline]
582    pub fn draw_polygon<P: Into<Polygon>>(&mut self, polygon: P, draw_type: DrawType) {
583        Drawable::from_obj(polygon.into(), draw_type).render(self)
584    }
585
586    #[inline]
587    pub fn draw_triangle<T: Into<Triangle>>(&mut self, triangle: T, draw_type: DrawType) {
588        Drawable::from_obj(triangle.into(), draw_type).render(self)
589    }
590
591    #[inline]
592    pub fn draw_ellipse<E: Into<Ellipse>>(&mut self, ellipse: E, draw_type: DrawType) {
593        Drawable::from_obj(ellipse.into(), draw_type).render(self)
594    }
595
596    /// Update a pixel color, replacing or blending depending on whether `color`s alpha is 255 or not
597    ///
598    /// If the alpha is 0 the call does nothing
599    #[inline]
600    pub fn set_pixel(&mut self, x: isize, y: isize, color: Color) {
601        update_pixel(
602            &mut self.buffer,
603            &self.translate,
604            &self.clip,
605            (self.width, self.height),
606            x,
607            y,
608            color,
609        );
610    }
611}
612
613/// Update a pixel color, using [set_pixel] or [blend_pixel] depending on whether `color`s alpha is 255 or not
614///
615/// If the alpha is 0 the call is does nothing
616fn update_pixel(
617    buffer: &mut GraphicsBuffer,
618    translate: &Coord,
619    clip: &Clip,
620    (width, height): (usize, usize),
621    x: isize,
622    y: isize,
623    color: Color,
624) {
625    let x = x + translate.x;
626    let y = y + translate.y;
627    match buffer {
628        RgbaU8(buffer) => {
629            let idx = ((x + y * width as isize) * 4) as usize;
630            if x >= 0
631                && y >= 0
632                && x < width as isize
633                && y < height as isize
634                && clip.is_valid((x, y))
635            {
636                match color.a {
637                    255 => set_pixel_u8_rgba(buffer, idx, color),
638                    0 => {}
639                    _ => blend_pixel_u8_rgba(buffer, idx, color),
640                }
641            }
642        }
643        GraphicsBuffer::RgbaU32(buffer) => {
644            let idx = (x + y * width as isize) as usize;
645            if x >= 0
646                && y >= 0
647                && x < width as isize
648                && y < height as isize
649                && clip.is_valid((x, y))
650            {
651                match color.a {
652                    255 => set_pixel_32(buffer, idx, color, Color::to_rgba),
653                    0 => {}
654                    _ => blend_pixel_32(buffer, idx, color, Color::to_rgba),
655                }
656            }
657        }
658        GraphicsBuffer::ArgbU32(buffer) => {
659            let idx = (x + y * width as isize) as usize;
660            if x >= 0
661                && y >= 0
662                && x < width as isize
663                && y < height as isize
664                && clip.is_valid((x, y))
665            {
666                match color.a {
667                    255 => set_pixel_32(buffer, idx, color, Color::to_argb),
668                    0 => {}
669                    _ => blend_pixel_32(buffer, idx, color, Color::to_argb),
670                }
671            }
672        }
673    }
674}
675
676/// Set the RGB values for a pixel
677///
678/// Generally you should use [update_pixel] instead
679///
680/// This ignores alpha, so 255,0,0,0 will draw a red pixel
681fn set_pixel_32(buffer: &mut [u32], idx: usize, color: Color, conv: fn(Color) -> u32) {
682    if idx < buffer.len() {
683        buffer[idx] = conv(color);
684    }
685}
686
687/// Set the RGB values for a pixel by blending it with the provided color
688/// This method uses alpha blending
689/// Generally you should use [update_pixel] instead
690fn blend_pixel_32(buffer: &mut [u32], idx: usize, color: Color, conv: fn(Color) -> u32) {
691    let existing_color = Color::from_rgba(buffer[idx]);
692    let new_color = existing_color.blend(color);
693    buffer[idx] = conv(new_color);
694}
695
696/// Set the RGB values for a pixel
697///
698/// Generally you should use [update_pixel] instead
699///
700/// This ignores alpha, so 255,0,0,0 will draw a red pixel
701fn set_pixel_u8_rgba(buffer: &mut [u8], idx: usize, color: Color) {
702    if idx < buffer.len() {
703        buffer[idx] = color.r;
704        buffer[idx + 1] = color.g;
705        buffer[idx + 2] = color.b;
706        buffer[idx + 3] = color.a;
707    }
708}
709
710/// Set the RGB values for a pixel by blending it with the provided color
711/// This method uses alpha blending
712/// Generally you should use [update_pixel] instead
713fn blend_pixel_u8_rgba(buffer: &mut [u8], idx: usize, color: Color) {
714    let existing_color = Color {
715        r: buffer[idx],
716        g: buffer[idx + 1],
717        b: buffer[idx + 2],
718        a: buffer[idx + 3],
719    };
720    let new_color = existing_color.blend(color);
721    buffer[idx] = new_color.r;
722    buffer[idx + 1] = new_color.g;
723    buffer[idx + 2] = new_color.b;
724}
725
726#[cfg(test)]
727mod test {
728    use super::*;
729    use crate::prelude::*;
730    use crate::shapes::polyline::Segment::*;
731    use crate::text::pos::TextPos::Px;
732
733    #[test]
734    fn is_inside() {
735        let mut buf = [0; 400];
736        let mut graphics = Graphics::new_u8_rgba(&mut buf, 10, 10).unwrap();
737        assert!(graphics.is_on_screen(Coord { x: 1, y: 1 }));
738        assert!(graphics.is_on_screen(Coord { x: 9, y: 9 }));
739        assert!(graphics.is_on_screen(Coord { x: 0, y: 0 }));
740        assert!(!graphics.is_on_screen(Coord { x: 10, y: 10 }));
741        assert!(!graphics.is_on_screen(Coord { x: 4, y: -1 }));
742        assert!(!graphics.is_on_screen(Coord { x: -1, y: 4 }));
743
744        graphics.set_translate(Coord { x: 2, y: -1 });
745        assert!(graphics.is_on_screen(Coord { x: 4, y: 4 }));
746        assert!(graphics.is_on_screen(Coord { x: 4, y: 0 }));
747        assert!(!graphics.is_on_screen(Coord { x: 0, y: 0 }));
748        assert!(!graphics.is_on_screen(Coord { x: 4, y: 9 }));
749    }
750
751    #[test]
752    fn check_draw() {
753        let mut buf = [0; 400];
754        let mut graphics = Graphics::new_u8_rgba(&mut buf, 10, 10).unwrap();
755
756        let drawable = Drawable::from_obj(Line::new((10, 10), (20, 20)), stroke(RED));
757        let text = Text::new("", Px(1, 1), WHITE);
758        let polyline = Polyline::new(
759            vec![Start(Coord::new(0, 0)), LineTo(Coord::new(0, 0))],
760            WHITE,
761        );
762
763        graphics.draw(&drawable);
764        graphics.draw(&text);
765        graphics.draw(&polyline);
766    }
767}