1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
use std::io::{Seek, SeekFrom, Write};

use binwrite::BinWrite;

use crate::{imagebytes::ImageAssemblyEntry, ImageBytes, WanError};

pub enum CompressionMethod {
    CompressionMethodOriginal,
    CompressionMethodOptimised {
        multiple_of_value: usize,
        min_transparent_to_compress: usize,
    },
    NoCompression,
}

impl CompressionMethod {
    pub fn compress<F: Write + Seek>(
        &self,
        image: &ImageBytes,
        pixel_list: &[u8],
        file: &mut F,
    ) -> Result<Vec<ImageAssemblyEntry>, WanError> {
        let mut assembly_table: Vec<ImageAssemblyEntry> = vec![];
        match self {
            Self::CompressionMethodOriginal => {
                enum ActualEntry {
                    Null(u64, u32),      //lenght (pixel), z_index
                    Some(u64, u64, u32), // initial_offset, lenght (pixel), z_index
                }

                impl ActualEntry {
                    fn new(is_all_black: bool, start_offset: u64, z_index: u32) -> ActualEntry {
                        if is_all_black {
                            ActualEntry::Null(64, z_index)
                        } else {
                            ActualEntry::Some(start_offset, 64, z_index)
                        }
                    }

                    fn to_assembly(&self) -> ImageAssemblyEntry {
                        match self {
                            ActualEntry::Null(lenght, z_index) => ImageAssemblyEntry {
                                pixel_src: 0,
                                pixel_amount: *lenght,
                                byte_amount: *lenght / 2,
                                _z_index: *z_index,
                            },
                            ActualEntry::Some(initial_offset, lenght, z_index) => {
                                ImageAssemblyEntry {
                                    pixel_src: *initial_offset,
                                    pixel_amount: *lenght,
                                    byte_amount: *lenght / 2,
                                    _z_index: *z_index,
                                }
                            }
                        }
                    }

                    fn advance(&self, lenght: u64) -> ActualEntry {
                        match self {
                            ActualEntry::Null(l, z) => ActualEntry::Null(*l + lenght, *z),
                            ActualEntry::Some(offset, l, z) => {
                                ActualEntry::Some(*offset, *l + lenght, *z)
                            }
                        }
                    }
                }

                let mut actual_entry: Option<ActualEntry> = None;

                for (loop_nb, _chunk) in pixel_list.chunks_exact(64).enumerate() {
                    let mut this_area = vec![];
                    let mut is_all_black = true;
                    for l in 0..64 {
                        let actual_pixel = pixel_list[(loop_nb * 64 + l) as usize];
                        this_area.push(actual_pixel);
                        if actual_pixel != 0 {
                            is_all_black = false;
                        };
                    }

                    let pos_before_area = file.seek(SeekFrom::Current(0))?;
                    if !is_all_black {
                        for byte_id in 0..32 {
                            (((this_area[byte_id * 2] << 4) + this_area[byte_id * 2 + 1]) as u8)
                                .write(file)?;
                        }
                    }

                    let need_to_create_new_entry = match &actual_entry {
                        Some(ActualEntry::Null(_, _)) => !is_all_black,
                        Some(ActualEntry::Some(_, _, _)) => is_all_black,
                        None => true,
                    };

                    if need_to_create_new_entry {
                        if let Some(entry) = actual_entry {
                            assembly_table.push(entry.to_assembly())
                        }

                        actual_entry = Some(ActualEntry::new(
                            is_all_black,
                            pos_before_area,
                            image.z_index,
                        ));
                    } else {
                        // no panic : need_to_create_new_entry is false if actual_entry is none
                        actual_entry = Some(actual_entry.unwrap().advance(64));
                    }
                }
                assembly_table.push(actual_entry.unwrap().to_assembly())
            }
            Self::CompressionMethodOptimised {
                multiple_of_value,
                min_transparent_to_compress,
            } => {
                let mut number_of_byte_to_include = 0;
                let mut byte_include_start = file.seek(SeekFrom::Current(0))?;

                let mut pixel_id = 0;
                loop {
                    debug_assert!(pixel_id % 2 == 0);
                    let mut should_create_new_transparent_entry = false;

                    if (pixel_id % multiple_of_value == 0)
                        && (pixel_id + min_transparent_to_compress < pixel_list.len())
                    {
                        let mut encontered_non_transparent = false;
                        for l in 0..*min_transparent_to_compress {
                            if pixel_list[pixel_id + l] != 0 {
                                encontered_non_transparent = true;
                                break;
                            };
                        }
                        if !encontered_non_transparent {
                            should_create_new_transparent_entry = true;
                        };
                    };

                    if should_create_new_transparent_entry {
                        //push the actual content
                        if number_of_byte_to_include > 0 {
                            assembly_table.push(ImageAssemblyEntry {
                                pixel_src: byte_include_start,
                                pixel_amount: number_of_byte_to_include * 2,
                                byte_amount: number_of_byte_to_include,
                                _z_index: image.z_index,
                            });
                            number_of_byte_to_include = 0;
                            byte_include_start = file.seek(SeekFrom::Current(0))?;
                        };
                        //create new entry for transparent stuff
                        //count the number of transparent tile
                        let mut transparent_tile_nb = 0;
                        loop {
                            if pixel_id >= pixel_list.len() {
                                break;
                            };
                            if pixel_list[pixel_id] == 0 {
                                transparent_tile_nb += 1;
                                pixel_id += 1;
                            } else {
                                break;
                            };
                        }
                        if pixel_id % multiple_of_value != 0 {
                            transparent_tile_nb -= pixel_id % multiple_of_value;
                            pixel_id -= pixel_id % multiple_of_value;
                        };
                        assembly_table.push(ImageAssemblyEntry {
                            pixel_src: 0,
                            pixel_amount: transparent_tile_nb as u64,
                            byte_amount: (transparent_tile_nb as u64) / 2, //TODO: take care of the tileset lenght
                            _z_index: image.z_index,
                        });

                        continue;
                    };

                    if pixel_id >= pixel_list.len() {
                        break;
                    };
                    debug_assert!(pixel_list[pixel_id] < 16);
                    debug_assert!(pixel_list[pixel_id + 1] < 16);
                    (((pixel_list[pixel_id] << 4) + pixel_list[pixel_id + 1]) as u8).write(file)?;
                    pixel_id += 2;
                    number_of_byte_to_include += 1;
                }
                if number_of_byte_to_include > 0 {
                    assembly_table.push(ImageAssemblyEntry {
                        pixel_src: byte_include_start,
                        pixel_amount: number_of_byte_to_include * 2,
                        byte_amount: number_of_byte_to_include,
                        _z_index: image.z_index,
                    });
                };
            }
            Self::NoCompression => {
                let mut byte_len = 0;
                let start_offset = file.seek(SeekFrom::Current(0))?;
                for pixels in pixel_list.chunks_exact(2) {
                    ((pixels[0] << 4) + pixels[1]).write(file)?;
                    byte_len += 1;
                }
                assembly_table.push(ImageAssemblyEntry {
                    pixel_src: start_offset,
                    pixel_amount: byte_len * 2,
                    byte_amount: byte_len,
                    _z_index: image.z_index,
                })
            }
        };
        Ok(assembly_table)
    }
}