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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
use std::io::{Seek, SeekFrom, Write};
use byteorder::WriteBytesExt;
use crate::{fragment_bytes::FragmentBytesAssemblyEntry, FragmentBytes, WanError};
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum CompressionMethod {
/// The compression used to compress creatures in base game
CompressionMethodOriginal,
/// No compression, used for other sprites in base game
NoCompression,
/* /// An original optimised compression algorithm (TODO: I think it is unfinished, need testing, or maybe just fuzzing)
CompressionMethodOptimised {
multiple_of_value: usize,
min_transparent_to_compress: usize,
},*/
}
impl CompressionMethod {
pub fn compress<F: Write + Seek>(
&self,
fragment_bytes: &FragmentBytes,
pixel_list: &[u8],
file: &mut F,
) -> Result<Vec<FragmentBytesAssemblyEntry>, WanError> {
let compression = if pixel_list.len() % 64 != 0 {
CompressionMethod::NoCompression
} else {
self.clone()
};
if pixel_list.is_empty() {
return Err(WanError::EmptyFragmentBytes);
}
let mut assembly_table: Vec<FragmentBytesAssemblyEntry> = vec![];
match compression {
Self::CompressionMethodOriginal => {
enum ActualEntry {
Null(u32, u32), //lenght (pixel), z_index
Some(u64, u32, 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) -> FragmentBytesAssemblyEntry {
match self {
ActualEntry::Null(lenght, z_index) => FragmentBytesAssemblyEntry {
pixel_src: 0,
pixel_amount: *lenght,
byte_amount: (*lenght / 2) as u16, //NOTE: lenght is always <= than 64x64
_z_index: *z_index,
},
ActualEntry::Some(initial_offset, lenght, z_index) => {
FragmentBytesAssemblyEntry {
pixel_src: *initial_offset,
pixel_amount: *lenght,
byte_amount: (*lenght / 2) as u16,
_z_index: *z_index,
}
}
}
}
fn advance(&self, lenght: u32) -> 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];
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 {
file.write_u8(
(this_area[byte_id * 2] << 4) + this_area[byte_id * 2 + 1],
)?;
}
}
let need_to_create_new_entry = match &actual_entry {
Some(ActualEntry::Null(_, _)) => !is_all_black,
Some(ActualEntry::Some(_, _, _)) => is_all_black,
None => true,
};
actual_entry = if need_to_create_new_entry {
if let Some(entry) = actual_entry {
assembly_table.push(entry.to_assembly())
}
Some(ActualEntry::new(
is_all_black,
pos_before_area,
fragment_bytes.z_index,
))
} else {
// no panic : need_to_create_new_entry is false if actual_entry is none
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: u16 = 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 as u32 * 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: u32 = 0; //TODO: somehow guarantee it never gets bigger than (16^2)*2
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) as u32;
pixel_id -= pixel_id % multiple_of_value;
};
assembly_table.push(ImageAssemblyEntry {
pixel_src: 0,
pixel_amount: transparent_tile_nb,
byte_amount: (transparent_tile_nb / 2) as u16, //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);
file.write_u8(((pixel_list[pixel_id] << 4) + pixel_list[pixel_id + 1]) as u8)?;
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 as u32 * 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) {
file.write_u8((pixels[0] << 4) + pixels[1])?;
byte_len += 1;
}
assembly_table.push(FragmentBytesAssemblyEntry {
pixel_src: start_offset,
pixel_amount: byte_len * 2,
byte_amount: byte_len as u16,
_z_index: fragment_bytes.z_index,
})
}
};
Ok(assembly_table)
}
}