pyxel/
image.rs

1use std::array;
2use std::collections::HashMap;
3use std::path::Path;
4
5use image::imageops;
6
7use crate::canvas::{Canvas, CopyArea, ToIndex};
8use crate::font::SharedFont;
9use crate::pyxel::{COLORS, FONT_IMAGE, IMAGES};
10use crate::rect_area::RectArea;
11use crate::settings::{
12    FONT_HEIGHT, FONT_WIDTH, MAX_COLORS, MAX_FONT_CODE, MIN_FONT_CODE, NUM_FONT_ROWS, TILE_SIZE,
13};
14use crate::tilemap::{ImageSource, SharedTilemap};
15use crate::utils;
16
17pub type Rgb24 = u32;
18pub type Color = u8;
19
20impl ToIndex for Color {
21    fn to_index(&self) -> usize {
22        *self as usize
23    }
24}
25
26pub struct Image {
27    pub(crate) canvas: Canvas<Color>,
28    pub(crate) palette: [Color; MAX_COLORS as usize],
29}
30
31pub type SharedImage = shared_type!(Image);
32
33impl Image {
34    pub fn new(width: u32, height: u32) -> SharedImage {
35        new_shared_type!(Self {
36            canvas: Canvas::new(width, height),
37            palette: array::from_fn(|i| i as Color),
38        })
39    }
40
41    pub fn from_image(filename: &str, include_colors: Option<bool>) -> SharedImage {
42        let include_colors = include_colors.unwrap_or(false);
43        let mut colors = COLORS.lock();
44        if include_colors {
45            colors.clear();
46        }
47
48        let file = image::open(Path::new(&filename));
49        if file.is_err() {
50            println!("Failed to open file '{filename}'");
51            return Self::new(1, 1);
52        }
53
54        let file_image = file.unwrap().to_rgb8();
55        let (width, height) = file_image.dimensions();
56        let image = Self::new(width, height);
57
58        {
59            let mut image = image.lock();
60            let mut color_table = HashMap::<(u8, u8, u8), Color>::new();
61
62            for y in 0..height {
63                for x in 0..width {
64                    let p = file_image.get_pixel(x, y);
65                    let src_rgb = (p[0], p[1], p[2]);
66
67                    if let Some(color) = color_table.get(&src_rgb) {
68                        image.canvas.write_data(x as usize, y as usize, *color);
69                    } else {
70                        let mut closest_color: Color = 0;
71
72                        if include_colors {
73                            colors.push(
74                                ((src_rgb.0 as u32) << 16)
75                                    | ((src_rgb.1 as u32) << 8)
76                                    | src_rgb.2 as u32,
77                            );
78                            closest_color = colors.len() as Color - 1;
79                        } else {
80                            let mut closest_dist: f64 = f64::MAX;
81                            for (i, pal_color) in colors.iter().enumerate() {
82                                let pal_rgb = (
83                                    (pal_color >> 16) as u8,
84                                    (pal_color >> 8) as u8,
85                                    *pal_color as u8,
86                                );
87                                let dist = Self::color_dist(src_rgb, pal_rgb);
88                                if dist < closest_dist {
89                                    closest_color = i as Color;
90                                    closest_dist = dist;
91                                }
92                            }
93                        }
94
95                        color_table.insert(src_rgb, closest_color);
96                        image
97                            .canvas
98                            .write_data(x as usize, y as usize, closest_color);
99                    }
100                }
101            }
102        }
103
104        image
105    }
106
107    pub const fn width(&self) -> u32 {
108        self.canvas.width()
109    }
110
111    pub const fn height(&self) -> u32 {
112        self.canvas.height()
113    }
114
115    pub fn data_ptr(&mut self) -> *mut Color {
116        self.canvas.data_ptr()
117    }
118
119    pub fn set(&mut self, x: i32, y: i32, data_str: &[&str]) {
120        let width = utils::simplify_string(data_str[0]).len() as u32;
121        let height = data_str.len() as u32;
122        let image = Self::new(width, height);
123
124        {
125            let mut image = image.lock();
126            for y in 0..height {
127                let src_data = utils::simplify_string(data_str[y as usize]);
128                for x in 0..width {
129                    let color =
130                        utils::parse_hex_string(&src_data[x as usize..=x as usize]).unwrap();
131                    image
132                        .canvas
133                        .write_data(x as usize, y as usize, color as Color);
134                }
135            }
136        }
137
138        self.blt(
139            x as f64,
140            y as f64,
141            image,
142            0.0,
143            0.0,
144            width as f64,
145            height as f64,
146            None,
147            None,
148            None,
149        );
150    }
151
152    pub fn load(&mut self, x: i32, y: i32, filename: &str, include_colors: Option<bool>) {
153        let image = Self::from_image(filename, include_colors);
154        let width = image.lock().width();
155        let height = image.lock().height();
156
157        self.blt(
158            x as f64,
159            y as f64,
160            image,
161            0.0,
162            0.0,
163            width as f64,
164            height as f64,
165            None,
166            None,
167            None,
168        );
169    }
170
171    pub fn save(&self, filename: &str, scale: u32) {
172        let colors = COLORS.lock();
173        let width = self.width();
174        let height = self.height();
175        let mut image = image::RgbImage::new(width, height);
176
177        for y in 0..height {
178            for x in 0..width {
179                let rgb = colors[self.canvas.read_data(x as usize, y as usize) as usize];
180                let r = (rgb >> 16) as u8;
181                let g = (rgb >> 8) as u8;
182                let b = rgb as u8;
183                image.put_pixel(x, y, image::Rgb([r, g, b]));
184            }
185        }
186
187        let image = imageops::resize(
188            &image,
189            width * scale,
190            height * scale,
191            imageops::FilterType::Nearest,
192        );
193        let filename = utils::add_file_extension(filename, ".png");
194        image
195            .save(&filename)
196            .unwrap_or_else(|_| panic!("Failed to open file '{filename}'"));
197    }
198
199    pub fn clip(&mut self, x: f64, y: f64, width: f64, height: f64) {
200        self.canvas.clip(x, y, width, height);
201    }
202
203    pub fn clip0(&mut self) {
204        self.canvas.clip0();
205    }
206
207    pub fn camera(&mut self, x: f64, y: f64) {
208        self.canvas.camera(x, y);
209    }
210
211    pub fn camera0(&mut self) {
212        self.canvas.camera0();
213    }
214
215    pub fn pal(&mut self, src_color: Color, dst_color: Color) {
216        self.palette[src_color as usize] = dst_color;
217    }
218
219    pub fn pal0(&mut self) {
220        for i in 0..self.palette.len() {
221            self.palette[i] = i as Color;
222        }
223    }
224
225    pub fn dither(&mut self, alpha: f32) {
226        self.canvas.dither(alpha);
227    }
228
229    pub fn cls(&mut self, color: Color) {
230        self.canvas.cls(self.palette[color as usize]);
231    }
232
233    pub fn pget(&mut self, x: f64, y: f64) -> Color {
234        self.canvas.pget(x, y)
235    }
236
237    pub fn pset(&mut self, x: f64, y: f64, color: Color) {
238        self.canvas.pset(x, y, self.palette[color as usize]);
239    }
240
241    pub fn line(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, color: Color) {
242        self.canvas
243            .line(x1, y1, x2, y2, self.palette[color as usize]);
244    }
245
246    pub fn rect(&mut self, x: f64, y: f64, width: f64, height: f64, color: Color) {
247        self.canvas
248            .rect(x, y, width, height, self.palette[color as usize]);
249    }
250
251    pub fn rectb(&mut self, x: f64, y: f64, width: f64, height: f64, color: Color) {
252        self.canvas
253            .rectb(x, y, width, height, self.palette[color as usize]);
254    }
255
256    pub fn circ(&mut self, x: f64, y: f64, radius: f64, color: Color) {
257        self.canvas.circ(x, y, radius, self.palette[color as usize]);
258    }
259
260    pub fn circb(&mut self, x: f64, y: f64, radius: f64, color: Color) {
261        self.canvas
262            .circb(x, y, radius, self.palette[color as usize]);
263    }
264
265    pub fn elli(&mut self, x: f64, y: f64, width: f64, height: f64, color: Color) {
266        self.canvas
267            .elli(x, y, width, height, self.palette[color as usize]);
268    }
269
270    pub fn ellib(&mut self, x: f64, y: f64, width: f64, height: f64, color: Color) {
271        self.canvas
272            .ellib(x, y, width, height, self.palette[color as usize]);
273    }
274
275    pub fn tri(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, color: Color) {
276        self.canvas
277            .tri(x1, y1, x2, y2, x3, y3, self.palette[color as usize]);
278    }
279
280    pub fn trib(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, color: Color) {
281        self.canvas
282            .trib(x1, y1, x2, y2, x3, y3, self.palette[color as usize]);
283    }
284
285    pub fn fill(&mut self, x: f64, y: f64, color: Color) {
286        self.canvas.fill(x, y, self.palette[color as usize]);
287    }
288
289    pub fn blt(
290        &mut self,
291        x: f64,
292        y: f64,
293        image: SharedImage,
294        image_x: f64,
295        image_y: f64,
296        width: f64,
297        height: f64,
298        transparent: Option<Color>,
299        rotate: Option<f64>,
300        scale: Option<f64>,
301    ) {
302        let rotate = rotate.unwrap_or(0.0);
303        let scale = scale.unwrap_or(1.0);
304        if rotate != 0.0 || scale != 1.0 {
305            self.blt_transform(
306                x,
307                y,
308                image,
309                image_x,
310                image_y,
311                width,
312                height,
313                transparent,
314                rotate,
315                scale,
316            );
317            return;
318        }
319
320        if let Some(image) = image.try_lock() {
321            self.canvas.blt(
322                x,
323                y,
324                &image.canvas,
325                image_x,
326                image_y,
327                width,
328                height,
329                transparent,
330                Some(&self.palette),
331            );
332        } else {
333            let copy_width = utils::f64_to_u32(width.abs());
334            let copy_height = utils::f64_to_u32(height.abs());
335            let mut canvas = Canvas::new(copy_width, copy_height);
336
337            canvas.blt(
338                0.0,
339                0.0,
340                &self.canvas,
341                image_x,
342                image_y,
343                copy_width as f64,
344                copy_height as f64,
345                None,
346                None,
347            );
348
349            self.canvas.blt(
350                x,
351                y,
352                &canvas,
353                0.0,
354                0.0,
355                width,
356                height,
357                transparent,
358                Some(&self.palette),
359            );
360        }
361    }
362
363    fn blt_transform(
364        &mut self,
365        x: f64,
366        y: f64,
367        image: SharedImage,
368        image_x: f64,
369        image_y: f64,
370        width: f64,
371        height: f64,
372        transparent: Option<Color>,
373        rotate: f64,
374        scale: f64,
375    ) {
376        if let Some(image) = image.try_lock() {
377            self.canvas.blt_transform(
378                x,
379                y,
380                &image.canvas,
381                image_x,
382                image_y,
383                width,
384                height,
385                transparent,
386                Some(&self.palette),
387                rotate,
388                scale,
389                false,
390            );
391        } else {
392            let copy_width = utils::f64_to_u32(width.abs());
393            let copy_height = utils::f64_to_u32(height.abs());
394            let mut canvas = Canvas::new(copy_width, copy_height);
395
396            canvas.blt(
397                0.0,
398                0.0,
399                &self.canvas,
400                image_x,
401                image_y,
402                copy_width as f64,
403                copy_height as f64,
404                None,
405                None,
406            );
407
408            self.canvas.blt_transform(
409                x,
410                y,
411                &canvas,
412                0.0,
413                0.0,
414                width,
415                height,
416                transparent,
417                Some(&self.palette),
418                rotate,
419                scale,
420                false,
421            );
422        }
423    }
424
425    pub fn bltm(
426        &mut self,
427        x: f64,
428        y: f64,
429        tilemap: SharedTilemap,
430        tilemap_x: f64,
431        tilemap_y: f64,
432        width: f64,
433        height: f64,
434        transparent: Option<Color>,
435        rotate: Option<f64>,
436        scale: Option<f64>,
437    ) {
438        let rotate = rotate.unwrap_or(0.0);
439        let scale = scale.unwrap_or(1.0);
440        if rotate != 0.0 || scale != 1.0 {
441            self.bltm_transform(
442                x,
443                y,
444                tilemap,
445                tilemap_x,
446                tilemap_y,
447                width,
448                height,
449                transparent,
450                rotate,
451                scale,
452            );
453            return;
454        }
455
456        let x = utils::f64_to_i32(x) - self.canvas.camera_x;
457        let y = utils::f64_to_i32(y) - self.canvas.camera_y;
458        let tilemap_x = utils::f64_to_i32(tilemap_x);
459        let tilemap_y = utils::f64_to_i32(tilemap_y);
460        let width = utils::f64_to_i32(width);
461        let height = utils::f64_to_i32(height);
462
463        let tilemap = tilemap.lock();
464        let tilemap_rect = RectArea::new(
465            tilemap.canvas.self_rect.left() * TILE_SIZE as i32,
466            tilemap.canvas.self_rect.top() * TILE_SIZE as i32,
467            tilemap.canvas.self_rect.width() * TILE_SIZE,
468            tilemap.canvas.self_rect.height() * TILE_SIZE,
469        );
470
471        let CopyArea {
472            dst_x,
473            dst_y,
474            src_x,
475            src_y,
476            sign_x,
477            sign_y,
478            offset_x,
479            offset_y,
480            width,
481            height,
482        } = CopyArea::new(
483            x,
484            y,
485            self.canvas.clip_rect,
486            tilemap_x,
487            tilemap_y,
488            tilemap_rect,
489            width,
490            height,
491        );
492        if width == 0 || height == 0 {
493            return;
494        }
495
496        let images = IMAGES.lock();
497        let image = match &tilemap.imgsrc {
498            ImageSource::Index(index) => images[*index as usize].lock(),
499            ImageSource::Image(image) => image.lock(),
500        };
501
502        for yi in 0..height {
503            for xi in 0..width {
504                let tilemap_x = src_x + sign_x * xi + offset_x;
505                let tilemap_y = src_y + sign_y * yi + offset_y;
506
507                let tile_x = tilemap_x / TILE_SIZE as i32;
508                let tile_y = tilemap_y / TILE_SIZE as i32;
509                let tile = tilemap.canvas.read_data(tile_x as usize, tile_y as usize);
510
511                let value_x = tile.0 as i32 * TILE_SIZE as i32 + tilemap_x % TILE_SIZE as i32;
512                if value_x < 0 || value_x >= image.width() as i32 {
513                    continue;
514                }
515                let value_y = tile.1 as i32 * TILE_SIZE as i32 + tilemap_y % TILE_SIZE as i32;
516                if value_y < 0 || value_y >= image.height() as i32 {
517                    continue;
518                }
519                let value = image.canvas.read_data(value_x as usize, value_y as usize);
520
521                if let Some(transparent) = transparent {
522                    if value == transparent {
523                        continue;
524                    }
525                }
526                let value = self.palette[value.to_index()];
527                self.canvas
528                    .write_data((dst_x + xi) as usize, (dst_y + yi) as usize, value);
529            }
530        }
531    }
532
533    fn bltm_transform(
534        &mut self,
535        x: f64,
536        y: f64,
537        tilemap: SharedTilemap,
538        tilemap_x: f64,
539        tilemap_y: f64,
540        width: f64,
541        height: f64,
542        transparent: Option<Color>,
543        rotate: f64,
544        scale: f64,
545    ) {
546        let copy_width = utils::f64_to_u32(width.abs());
547        let copy_height = utils::f64_to_u32(height.abs());
548        let tilemap_width = tilemap.lock().width() as f64;
549        let tilemap_height = tilemap.lock().height() as f64;
550        let image = Self::new(copy_width, copy_height);
551
552        {
553            let mut image = image.lock();
554            image.bltm(
555                0.0,
556                0.0,
557                tilemap,
558                tilemap_x,
559                tilemap_y,
560                width.abs(),
561                height.abs(),
562                None,
563                None,
564                None,
565            );
566            image.clip(
567                -tilemap_x,
568                -tilemap_y,
569                tilemap_width * TILE_SIZE as f64,
570                tilemap_height * TILE_SIZE as f64,
571            );
572            self.canvas.blt_transform(
573                x,
574                y,
575                &image.canvas,
576                0.0,
577                0.0,
578                width,
579                height,
580                transparent,
581                Some(&self.palette),
582                rotate,
583                scale,
584                true,
585            );
586        }
587    }
588
589    pub fn text(&mut self, x: f64, y: f64, string: &str, color: Color, font: Option<SharedFont>) {
590        if let Some(font) = font {
591            let x = utils::f64_to_i32(x) - self.canvas.camera_x;
592            let y = utils::f64_to_i32(y) - self.canvas.camera_y;
593            let color = self.palette[color as usize];
594            font.lock().draw(&mut self.canvas, x, y, string, color);
595            return;
596        }
597
598        let mut x = utils::f64_to_i32(x); // No need to reflect camera_x
599        let mut y = utils::f64_to_i32(y); // No need to reflect camera_y
600        let color = self.palette[color as usize];
601        let palette1 = self.palette[1];
602        self.pal(1, color);
603
604        let start_x = x;
605        for c in string.chars() {
606            if c == '\n' {
607                x = start_x;
608                y += FONT_HEIGHT as i32;
609                continue;
610            }
611            if c < MIN_FONT_CODE || c > MAX_FONT_CODE {
612                continue;
613            }
614
615            let code = c as i32 - MIN_FONT_CODE as i32;
616            let src_x = (code % NUM_FONT_ROWS as i32) * FONT_WIDTH as i32;
617            let src_y = (code / NUM_FONT_ROWS as i32) * FONT_HEIGHT as i32;
618
619            self.blt(
620                x as f64,
621                y as f64,
622                FONT_IMAGE.clone(),
623                src_x as f64,
624                src_y as f64,
625                FONT_WIDTH as f64,
626                FONT_HEIGHT as f64,
627                Some(0),
628                Some(0.0),
629                Some(1.0),
630            );
631            x += FONT_WIDTH as i32;
632        }
633        self.pal(1, palette1);
634    }
635
636    fn color_dist(rgb1: (u8, u8, u8), rgb2: (u8, u8, u8)) -> f64 {
637        let (r1, g1, b1) = rgb1;
638        let (r2, g2, b2) = rgb2;
639        let dx = (r1 as f64 - r2 as f64) * 0.30;
640        let dy = (g1 as f64 - g2 as f64) * 0.59;
641        let dz = (b1 as f64 - b2 as f64) * 0.11;
642
643        dx * dx + dy * dy + dz * dz
644    }
645}