Malerwerkst.at

Scale the Map!

Introduction

R.


Rust

Scale the Map!

Posted by R. on .
Featured

Rust

Scale the Map!

Posted by R. on .

Als wir uns an die bildhafte Darstellung von Santa's Path gewagt hatten, mussten wir am Ende feststellen, dass das fertige Bild recht klein war (118x92). Daher habe ich nach einer Möglichkeit gesucht, dass Bild zu skalieren - wir wollen ja schließlich auch was erkennen.
Zunächst galt es einen (einfachen) Algorithmus zu finden. Ein solcher wäre der "Nearest Neighbor" der hier ganz gut beschrieben wird. Die Umsetzung in Rust, die auf dem bestehenden Code zu Santa's Path aufbaut, könnte dann so aussehen:

const SCALE_FACTOR:f32 = 10.0;

[...]

let mut dst_imagebuffer: RgbImage = ImageBuffer::new(maxx * SCALE_FACTOR as u32, maxy * SCALE_FACTOR as u32);

let x_ratio:f32 = maxx as f32 / (maxx as f32 * SCALE_FACTOR);  
let y_ratio:f32 = maxy as f32 / (maxy as f32 * SCALE_FACTOR);

for (x, y, pixel) in dst_imagebuffer.enumerate_pixels_mut() {  
    let px=(x as f32 *x_ratio) as u32;
    let py=(y as f32 *y_ratio) as u32;
    *pixel=*src_imagebuffer.get_pixel(px,py);
}
dst_imagebuffer.save("output_dst.png").unwrap();  

Danach habe ich mich aber noch ein wenig näher mit dem image-crate der Piston-Entwickler beschäftigt und festgestellt, dass diese bereits verschiedene Algorithmen zum skalieren von Bildern quasi von Haus aus anbieten. Und da wir das crate eh schon eingebunden haben, können wir die Funktionen ja auch gleich nutzen.

Garniert mit der Abfrage von Benutzereingaben (Skalierungsfaktor, Auswahl des Filters) könnte der fertige Code dann so aussehen:

extern crate image;

use std::fs::File;  
use std::io::{self, Read, Write};

use std::collections::HashSet;

use image::{Rgba, DynamicImage, GenericImage};

fn get_data(fname: &str) -> String {  
    let mut file = match File::open(fname) {
        Err(e) => panic!("file error: {}",e),
        Ok(file) => file,
    };

    let mut data = String::new();

    match file.read_to_string(&mut data) {
        Err(e) => panic!("read error: {}", e),
        Ok(_) => {},
    }
    data
}

fn get_scale_factor() -> u32 {  
    let mut s = String::new();

    print!("Bitte den Scale Factor eingeben [5]: ");
    io::stdout().flush().unwrap();
    io::stdin().read_line(&mut s).unwrap();

    match s.trim_right().parse::<u32>() {
        Ok(i) => i,
        Err(_) => 5,
    }
}

fn get_filter_type() -> image::FilterType {  
    let mut s = String::new();

    println!("Bitte einen Filter für die Scalierung auswählen:");
    println!("  1) Nearest Neighbor");
    println!("  2) Linear Filter");
    println!("  3) Cubic Filter");
    println!("  4) Gaussian Filter");
    println!("  5) Lanczos with window 3");
    print!("Ihre Wahl (1-5) [1]: ");
    io::stdout().flush().unwrap();

    io::stdin().read_line(&mut s).unwrap();

    match s.trim_right().parse::<u32>() {
        Ok(i) => match i {
            2 => image::FilterType::Triangle,
            3 => image::FilterType::CatmullRom,
            4 => image::FilterType::Gaussian,
            5 => image::FilterType::Lanczos3,
            _ => image::FilterType::Nearest,
        },
        Err(_) => image::FilterType::Nearest,
    }
}

fn main() {  
    let mut houses = HashSet::new();
    let mut pos=(0,0);

    let data=get_data("input.txt");

    for ch in data.chars() {
        match ch {
            '^' => pos.1+=1,
            '>' => pos.0+=1,
            'v' => pos.1-=1,
            '<' => pos.0-=1,
            _ => {},
        }
        houses.insert(pos);
    }

    let mut xpos:Vec<i32> = Vec::new();
    let mut ypos:Vec<i32> = Vec::new();

    for h in &houses {
        xpos.push(h.0);
        ypos.push(h.1);
    }

    let minx = xpos.iter().min().unwrap().abs();
    let miny = ypos.iter().min().unwrap().abs();

    let maxx = (xpos.iter().max().unwrap() + minx + 1) as u32;
    let maxy = (ypos.iter().max().unwrap() + miny + 1) as u32;

    println!("Die Originalbildgröße beträgt {} x {}",maxx,maxy);
    print!("Soll das Bild scaliert werden? (j/n) [n]: ");
    io::stdout().flush().unwrap();

    let mut s = String::new();
    io::stdin().read_line(&mut s).unwrap();
    let scale_factor = match s.trim() {
        "j"|"J"|"y"|"Y" => get_scale_factor(),
        _ => 1
    };

    let mut out_image=DynamicImage::new_rgb8(maxx,maxy);

    for h in houses {
        out_image.put_pixel((h.0 + minx) as u32, (h.1 + miny) as u32,Rgba([240,240,240,255]));
    }

    let ref mut out_f=File::create("output.png").unwrap();
    if scale_factor != 1 {
        let filter_type = get_filter_type();
        let _ = out_image.resize(maxx * scale_factor, maxy * scale_factor, filter_type).save(out_f,image::PNG); 
    } else {
        let _ = out_image.save(out_f,image::PNG);
    }
}

Und ein skaliertes Bild mit den Standardwerten dann so:

Anregungen? Kritik? Verbesserungsvorschläge? Dann ab damit in die Kommentare!

R.

View Comments...