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    pub fn draw_indexed_image<P: Into<Coord>>(&mut self, xy: P, image: &IndexedImage) {
205        let xy = xy.into();
206        let palette = image.get_palette();
207        let (width, height) = image.size();
208        for x in 0..width {
209            for y in 0..height {
210                let i = image.get_pixel_index(x, y).unwrap();
211                let color_idx = image.get_pixel(i).unwrap() as usize;
212                let color = palette[color_idx];
213                update_pixel(
214                    &mut self.buffer,
215                    &self.translate,
216                    &self.clip,
217                    (self.width, self.height),
218                    x as isize + xy.x,
219                    y as isize + xy.y,
220                    color,
221                );
222            }
223        }
224    }
225
226    pub fn draw_wrapped_image<P: Into<Coord>>(&mut self, xy: P, image: &IndexedWrapper) {
227        match image {
228            IndexedWrapper::Static(img) => self.draw_indexed_image(xy, img),
229            IndexedWrapper::Animated(img) => self.draw_animated_image(xy, img),
230        }
231    }
232
233    /// Draw an animated image at `x`, `y`
234    pub fn draw_animated_image<P: Into<Coord>>(&mut self, xy: P, image: &AnimatedIndexedImage) {
235        let xy = xy.into();
236        let palette = image.get_palette();
237        let (width, height) = image.size();
238        let current_frame = image.get_current_frame_pixels();
239        for x in 0..width {
240            for y in 0..height {
241                let i = image.get_pixel_index(x, y).unwrap();
242                let color_idx = current_frame[i] as usize;
243                let color = palette[color_idx];
244                update_pixel(
245                    &mut self.buffer,
246                    &self.translate,
247                    &self.clip,
248                    (self.width, self.height),
249                    x as isize + xy.x,
250                    y as isize + xy.y,
251                    color,
252                );
253            }
254        }
255    }
256
257    pub fn draw_arc(
258        &mut self,
259        center: Coord,
260        angle_start: isize,
261        angle_end: isize,
262        radius: usize,
263        close: bool,
264        color: Color,
265    ) {
266        for r in angle_start..=angle_end {
267            let px = Coord::from_angle(center, radius, r);
268            update_pixel(
269                &mut self.buffer,
270                &self.translate,
271                &self.clip,
272                (self.width, self.height),
273                px.x,
274                px.y,
275                color,
276            );
277        }
278        if close {
279            self.draw_line(
280                center,
281                Coord::from_angle(center, radius, angle_start),
282                color,
283            );
284            self.draw_line(center, Coord::from_angle(center, radius, angle_end), color);
285        }
286    }
287
288    pub fn draw_line<P1: Into<Coord>, P2: Into<Coord>>(
289        &mut self,
290        start: P1,
291        end: P2,
292        color: Color,
293    ) {
294        let mut start = start.into();
295        let mut end = end.into();
296        if start.x > end.x || start.y > end.y {
297            swap(&mut start, &mut end);
298        }
299        if start.x == end.x {
300            for y in start.y..=end.y {
301                update_pixel(
302                    &mut self.buffer,
303                    &self.translate,
304                    &self.clip,
305                    (self.width, self.height),
306                    start.x,
307                    y,
308                    color,
309                );
310            }
311        } else if start.y == end.y {
312            for x in start.x..=end.x {
313                update_pixel(
314                    &mut self.buffer,
315                    &self.translate,
316                    &self.clip,
317                    (self.width, self.height),
318                    x,
319                    start.y,
320                    color,
321                );
322            }
323        } else {
324            let mut delta = 0;
325            let x1 = start.x;
326            let y1 = start.y;
327            let x2 = end.x;
328            let y2 = end.y;
329            let dx = isize::abs(x2 - x1);
330            let dy = isize::abs(y2 - y1);
331            let dx2 = dx * 2;
332            let dy2 = dy * 2;
333            let ix: isize = if x1 < x2 { 1 } else { -1 };
334            let iy: isize = if y1 < y2 { 1 } else { -1 };
335            let mut x = x1;
336            let mut y = y1;
337            if dx >= dy {
338                loop {
339                    update_pixel(
340                        &mut self.buffer,
341                        &self.translate,
342                        &self.clip,
343                        (self.width, self.height),
344                        x,
345                        y,
346                        color,
347                    );
348                    if x == x2 {
349                        break;
350                    }
351                    x += ix;
352                    delta += dy2;
353                    if delta > dx {
354                        y += iy;
355                        delta -= dx2;
356                    }
357                }
358            } else {
359                loop {
360                    update_pixel(
361                        &mut self.buffer,
362                        &self.translate,
363                        &self.clip,
364                        (self.width, self.height),
365                        x,
366                        y,
367                        color,
368                    );
369                    if y == y2 {
370                        break;
371                    }
372                    y += iy;
373                    delta += dx2;
374                    if delta > dy {
375                        x += ix;
376                        delta -= dy2;
377                    }
378                }
379            }
380        }
381    }
382
383    /// Draw renderable offset by [xy]
384    #[inline(always)]
385    pub fn draw_offset<T, P: Into<Coord>>(&mut self, xy: P, renderable: &dyn Renderable<T>) {
386        self.with_translate(xy.into(), |g| renderable.render(g));
387    }
388
389    /// Draw renderable
390    #[inline(always)]
391    pub fn draw<T>(&mut self, renderable: &dyn Renderable<T>) {
392        renderable.render(self);
393    }
394
395    /// Get the RGB values for a pixel
396    /// Alpha will always be 255
397    ///
398    /// If `use_translate` is true then the x,y will be updated with `self.translate`
399    ///
400    /// # Returns
401    ///
402    /// `Some(Color)` if x,y is within bounds
403    #[inline]
404    pub fn get_pixel(&self, x: isize, y: isize, use_translate: bool) -> Option<Color> {
405        let (x, y) = if use_translate {
406            (x + self.translate.x, y + self.translate.y)
407        } else {
408            (x, y)
409        };
410
411        let len = self.width * self.height;
412        if x >= 0 && y >= 0 && x < self.width as isize {
413            let idx = self.index(x as usize, y as usize);
414            if idx < len {
415                return Some(self.buffer.get_color(idx));
416            }
417        }
418
419        None
420    }
421
422    /// Set every pixel to `color`, this ignores translate and clip
423    #[inline(always)]
424    pub fn clear(&mut self, color: Color) {
425        (self.clear_method)(&mut self.buffer, color);
426    }
427
428    /// Set/blend every pixel with `color`, same as [clear] but this follows translate and clip
429    pub fn clear_aware(&mut self, color: Color) {
430        for y in 0..self.height {
431            for x in 0..self.width {
432                self.set_pixel(x as isize, y as isize, color);
433            }
434        }
435    }
436
437    /// Draw `chr` at `pos`
438    ///
439    /// # Usage
440    /// ```
441    ///# use buffer_graphics_lib::prelude::*;
442    ///# fn doc(graphics: &mut Graphics) {
443    ///     graphics.draw_letter((20,20), 'A', PixelFont::Limited3x5, BLUE);
444    ///# }
445    /// ```
446    #[inline(always)]
447    pub fn draw_letter(&mut self, pos: (isize, isize), chr: char, font: PixelFont, color: Color) {
448        self.draw_ascii_letter(pos, chr_to_code(chr), font, color);
449    }
450
451    /// Shouldn't be called in normal usage
452    pub fn draw_ascii_letter(
453        &mut self,
454        pos: (isize, isize),
455        code: u8,
456        font: PixelFont,
457        color: Color,
458    ) {
459        if code == 32 || code == 9 {
460            return;
461        }
462        let (width, height) = font.size();
463
464        let px: &[bool] = if let Some(custom) = self.custom_font.get(&code) {
465            match font {
466                PixelFont::Standard4x4 => &custom._4x4,
467                PixelFont::Script8x8 => &custom._8x8,
468                PixelFont::Outline7x9 => &custom._7x9,
469                PixelFont::Standard4x5 => &custom._4x5,
470                PixelFont::Standard6x7 => &custom._6x7,
471                PixelFont::Standard8x10 => &custom._8x10,
472                PixelFont::Limited3x5 => &custom._3x5,
473            }
474        } else {
475            font.pixels(code)
476        };
477
478        for x in 0..width {
479            for y in 0..height {
480                let i = x + y * width;
481                if px[i] {
482                    update_pixel(
483                        &mut self.buffer,
484                        &self.translate,
485                        &self.clip,
486                        (self.width, self.height),
487                        x as isize + pos.0,
488                        y as isize + pos.1,
489                        color,
490                    );
491                }
492            }
493        }
494    }
495
496    /// Should only be used by Text::render
497    /// `text` param must already be corrected wrapped
498    pub fn draw_ascii<P: Into<TextPos>, F: Into<TextFormat>>(
499        &mut self,
500        text: &[Vec<u8>],
501        pos: P,
502        format: F,
503    ) {
504        let format = format.into();
505        let font = format.font();
506        let color = format.color();
507        let per_x = format.char_width();
508        let per_y = format.line_height();
509
510        if per_y == 0 || per_x == 0 {
511            return;
512        }
513
514        let (start_x, start_y) = format.positioning().calc(
515            pos.into().to_coord(font),
516            text.iter().map(|list| list.len()).max().unwrap() * per_x.unsigned_abs(),
517            text.len() * per_y.unsigned_abs(),
518        );
519
520        for (y, line) in text.iter().enumerate() {
521            let y = y as isize * per_y;
522            for (x, char) in line.iter().enumerate() {
523                let x = x as isize * per_x;
524                self.draw_ascii_letter((start_x + x, start_y + y), *char, font, color);
525            }
526        }
527    }
528
529    /// Draw `text` on screen at `pos` using `format`
530    ///
531    /// # Params
532    /// * `format` See [TextFormat], can be
533    ///     * `TextFormat`
534    ///     * `Color`
535    ///     * `(Color, PixelFont)`
536    ///     * `(Color, PixelFont, Positioning)`
537    ///     * `(Color, PixelFont, WrappingStrategy, Positioning)`
538    ///     * `(Color, PixelFont, WrappingStrategy)`
539    ///     * `(Color, PixelFont, WrappingStrategy, f32)` (f32 = line height)
540    ///     * `(Color, PixelFont, WrappingStrategy, f32, f32)` (1st f32 = line height, 2nd f32 = char width)
541    ///     * `(Color, PixelFont, WrappingStrategy, f32, f32, Positioning)` (1st f32 = line height, 2nd f32 = char width)
542    ///
543    /// # Usage
544    ///
545    /// ```
546    ///# use buffer_graphics_lib::prelude::*;
547    ///# fn doc(graphics: &mut Graphics) {
548    ///  //simple example
549    ///  graphics.draw_text("Test", TextPos::ColRow(1,1), RED);
550    ///# }
551    ///
552    /// //full example
553    /// fn draw_message(graphics: &mut Graphics, msg: String) {
554    ///     let width_in_columns = PixelFont::Standard6x7.get_max_characters(graphics.width(), graphics.height()).0;
555    ///     graphics.draw_text(&msg, TextPos::px(coord!(8,8)), (BLACK, PixelFont::Standard6x7, WrappingStrategy::AtCol(width_in_columns - 1)));
556    /// }
557    /// ```
558    #[inline]
559    pub fn draw_text<P: Into<TextPos>, F: Into<TextFormat>>(
560        &mut self,
561        text: &str,
562        pos: P,
563        format: F,
564    ) {
565        let text = Text::new(text, pos.into(), format.into());
566        text.render(self);
567    }
568
569    #[inline]
570    pub fn draw_rect<R: Into<Rect>>(&mut self, rect: R, draw_type: DrawType) {
571        Drawable::from_obj(rect.into(), draw_type).render(self)
572    }
573
574    #[inline]
575    pub fn draw_circle<C: Into<Circle>>(&mut self, circle: C, draw_type: DrawType) {
576        Drawable::from_obj(circle.into(), draw_type).render(self)
577    }
578
579    #[inline]
580    pub fn draw_polygon<P: Into<Polygon>>(&mut self, polygon: P, draw_type: DrawType) {
581        Drawable::from_obj(polygon.into(), draw_type).render(self)
582    }
583
584    #[inline]
585    pub fn draw_triangle<T: Into<Triangle>>(&mut self, triangle: T, draw_type: DrawType) {
586        Drawable::from_obj(triangle.into(), draw_type).render(self)
587    }
588
589    #[inline]
590    pub fn draw_ellipse<E: Into<Ellipse>>(&mut self, ellipse: E, draw_type: DrawType) {
591        Drawable::from_obj(ellipse.into(), draw_type).render(self)
592    }
593
594    /// Update a pixel color, replacing or blending depending on whether `color`s alpha is 255 or not
595    ///
596    /// If the alpha is 0 the call does nothing
597    #[inline]
598    pub fn set_pixel(&mut self, x: isize, y: isize, color: Color) {
599        update_pixel(
600            &mut self.buffer,
601            &self.translate,
602            &self.clip,
603            (self.width, self.height),
604            x,
605            y,
606            color,
607        );
608    }
609}
610
611/// Update a pixel color, using [set_pixel] or [blend_pixel] depending on whether `color`s alpha is 255 or not
612///
613/// If the alpha is 0 the call is does nothing
614fn update_pixel(
615    buffer: &mut GraphicsBuffer,
616    translate: &Coord,
617    clip: &Clip,
618    (width, height): (usize, usize),
619    x: isize,
620    y: isize,
621    color: Color,
622) {
623    let x = x + translate.x;
624    let y = y + translate.y;
625    match buffer {
626        RgbaU8(buffer) => {
627            let idx = ((x + y * width as isize) * 4) as usize;
628            if x >= 0
629                && y >= 0
630                && x < width as isize
631                && y < height as isize
632                && clip.is_valid((x, y))
633            {
634                match color.a {
635                    255 => set_pixel_u8_rgba(buffer, idx, color),
636                    0 => {}
637                    _ => blend_pixel_u8_rgba(buffer, idx, color),
638                }
639            }
640        }
641        GraphicsBuffer::RgbaU32(buffer) => {
642            let idx = (x + y * width as isize) as usize;
643            if x >= 0
644                && y >= 0
645                && x < width as isize
646                && y < height as isize
647                && clip.is_valid((x, y))
648            {
649                match color.a {
650                    255 => set_pixel_32(buffer, idx, color, Color::to_rgba),
651                    0 => {}
652                    _ => blend_pixel_32(buffer, idx, color, Color::to_rgba),
653                }
654            }
655        }
656        GraphicsBuffer::ArgbU32(buffer) => {
657            let idx = (x + y * width as isize) as usize;
658            if x >= 0
659                && y >= 0
660                && x < width as isize
661                && y < height as isize
662                && clip.is_valid((x, y))
663            {
664                match color.a {
665                    255 => set_pixel_32(buffer, idx, color, Color::to_argb),
666                    0 => {}
667                    _ => blend_pixel_32(buffer, idx, color, Color::to_argb),
668                }
669            }
670        }
671    }
672}
673
674/// Set the RGB values for a pixel
675///
676/// Generally you should use [update_pixel] instead
677///
678/// This ignores alpha, so 255,0,0,0 will draw a red pixel
679fn set_pixel_32(buffer: &mut [u32], idx: usize, color: Color, conv: fn(Color) -> u32) {
680    if idx < buffer.len() {
681        buffer[idx] = conv(color);
682    }
683}
684
685/// Set the RGB values for a pixel by blending it with the provided color
686/// This method uses alpha blending
687/// Generally you should use [update_pixel] instead
688fn blend_pixel_32(buffer: &mut [u32], idx: usize, color: Color, conv: fn(Color) -> u32) {
689    let existing_color = Color::from_rgba(buffer[idx]);
690    let new_color = existing_color.blend(color);
691    buffer[idx] = conv(new_color);
692}
693
694/// Set the RGB values for a pixel
695///
696/// Generally you should use [update_pixel] instead
697///
698/// This ignores alpha, so 255,0,0,0 will draw a red pixel
699fn set_pixel_u8_rgba(buffer: &mut [u8], idx: usize, color: Color) {
700    if idx < buffer.len() {
701        buffer[idx] = color.r;
702        buffer[idx + 1] = color.g;
703        buffer[idx + 2] = color.b;
704        buffer[idx + 3] = color.a;
705    }
706}
707
708/// Set the RGB values for a pixel by blending it with the provided color
709/// This method uses alpha blending
710/// Generally you should use [update_pixel] instead
711fn blend_pixel_u8_rgba(buffer: &mut [u8], idx: usize, color: Color) {
712    let existing_color = Color {
713        r: buffer[idx],
714        g: buffer[idx + 1],
715        b: buffer[idx + 2],
716        a: buffer[idx + 3],
717    };
718    let new_color = existing_color.blend(color);
719    buffer[idx] = new_color.r;
720    buffer[idx + 1] = new_color.g;
721    buffer[idx + 2] = new_color.b;
722}
723
724#[cfg(test)]
725mod test {
726    use super::*;
727    use crate::prelude::*;
728    use crate::shapes::polyline::Segment::*;
729    use crate::text::pos::TextPos::Px;
730
731    #[test]
732    fn is_inside() {
733        let mut buf = [0; 400];
734        let mut graphics = Graphics::new_u8_rgba(&mut buf, 10, 10).unwrap();
735        assert!(graphics.is_on_screen(Coord { x: 1, y: 1 }));
736        assert!(graphics.is_on_screen(Coord { x: 9, y: 9 }));
737        assert!(graphics.is_on_screen(Coord { x: 0, y: 0 }));
738        assert!(!graphics.is_on_screen(Coord { x: 10, y: 10 }));
739        assert!(!graphics.is_on_screen(Coord { x: 4, y: -1 }));
740        assert!(!graphics.is_on_screen(Coord { x: -1, y: 4 }));
741
742        graphics.set_translate(Coord { x: 2, y: -1 });
743        assert!(graphics.is_on_screen(Coord { x: 4, y: 4 }));
744        assert!(graphics.is_on_screen(Coord { x: 4, y: 0 }));
745        assert!(!graphics.is_on_screen(Coord { x: 0, y: 0 }));
746        assert!(!graphics.is_on_screen(Coord { x: 4, y: 9 }));
747    }
748
749    #[test]
750    fn check_draw() {
751        let mut buf = [0; 400];
752        let mut graphics = Graphics::new_u8_rgba(&mut buf, 10, 10).unwrap();
753
754        let drawable = Drawable::from_obj(Line::new((10, 10), (20, 20)), stroke(RED));
755        let text = Text::new("", Px(1, 1), WHITE);
756        let polyline = Polyline::new(
757            vec![Start(Coord::new(0, 0)), LineTo(Coord::new(0, 0))],
758            WHITE,
759        );
760
761        graphics.draw(&drawable);
762        graphics.draw(&text);
763        graphics.draw(&polyline);
764    }
765}