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) {
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 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 #[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 #[inline(always)]
391 pub fn draw<T>(&mut self, renderable: &dyn Renderable<T>) {
392 renderable.render(self);
393 }
394
395 #[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 #[inline(always)]
424 pub fn clear(&mut self, color: Color) {
425 (self.clear_method)(&mut self.buffer, color);
426 }
427
428 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 #[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 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 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 #[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 #[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
611fn 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
674fn 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
685fn 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
694fn 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
708fn 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}