1use crate::error::{IffError, Result};
26use crate::fixed::Fixed;
27use crate::noise::{NoiseParams, PerlinNoise, PpfNoise};
28use serde::{Deserialize, Serialize};
29
30#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
32pub struct Region {
33 pub x: u16,
35 pub y: u16,
37 pub w: u16,
39 pub h: u16,
41 pub seed: u32,
43 pub chaos_level: u8,
45 pub scale: u8,
47 pub persistence: u8,
49 pub noise_type: NoiseType,
51 pub base_color: [u8; 3],
53 pub amplitude: u8,
55}
56
57impl Region {
58 pub fn new(x: u16, y: u16, w: u16, h: u16) -> Self {
60 Region {
61 x,
62 y,
63 w,
64 h,
65 seed: 0,
66 chaos_level: 4,
67 scale: 1,
68 persistence: 128, noise_type: NoiseType::Fbm,
70 base_color: [128, 128, 128],
71 amplitude: 64,
72 }
73 }
74
75 pub fn contains(&self, x: u16, y: u16) -> bool {
77 x >= self.x && x < self.x + self.w && y >= self.y && y < self.y + self.h
78 }
79
80 pub fn noise_params(&self) -> NoiseParams {
82 NoiseParams {
83 seed: self.seed,
84 octaves: self.chaos_level.min(8),
85 scale: self.scale.min(7),
86 persistence: self.persistence as f32 / 255.0,
87 lacunarity: 2.0,
88 }
89 }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
94#[repr(u8)]
95pub enum NoiseType {
96 Fbm = 0,
98 Turbulence = 1,
100 Ridged = 2,
102 Warped = 3,
104 Cellular = 4,
106 Perlin = 5,
108}
109
110impl Default for NoiseType {
111 fn default() -> Self {
112 NoiseType::Fbm
113 }
114}
115
116pub struct TextureSynthesizer {
118 regions: Vec<Region>,
120}
121
122impl TextureSynthesizer {
123 pub fn new() -> Self {
125 TextureSynthesizer {
126 regions: Vec::new(),
127 }
128 }
129
130 pub fn add_region(&mut self, region: Region) {
132 self.regions.push(region);
133 }
134
135 pub fn regions(&self) -> &[Region] {
137 &self.regions
138 }
139
140 pub fn synthesize_pixel(&self, x: u16, y: u16) -> Option<[u8; 3]> {
142 let region = self.regions.iter().find(|r| r.contains(x, y))?;
144
145 let local_x = x - region.x;
147 let local_y = y - region.y;
148
149 Some(self.synthesize_region_pixel(region, local_x, local_y))
151 }
152
153 pub fn synthesize_region_pixel(&self, region: &Region, local_x: u16, local_y: u16) -> [u8; 3] {
155 let x = Fixed::from(local_x);
157 let y = Fixed::from(local_y);
158
159 let noise_val = match region.noise_type {
161 NoiseType::Fbm => {
162 let noise = PpfNoise::new(region.noise_params());
163 noise.fbm(x, y)
164 }
165 NoiseType::Turbulence => {
166 let noise = PpfNoise::new(region.noise_params());
167 noise.turbulence(x, y)
168 }
169 NoiseType::Ridged => {
170 let noise = PpfNoise::new(region.noise_params());
171 noise.ridged(x, y)
172 }
173 NoiseType::Warped => {
174 let noise = PpfNoise::new(region.noise_params());
175 let warp_strength = Fixed::from_int(2);
176 noise.warped(x, y, warp_strength)
177 }
178 NoiseType::Cellular => {
179 let noise = PpfNoise::new(region.noise_params());
180 let cell_size = Fixed::from_int(10);
181 noise.cellular(x, y, cell_size)
182 }
183 NoiseType::Perlin => {
184 let perlin = PerlinNoise::new(region.seed);
185 perlin.noise(x, y)
186 }
187 };
188
189 let noise_centered = noise_val - Fixed::HALF;
191
192 let amplitude = Fixed::from_f32(region.amplitude as f32 / 255.0);
194 let noise_scaled = noise_centered * amplitude * Fixed::from_int(255);
195
196 let mut color = [0u8; 3];
198 for i in 0..3 {
199 let base = region.base_color[i] as i32;
200 let modulated = base + noise_scaled.to_int();
201 color[i] = modulated.clamp(0, 255) as u8;
202 }
203
204 color
205 }
206
207 pub fn synthesize_region(&self, region: &Region, buffer: &mut [[u8; 3]]) -> Result<()> {
209 if buffer.len() != (region.w as usize * region.h as usize) {
210 return Err(IffError::Other(
211 "Buffer size doesn't match region dimensions".to_string(),
212 ));
213 }
214
215 for y in 0..region.h {
216 for x in 0..region.w {
217 let idx = (y as usize * region.w as usize) + x as usize;
218 buffer[idx] = self.synthesize_region_pixel(region, x, y);
219 }
220 }
221
222 Ok(())
223 }
224}
225
226impl Default for TextureSynthesizer {
227 fn default() -> Self {
228 Self::new()
229 }
230}
231
232#[cfg(feature = "encoder")]
234pub struct TextureAnalyzer {
235 similarity_threshold: f32,
237}
238
239#[cfg(feature = "encoder")]
240impl TextureAnalyzer {
241 pub fn new(similarity_threshold: f32) -> Self {
243 TextureAnalyzer {
244 similarity_threshold,
245 }
246 }
247
248 pub fn detect_texture_regions(
250 &self,
251 image: &[[u8; 3]],
252 width: usize,
253 height: usize,
254 min_size: usize,
255 ) -> Vec<Region> {
256 let mut regions = Vec::new();
257
258 let region_size = min_size.max(32);
260
261 for y in (0..height).step_by(region_size) {
262 for x in (0..width).step_by(region_size) {
263 let w = (region_size).min(width - x);
264 let h = (region_size).min(height - y);
265
266 if w < min_size || h < min_size {
267 continue;
268 }
269
270 let entropy = self.calculate_entropy(image, x, y, w, h, width);
272
273 log::debug!("Region ({}, {}) entropy: {}", x, y, entropy);
274
275 if entropy > self.similarity_threshold {
278 let region = Region::new(x as u16, y as u16, w as u16, h as u16);
279 regions.push(region);
280 }
281 }
282 }
283
284 regions
285 }
286
287 fn calculate_entropy(
289 &self,
290 image: &[[u8; 3]],
291 x: usize,
292 y: usize,
293 w: usize,
294 h: usize,
295 stride: usize,
296 ) -> f32 {
297 let mut sum = [0f32; 3];
299 let mut sum_sq = [0f32; 3];
300 let mut count = 0;
301
302 for dy in 0..h {
303 for dx in 0..w {
304 let idx = (y + dy) * stride + (x + dx);
305 if idx < image.len() {
306 let pixel = image[idx];
307 for c in 0..3 {
308 let val = pixel[c] as f32;
309 sum[c] += val;
310 sum_sq[c] += val * val;
311 }
312 count += 1;
313 }
314 }
315 }
316
317 if count == 0 {
318 return 0.0;
319 }
320
321 let count_f = count as f32;
323 let mut total_variance = 0.0;
324
325 for c in 0..3 {
326 let mean = sum[c] / count_f;
327 let variance = (sum_sq[c] / count_f) - (mean * mean);
328 total_variance += variance;
329 }
330
331 (total_variance / (3.0 * 255.0 * 255.0)).min(1.0)
333 }
334
335 pub fn optimize_region(
337 &self,
338 image: &[[u8; 3]],
339 region: &mut Region,
340 stride: usize,
341 max_iterations: usize,
342 error_threshold: f32,
343 ) -> Result<f32> {
344 let mut best_seed = 0u32;
345 let mut best_error = f32::MAX;
346
347 let region_pixels = self.extract_region(image, region, stride)?;
349
350 let base_color = self.calculate_base_color(®ion_pixels);
352 region.base_color = base_color;
353
354 for iteration in 0..max_iterations {
356 let seed = iteration as u32 * 12345; region.seed = seed;
358
359 let synthesizer = TextureSynthesizer::new();
361 let mut synth_buffer = vec![[0u8; 3]; region_pixels.len()];
362 synthesizer.synthesize_region(region, &mut synth_buffer)?;
363
364 let error = self.calculate_l2_error(®ion_pixels, &synth_buffer);
366
367 if error < best_error {
368 best_error = error;
369 best_seed = seed;
370 }
371
372 if error < error_threshold {
374 break;
375 }
376 }
377
378 log::debug!("Region optimized: best_error = {}, threshold = {}", best_error, error_threshold);
379
380 region.seed = best_seed;
381 Ok(best_error)
382 }
383
384 fn extract_region(&self, image: &[[u8; 3]], region: &Region, stride: usize) -> Result<Vec<[u8; 3]>> {
386 let mut pixels = Vec::with_capacity((region.w as usize) * (region.h as usize));
387
388 for y in 0..region.h {
389 for x in 0..region.w {
390 let img_x = region.x as usize + x as usize;
391 let img_y = region.y as usize + y as usize;
392 let idx = img_y * stride + img_x;
393
394 if idx < image.len() {
395 pixels.push(image[idx]);
396 } else {
397 pixels.push([0, 0, 0]);
398 }
399 }
400 }
401
402 Ok(pixels)
403 }
404
405 fn calculate_base_color(&self, pixels: &[[u8; 3]]) -> [u8; 3] {
407 let mut sum = [0u32; 3];
408
409 for pixel in pixels {
410 for c in 0..3 {
411 sum[c] += pixel[c] as u32;
412 }
413 }
414
415 let count = pixels.len() as u32;
416 [
417 (sum[0] / count) as u8,
418 (sum[1] / count) as u8,
419 (sum[2] / count) as u8,
420 ]
421 }
422
423 fn calculate_l2_error(&self, original: &[[u8; 3]], synthesized: &[[u8; 3]]) -> f32 {
425 let mut sum_sq_error = 0.0;
426
427 for (orig, synth) in original.iter().zip(synthesized.iter()) {
428 for c in 0..3 {
429 let diff = orig[c] as f32 - synth[c] as f32;
430 sum_sq_error += diff * diff;
431 }
432 }
433
434 (sum_sq_error / (original.len() as f32 * 3.0)).sqrt()
435 }
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441
442 #[test]
443 fn test_region_contains() {
444 let region = Region::new(10, 20, 100, 50);
445
446 assert!(region.contains(10, 20)); assert!(region.contains(109, 69)); assert!(region.contains(50, 40)); assert!(!region.contains(9, 20)); assert!(!region.contains(110, 40)); assert!(!region.contains(50, 19)); assert!(!region.contains(50, 70)); }
455
456 #[test]
457 fn test_noise_params() {
458 let region = Region {
459 chaos_level: 6,
460 scale: 2,
461 persistence: 128,
462 ..Region::new(0, 0, 64, 64)
463 };
464
465 let params = region.noise_params();
466 assert_eq!(params.octaves, 6);
467 assert_eq!(params.scale, 2);
468 assert!((params.persistence - 0.5).abs() < 0.01);
469 }
470
471 #[test]
472 fn test_synthesizer() {
473 let mut synth = TextureSynthesizer::new();
474
475 let region = Region {
476 seed: 42,
477 chaos_level: 4,
478 base_color: [100, 150, 200],
479 amplitude: 50,
480 ..Region::new(0, 0, 64, 64)
481 };
482
483 synth.add_region(region);
484
485 let pixel = synth.synthesize_pixel(10, 20);
487 assert!(pixel.is_some());
488
489 let color = pixel.unwrap();
490 assert!(color[0] > 50 && color[0] < 150);
492 assert!(color[1] > 100 && color[1] < 200);
493 assert!(color[2] > 150 && color[2] < 250);
494 }
495
496 #[test]
497 fn test_synthesize_determinism() {
498 let synth = TextureSynthesizer::new();
499
500 let region = Region {
501 seed: 123,
502 ..Region::new(0, 0, 64, 64)
503 };
504
505 let color1 = synth.synthesize_region_pixel(®ion, 10, 20);
507 let color2 = synth.synthesize_region_pixel(®ion, 10, 20);
508
509 assert_eq!(color1, color2);
510 }
511
512 #[test]
513 fn test_synthesize_region() {
514 let synth = TextureSynthesizer::new();
515
516 let region = Region::new(0, 0, 16, 16);
517 let mut buffer = vec![[0u8; 3]; 16 * 16];
518
519 let result = synth.synthesize_region(®ion, &mut buffer);
520 assert!(result.is_ok());
521
522 assert!(buffer.iter().any(|&pixel| pixel != [0, 0, 0]));
524 }
525
526 #[cfg(feature = "encoder")]
527 #[test]
528 fn test_texture_analyzer() {
529 let analyzer = TextureAnalyzer::new(0.05); let width = 64;
533 let height = 64;
534 let mut image = vec![[0u8; 3]; width * height];
535
536 for y in 0..height {
538 for x in 0..width {
539 let idx = y * width + x;
540 let r = ((x * 2654435761u32 as usize + y * 2246822519u32 as usize) ^ (x * y)) % 256;
542 let g = ((x * 3266489917u32 as usize + y * 668265263u32 as usize) ^ (x + y)) % 256;
543 let b = ((x * 374761393u32 as usize + y * 1935289041u32 as usize) ^ (x | y)) % 256;
544 image[idx] = [r as u8, g as u8, b as u8];
545 }
546 }
547
548 let regions = analyzer.detect_texture_regions(&image, width, height, 16);
549
550 assert!(!regions.is_empty(), "Expected to detect texture regions");
552 }
553
554 #[test]
555 fn test_different_noise_types() {
556 let synth = TextureSynthesizer::new();
557
558 let noise_types = [
559 NoiseType::Fbm,
560 NoiseType::Turbulence,
561 NoiseType::Ridged,
562 NoiseType::Warped,
563 NoiseType::Cellular,
564 NoiseType::Perlin,
565 ];
566
567 for noise_type in &noise_types {
568 let region = Region {
569 noise_type: *noise_type,
570 seed: 42,
571 ..Region::new(0, 0, 64, 64)
572 };
573
574 let color = synth.synthesize_region_pixel(®ion, 10, 20);
575
576 assert!(color[0] <= 255);
578 assert!(color[1] <= 255);
579 assert!(color[2] <= 255);
580 }
581 }
582}