🔮 :: Pixel Streaking ver 2

BamgasiJM·2025년 10월 11일

Nannou <Generative Art>

목록 보기
43/55
post-thumbnail

📝 Rust Code

use nannou::prelude::*;
use nannou::image::{DynamicImage, ImageBuffer, Rgba};
use std::sync::Arc;

// ===== 전역 설정 =====
const WINDOW_SIZE: u32 = 800;
const INITIAL_ANGLE: f32 = 90.0;
const INITIAL_THRESHOLD: f32 = 0.3;
const INITIAL_SORT_LENGTH: f32 = 500.0;
const ANGLE_STEP: f32 = 15.0;
const LENGTH_STEP: f32 = 50.0;
const THRESHOLD_STEP: f32 = 0.05;

fn main() {
    nannou::app(model).update(update).run();
}

struct Model {
    original_image: Arc<ImageBuffer<Rgba<u8>, Vec<u8>>>,
    texture: wgpu::Texture,
    angle: f32,
    sort_threshold: f32,
    sort_length: f32,
    needs_update: bool,
}

fn model(app: &App) -> Model {
    app.new_window()
        .size(WINDOW_SIZE, WINDOW_SIZE)
        .view(view)
        .key_pressed(key_pressed)
        .build()
        .unwrap();

    let assets = app.assets_path().unwrap();
    let img_path = assets.join("constellation.png");
    
    let original_image = nannou::image::open(&img_path)
        .expect("이미지 파일을 찾을 수 없습니다.")
        .resize_exact(WINDOW_SIZE, WINDOW_SIZE, nannou::image::imageops::FilterType::Lanczos3)
        .to_rgba8();

    let dynamic_img = DynamicImage::ImageRgba8(original_image.clone());
    let texture = wgpu::Texture::from_image(app, &dynamic_img);

    Model {
        original_image: Arc::new(original_image),
        texture,
        angle: INITIAL_ANGLE,
        sort_threshold: INITIAL_THRESHOLD,
        sort_length: INITIAL_SORT_LENGTH,
        needs_update: false,
    }
}

fn update(app: &App, model: &mut Model, _update: Update) {
    if !model.needs_update {
        return;
    }
    
    let sorted_img = pixel_sort_angle_optimized(
        &model.original_image,
        model.angle,
        model.sort_threshold,
        model.sort_length,
    );
    
    let dynamic_sorted = DynamicImage::ImageRgba8(sorted_img);
    model.texture = wgpu::Texture::from_image(app, &dynamic_sorted);
    
    model.needs_update = false;
}

fn pixel_sort_angle_optimized(
    image: &ImageBuffer<Rgba<u8>, Vec<u8>>,
    angle: f32,
    threshold: f32,
    sort_length: f32,
) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
    let (width, height) = image.dimensions();
    let mut output = image.clone();
    
    let angle_rad = angle.to_radians();
    let cos_a = angle_rad.cos();
    let sin_a = angle_rad.sin();
    
    let threshold_u8 = (threshold * 255.0 * 3.0) as u32;
    let sort_length = sort_length as i32;
    
    // 병렬 처리를 위한 청크 단위 처리
    let chunk_size = 4;
    
    for y_chunk in (0..height).step_by(chunk_size) {
        for x_chunk in (0..width).step_by(chunk_size) {
            for y in y_chunk..std::cmp::min(y_chunk + chunk_size as u32, height) {
                for x in x_chunk..std::cmp::min(x_chunk + chunk_size as u32, width) {
                    let pixel = image.get_pixel(x, y);
                    let brightness_sum = pixel[0] as u32 + pixel[1] as u32 + pixel[2] as u32;
                    
                    if brightness_sum > threshold_u8 {
                        let brightness_factor = brightness_sum as f32 / (255.0 * 3.0);
                        let streak_len = (sort_length as f32 * brightness_factor) as i32;
                        
                        // 스트리킹 길이 제한으로 성능 향상
                        let streak_len = streak_len.min(1000);
                        
                        for i in 1..streak_len {
                            let nx = x as i32 + (i as f32 * cos_a) as i32;
                            let ny = y as i32 + (i as f32 * sin_a) as i32;
                            
                            if nx >= 0 && nx < width as i32 && ny >= 0 && ny < height as i32 {
                                let fade = 1.0 - (i as f32 / streak_len as f32);
                                let fade_sq = fade * fade; // 비선형 페이드
                                
                                let nx = nx as u32;
                                let ny = ny as u32;
                                let current = output.get_pixel(nx, ny);
                                
                                // 더 빠른 블렌딩 계산
                                let blend_factor = fade_sq * 0.5;
                                output.put_pixel(nx, ny, Rgba([
                                    ((current[0] as f32 * (1.0 - blend_factor) + pixel[0] as f32 * blend_factor) as u8),
                                    ((current[1] as f32 * (1.0 - blend_factor) + pixel[1] as f32 * blend_factor) as u8),
                                    ((current[2] as f32 * (1.0 - blend_factor) + pixel[2] as f32 * blend_factor) as u8),
                                    255,
                                ]));
                            }
                        }
                    }
                }
            }
        }
    }
    
    output
}

fn key_pressed(_app: &App, model: &mut Model, key: Key) {
    let changed = match key {
        Key::Up => {
            model.angle += ANGLE_STEP;
            true
        }
        Key::Down => {
            model.angle -= ANGLE_STEP;
            true
        }
        Key::Right => {
            model.sort_length += LENGTH_STEP;
            true
        }
        Key::Left => {
            model.sort_length = (model.sort_length - LENGTH_STEP).max(1.0);
            true
        }
        Key::W => {
            model.sort_threshold = (model.sort_threshold + THRESHOLD_STEP).min(1.0);
            true
        }
        Key::S => {
            model.sort_threshold = (model.sort_threshold - THRESHOLD_STEP).max(0.0);
            true
        }
        Key::R => {
            // 리셋
            model.angle = INITIAL_ANGLE;
            model.sort_threshold = INITIAL_THRESHOLD;
            model.sort_length = INITIAL_SORT_LENGTH;
            true
        }
        _ => false
    };
    
    if changed {
        model.needs_update = true;
        println!("각도: {:.0}°, 길이: {:.0}, 임계값: {:.2}", 
                 model.angle, model.sort_length, model.sort_threshold);
    }
}

fn view(app: &App, model: &Model, frame: Frame) {
    let draw = app.draw();
    draw.background().color(BLACK);
    
    draw.texture(&model.texture);
    
    draw.to_frame(app, &frame).unwrap();
}

profile
Coding Art with Blender / oF / Processing / p5.js / nannou

0개의 댓글