

📝 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();
}