tx2_iff/
encoder.rs

1//! Image encoder (Layer composition and optimization)
2//!
3//! The encoder analyzes an input image and decomposes it into three layers:
4//! 1. Wavelet skeleton (sharp edges, text, structure)
5//! 2. Texture synthesis regions (high-entropy content)
6//! 3. Warp fields (geometric deformation)
7//!
8//! Plus a sparse residual for anything the layers can't represent.
9
10use 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/// Encoder configuration
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct EncoderConfig {
20    /// Wavelet decomposition levels
21    pub wavelet_levels: usize,
22    /// Base quantization value
23    pub base_quantization: u16,
24    /// Texture region minimum size
25    pub texture_min_size: usize,
26    /// Texture synthesis iterations
27    pub texture_iterations: usize,
28    /// Residual threshold (RMSE)
29    pub residual_threshold: f32,
30    /// Texture entropy threshold (0.0-1.0)
31    pub texture_entropy_threshold: f32,
32    /// Enable Layer 2 (texture synthesis)
33    pub enable_layer2: bool,
34    /// Enable Layer 3 (warp fields)
35    pub enable_layer3: bool,
36    /// Use YCoCg-R color space with 4:2:0 subsampling
37    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
56/// Image encoder
57pub struct Encoder {
58    config: EncoderConfig,
59}
60
61impl Encoder {
62    /// Create a new encoder with given configuration
63    pub fn new(config: EncoderConfig) -> Self {
64        Encoder { config }
65    }
66
67    /// Encode an image to IFF format
68    #[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        // Convert to working format
75        let image_data: Vec<[u8; 3]> = rgb_image
76            .pixels()
77            .map(|p| [p[0], p[1], p[2]])
78            .collect();
79
80        // Create IFF image structure
81        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        // Layer 1: Wavelet transform
85        log::info!("Encoding Layer 1: Wavelet skeleton");
86        self.encode_layer1(&mut iff_image, &image_data, width, height)?;
87
88        // Reconstruct from Layer 1 to calculate residual
89        let mut reconstruction = self.decode_layer1(&iff_image, width, height)?;
90
91        // Layer 2: Texture synthesis (if enabled)
92        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        // Layer 3: Warp field (if enabled)
98        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        // Calculate final residual
104        log::info!("Calculating residual");
105        self.calculate_residual(&mut iff_image, &image_data, &reconstruction, width, height)?;
106
107        Ok(iff_image)
108    }
109
110    /// Encode Layer 1: Wavelet skeleton
111    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            // Convert to YCoCg
123            let ycocg = rgb_to_ycocg(image_data, width, height);
124            
125            // Subsample chroma
126            let co_sub = subsample_420(&ycocg.co);
127            let cg_sub = subsample_420(&ycocg.cg);
128
129            // Transform Y
130            let mut y_coeffs = transform.forward(&ycocg.y.data, width, height)?;
131            transform.quantize(&mut y_coeffs, width, height, &quantization_table);
132            
133            // Transform Co
134            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            // Transform Cg
138            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            // Store
142            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            // Standard RGB encoding (legacy/lossless mode)
149            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            // We need to fit this into the new Layer1 structure
162            // We'll put R in Y, G in Co, B in Cg (hacky but works for storage)
163            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    /// Decode Layer 1 for residual calculation
174    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            // Decompress coefficients
184            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            // Inverse transform
189            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            // Upsample chroma
194            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            // Convert to RGB
201            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            // Legacy RGB mode
212            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    /// Encode Layer 2: Texture synthesis
233    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        // Detect high-entropy regions
246        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        // Optimize each region
256        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            // Only keep region if it improves compression
266            if error < self.config.residual_threshold {
267                // Apply synthesized texture to reconstruction
268                self.apply_texture_region(&region, reconstruction, width, height);
269
270                // Add to IFF
271                iff_image.layer2.add_region(region);
272            }
273        }
274
275        Ok(())
276    }
277
278    /// Apply a texture region to reconstruction
279    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    /// Encode Layer 3: Warp fields
304    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        // Simple warp field detection: find regions with self-similarity
315        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        // Apply warping to reconstruction
324        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    /// Detect regions that would benefit from warping
332    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        // Simple detection: look for regions with high gradient magnitude
338        // indicating deformation (wrinkles, folds, etc.)
339        let step = 32; // Check every 32 pixels
340
341        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                // High gradient suggests deformation
346                if gradient > 50.0 {
347                    // Create a small vortex at this location
348                    let vortex = Vortex::new(
349                        x as u16,
350                        y as u16,
351                        (gradient as i16).clamp(-1000, 1000),
352                        16, // Small radius
353                        128, // Medium decay
354                    );
355                    vortices.push(vortex);
356                }
357            }
358        }
359
360        // Limit number of vortices to avoid overhead
361        vortices.truncate(50);
362
363        vortices
364    }
365
366    /// Calculate gradient magnitude at a point
367    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    /// Apply warp field to reconstruction
392    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    /// Calculate residual between original and reconstruction
416    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                // Only store if very significant (threshold increased for better compression)
434                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}