1use crate::error::Result;
11use crate::format::{IffImage, Layer1, Residual};
12use crate::prime::QuantizationTable;
13use crate::wavelet::{Cdf53Transform, WaveletDecomposition};
14use crate::color::{rgb_to_ycocg, subsample_420, ycocg_to_rgb, upsample_420, YCoCgImage};
15use serde::{Deserialize, Serialize};
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct EncoderConfig {
20 pub wavelet_levels: usize,
22 pub base_quantization: u16,
24 pub texture_min_size: usize,
26 pub texture_iterations: usize,
28 pub residual_threshold: f32,
30 pub texture_entropy_threshold: f32,
32 pub enable_layer2: bool,
34 pub enable_layer3: bool,
36 pub use_ycocg_420: bool,
38}
39
40impl Default for EncoderConfig {
41 fn default() -> Self {
42 EncoderConfig {
43 wavelet_levels: 5,
44 base_quantization: 8,
45 texture_min_size: 32,
46 texture_iterations: 100,
47 residual_threshold: 40.0,
48 texture_entropy_threshold: 0.05,
49 enable_layer2: true,
50 enable_layer3: true,
51 use_ycocg_420: true,
52 }
53 }
54}
55
56pub struct Encoder {
58 config: EncoderConfig,
59}
60
61impl Encoder {
62 pub fn new(config: EncoderConfig) -> Self {
64 Encoder { config }
65 }
66
67 #[cfg(feature = "encoder")]
69 pub fn encode(&self, image: &image::DynamicImage) -> Result<IffImage> {
70 let rgb_image = image.to_rgb8();
71 let width = rgb_image.width() as usize;
72 let height = rgb_image.height() as usize;
73
74 let image_data: Vec<[u8; 3]> = rgb_image
76 .pixels()
77 .map(|p| [p[0], p[1], p[2]])
78 .collect();
79
80 let mut iff_image = IffImage::new(width as u32, height as u32, self.config.wavelet_levels as u8);
82 iff_image.header.flags.ycocg_420 = self.config.use_ycocg_420;
83
84 log::info!("Encoding Layer 1: Wavelet skeleton");
86 self.encode_layer1(&mut iff_image, &image_data, width, height)?;
87
88 let mut reconstruction = self.decode_layer1(&iff_image, width, height)?;
90
91 if self.config.enable_layer2 {
93 log::info!("Encoding Layer 2: Texture synthesis");
94 self.encode_layer2(&mut iff_image, &image_data, &mut reconstruction, width, height)?;
95 }
96
97 if self.config.enable_layer3 {
99 log::info!("Encoding Layer 3: Warp fields");
100 self.encode_layer3(&mut iff_image, &image_data, &mut reconstruction, width, height)?;
101 }
102
103 log::info!("Calculating residual");
105 self.calculate_residual(&mut iff_image, &image_data, &reconstruction, width, height)?;
106
107 Ok(iff_image)
108 }
109
110 fn encode_layer1(
112 &self,
113 iff_image: &mut IffImage,
114 image_data: &[[u8; 3]],
115 width: usize,
116 height: usize,
117 ) -> Result<()> {
118 let transform = Cdf53Transform::new(self.config.wavelet_levels);
119 let quantization_table = QuantizationTable::new(self.config.base_quantization);
120
121 if self.config.use_ycocg_420 {
122 let ycocg = rgb_to_ycocg(image_data, width, height);
124
125 let co_sub = subsample_420(&ycocg.co);
127 let cg_sub = subsample_420(&ycocg.cg);
128
129 let mut y_coeffs = transform.forward(&ycocg.y.data, width, height)?;
131 transform.quantize(&mut y_coeffs, width, height, &quantization_table);
132
133 let mut co_coeffs = transform.forward(&co_sub.data, co_sub.width, co_sub.height)?;
135 transform.quantize(&mut co_coeffs, co_sub.width, co_sub.height, &quantization_table);
136
137 let mut cg_coeffs = transform.forward(&cg_sub.data, cg_sub.width, cg_sub.height)?;
139 transform.quantize(&mut cg_coeffs, cg_sub.width, cg_sub.height, &quantization_table);
140
141 iff_image.layer1 = Layer1 {
143 y: WaveletDecomposition::from_dense(width as u32, height as u32, self.config.wavelet_levels, &[y_coeffs])?,
144 co: WaveletDecomposition::from_dense(co_sub.width as u32, co_sub.height as u32, self.config.wavelet_levels, &[co_coeffs])?,
145 cg: WaveletDecomposition::from_dense(cg_sub.width as u32, cg_sub.height as u32, self.config.wavelet_levels, &[cg_coeffs])?,
146 };
147 } else {
148 let mut channel_coeffs = Vec::with_capacity(3);
150 for c in 0..3 {
151 let channel: Vec<i32> = image_data
152 .iter()
153 .map(|p| p[c] as i32 - 128)
154 .collect();
155
156 let mut coeffs = transform.forward(&channel, width, height)?;
157 transform.quantize(&mut coeffs, width, height, &quantization_table);
158 channel_coeffs.push(coeffs);
159 }
160
161 iff_image.layer1 = Layer1 {
164 y: WaveletDecomposition::from_dense(width as u32, height as u32, self.config.wavelet_levels, &[channel_coeffs[0].clone()])?,
165 co: WaveletDecomposition::from_dense(width as u32, height as u32, self.config.wavelet_levels, &[channel_coeffs[1].clone()])?,
166 cg: WaveletDecomposition::from_dense(width as u32, height as u32, self.config.wavelet_levels, &[channel_coeffs[2].clone()])?,
167 };
168 }
169
170 Ok(())
171 }
172
173 fn decode_layer1(
175 &self,
176 iff_image: &IffImage,
177 width: usize,
178 height: usize,
179 ) -> Result<Vec<[u8; 3]>> {
180 let transform = Cdf53Transform::new(self.config.wavelet_levels);
181
182 if iff_image.header.flags.ycocg_420 {
183 let y_coeffs = iff_image.layer1.y.to_dense()?;
185 let co_coeffs = iff_image.layer1.co.to_dense()?;
186 let cg_coeffs = iff_image.layer1.cg.to_dense()?;
187
188 let y_data = transform.inverse(&y_coeffs[0], width, height)?;
190 let co_data = transform.inverse(&co_coeffs[0], iff_image.layer1.co.width as usize, iff_image.layer1.co.height as usize)?;
191 let cg_data = transform.inverse(&cg_coeffs[0], iff_image.layer1.cg.width as usize, iff_image.layer1.cg.height as usize)?;
192
193 let co_channel = crate::color::Channel { width: iff_image.layer1.co.width as usize, height: iff_image.layer1.co.height as usize, data: co_data };
195 let cg_channel = crate::color::Channel { width: iff_image.layer1.cg.width as usize, height: iff_image.layer1.cg.height as usize, data: cg_data };
196
197 let co_up = upsample_420(&co_channel, width, height);
198 let cg_up = upsample_420(&cg_channel, width, height);
199
200 let ycocg = YCoCgImage {
202 width,
203 height,
204 y: crate::color::Channel { width, height, data: y_data },
205 co: co_up,
206 cg: cg_up,
207 };
208
209 ycocg_to_rgb(&ycocg)
210 } else {
211 let r_coeffs = iff_image.layer1.y.to_dense()?;
213 let g_coeffs = iff_image.layer1.co.to_dense()?;
214 let b_coeffs = iff_image.layer1.cg.to_dense()?;
215
216 let r_data = transform.inverse(&r_coeffs[0], width, height)?;
217 let g_data = transform.inverse(&g_coeffs[0], width, height)?;
218 let b_data = transform.inverse(&b_coeffs[0], width, height)?;
219
220 let mut result = Vec::with_capacity(width * height);
221 for i in 0..(width * height) {
222 result.push([
223 (r_data[i] + 128).clamp(0, 255) as u8,
224 (g_data[i] + 128).clamp(0, 255) as u8,
225 (b_data[i] + 128).clamp(0, 255) as u8,
226 ]);
227 }
228 Ok(result)
229 }
230 }
231
232 fn encode_layer2(
234 &self,
235 iff_image: &mut IffImage,
236 original: &[[u8; 3]],
237 reconstruction: &mut Vec<[u8; 3]>,
238 width: usize,
239 height: usize,
240 ) -> Result<()> {
241 use crate::texture::TextureAnalyzer;
242
243 let analyzer = TextureAnalyzer::new(self.config.texture_entropy_threshold);
244
245 let regions = analyzer.detect_texture_regions(
247 original,
248 width,
249 height,
250 self.config.texture_min_size,
251 );
252
253 log::info!("Detected {} texture regions", regions.len());
254
255 for mut region in regions {
257 let error = analyzer.optimize_region(
258 original,
259 &mut region,
260 width,
261 self.config.texture_iterations,
262 self.config.residual_threshold,
263 )?;
264
265 if error < self.config.residual_threshold {
267 self.apply_texture_region(®ion, reconstruction, width, height);
269
270 iff_image.layer2.add_region(region);
272 }
273 }
274
275 Ok(())
276 }
277
278 fn apply_texture_region(
280 &self,
281 region: &crate::texture::Region,
282 reconstruction: &mut [[u8; 3]],
283 width: usize,
284 _height: usize,
285 ) {
286 use crate::texture::TextureSynthesizer;
287
288 let synthesizer = TextureSynthesizer::new();
289
290 for y in 0..region.h {
291 for x in 0..region.w {
292 let global_x = region.x + x;
293 let global_y = region.y + y;
294 let idx = (global_y as usize) * width + (global_x as usize);
295
296 if idx < reconstruction.len() {
297 reconstruction[idx] = synthesizer.synthesize_region_pixel(region, x, y);
298 }
299 }
300 }
301 }
302
303 fn encode_layer3(
305 &self,
306 iff_image: &mut IffImage,
307 original: &[[u8; 3]],
308 reconstruction: &mut Vec<[u8; 3]>,
309 width: usize,
310 height: usize,
311 ) -> Result<()> {
312
313
314 let vortices = self.detect_warp_regions(original, width, height);
316
317 log::info!("Detected {} warp vortices", vortices.len());
318
319 for vortex in vortices {
320 iff_image.layer3.add_vortex(vortex);
321 }
322
323 if !iff_image.layer3.vortices.is_empty() {
325 self.apply_warp_field(&iff_image.layer3, reconstruction, width, height);
326 }
327
328 Ok(())
329 }
330
331 fn detect_warp_regions(&self, image: &[[u8; 3]], width: usize, height: usize) -> Vec<crate::warp::Vortex> {
333 use crate::warp::Vortex;
334
335 let mut vortices = Vec::new();
336
337 let step = 32; for y in (step..height - step).step_by(step) {
342 for x in (step..width - step).step_by(step) {
343 let gradient = self.calculate_gradient_magnitude(image, x, y, width);
344
345 if gradient > 50.0 {
347 let vortex = Vortex::new(
349 x as u16,
350 y as u16,
351 (gradient as i16).clamp(-1000, 1000),
352 16, 128, );
355 vortices.push(vortex);
356 }
357 }
358 }
359
360 vortices.truncate(50);
362
363 vortices
364 }
365
366 fn calculate_gradient_magnitude(&self, image: &[[u8; 3]], x: usize, y: usize, width: usize) -> f32 {
368 let idx = y * width + x;
369 if idx >= image.len() || x == 0 || y == 0 {
370 return 0.0;
371 }
372
373 let idx_left = y * width + (x - 1);
374 let idx_up = (y - 1) * width + x;
375
376 if idx_left >= image.len() || idx_up >= image.len() {
377 return 0.0;
378 }
379
380 let mut grad_x = 0.0f32;
381 let mut grad_y = 0.0f32;
382
383 for c in 0..3 {
384 grad_x += (image[idx][c] as f32 - image[idx_left][c] as f32).abs();
385 grad_y += (image[idx][c] as f32 - image[idx_up][c] as f32).abs();
386 }
387
388 (grad_x * grad_x + grad_y * grad_y).sqrt()
389 }
390
391 fn apply_warp_field(
393 &self,
394 warp_field: &crate::warp::WarpField,
395 reconstruction: &mut [[u8; 3]],
396 width: usize,
397 height: usize,
398 ) {
399 use crate::warp::BicubicSampler;
400
401 let original = reconstruction.to_vec();
402
403 for y in 0..height {
404 for x in 0..width {
405 let (src_x, src_y) = warp_field.warp_backwards(x as u16, y as u16);
406
407 let color = BicubicSampler::sample(&original, width, height, src_x, src_y);
408
409 let idx = y * width + x;
410 reconstruction[idx] = color;
411 }
412 }
413 }
414
415 fn calculate_residual(
417 &self,
418 iff_image: &mut IffImage,
419 original: &[[u8; 3]],
420 reconstruction: &[[u8; 3]],
421 width: usize,
422 height: usize,
423 ) -> Result<()> {
424 let mut residual_pixels = Vec::with_capacity(original.len());
425
426 for (orig, recon) in original.iter().zip(reconstruction.iter()) {
427 let mut pixel = [0i16; 3];
428 let mut has_residual = false;
429
430 for c in 0..3 {
431 let diff = orig[c] as i16 - recon[c] as i16;
432
433 if (diff.abs() as f32) > self.config.residual_threshold {
435 pixel[c] = diff;
436 has_residual = true;
437 }
438 }
439
440 if !has_residual {
441 pixel = [0, 0, 0];
442 }
443
444 residual_pixels.push(pixel);
445 }
446
447 iff_image.residual = Residual::from_dense(width as u32, height as u32, &residual_pixels)?;
448
449 log::info!("Residual compressed size: {} bytes", iff_image.residual.data.len());
450
451 Ok(())
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458
459 #[test]
460 fn test_encoder_config_default() {
461 let config = EncoderConfig::default();
462 assert_eq!(config.wavelet_levels, 5);
463 assert_eq!(config.base_quantization, 10);
464 assert!(config.use_ycocg_420);
465 }
466}