use crate::error::{IffError, Result};
use crate::fixed::Fixed;
use crate::noise::{NoiseParams, PerlinNoise, PpfNoise};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Region {
pub x: u16,
pub y: u16,
pub w: u16,
pub h: u16,
pub seed: u32,
pub chaos_level: u8,
pub scale: u8,
pub persistence: u8,
pub noise_type: NoiseType,
pub base_color: [u8; 3],
pub amplitude: u8,
}
impl Region {
pub fn new(x: u16, y: u16, w: u16, h: u16) -> Self {
Region {
x,
y,
w,
h,
seed: 0,
chaos_level: 4,
scale: 1,
persistence: 128, noise_type: NoiseType::Fbm,
base_color: [128, 128, 128],
amplitude: 64,
}
}
pub fn contains(&self, x: u16, y: u16) -> bool {
x >= self.x && x < self.x + self.w && y >= self.y && y < self.y + self.h
}
pub fn noise_params(&self) -> NoiseParams {
NoiseParams {
seed: self.seed,
octaves: self.chaos_level.min(8),
scale: self.scale.min(7),
persistence: self.persistence as f32 / 255.0,
lacunarity: 2.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum NoiseType {
Fbm = 0,
Turbulence = 1,
Ridged = 2,
Warped = 3,
Cellular = 4,
Perlin = 5,
}
impl Default for NoiseType {
fn default() -> Self {
NoiseType::Fbm
}
}
pub struct TextureSynthesizer {
regions: Vec<Region>,
}
impl TextureSynthesizer {
pub fn new() -> Self {
TextureSynthesizer {
regions: Vec::new(),
}
}
pub fn add_region(&mut self, region: Region) {
self.regions.push(region);
}
pub fn regions(&self) -> &[Region] {
&self.regions
}
pub fn synthesize_pixel(&self, x: u16, y: u16) -> Option<[u8; 3]> {
let region = self.regions.iter().find(|r| r.contains(x, y))?;
let local_x = x - region.x;
let local_y = y - region.y;
Some(self.synthesize_region_pixel(region, local_x, local_y))
}
pub fn synthesize_region_pixel(&self, region: &Region, local_x: u16, local_y: u16) -> [u8; 3] {
let x = Fixed::from(local_x);
let y = Fixed::from(local_y);
let noise_val = match region.noise_type {
NoiseType::Fbm => {
let noise = PpfNoise::new(region.noise_params());
noise.fbm(x, y)
}
NoiseType::Turbulence => {
let noise = PpfNoise::new(region.noise_params());
noise.turbulence(x, y)
}
NoiseType::Ridged => {
let noise = PpfNoise::new(region.noise_params());
noise.ridged(x, y)
}
NoiseType::Warped => {
let noise = PpfNoise::new(region.noise_params());
let warp_strength = Fixed::from_int(2);
noise.warped(x, y, warp_strength)
}
NoiseType::Cellular => {
let noise = PpfNoise::new(region.noise_params());
let cell_size = Fixed::from_int(10);
noise.cellular(x, y, cell_size)
}
NoiseType::Perlin => {
let perlin = PerlinNoise::new(region.seed);
perlin.noise(x, y)
}
};
let noise_centered = noise_val - Fixed::HALF;
let amplitude = Fixed::from_f32(region.amplitude as f32 / 255.0);
let noise_scaled = noise_centered * amplitude * Fixed::from_int(255);
let mut color = [0u8; 3];
for i in 0..3 {
let base = region.base_color[i] as i32;
let modulated = base + noise_scaled.to_int();
color[i] = modulated.clamp(0, 255) as u8;
}
color
}
pub fn synthesize_region(&self, region: &Region, buffer: &mut [[u8; 3]]) -> Result<()> {
if buffer.len() != (region.w as usize * region.h as usize) {
return Err(IffError::Other(
"Buffer size doesn't match region dimensions".to_string(),
));
}
for y in 0..region.h {
for x in 0..region.w {
let idx = (y as usize * region.w as usize) + x as usize;
buffer[idx] = self.synthesize_region_pixel(region, x, y);
}
}
Ok(())
}
}
impl Default for TextureSynthesizer {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "encoder")]
pub struct TextureAnalyzer {
similarity_threshold: f32,
}
#[cfg(feature = "encoder")]
impl TextureAnalyzer {
pub fn new(similarity_threshold: f32) -> Self {
TextureAnalyzer {
similarity_threshold,
}
}
pub fn detect_texture_regions(
&self,
image: &[[u8; 3]],
width: usize,
height: usize,
min_size: usize,
) -> Vec<Region> {
let mut regions = Vec::new();
let region_size = min_size.max(32);
for y in (0..height).step_by(region_size) {
for x in (0..width).step_by(region_size) {
let w = (region_size).min(width - x);
let h = (region_size).min(height - y);
if w < min_size || h < min_size {
continue;
}
let entropy = self.calculate_entropy(image, x, y, w, h, width);
log::debug!("Region ({}, {}) entropy: {}", x, y, entropy);
if entropy > self.similarity_threshold {
let region = Region::new(x as u16, y as u16, w as u16, h as u16);
regions.push(region);
}
}
}
regions
}
fn calculate_entropy(
&self,
image: &[[u8; 3]],
x: usize,
y: usize,
w: usize,
h: usize,
stride: usize,
) -> f32 {
let mut sum = [0f32; 3];
let mut sum_sq = [0f32; 3];
let mut count = 0;
for dy in 0..h {
for dx in 0..w {
let idx = (y + dy) * stride + (x + dx);
if idx < image.len() {
let pixel = image[idx];
for c in 0..3 {
let val = pixel[c] as f32;
sum[c] += val;
sum_sq[c] += val * val;
}
count += 1;
}
}
}
if count == 0 {
return 0.0;
}
let count_f = count as f32;
let mut total_variance = 0.0;
for c in 0..3 {
let mean = sum[c] / count_f;
let variance = (sum_sq[c] / count_f) - (mean * mean);
total_variance += variance;
}
(total_variance / (3.0 * 255.0 * 255.0)).min(1.0)
}
pub fn optimize_region(
&self,
image: &[[u8; 3]],
region: &mut Region,
stride: usize,
max_iterations: usize,
error_threshold: f32,
) -> Result<f32> {
let mut best_seed = 0u32;
let mut best_error = f32::MAX;
let region_pixels = self.extract_region(image, region, stride)?;
let base_color = self.calculate_base_color(®ion_pixels);
region.base_color = base_color;
for iteration in 0..max_iterations {
let seed = iteration as u32 * 12345; region.seed = seed;
let synthesizer = TextureSynthesizer::new();
let mut synth_buffer = vec![[0u8; 3]; region_pixels.len()];
synthesizer.synthesize_region(region, &mut synth_buffer)?;
let error = self.calculate_l2_error(®ion_pixels, &synth_buffer);
if error < best_error {
best_error = error;
best_seed = seed;
}
if error < error_threshold {
break;
}
}
log::debug!("Region optimized: best_error = {}, threshold = {}", best_error, error_threshold);
region.seed = best_seed;
Ok(best_error)
}
fn extract_region(&self, image: &[[u8; 3]], region: &Region, stride: usize) -> Result<Vec<[u8; 3]>> {
let mut pixels = Vec::with_capacity((region.w as usize) * (region.h as usize));
for y in 0..region.h {
for x in 0..region.w {
let img_x = region.x as usize + x as usize;
let img_y = region.y as usize + y as usize;
let idx = img_y * stride + img_x;
if idx < image.len() {
pixels.push(image[idx]);
} else {
pixels.push([0, 0, 0]);
}
}
}
Ok(pixels)
}
fn calculate_base_color(&self, pixels: &[[u8; 3]]) -> [u8; 3] {
let mut sum = [0u32; 3];
for pixel in pixels {
for c in 0..3 {
sum[c] += pixel[c] as u32;
}
}
let count = pixels.len() as u32;
[
(sum[0] / count) as u8,
(sum[1] / count) as u8,
(sum[2] / count) as u8,
]
}
fn calculate_l2_error(&self, original: &[[u8; 3]], synthesized: &[[u8; 3]]) -> f32 {
let mut sum_sq_error = 0.0;
for (orig, synth) in original.iter().zip(synthesized.iter()) {
for c in 0..3 {
let diff = orig[c] as f32 - synth[c] as f32;
sum_sq_error += diff * diff;
}
}
(sum_sq_error / (original.len() as f32 * 3.0)).sqrt()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_region_contains() {
let region = Region::new(10, 20, 100, 50);
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)); }
#[test]
fn test_noise_params() {
let region = Region {
chaos_level: 6,
scale: 2,
persistence: 128,
..Region::new(0, 0, 64, 64)
};
let params = region.noise_params();
assert_eq!(params.octaves, 6);
assert_eq!(params.scale, 2);
assert!((params.persistence - 0.5).abs() < 0.01);
}
#[test]
fn test_synthesizer() {
let mut synth = TextureSynthesizer::new();
let region = Region {
seed: 42,
chaos_level: 4,
base_color: [100, 150, 200],
amplitude: 50,
..Region::new(0, 0, 64, 64)
};
synth.add_region(region);
let pixel = synth.synthesize_pixel(10, 20);
assert!(pixel.is_some());
let color = pixel.unwrap();
assert!(color[0] > 50 && color[0] < 150);
assert!(color[1] > 100 && color[1] < 200);
assert!(color[2] > 150 && color[2] < 250);
}
#[test]
fn test_synthesize_determinism() {
let synth = TextureSynthesizer::new();
let region = Region {
seed: 123,
..Region::new(0, 0, 64, 64)
};
let color1 = synth.synthesize_region_pixel(®ion, 10, 20);
let color2 = synth.synthesize_region_pixel(®ion, 10, 20);
assert_eq!(color1, color2);
}
#[test]
fn test_synthesize_region() {
let synth = TextureSynthesizer::new();
let region = Region::new(0, 0, 16, 16);
let mut buffer = vec![[0u8; 3]; 16 * 16];
let result = synth.synthesize_region(®ion, &mut buffer);
assert!(result.is_ok());
assert!(buffer.iter().any(|&pixel| pixel != [0, 0, 0]));
}
#[cfg(feature = "encoder")]
#[test]
fn test_texture_analyzer() {
let analyzer = TextureAnalyzer::new(0.05);
let width = 64;
let height = 64;
let mut image = vec![[0u8; 3]; width * height];
for y in 0..height {
for x in 0..width {
let idx = y * width + x;
let r = ((x * 2654435761u32 as usize + y * 2246822519u32 as usize) ^ (x * y)) % 256;
let g = ((x * 3266489917u32 as usize + y * 668265263u32 as usize) ^ (x + y)) % 256;
let b = ((x * 374761393u32 as usize + y * 1935289041u32 as usize) ^ (x | y)) % 256;
image[idx] = [r as u8, g as u8, b as u8];
}
}
let regions = analyzer.detect_texture_regions(&image, width, height, 16);
assert!(!regions.is_empty(), "Expected to detect texture regions");
}
#[test]
fn test_different_noise_types() {
let synth = TextureSynthesizer::new();
let noise_types = [
NoiseType::Fbm,
NoiseType::Turbulence,
NoiseType::Ridged,
NoiseType::Warped,
NoiseType::Cellular,
NoiseType::Perlin,
];
for noise_type in &noise_types {
let region = Region {
noise_type: *noise_type,
seed: 42,
..Region::new(0, 0, 64, 64)
};
let color = synth.synthesize_region_pixel(®ion, 10, 20);
assert!(color[0] <= 255);
assert!(color[1] <= 255);
assert!(color[2] <= 255);
}
}
}