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::{HashMap, HashSet};
20use std::mem::swap;
21
22pub 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)] 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 #[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 #[inline(always)]
94 pub fn get_translate(&self) -> Coord {
95 self.translate
96 }
97
98 #[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 #[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 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 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 color_index: HashMap<Color, u8> = colors
162 .iter()
163 .enumerate()
164 .map(|(i, c)| (*c, i as u8))
165 .collect();
166
167 let pixels = pixels
168 .iter()
169 .map(|c| {
170 *color_index.get(c).unwrap_or_else(|| {
171 panic!("color disappeared, please create GitHub issue for buffer-graphics-lib")
172 })
173 })
174 .collect();
175
176 IndexedImage::new(width, height, colors, pixels).map_err(GraphicsError::ImageError)
177 }
178
179 pub fn get_px_for_char(col: usize, row: usize, font: &PixelFont) -> (usize, usize) {
181 (col * font.char_width(), row * font.line_height())
182 }
183
184 pub fn draw_image<P: Into<Coord>>(&mut self, xy: P, image: &Image) {
187 let xy = xy.into();
188 let mut x = 0;
189 let mut y = 0;
190 for pixel in image.pixels() {
191 update_pixel(
192 &mut self.buffer,
193 &self.translate,
194 &self.clip,
195 (self.width, self.height),
196 xy.x + x as isize,
197 xy.y + y,
198 *pixel,
199 );
200 x += 1;
201 if x >= image.width() {
202 x = 0;
203 y += 1;
204 }
205 }
206 }
207
208 pub fn draw_indexed_image<P: Into<Coord>>(&mut self, xy: P, image: &IndexedImage) {
210 let xy = xy.into();
211 let palette = image.get_palette();
212 let (width, height) = image.size();
213 for x in 0..width {
214 for y in 0..height {
215 let i = image.get_pixel_index(x, y).unwrap();
216 let color_idx = image.get_pixel(i).unwrap() as usize;
217 let color = palette[color_idx];
218 update_pixel(
219 &mut self.buffer,
220 &self.translate,
221 &self.clip,
222 (self.width, self.height),
223 x as isize + xy.x,
224 y as isize + xy.y,
225 color,
226 );
227 }
228 }
229 }
230
231 pub fn draw_wrapped_image<P: Into<Coord>>(&mut self, xy: P, image: &IndexedWrapper) {
232 match image {
233 IndexedWrapper::Static(img) => self.draw_indexed_image(xy, img),
234 IndexedWrapper::Animated(img) => self.draw_animated_image(xy, img),
235 }
236 }
237
238 pub fn draw_animated_image<P: Into<Coord>>(&mut self, xy: P, image: &AnimatedIndexedImage) {
240 let xy = xy.into();
241 let palette = image.get_palette();
242 let (width, height) = image.size();
243 let current_frame = image.get_current_frame_pixels();
244 for x in 0..width {
245 for y in 0..height {
246 let i = image.get_pixel_index(x, y).unwrap();
247 let color_idx = current_frame[i] as usize;
248 let color = palette[color_idx];
249 update_pixel(
250 &mut self.buffer,
251 &self.translate,
252 &self.clip,
253 (self.width, self.height),
254 x as isize + xy.x,
255 y as isize + xy.y,
256 color,
257 );
258 }
259 }
260 }
261
262 pub fn draw_arc(
263 &mut self,
264 center: Coord,
265 angle_start: isize,
266 angle_end: isize,
267 radius: usize,
268 close: bool,
269 color: Color,
270 ) {
271 for r in angle_start..=angle_end {
272 let px = Coord::from_angle(center, radius, r);
273 update_pixel(
274 &mut self.buffer,
275 &self.translate,
276 &self.clip,
277 (self.width, self.height),
278 px.x,
279 px.y,
280 color,
281 );
282 }
283 if close {
284 self.draw_line(
285 center,
286 Coord::from_angle(center, radius, angle_start),
287 color,
288 );
289 self.draw_line(center, Coord::from_angle(center, radius, angle_end), color);
290 }
291 }
292
293 pub fn draw_line<P1: Into<Coord>, P2: Into<Coord>>(
294 &mut self,
295 start: P1,
296 end: P2,
297 color: Color,
298 ) {
299 let mut start = start.into();
300 let mut end = end.into();
301 if start.x > end.x || start.y > end.y {
302 swap(&mut start, &mut end);
303 }
304 if start.x == end.x {
305 for y in start.y..=end.y {
306 update_pixel(
307 &mut self.buffer,
308 &self.translate,
309 &self.clip,
310 (self.width, self.height),
311 start.x,
312 y,
313 color,
314 );
315 }
316 } else if start.y == end.y {
317 for x in start.x..=end.x {
318 update_pixel(
319 &mut self.buffer,
320 &self.translate,
321 &self.clip,
322 (self.width, self.height),
323 x,
324 start.y,
325 color,
326 );
327 }
328 } else {
329 let mut delta = 0;
330 let x1 = start.x;
331 let y1 = start.y;
332 let x2 = end.x;
333 let y2 = end.y;
334 let dx = isize::abs(x2 - x1);
335 let dy = isize::abs(y2 - y1);
336 let dx2 = dx * 2;
337 let dy2 = dy * 2;
338 let ix: isize = if x1 < x2 { 1 } else { -1 };
339 let iy: isize = if y1 < y2 { 1 } else { -1 };
340 let mut x = x1;
341 let mut y = y1;
342 if dx >= dy {
343 loop {
344 update_pixel(
345 &mut self.buffer,
346 &self.translate,
347 &self.clip,
348 (self.width, self.height),
349 x,
350 y,
351 color,
352 );
353 if x == x2 {
354 break;
355 }
356 x += ix;
357 delta += dy2;
358 if delta > dx {
359 y += iy;
360 delta -= dx2;
361 }
362 }
363 } else {
364 loop {
365 update_pixel(
366 &mut self.buffer,
367 &self.translate,
368 &self.clip,
369 (self.width, self.height),
370 x,
371 y,
372 color,
373 );
374 if y == y2 {
375 break;
376 }
377 y += iy;
378 delta += dx2;
379 if delta > dy {
380 x += ix;
381 delta -= dy2;
382 }
383 }
384 }
385 }
386 }
387
388 #[inline(always)]
390 pub fn draw_offset<T, P: Into<Coord>>(&mut self, xy: P, renderable: &dyn Renderable<T>) {
391 self.with_translate(xy.into(), |g| renderable.render(g));
392 }
393
394 #[inline(always)]
396 pub fn draw<T>(&mut self, renderable: &dyn Renderable<T>) {
397 renderable.render(self);
398 }
399
400 #[inline]
409 pub fn get_pixel(&self, x: isize, y: isize, use_translate: bool) -> Option<Color> {
410 let (x, y) = if use_translate {
411 (x + self.translate.x, y + self.translate.y)
412 } else {
413 (x, y)
414 };
415
416 if x >= 0 && y >= 0 && x < self.width as isize && y < self.height as isize {
417 let idx = self.index(x as usize, y as usize);
418 return Some(self.buffer.get_color(idx));
419 }
420
421 None
422 }
423
424 #[inline(always)]
426 pub fn clear(&mut self, color: Color) {
427 (self.clear_method)(&mut self.buffer, color);
428 }
429
430 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 #[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 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.font_4x4,
469 PixelFont::Script8x8 => &custom.font_8x8,
470 PixelFont::Outline7x9 => &custom.font_7x9,
471 PixelFont::Standard4x5 => &custom.font_4x5,
472 PixelFont::Standard6x7 => &custom.font_6x7,
473 PixelFont::Standard8x10 => &custom.font_8x10,
474 PixelFont::Limited3x5 => &custom.font_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 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 #[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 #[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
613fn 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::from_rgba, 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::from_argb, Color::to_argb),
670 }
671 }
672 }
673 }
674}
675
676fn 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
687fn blend_pixel_32(
691 buffer: &mut [u32],
692 idx: usize,
693 color: Color,
694 conv2: fn(u32) -> Color,
695 conv: fn(Color) -> u32,
696) {
697 let existing_color = conv2(buffer[idx]);
698 let new_color = existing_color.blend(color);
699 buffer[idx] = conv(new_color);
700}
701
702fn set_pixel_u8_rgba(buffer: &mut [u8], idx: usize, color: Color) {
708 if idx < buffer.len() {
709 buffer[idx] = color.r;
710 buffer[idx + 1] = color.g;
711 buffer[idx + 2] = color.b;
712 buffer[idx + 3] = color.a;
713 }
714}
715
716fn blend_pixel_u8_rgba(buffer: &mut [u8], idx: usize, color: Color) {
720 let existing_color = Color {
721 r: buffer[idx],
722 g: buffer[idx + 1],
723 b: buffer[idx + 2],
724 a: buffer[idx + 3],
725 };
726 let new_color = existing_color.blend(color);
727 buffer[idx] = new_color.r;
728 buffer[idx + 1] = new_color.g;
729 buffer[idx + 2] = new_color.b;
730 buffer[idx + 3] = new_color.a;
731}
732
733#[cfg(test)]
734mod test {
735 use super::*;
736 use crate::prelude::*;
737 use crate::shapes::polyline::Segment::*;
738 use crate::text::pos::TextPos::Px;
739
740 #[test]
741 fn is_inside() {
742 let mut buf = [0; 400];
743 let mut graphics = Graphics::new_u8_rgba(&mut buf, 10, 10).unwrap();
744 assert!(graphics.is_on_screen(Coord { x: 1, y: 1 }));
745 assert!(graphics.is_on_screen(Coord { x: 9, y: 9 }));
746 assert!(graphics.is_on_screen(Coord { x: 0, y: 0 }));
747 assert!(!graphics.is_on_screen(Coord { x: 10, y: 10 }));
748 assert!(!graphics.is_on_screen(Coord { x: 4, y: -1 }));
749 assert!(!graphics.is_on_screen(Coord { x: -1, y: 4 }));
750
751 graphics.set_translate(Coord { x: 2, y: -1 });
752 assert!(graphics.is_on_screen(Coord { x: 4, y: 4 }));
753 assert!(graphics.is_on_screen(Coord { x: 4, y: 0 }));
754 assert!(!graphics.is_on_screen(Coord { x: 0, y: 0 }));
755 assert!(!graphics.is_on_screen(Coord { x: 4, y: 9 }));
756 }
757
758 #[test]
759 fn check_draw() {
760 let mut buf = [0; 400];
761 let mut graphics = Graphics::new_u8_rgba(&mut buf, 10, 10).unwrap();
762
763 let drawable = Drawable::from_obj(Line::new((10, 10), (20, 20)), stroke(RED));
764 let text = Text::new("", Px(1, 1), WHITE);
765 let polyline = Polyline::new(
766 vec![Start(Coord::new(0, 0)), LineTo(Coord::new(0, 0))],
767 WHITE,
768 );
769
770 graphics.draw(&drawable);
771 graphics.draw(&text);
772 graphics.draw(&polyline);
773 }
774
775 #[test]
776 fn get_pixel_u8_all_rows() {
777 let mut buf = Graphics::create_buffer_u8(10, 10);
780 let mut graphics = Graphics::new_u8_rgba(&mut buf, 10, 10).unwrap();
781 graphics.set_pixel(0, 0, RED);
782 graphics.set_pixel(0, 3, GREEN); graphics.set_pixel(9, 9, BLUE);
784
785 assert_eq!(graphics.get_pixel(0, 0, false), Some(RED));
786 assert_eq!(graphics.get_pixel(0, 3, false), Some(GREEN));
787 assert_eq!(graphics.get_pixel(9, 9, false), Some(BLUE));
788 assert_eq!(graphics.get_pixel(10, 0, false), None);
790 assert_eq!(graphics.get_pixel(0, 10, false), None);
791 }
792}