1#![allow(clippy::needless_range_loop)]
2
3use std::collections::HashSet;
4
5use anyhow::Context;
6use cpclib_common::camino::Utf8Path;
7use cpclib_common::itertools::Itertools;
8#[cfg(all(not(target_arch = "wasm32"), feature = "rayon"))]
9use cpclib_common::rayon::iter::{IntoParallelRefIterator, ParallelIterator};
10use {anyhow, image as im};
11
12use crate::ga::*;
13use crate::pixels;
14use crate::pixels::bytes_to_pens;
15
16#[derive(Clone, Copy, PartialEq, Debug)]
18pub enum Mode {
19 Zero,
21 One,
23 Two,
25 Three
27}
28
29impl From<u8> for Mode {
30 fn from(val: u8) -> Self {
31 match val {
32 0 => Mode::Zero,
33 1 => Mode::One,
34 2 => Mode::Two,
35 3 => Mode::Three,
36 _ => panic!("{val} is not a valid mode.")
37 }
38 }
39}
40
41#[allow(missing_docs)]
42impl Mode {
43 pub fn max_colors(&self) -> usize {
45 match self {
46 Mode::Zero => 16,
47 Mode::One | Mode::Three => 4,
48 Mode::Two => 2
49 }
50 }
51
52 pub fn nb_pixels_per_byte(&self) -> usize {
54 match self {
55 Mode::Zero | Mode::Three => 2,
56 Mode::One => 4,
57 Mode::Two => 8
58 }
59 }
60
61 pub fn nb_pixels_for_bytes_width(&self, width: usize) -> usize {
62 width * self.nb_pixels_per_byte()
63 }
64
65 pub fn nb_bytes_for_pixels_width(self, width: usize) -> usize {
66 let extra = if !width.is_multiple_of(self.nb_pixels_per_byte()) {
67 1
68 }
69 else {
70 0
71 };
72 width / self.nb_pixels_per_byte() + extra
73 }
74}
75
76#[derive(Copy, Clone, Debug)]
78pub enum ConversionRule {
79 AnyModeUseAllPixels,
81 ZeroSkipOddPixels
83}
84
85#[allow(unused)]
87fn get_unique_colors(img: &im::ImageBuffer<im::Rgb<u8>, Vec<u8>>) -> HashSet<im::Rgb<u8>> {
88 let mut set = HashSet::new();
89 for pixel in img.pixels() {
90 set.insert(*pixel);
91 }
92 set
93}
94
95#[allow(unused)]
97fn extract_palette(img: &im::ImageBuffer<im::Rgb<u8>, Vec<u8>>) -> Palette {
98 let colors = get_unique_colors(img);
99 let mut p = Palette::empty();
100
101 assert!(colors.len() <= 16);
102
103 for (idx, color) in colors.iter().enumerate() {
104 let color = *color;
105 p.set(Pen::from(idx as u8), Ink::from(color))
106 }
107
108 p
109}
110
111fn encode(pens: &[Vec<Pen>], mode: Mode, missing_pen: Option<Pen>) -> Vec<Vec<u8>> {
113 let mut rows = Vec::new();
114 for input_row in pens.iter() {
115 let row = {
116 if let Some(replacement) = missing_pen {
117 match mode {
118 Mode::Zero => {
119 pixels::mode0::pens_to_vec_with_replacement(input_row, replacement)
120 },
121 Mode::One => {
122 pixels::mode1::pens_to_vec_with_replacement(input_row, replacement)
123 },
124 Mode::Two => {
125 pixels::mode2::pens_to_vec_with_replacement(input_row, replacement)
126 },
127 _ => panic!("Unimplemented yet ...")
128 }
129 }
130 else {
131 match mode {
132 Mode::Zero => pixels::mode0::pens_to_vec_with_crop(input_row),
133 Mode::One => pixels::mode1::pens_to_vec_with_crop(input_row),
134 Mode::Two => pixels::mode2::pens_to_vec_with_crop(input_row),
135 _ => panic!("Unimplemented yet ...")
136 }
137 }
138 };
139 rows.push(row);
140 }
141
142 rows
143}
144
145fn merge_mode0_mode3(line1: &[u8], line2: &[u8]) -> Vec<u8> {
147 assert_eq!(line1.len(), line2.len());
148
149 line1
150 .iter()
151 .zip(line2.iter())
152 .map(|(&u1, &u2)| {
153 let [p10, p11] = pixels::mode0::byte_to_pens(u1);
154 let [p20, p21] = pixels::mode0::byte_to_pens(u2);
155
156 let p0 = pixels::mode0::mix_mode0_mode3(p10, p20);
157 let p1 = pixels::mode0::mix_mode0_mode3(p11, p21);
158
159 eprintln!("{}/{} {:?} + {:?} = {:?}", u1, u2, &p10, &p20, &p0);
160 eprintln!("{}/{} {:?} + {:?} = {:?}", u1, u2, &p11, &p21, &p1);
161
162 pixels::mode0::pens_to_byte(p0, p1)
163 })
164 .collect::<Vec<u8>>()
165}
166
167fn inks_to_pens(inks: &[Vec<Ink>], p: &Palette) -> Vec<Vec<Pen>> {
169 #[cfg(all(not(target_arch = "wasm32"), feature = "rayon"))]
170 let iter = inks.par_iter();
171 #[cfg(any(target_arch = "wasm32", not(feature = "rayon")))]
172 let iter = inks.iter();
173
174 iter.map(|line| {
175 line.iter()
176 .map(|ink| {
177 p.get_pen_for_ink(*ink).unwrap_or_else(|| {
178 panic!("Unable to find a correspondance for ink {ink:?} in given palette {p:?}")
179 })
180 })
181 .collect::<Vec<Pen>>()
182 })
183 .collect::<Vec<_>>()
184}
185
186#[derive(Clone, Debug, PartialEq, Eq, Hash)]
190pub struct ColorMatrix {
191 data: Vec<Vec<Ink>>
193}
194
195impl From<Vec<Vec<Ink>>> for ColorMatrix {
196 fn from(data: Vec<Vec<Ink>>) -> Self {
197 ColorMatrix { data }
198 }
199}
200
201#[derive(Debug, Copy, Clone)]
204pub enum ColorConversionStrategy {
205 ReplaceWrongColorByFirstColor,
207 ReplaceWrongColorByClosestInk,
209 Fail
211}
212
213impl ColorMatrix {
214 pub const INK_MASK_BACKGROUND: Ink = Ink::BRIGHTWHITE;
215 pub const INK_MASK_FOREGROUND: Ink = Ink::BLACK;
216 pub const INK_NOT_USED_IN_MASK: Ink = Ink::RED;
217
218 pub fn from_screen(data: &[u8], bytes_width: usize, mode: Mode, palette: &Palette) -> Self {
219 let pixel_height = {
220 let mut height = 0x4000 / bytes_width;
221 while !height.is_multiple_of(8) {
222 height -= 1;
223 }
224 height
225 };
226
227 let _pixel_width = mode.nb_pixels_for_bytes_width(bytes_width);
228
229 (0..pixel_height)
230 .map(|line| {
231 let screen_address = 0xC000 + ((line / 8) * bytes_width) + ((line % 8) * 0x800);
232 let data_address = screen_address - 0xC000;
233 let line_bytes = &data[data_address..(data_address + bytes_width)];
234 line_bytes
235 .iter()
236 .flat_map(|b| pixels::byte_to_pens(*b, mode))
237 .collect_vec()
238 })
239 .map(move |pens| {
240 pens.iter()
242 .map(|pen| palette.get(pen))
243 .cloned()
244 .collect_vec()
245 })
246 .collect_vec()
247 .into()
248 }
249
250 pub fn from_sprite(data: &[u8], pixels_width: u16, mode: Mode, palette: &Palette) -> Self {
251 let width = mode.nb_bytes_for_pixels_width(pixels_width as _);
252
253 data.chunks_exact(width)
255 .map(|line| {
256 let line = line.iter();
258 line.flat_map(|b| pixels::byte_to_pens(*b, mode))
259 .collect_vec()
260 })
261 .map(move |pens| {
262 pens.iter()
264 .map(|pen| palette.get(pen))
265 .cloned()
266 .collect_vec()
267 })
268 .collect_vec()
269 .into()
270 }
271}
272
273#[allow(missing_docs)]
274impl ColorMatrix {
275 pub fn new(width: usize, height: usize) -> Self {
277 Self {
278 data: vec![vec![Ink::from(0); width]; height]
279 }
280 }
281
282 pub fn extract_mask_and_sprite(
287 &self,
288 mask_ink: impl Into<Ink>,
289 replacement_ink: impl Into<Ink>
290 ) -> (Self, Self) {
291 let mask_ink = mask_ink.into();
292 let replacement_ink = replacement_ink.into();
293
294 let mut mask_data = self.clone();
295 mask_data.convert_to_mask(mask_ink);
296
297 let mut sprite_data = self.clone();
298 sprite_data.replace_ink(mask_ink, replacement_ink);
299
300 (mask_data, sprite_data)
301 }
302
303 pub fn convert_to_mask(&mut self, mask: Ink) -> &mut Self {
305 self.data.iter_mut().for_each(|row| {
306 row.iter_mut().for_each(|ink| {
307 *ink = if *ink == mask {
308 Self::INK_MASK_BACKGROUND
309 }
310 else {
311 Self::INK_MASK_FOREGROUND
312 }
313 })
314 });
315 self
316 }
317
318 pub fn replace_ink(&mut self, from: Ink, to: Ink) -> &mut Self {
320 self.data.iter_mut().for_each(|row| {
321 row.iter_mut().for_each(|ink| {
322 if *ink == from {
323 *ink = to;
324 }
325 })
326 });
327 self
328 }
329
330 pub fn empty() -> Self {
331 Self { data: Vec::new() }
332 }
333
334 pub fn empty_like(&self) -> Self {
336 Self {
337 data: vec![vec![Ink::from(0); self.width() as usize]; self.height() as usize]
338 }
339 }
340
341 #[allow(clippy::needless_range_loop, clippy::identity_op)]
343 pub fn double_horizontally(&mut self) {
344 let mut new_data =
346 vec![vec![Ink::from(0); (2 * self.width()) as usize]; self.height() as usize];
347 for x in 0..(self.width() as usize) {
348 for y in 0..(self.height() as usize) {
349 let color = self.get_ink(x, y);
350 new_data[y][x * 2 + 0] = *color;
351 new_data[y][x * 2 + 1] = *color;
352 }
353 }
354
355 std::mem::swap(&mut self.data, &mut new_data)
357 }
358
359 pub fn remove_odd_columns(&mut self) {
360 let mut new_data =
362 vec![vec![Ink::from(0); (self.width() / 2) as usize]; self.height() as usize];
363 for x in 0..((self.width() / 2) as usize) {
364 for y in 0..(self.height() as usize) {
365 let color = self.get_ink(x * 2, y);
366 new_data[y][x] = *color;
367 }
368 }
369
370 std::mem::swap(&mut self.data, &mut new_data)
372 }
373
374 pub fn height(&self) -> u32 {
377 self.data.len() as u32
378 }
379
380 pub fn get_ink(&self, x: usize, y: usize) -> &Ink {
382 &self.data[y][x]
383 }
384
385 pub fn set_ink(&mut self, x: usize, y: usize, ink: Ink) {
387 self.data[y][x] = ink;
388 }
389
390 pub fn add_line(&mut self, position: usize, line: &[Ink]) {
393 assert_eq!(line.len(), self.width() as usize);
394 self.data.insert(position, line.to_vec());
395 }
396
397 pub fn get_line(&self, y: usize) -> &[Ink] {
399 &self.data[y]
400 }
401
402 fn get_line_mut(&mut self, y: usize) -> &mut Vec<Ink> {
404 &mut self.data[y]
405 }
406
407 pub fn add_column(&mut self, position: usize, column: &[Ink]) {
410 assert_eq!(column.len(), self.height() as usize);
411 for (row, ink) in column.iter().enumerate() {
412 self.get_line_mut(row).insert(position, *ink);
413 }
414 }
415
416 pub fn get_column(&self, x: usize) -> Vec<Ink> {
418 self.data.iter().map(|line| line[x]).collect::<Vec<Ink>>()
419 }
420
421 pub fn window(&self, start_x: usize, start_y: usize, width: usize, height: usize) -> Self {
423 let selected_lines = &self.data[start_y..start_y + height];
424 let window = selected_lines
425 .iter()
426 .map(|line| &line[start_x..start_x + width])
427 .map(|line| {
428 let mut new_line = Vec::with_capacity(line.len());
429 new_line.extend_from_slice(line);
430 new_line
431 })
432 .collect();
433 Self { data: window }
434 }
435
436 pub fn nb_inks(&self) -> usize {
438 self.data.iter().flatten().unique().count()
439 }
440
441 pub fn extract_palette(&self, mode: Mode) -> Palette {
443 let mut p = Palette::empty();
444 for (idx, color) in self.data.iter().flatten().unique().sorted().enumerate() {
445 if idx >= mode.max_colors() {
446 panic!(
448 "[ERROR] your picture uses more than 16 different colors. Palette: {p:?}. Wrong ink: {color:?}"
449 );
450 }
451 p.set(Pen::from(idx as u8), *color);
452 }
453 p
454 }
455
456 pub fn reduce_colors_for_mode(
458 &mut self,
459 mode: Mode,
460 strategy: ColorConversionStrategy
461 ) -> Result<(), anyhow::Error> {
462 let inks = self
464 .data
465 .iter()
466 .flatten()
467 .unique()
468 .copied()
469 .collect::<Vec<Ink>>();
470 let max_count = mode.max_colors().min(inks.len());
471 let inks = &inks[..max_count];
472
473 self.reduce_colors_with(inks, strategy)
474 }
475
476 pub fn reduce_colors_with(
478 &mut self,
479 inks: &[Ink],
480 strategy: ColorConversionStrategy
481 ) -> Result<(), anyhow::Error> {
482 for y in 0..(self.height() as usize) {
483 for x in 0..(self.width() as usize) {
484 let ink = &mut self.data[y][x];
485 if !inks.contains(ink) {
486 match strategy {
487 ColorConversionStrategy::ReplaceWrongColorByFirstColor => {
488 *ink = inks[0];
489 },
490 ColorConversionStrategy::ReplaceWrongColorByClosestInk => unimplemented!(),
491 ColorConversionStrategy::Fail => {
492 return Err(anyhow::anyhow!(
493 "{:?} not available in {:?} at [{}, {}]",
494 ink,
495 inks,
496 x,
497 y
498 ));
499 }
500 }
501 }
502 }
503 }
504
505 Ok(())
506 }
507
508 pub fn width(&self) -> u32 {
511 match self.height() {
512 0 => 0,
513 _ => self.data[0].len() as u32
514 }
515 }
516
517 pub fn convert_from_fname(fname: &str, conversion: ConversionRule) -> anyhow::Result<Self> {
518 let img = im::open(fname).with_context(|| format!("{fname} does not exists."))?;
519 Ok(Self::convert(&img.to_rgb8(), conversion))
520 }
521
522 pub fn convert(
523 img: &im::ImageBuffer<im::Rgb<u8>, Vec<u8>>,
524 conversion: ConversionRule
525 ) -> Self {
526 let height = img.height();
528 let width = {
529 match conversion {
530 ConversionRule::AnyModeUseAllPixels => img.width(),
531 ConversionRule::ZeroSkipOddPixels => img.width() / 2
532 }
533 };
534
535 let mut lines = Vec::new();
537 lines.reserve(height as usize);
538 for y in 0..height {
539 let src_y = y;
540 let mut line = Vec::new();
541 line.reserve(width as usize);
542 for x in 0..width {
543 let src_x = {
544 match conversion {
545 ConversionRule::AnyModeUseAllPixels => x,
546 ConversionRule::ZeroSkipOddPixels => x * 2
547 }
548 };
549
550 let src_color = img.get_pixel(src_x, src_y);
551 let dest_ink = Ink::from(*src_color);
552
553 line.push(dest_ink);
555 }
556 lines.push(line);
558 }
559
560 Self { data: lines }
562 }
563
564 pub fn diff(&self, other: &Self) -> Self {
566 let mut data = vec![vec![Ink::from(26); other.width() as usize]; other.height() as usize];
568
569 for x in 0..(self.width() as usize) {
571 for y in 0..(self.height() as usize) {
572 if self.data[y][x] != other.data[y][x] {
573 data[y][x] = Ink::from(0);
574 }
575 }
576 }
577
578 Self { data }
580 }
581
582 pub fn diff_to_positions(&self) -> Vec<(usize, usize)> {
584 let mut res = Vec::new();
585 for x in 0..(self.width() as usize) {
586 for y in 0..(self.height() as usize) {
587 if self.data[y][x] == Ink::from(0) {
588 res.push((x, y));
589 }
590 }
591 }
592 res
593 }
594
595 pub fn as_image(&self) -> im::ImageBuffer<im::Rgba<u8>, Vec<u8>> {
597 let mut buffer: im::ImageBuffer<im::Rgba<u8>, Vec<u8>> =
598 im::ImageBuffer::new(self.width(), self.height());
599
600 for x in 0..(self.width()) {
601 for y in 0..(self.height()) {
602 buffer.put_pixel(x, y, self.get_ink(x as usize, y as usize).color());
603 }
604 }
605
606 buffer
607 }
608
609 pub fn as_sprite(
611 &self,
612 mode: Mode,
613 palette: Option<Palette>,
614 missing_pen: Option<Pen>
615 ) -> Sprite {
616 let palette = palette.unwrap_or_else(|| self.extract_palette(mode));
618
619 let pens = inks_to_pens(&self.data, &palette);
621
622 Sprite {
624 mode: Some(mode),
625 palette: Some(palette),
626 data: encode(&pens, mode, missing_pen)
627 }
628 }
629
630 pub fn as_mode1_sprite_with_different_inks_per_line(
632 &self,
633 palette: &[(Ink, Ink, Ink, Ink)],
634 dummy_palette: &Palette,
635 missing_pen: Option<Pen>
636 ) -> Sprite {
637 let mut data: Vec<Vec<Pen>> = Vec::new();
639 for y in 0..self.height() {
640 let y = y as usize;
641
642 let line_palette = {
644 let mut p = Palette::new(); p.set(Pen::from(0), palette[y].0);
646 p.set(Pen::from(1), palette[y].1);
647 p.set(Pen::from(2), palette[y].2);
648 p.set(Pen::from(3), palette[y].3);
649 p
650 };
651
652 let pens = self
654 .get_line(y)
655 .iter()
656 .map(|ink| -> Pen {
657 let pen = line_palette.get_pen_for_ink(*ink);
658 if let Some(pen) = pen {
659 pen
660 }
661 else {
662 Pen::from(0)
670 } })
672 .collect::<Vec<Pen>>();
673
674 data.push(pens);
676 }
677
678 let encoded_pixels = encode(&data, Mode::One, missing_pen);
679
680 Sprite {
682 mode: Some(Mode::One),
683 palette: Some(dummy_palette.clone()),
684 data: encoded_pixels
685 }
686 }
687
688 pub fn inks(&self) -> Inks<'_> {
690 Inks {
691 image: self,
692 x: 0,
693 y: 0,
694 width: self.width(),
695 height: self.height()
696 }
697 }
698}
699
700#[derive(Debug)]
702pub struct Inks<'a> {
703 image: &'a ColorMatrix,
704 x: u32,
705 y: u32,
706 width: u32,
707 height: u32
708}
709
710impl Iterator for Inks<'_> {
711 type Item = (u32, u32, Ink);
712
713 fn next(&mut self) -> Option<(u32, u32, Ink)> {
714 if self.x >= self.width {
715 self.x = 0;
716 self.y += 1;
717 }
718
719 if self.y >= self.height {
720 None
721 }
722 else {
723 let ink = self.image.get_ink(self.x as _, self.y as _);
724 let i = (self.x, self.y, *ink);
725
726 self.x += 1;
727
728 Some(i)
729 }
730 }
731}
732
733#[derive(Debug)]
735pub struct ColorMatrixList(Vec<ColorMatrix>);
736
737impl From<Vec<ColorMatrix>> for ColorMatrixList {
738 fn from(src: Vec<ColorMatrix>) -> Self {
739 ColorMatrixList(src)
740 }
741}
742
743impl From<&ColorMatrixList> for Vec<ColorMatrix> {
744 fn from(val: &ColorMatrixList) -> Self {
745 val.0.clone()
746 }
747}
748
749impl std::ops::Deref for ColorMatrixList {
750 type Target = Vec<ColorMatrix>;
751
752 fn deref(&self) -> &Self::Target {
753 &self.0
754 }
755}
756
757#[derive(PartialEq, Copy, Clone, Debug)]
759pub enum HorizontalCropConstraint {
760 None,
762 CompleteByteForMode(Mode)
764}
765
766#[derive(PartialEq, Copy, Clone, Debug)]
768pub enum HorizontalCrop {
769 Right(HorizontalCropConstraint),
771 Left(HorizontalCropConstraint),
773 Both(HorizontalCropConstraint, HorizontalCropConstraint),
775 None
777}
778
779#[derive(PartialEq, Copy, Clone, Debug)]
781pub enum VerticalCrop {
782 Top,
784 Bottom,
786 Both,
788 None
790}
791
792impl ColorMatrixList {
793 pub fn to_vec(&self) -> Vec<ColorMatrix> {
795 self.into()
796 }
797
798 pub fn convert_from_fname(fname: &str, conversion: ConversionRule) -> anyhow::Result<Self> {
801 use std::fs::File;
802
803 let input = File::open(fname)?;
805 let mut options = gif::DecodeOptions::new();
806 options.set_color_output(gif::ColorOutput::Indexed);
807 let mut decoder = options.read_info(input).unwrap();
808 let mut screen = gif_dispose::Screen::new_decoder(&decoder);
809
810 let mut matrix_list = ColorMatrixList(Vec::new());
811 while let Some(frame) = decoder.read_next_frame()? {
812 screen.blit_frame(frame)?;
813
814 let content = image::ImageBuffer::<image::Rgb<u8>, Vec<u8>>::from_raw(
815 screen.pixels.width() as u32,
816 screen.pixels.height() as u32,
817 screen
818 .pixels
819 .buf()
820 .iter()
821 .flat_map(|pix| [pix.r, pix.g, pix.b].to_vec())
822 .collect::<Vec<u8>>()
823 )
824 .unwrap();
825
826 matrix_list
827 .0
828 .push(ColorMatrix::convert(&content, conversion));
829 }
830
831 Ok(matrix_list)
832 }
833
834 pub fn reduce_colors_with(
836 &mut self,
837 inks: &[Ink],
838 strategy: ColorConversionStrategy
839 ) -> Result<(), anyhow::Error> {
840 self.0
841 .iter_mut()
842 .try_for_each(|matrix| matrix.reduce_colors_with(inks, strategy))
843 }
844
845 pub fn len(&self) -> usize {
847 self.0.len()
848 }
849
850 pub fn width(&self) -> u32 {
852 self.0[0].width()
853 }
854
855 pub fn height(&self) -> u32 {
857 self.0[0].height()
858 }
859
860 pub fn as_sprites(
862 &self,
863 mode: Mode,
864 palette: Option<Palette>,
865 missing_pen: Option<Pen>
866 ) -> SpriteList {
867 self.to_vec()
868 .iter()
869 .map(|matrix| matrix.as_sprite(mode, palette.clone(), missing_pen))
870 .collect::<Vec<Sprite>>()
871 .into()
872 }
873
874 pub fn crop(&mut self, hor_conf: HorizontalCrop, vert_conf: VerticalCrop) -> Self {
876 use std::collections::BTreeSet;
877
878 let (modified_x, modified_y) = {
880 let mut modified_x = BTreeSet::new();
881 let mut modified_y = BTreeSet::new();
882
883 for (mata, matb) in self.0.iter().tuple_windows() {
884 let diff = mata.diff(matb);
885 let diff_coords = diff.diff_to_positions();
886
887 diff_coords.iter().for_each(|(x, y)| {
888 modified_x.insert(*x);
889 modified_y.insert(*y);
890 });
891 }
892
893 (
894 modified_x.iter().map(|x| *x as u32).collect::<Vec<_>>(),
895 modified_y.iter().map(|y| *y as u32).collect::<Vec<_>>()
896 )
897 };
898
899 let mut start_x = match hor_conf {
901 HorizontalCrop::Both(..) | HorizontalCrop::Left(_) => {
902 let mut current_x = 0;
903 while current_x < self.width() - 1 && current_x < modified_x[0] {
904 current_x += 1;
905 }
906 current_x
907 },
908 _ => 0
909 } as usize;
910
911 let mut stop_x = match hor_conf {
913 HorizontalCrop::Both(..) | HorizontalCrop::Right(_) => {
914 let mut current_x = self.width() - 1;
915 while current_x > 0 && current_x > *modified_x.last().unwrap() {
916 current_x -= 1;
917 }
918 current_x
919 },
920 _ => self.width() - 1
921 } as usize;
922
923 let start_y = match vert_conf {
925 VerticalCrop::Both | VerticalCrop::Top => {
926 let mut current_y = 0;
927 while current_y < self.height() - 1 && current_y < modified_y[0] {
928 current_y += 1;
929 }
930 current_y
931 },
932 _ => 0
933 } as usize;
934
935 let stop_y = match vert_conf {
937 VerticalCrop::Both | VerticalCrop::Bottom => {
938 let mut current_y = self.height() - 1;
939 while current_y > 0 && current_y > *modified_y.last().unwrap() {
940 current_y -= 1;
941 }
942 current_y
943 },
944 _ => self.height() - 1
945 } as usize;
946
947 match hor_conf {
949 HorizontalCrop::Left(HorizontalCropConstraint::CompleteByteForMode(ref mode))
950 | HorizontalCrop::Both(HorizontalCropConstraint::CompleteByteForMode(ref mode), _) => {
951 while !start_x.is_multiple_of(mode.nb_pixels_per_byte()) {
952 start_x -= 1;
953 }
954 },
955 _ => {}
956 }
957
958 match hor_conf {
960 HorizontalCrop::Right(HorizontalCropConstraint::CompleteByteForMode(ref mode))
961 | HorizontalCrop::Both(_, HorizontalCropConstraint::CompleteByteForMode(ref mode)) => {
962 while !(stop_x + 1).is_multiple_of(mode.nb_pixels_per_byte()) {
963 stop_x += 1;
964 }
965 },
966 _ => {}
967 }
968
969 self.window(start_x, start_y, stop_x - start_x + 1, stop_y - start_y + 1)
971 }
972
973 pub fn window(&self, start_x: usize, start_y: usize, width: usize, height: usize) -> Self {
975 self.to_vec()
976 .iter()
977 .map(|matrix| matrix.window(start_x, start_y, width, height))
978 .collect::<Vec<ColorMatrix>>()
979 .into()
980 }
981}
982
983#[derive(Debug)]
985pub struct SpriteList(Vec<Sprite>);
986
987impl From<Vec<Sprite>> for SpriteList {
988 fn from(src: Vec<Sprite>) -> Self {
989 SpriteList(src)
990 }
991}
992
993impl std::ops::Deref for SpriteList {
994 type Target = Vec<Sprite>;
995
996 fn deref(&self) -> &Self::Target {
997 &self.0
998 }
999}
1000
1001#[derive(Debug)]
1006pub struct Sprite {
1007 pub(crate) mode: Option<Mode>,
1009 pub(crate) palette: Option<Palette>,
1011 pub(crate) data: Vec<Vec<u8>>
1013}
1014
1015#[allow(missing_docs)]
1016impl Sprite {
1017 pub fn from_pens(pens: &[Vec<Pen>], mode: Mode, palette: Option<Palette>) -> Self {
1018 let data = pens
1019 .iter()
1020 .map(|line| crate::pixels::pens_to_vec(line, mode))
1021 .collect_vec();
1022 Sprite {
1023 data,
1024 mode: Some(mode),
1025 palette
1026 }
1027 }
1028
1029 pub fn from_bytes(bytes: &[u8], bytes_width: usize, mode: Mode, palette: Palette) -> Self {
1030 let pens = bytes
1031 .chunks(bytes_width)
1032 .map(|chunk| pixels::bytes_to_pens(chunk, mode).collect_vec())
1033 .collect_vec();
1034
1035 Self::from_pens(&pens, mode, Some(palette))
1036 }
1037
1038 pub fn to_color_matrix(&self) -> Option<ColorMatrix> {
1041 if self.mode.is_none() && self.palette.is_none() {
1042 return None;
1043 }
1044
1045 let mut data = Vec::with_capacity(self.data.len());
1046 let p = self.palette.as_ref().unwrap();
1047 for line in &self.data {
1048 let inks = match self.mode {
1049 Some(Mode::Zero) | Some(Mode::Three) => {
1050 line.iter()
1051 .flat_map(|b: &u8| {
1052 let pens = {
1053 let mut pens = pixels::mode0::byte_to_pens(*b);
1054 pens[0].limit(self.mode.unwrap());
1055 pens[1].limit(self.mode.unwrap());
1056 pens
1057 };
1058 vec![*p.get(&pens[0]), *p.get(&pens[1])]
1059 })
1060 .collect::<Vec<Ink>>()
1061 },
1062
1063 Some(mode) => {
1064 bytes_to_pens(line, mode)
1065 .map(|pen| *p.get(&pen))
1066 .collect_vec()
1067 },
1068
1069 _ => unimplemented!()
1070 };
1071 data.push(inks);
1072 }
1073
1074 Some(ColorMatrix { data })
1075 }
1076
1077 pub fn to_linear_vec(&self) -> Vec<u8> {
1079 let size = self.height() * self.bytes_width();
1080 let mut bytes: Vec<u8> = Vec::with_capacity(size as usize);
1081
1082 for y in 0..self.height() {
1083 bytes.extend_from_slice(&self.data[y as usize]);
1084 }
1085
1086 bytes
1087 }
1088
1089 pub fn palette(&self) -> Option<Palette> {
1091 self.palette.clone()
1092 }
1093
1094 pub fn set_palette(&mut self, palette: Palette) {
1095 self.palette = Some(palette);
1096 }
1097
1098 pub fn bytes(&self) -> &Vec<Vec<u8>> {
1099 &self.data
1100 }
1101
1102 pub fn mode(&self) -> Option<Mode> {
1105 self.mode
1106 }
1107
1108 pub fn height(&self) -> u32 {
1111 self.data.len() as u32
1112 }
1113
1114 pub fn bytes_width(&self) -> u32 {
1117 match self.height() {
1118 0 => 0,
1119 _ => self.data[0].len() as u32
1120 }
1121 }
1122
1123 pub fn pixel_width(&self) -> u32 {
1126 match self.mode {
1127 None => panic!("Unable to get the pixel width when mode is not specified"),
1128 Some(mode) => mode.nb_pixels_per_byte() as u32 * self.bytes_width()
1129 }
1130 }
1131
1132 pub fn get_byte(&self, x: usize, y: usize) -> u8 {
1134 let line = &self.data[y];
1135 line[x]
1136 }
1137
1138 pub fn get_byte_safe(&self, x: usize, y: usize) -> Option<u8> {
1140 self.data.get(y).and_then(|v| v.get(x)).copied()
1141 }
1142
1143 pub fn get_line(&self, y: usize) -> &[u8] {
1145 self.data[y].as_ref()
1146 }
1147
1148 pub fn convert(
1152 img: &im::ImageBuffer<im::Rgb<u8>, Vec<u8>>,
1153 mode: Mode,
1154 conversion: ConversionRule,
1155 palette: Option<Palette>,
1156 missing_pen: Option<Pen>
1157 ) -> Self {
1158 let matrix = ColorMatrix::convert(img, conversion);
1160 matrix.as_sprite(mode, palette, missing_pen)
1161 }
1162
1163 pub fn convert_from_fname<P: AsRef<Utf8Path>>(
1164 fname: P,
1165 mode: Mode,
1166 conversion: ConversionRule,
1167 palette: Option<Palette>,
1168 missing_pen: Option<Pen>
1169 ) -> Result<Self, im::ImageError> {
1170 let img = im::open(fname.as_ref())?;
1171 Ok(Self::convert(
1172 &img.to_rgb8(),
1173 mode,
1174 conversion,
1175 palette,
1176 missing_pen
1177 ))
1178 }
1179
1180 pub fn horizontal_transform<F>(&mut self, f: F)
1183 where F: Fn(&Vec<u8>) -> Vec<u8> {
1184 let mut transformed = self.data.iter().map(f).collect::<Vec<_>>();
1185 ::std::mem::swap(&mut transformed, &mut self.data);
1186 }
1187
1188 pub fn as_image(&self) -> im::ImageBuffer<im::Rgba<u8>, Vec<u8>> {
1189 self.to_color_matrix().unwrap().as_image()
1190 }
1191}
1192
1193#[derive(Clone, Debug)]
1196#[allow(missing_docs, unused)]
1197pub struct MultiModeSprite {
1198 mode: Vec<Mode>,
1199 palette: Palette,
1200 data: Vec<Vec<u8>>
1201}
1202
1203#[derive(Copy, Clone, Debug)]
1204#[allow(missing_docs)]
1205pub enum MultiModeConversion {
1206 FirstHalfSecondHalf,
1207 OddEven
1208}
1209
1210#[allow(missing_docs)]
1211impl MultiModeSprite {
1212 pub fn new(p: Palette) -> Self {
1214 Self {
1215 palette: p,
1216 mode: Vec::new(), data: Vec::new() }
1219 }
1220
1221 pub fn bytes(&self) -> &Vec<Vec<u8>> {
1222 &self.data
1223 }
1224
1225 pub fn height(&self) -> usize {
1226 self.data.len()
1227 }
1228
1229 pub fn width(&self) -> usize {
1230 self.data[0].len()
1231 }
1232
1233 pub fn to_mode0_sprite(&self) -> Sprite {
1238 Sprite {
1239 mode: Some(Mode::Zero),
1240 palette: Some(self.palette.clone()),
1241 data: self.data.clone()
1242 }
1243 }
1244
1245 pub fn to_mode3_sprite(&self) -> Sprite {
1246 Sprite {
1247 mode: Some(Mode::Three),
1248 palette: Some(self.palette.clone()),
1249 data: self.data.clone()
1250 }
1251 }
1252
1253 #[allow(clippy::similar_names, clippy::identity_op)]
1255 pub fn mode0_mode3_mix_from_mode0(sprite: &Sprite, conversion: MultiModeConversion) -> Self {
1256 let p_orig = sprite.palette().unwrap();
1258
1259 let p = {
1261 let mut p = Palette::new();
1262
1263 for i in 0..4 {
1265 p.set(i, *p_orig.get(i.into()));
1266 }
1267
1268 let lut = [
1270 (0, [5, 6, 7]),
1271 (1, [8, 10, 11]),
1272 (2, [12, 13, 15]),
1273 (3, [4, 9, 14])
1274 ];
1275
1276 for (src, dsts) in &lut {
1278 dsts.iter().for_each(|dst| {
1279 p.set(*dst, *p_orig.get((*src).into()));
1280 });
1281 }
1282
1283 p
1284 };
1285
1286 let (modes, lines) = match conversion {
1288 MultiModeConversion::FirstHalfSecondHalf => {
1289 let sprite_height = sprite.height() as usize;
1290 let encoded_height = if sprite_height % 2 == 1 {
1291 sprite_height / 2 + 1
1292 }
1293 else {
1294 sprite_height / 2 + 0
1295 };
1296
1297 let mut modes = Vec::with_capacity(sprite_height);
1298 let mut lines = Vec::with_capacity(encoded_height);
1299
1300 for i in 0..sprite_height {
1302 let mode = if i < encoded_height {
1303 Mode::Zero
1304 }
1305 else {
1306 Mode::Three
1307 };
1308 modes.push(mode);
1309 }
1310
1311 for i in 0..encoded_height {
1313 let line1 = &sprite.data[i + 0]; let line2 = sprite.data.get(i + encoded_height); let line = match line2 {
1317 Some(line2) => merge_mode0_mode3(line1, line2),
1318 None => line1.clone()
1319 };
1320
1321 lines.push(line);
1322 }
1323
1324 (modes, lines)
1325 },
1326
1327 _ => unimplemented!()
1328 };
1329
1330 Self {
1331 palette: p,
1332 mode: modes,
1333 data: lines
1334 }
1335 }
1336}
1337
1338#[cfg(test)]
1339mod tests {
1340 use super::ColorMatrix;
1341 use crate::ga::Ink;
1342
1343 #[test]
1344 fn test_masking() {
1345 let fg1 = Ink::BLUE;
1346 let fg2 = Ink::SKY_BLUE;
1347 let fg3 = Ink::PASTEL_BLUE;
1348 let fg4 = Ink::BRIGHT_BLUE;
1349 let bg_ = Ink::RED;
1350 let rep = Ink::BLACK;
1351
1352 let sprite_with_mask = ColorMatrix {
1353 data: vec![
1354 vec![bg_, fg2, fg3, fg4],
1355 vec![bg_, bg_, fg1, fg2],
1356 vec![bg_, bg_, bg_, fg3],
1357 vec![bg_, bg_, bg_, fg4],
1358 vec![bg_, bg_, bg_, bg_],
1359 ]
1360 };
1361
1362 let (mask, sprite) = sprite_with_mask.extract_mask_and_sprite(bg_, rep);
1363
1364 assert_eq!(
1365 sprite.data,
1366 vec![
1367 vec![rep, fg2, fg3, fg4],
1368 vec![rep, rep, fg1, fg2],
1369 vec![rep, rep, rep, fg3],
1370 vec![rep, rep, rep, fg4],
1371 vec![rep, rep, rep, rep],
1372 ]
1373 );
1374
1375 assert_eq!(
1376 mask.data,
1377 vec![
1378 vec![Ink::BRIGHT_WHITE, Ink::BLACK, Ink::BLACK, Ink::BLACK],
1379 vec![Ink::BRIGHT_WHITE, Ink::BRIGHT_WHITE, Ink::BLACK, Ink::BLACK],
1380 vec![
1381 Ink::BRIGHT_WHITE,
1382 Ink::BRIGHT_WHITE,
1383 Ink::BRIGHT_WHITE,
1384 Ink::BLACK
1385 ],
1386 vec![
1387 Ink::BRIGHT_WHITE,
1388 Ink::BRIGHT_WHITE,
1389 Ink::BRIGHT_WHITE,
1390 Ink::BLACK
1391 ],
1392 vec![
1393 Ink::BRIGHT_WHITE,
1394 Ink::BRIGHT_WHITE,
1395 Ink::BRIGHT_WHITE,
1396 Ink::BRIGHT_WHITE
1397 ],
1398 ]
1399 );
1400
1401 let mask2 = sprite_with_mask.clone().convert_to_mask(bg_).clone();
1402 let sprite2 = sprite_with_mask.clone().replace_ink(bg_, rep).clone();
1403
1404 assert_eq!(mask, mask2);
1405 assert_eq!(sprite, sprite2);
1406 }
1407}