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
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 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 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 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 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 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 #[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 #[inline(always)]
393 pub fn draw<T>(&mut self, renderable: &dyn Renderable<T>) {
394 renderable.render(self);
395 }
396
397 #[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 #[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._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 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::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
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(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
696fn 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
710fn 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}