<commit_before>extern crate find_folder;

use ::screen;
use ::screen::Screen;
use ::events;
use ::object;

use piston_window;
use piston_window::Transformed;

use gfx_device_gl;

pub struct Game {
    window: piston_window::PistonWindow,
    screens: Vec<Box<screen::Screen>>,
    events: events::Events,
    player: object::Object,
}

// TODO: Remove
fn load_sprite(window: &piston_window::PistonWindow, name: &str) -> piston_window::Texture<gfx_device_gl::Resources> {
    // TODO: Create and use default sprite in case of failure during loading?
    let assets = find_folder::Search::ParentsThenKids(3, 3).for_folder("assets").unwrap();
    let sprite = assets.join(name);
    piston_window::Texture::from_path(&mut *window.factory.borrow_mut(), &sprite, piston_window::Flip::None, &piston_window::TextureSettings::new()).unwrap()
}

impl Game {
    pub fn new() -> Game {
        let window: piston_window::PistonWindow = piston_window::WindowSettings::new("rrthozopsi", [600, 600]).exit_on_esc(true).build().unwrap();
        let sprite = load_sprite(&window, "skeleton.png");
        Game {
            window: window,
            screens: Vec::new(),
            events: events::Events::new(),
            player: object::Object::new(Some(sprite)),
        }
    }

    pub fn run(&mut self) {
        use piston_window::Event;

        // TODO: Remove clone?
        for window in &mut self.window {
            match window.event {
                Some(Event::Update(args)) => { window.on_update(&args); }
                Some(Event::Render(args)) => { window.on_draw(&args); }
                Some(Event::Input(input)) => { window.on_input(&input); }
                _ => {}
            }
        }
    }

    pub fn on_update(&mut self, args: &piston_window::UpdateArgs) {
        if self.events.is_up()   { self.player.mov(0.0, -150.0 * args.dt); }
        if self.events.is_down() { self.player.mov(0.0, 150.0 * args.dt); }
        if self.events.is_left() { self.player.mov(-150.0 * args.dt, 0.0); }
        if self.events.is_right() { self.player.mov(150.0 * args.dt, 0.0); }
    }

    pub fn on_draw(&mut self, args: &piston_window::RenderArgs, window: &piston_window::PistonWindow) {
        window.draw_2d(|context, graphics| {
            piston_window::clear([0.0, 0.0, 0.0, 1.0], graphics);

            let center = context.transform.trans((args.width / 2) as f64, (args.height / 2) as f64);
            self.player.render(graphics, &center);
        });
    }

    pub fn on_input(&mut self, input: &piston_window::Input) {
        self.events.process_input(&input);
    }
}
<commit_msg>'Work in progress'<commit_after>extern crate find_folder;

use ::screen;
use ::screen::Screen;
use ::events;
use ::object;

use piston_window;
use piston_window::Transformed;

use gfx_device_gl;

pub struct Game {
    window: piston_window::PistonWindow,
    screens: Vec<Box<screen::Screen>>,
    events: events::Events,
    player: object::Object,
}

// TODO: Remove
fn load_sprite(window: &piston_window::PistonWindow, name: &str) -> piston_window::Texture<gfx_device_gl::Resources> {
    // TODO: Create and use default sprite in case of failure during loading?
    let assets = find_folder::Search::ParentsThenKids(3, 3).for_folder("assets").unwrap();
    let sprite = assets.join(name);
    piston_window::Texture::from_path(&mut *window.factory.borrow_mut(), &sprite, piston_window::Flip::None, &piston_window::TextureSettings::new()).unwrap()
}

impl Game {
    pub fn new() -> Game {
        let window: piston_window::PistonWindow = piston_window::WindowSettings::new("rrthozopsi", [600, 600]).exit_on_esc(true).build().unwrap();
        let sprite = load_sprite(&window, "skeleton.png");
        Game {
            window: window,
            screens: Vec::new(),
            events: events::Events::new(),
            player: object::Object::new(Some(sprite)),
        }
    }

    pub fn run(&mut self) {
        use piston_window::Event;

        let mut screen = self.screens.last().unwrap();

        // TODO: Remove clone?
        for window in &mut self.window {
            match window.event {
                Some(Event::Update(args)) => { screen.on_update(&args); }
                Some(Event::Render(args)) => { screen.on_draw(&args, &window); }
                Some(Event::Input(input)) => { screen.on_input(&input); }
                _ => {}
            }
        }
    }

    pub fn on_update(&mut self, args: &piston_window::UpdateArgs) {
        if self.events.is_up()   { self.player.mov(0.0, -150.0 * args.dt); }
        if self.events.is_down() { self.player.mov(0.0, 150.0 * args.dt); }
        if self.events.is_left() { self.player.mov(-150.0 * args.dt, 0.0); }
        if self.events.is_right() { self.player.mov(150.0 * args.dt, 0.0); }
    }

    pub fn on_draw(&mut self, args: &piston_window::RenderArgs, window: &piston_window::PistonWindow) {
        window.draw_2d(|context, graphics| {
            piston_window::clear([0.0, 0.0, 0.0, 1.0], graphics);

            let center = context.transform.trans((args.width / 2) as f64, (args.height / 2) as f64);
            self.player.render(graphics, &center);
        });
    }

    pub fn on_input(&mut self, input: &piston_window::Input) {
        self.events.process_input(&input);
    }
}
<|endoftext|>


<commit_before>//! Defines the trait that must be implemented by ISPC task execution systems
//! and provides a default threaded one for use.

use libc;
use num_cpus;

use std::mem;
use std::cell::RefCell;
use std::sync::{Arc, RwLock, Mutex};
use std::sync::atomic::{self, AtomicUsize};
use std::thread::{self, JoinHandle};

use task::{ISPCTaskFn, Context};

/// Trait to be implemented to provide ISPC task execution functionality.
///
/// The runtime [required functions](http://ispc.github.io/ispc.html#task-parallelism-runtime-requirements)
/// will be forwarded directly to your struct, making this interface unsafe.
pub trait TaskSystem {
    /// Alloc is called when memory must be allocated to store parameters to pass to a task
    /// and must return a pointer to an allocation of `size` bytes aligned to `align`.
    ///
    /// The `handle_ptr` will be `NULL` if this is the first time launch has been called in
    /// the function or is the first launch call after an explicit `sync` statement. Both
    /// situations should be treated equivalently as creating a new exeuction context for tasks.
    /// The `handle_ptr` should be set to some context tracking facility so that you can later
    /// track task groups launched in the context and perform finer grained synchronization in
    /// `sync`.
    unsafe fn alloc(&self, handle_ptr: *mut *mut libc::c_void, size: i64, align: i32) -> *mut libc::c_void;
    /// Launch is called when a new group of tasks is being launched and should schedule them to
    /// be executed in some way.
    ///
    /// The `handle_ptr` will point to the same handle you set up in `alloc` and can be used to
    /// associate groups of tasks with a context of execution as mentioned before. The function `f`
    /// should be executed `count0 * count1 * count2` times and indices passed to the function
    /// should be as if running in a nested for loop:
    ///
    /// ```ignore
    /// let total_tasks = count0 * count1 * count2;
    /// for z in 0..count2 {
    ///     for y in 0..count1 {
    ///         for x in 0..count0 {
    ///             let task_id = x + y * count0 + z * count0 * count1;
    ///             f(data, thread_id, total_threads, task_id, total_tasks,
    ///               x, y, z, count0, count1, count2);
    ///         }
    ///     }
    /// }
    /// ```
    ///
    /// The `data` pointer points to the ISPC task specific parameter pointer and should be passed
    /// through to the function.
    unsafe fn launch(&self, handle_ptr: *mut *mut libc::c_void, f: ISPCTaskFn, data: *mut libc::c_void,
                     count0: i32, count1: i32, count2: i32);
    /// Synchronize an execution context with the tasks it's launched. Use `handle` to determine
    /// the task context being synchronized.
    ///
    /// This function should not return until all tasks launched within the context being
    /// synchronized with have been completed. You can use the `handle` to determine which context
    /// is being synchronized with and thus which tasks must be completed before returning.
    unsafe fn sync(&self, handle: *mut libc::c_void);
}

// Thread local storage to store the thread's id, otherwise we don't know
// who we are in sync. The thread id starts at an invalid value but will be set
// upon thread launch
thread_local!(static THREAD_ID: RefCell<usize> = RefCell::new(0));

/// A multithreaded execution environment for the tasks.
pub struct Parallel {
    context_list: RwLock<Vec<Arc<Context>>>,
    next_context_id: AtomicUsize,
    threads: Mutex<Vec<JoinHandle<()>>>,
    chunk_size: i32,
}

impl Parallel {
    pub fn new() -> Parallel {
        let par = Parallel { context_list: RwLock::new(Vec::new()),
                             next_context_id: AtomicUsize::new(0),
                             threads: Mutex::new(Vec::new()),
                             chunk_size: 8 };
        {
            let mut threads = par.threads.lock().unwrap();
            let num_threads = num_cpus::get();
            let chunk_size = par.chunk_size;
            println!("Launching {} threads", num_threads);
            for i in 0..num_threads {
                // Note that the spawned thread ids start at 1 since the main thread is 0
                threads.push(thread::spawn(move || worker_thread(i + 1, num_threads + 1, chunk_size)));
            }
        }
        par
    }
}

impl TaskSystem for Parallel {
    unsafe fn alloc(&self, handle_ptr: *mut *mut libc::c_void, size: i64, align: i32) -> *mut libc::c_void {
        println!("ISPCAlloc, size: {}, align: {}", size, align);
        // If the handle is null this is the first time this function has spawned tasks
        // and we should create a new Context structure in the TASK_LIST for it, otherwise
        // it's the pointer to where we should append the new Group
        let mut context_list = self.context_list.write().unwrap();
        let context = if (*handle_ptr).is_null() {
            println!("handle ptr is null");
            // This is a bit hairy. We allocate the new task context in a box, then
            // unbox it into a raw ptr to get a ptr we can pass back to ISPC through
            // the handle_ptr and then re-box it into our TASK_LIST so it will
            // be free'd properly when we erase it from the vector in ISPCSync
            let c = Arc::new(Context::new(self.next_context_id.fetch_add(1, atomic::Ordering::SeqCst)));
            {
                let h = &*c;
                *handle_ptr = mem::transmute(h);
            }
            context_list.push(c);
            context_list.last_mut().unwrap()
        } else {
            // TODO: I'm not sure if it'd be worth optimizing this path to just require a read lock
            println!("handle ptr is not null");
            let handle_ctx: *mut Context = mem::transmute(*handle_ptr);
            context_list.iter_mut().find(|c| (*handle_ctx).id == c.id).unwrap()
        };
        println!("context.id = {}", context.id);
        context.alloc(size as usize, align as usize)
    }
    unsafe fn launch(&self, handle_ptr: *mut *mut libc::c_void, f: ISPCTaskFn, data: *mut libc::c_void,
                     count0: i32, count1: i32, count2: i32) {
        // Push the tasks being launched on to the list of task groups for this function
        let context: &mut Context = mem::transmute(*handle_ptr);
        println!("ISPCLaunch, context.id = {}, counts: [{}, {}, {}]", context.id, count0, count1, count2);
        context.launch((count0, count1, count2), data, f);
        // Unpark any sleeping threads since we have jobs for them
        let threads = self.threads.lock().unwrap();
        for t in threads.iter() {
            t.thread().unpark();
        }
    }
    unsafe fn sync(&self, handle: *mut libc::c_void) {
        let context: &mut Context = mem::transmute(handle);
        let thread = THREAD_ID.with(|f| *f.borrow());
        let total_threads = num_cpus::get();
        println!("Thread {} is syncing", thread);
        // Make sure all tasks are done, and execute them if not for this simple
        // serial version. TODO: In the future we'd wait on each Group's semaphore or atomic bool
        // Maybe the waiting thread could help execute tasks as well, otherwise it might be
        // possible to deadlock, where all threads are waiting for some enqueue'd tasks but no
        // threads are available to run them. Just running tasks in our context is not sufficient
        // to prevent deadlock actually, because those tasks could in turn launch & sync and get stuck
        // so if our tasks aren't done and there's none left to run in our context we should start
        // running tasks from other contexts to help out
        println!("ISPCSync, context.id = {}", context.id);
        for tg in context.iter() {
            for chunk in tg.chunks(self.chunk_size) {
                println!("syncing thread {} running local chunk {:?}", thread, chunk);
                // TODO: We need to figure out which thread we are
                chunk.execute(thread as i32, total_threads as i32);
            }
        }
        // If all the tasks for this context have been finished we're done sync'ing and can
        // clean up memory and remove the context from the TASK_LIST. Otherwise there are some
        // unfinished groups further down the the tree that were spawned by our direct tasks that
        // those are now sync'ing on and we need to help out. However since we don't know the tree
        // our best option is to just start grabbing chunks from unfinished groups in the TASK_LIST
        // and running them to at least ensure global forward progress, which will eventually get
        // the stuff we're waiting on to finish. After each chunk execution we should check if
        // our sync'ing context is done and break
        while !context.current_tasks_done() {
            println!("Not all tasks for context {} are done, thread {} helping out!", context.id, thread);
            // Get a task group to run
            let ctx = {
                // Do it in a block to limit the scope of the lock
                ::get_task_system().context_list.read().unwrap().iter()
                    .find(|c| !c.current_tasks_done()).map(|c| c.clone())
            };
            if let Some(c) = ctx {
                println!("syncing thread {} got a context {:?}", thread, c);
                for tg in c.iter() {
                    println!("syncing thread {} looking at task group {:?}", thread, tg);
                    for chunk in tg.chunks(self.chunk_size) {
                        println!("syncing thread {} running nonlocal chunk {:?}", thread, chunk);
                        chunk.execute(thread as i32, total_threads as i32);
                    }
                }
            } else {
                println!("Syncing thread didn't get a context");
            }
        }
        // Now erase this context from our vector
        let mut context_list = self.context_list.write().unwrap();
        let pos = context_list.iter().position(|c| context.id == c.id).unwrap();
        context_list.remove(pos);
    }
}

fn worker_thread(thread: usize, total_threads: usize, chunk_size: i32) {
    THREAD_ID.with(|f| *f.borrow_mut() = thread);
    let task_system = ::get_task_system();
    println!("thread {} is spawned", thread);
    loop {
        thread::park();
        println!("thread {} was woken up to work!", thread);
        // Get a task group to run
        let ctx = {
            // Do it in a block to limit the scope of the lock
            task_system.context_list.read().unwrap().iter()
                .find(|c| !c.current_tasks_done()).map(|c| c.clone())
        };
        if let Some(c) = ctx {
            for tg in c.iter() {
                for chunk in tg.chunks(chunk_size) {
                    println!("thread {} running chunk {:?}", thread, chunk);
                    chunk.execute(thread as i32, total_threads as i32);
                }
            }
        }
    }
}

<commit_msg>Only park a thread if we didn't get a context<commit_after>//! Defines the trait that must be implemented by ISPC task execution systems
//! and provides a default threaded one for use.

use libc;
use num_cpus;

use std::mem;
use std::cell::RefCell;
use std::sync::{Arc, RwLock, Mutex};
use std::sync::atomic::{self, AtomicUsize};
use std::thread::{self, JoinHandle};

use task::{ISPCTaskFn, Context};

/// Trait to be implemented to provide ISPC task execution functionality.
///
/// The runtime [required functions](http://ispc.github.io/ispc.html#task-parallelism-runtime-requirements)
/// will be forwarded directly to your struct, making this interface unsafe.
pub trait TaskSystem {
    /// Alloc is called when memory must be allocated to store parameters to pass to a task
    /// and must return a pointer to an allocation of `size` bytes aligned to `align`.
    ///
    /// The `handle_ptr` will be `NULL` if this is the first time launch has been called in
    /// the function or is the first launch call after an explicit `sync` statement. Both
    /// situations should be treated equivalently as creating a new exeuction context for tasks.
    /// The `handle_ptr` should be set to some context tracking facility so that you can later
    /// track task groups launched in the context and perform finer grained synchronization in
    /// `sync`.
    unsafe fn alloc(&self, handle_ptr: *mut *mut libc::c_void, size: i64, align: i32) -> *mut libc::c_void;
    /// Launch is called when a new group of tasks is being launched and should schedule them to
    /// be executed in some way.
    ///
    /// The `handle_ptr` will point to the same handle you set up in `alloc` and can be used to
    /// associate groups of tasks with a context of execution as mentioned before. The function `f`
    /// should be executed `count0 * count1 * count2` times and indices passed to the function
    /// should be as if running in a nested for loop:
    ///
    /// ```ignore
    /// let total_tasks = count0 * count1 * count2;
    /// for z in 0..count2 {
    ///     for y in 0..count1 {
    ///         for x in 0..count0 {
    ///             let task_id = x + y * count0 + z * count0 * count1;
    ///             f(data, thread_id, total_threads, task_id, total_tasks,
    ///               x, y, z, count0, count1, count2);
    ///         }
    ///     }
    /// }
    /// ```
    ///
    /// The `data` pointer points to the ISPC task specific parameter pointer and should be passed
    /// through to the function.
    unsafe fn launch(&self, handle_ptr: *mut *mut libc::c_void, f: ISPCTaskFn, data: *mut libc::c_void,
                     count0: i32, count1: i32, count2: i32);
    /// Synchronize an execution context with the tasks it's launched. Use `handle` to determine
    /// the task context being synchronized.
    ///
    /// This function should not return until all tasks launched within the context being
    /// synchronized with have been completed. You can use the `handle` to determine which context
    /// is being synchronized with and thus which tasks must be completed before returning.
    unsafe fn sync(&self, handle: *mut libc::c_void);
}

// Thread local storage to store the thread's id, otherwise we don't know
// who we are in sync. The thread id starts at an invalid value but will be set
// upon thread launch
thread_local!(static THREAD_ID: RefCell<usize> = RefCell::new(0));

/// A multithreaded execution environment for the tasks.
pub struct Parallel {
    context_list: RwLock<Vec<Arc<Context>>>,
    next_context_id: AtomicUsize,
    threads: Mutex<Vec<JoinHandle<()>>>,
    chunk_size: i32,
}

impl Parallel {
    pub fn new() -> Parallel {
        let par = Parallel { context_list: RwLock::new(Vec::new()),
                             next_context_id: AtomicUsize::new(0),
                             threads: Mutex::new(Vec::new()),
                             chunk_size: 8 };
        {
            let mut threads = par.threads.lock().unwrap();
            let num_threads = num_cpus::get();
            let chunk_size = par.chunk_size;
            println!("Launching {} threads", num_threads);
            for i in 0..num_threads {
                // Note that the spawned thread ids start at 1 since the main thread is 0
                threads.push(thread::spawn(move || worker_thread(i + 1, num_threads + 1, chunk_size)));
            }
        }
        par
    }
}

impl TaskSystem for Parallel {
    unsafe fn alloc(&self, handle_ptr: *mut *mut libc::c_void, size: i64, align: i32) -> *mut libc::c_void {
        println!("ISPCAlloc, size: {}, align: {}", size, align);
        // If the handle is null this is the first time this function has spawned tasks
        // and we should create a new Context structure in the TASK_LIST for it, otherwise
        // it's the pointer to where we should append the new Group
        let mut context_list = self.context_list.write().unwrap();
        let context = if (*handle_ptr).is_null() {
            println!("handle ptr is null");
            // This is a bit hairy. We allocate the new task context in a box, then
            // unbox it into a raw ptr to get a ptr we can pass back to ISPC through
            // the handle_ptr and then re-box it into our TASK_LIST so it will
            // be free'd properly when we erase it from the vector in ISPCSync
            let c = Arc::new(Context::new(self.next_context_id.fetch_add(1, atomic::Ordering::SeqCst)));
            {
                let h = &*c;
                *handle_ptr = mem::transmute(h);
            }
            context_list.push(c);
            context_list.last_mut().unwrap()
        } else {
            // TODO: I'm not sure if it'd be worth optimizing this path to just require a read lock
            println!("handle ptr is not null");
            let handle_ctx: *mut Context = mem::transmute(*handle_ptr);
            context_list.iter_mut().find(|c| (*handle_ctx).id == c.id).unwrap()
        };
        println!("context.id = {}", context.id);
        context.alloc(size as usize, align as usize)
    }
    unsafe fn launch(&self, handle_ptr: *mut *mut libc::c_void, f: ISPCTaskFn, data: *mut libc::c_void,
                     count0: i32, count1: i32, count2: i32) {
        // Push the tasks being launched on to the list of task groups for this function
        let context: &mut Context = mem::transmute(*handle_ptr);
        println!("ISPCLaunch, context.id = {}, counts: [{}, {}, {}]", context.id, count0, count1, count2);
        context.launch((count0, count1, count2), data, f);
        // Unpark any sleeping threads since we have jobs for them
        let threads = self.threads.lock().unwrap();
        for t in threads.iter() {
            t.thread().unpark();
        }
    }
    unsafe fn sync(&self, handle: *mut libc::c_void) {
        let context: &mut Context = mem::transmute(handle);
        let thread = THREAD_ID.with(|f| *f.borrow());
        let total_threads = num_cpus::get();
        println!("Thread {} is syncing", thread);
        // Make sure all tasks are done, and execute them if not for this simple
        // serial version. TODO: In the future we'd wait on each Group's semaphore or atomic bool
        // Maybe the waiting thread could help execute tasks as well, otherwise it might be
        // possible to deadlock, where all threads are waiting for some enqueue'd tasks but no
        // threads are available to run them. Just running tasks in our context is not sufficient
        // to prevent deadlock actually, because those tasks could in turn launch & sync and get stuck
        // so if our tasks aren't done and there's none left to run in our context we should start
        // running tasks from other contexts to help out
        println!("ISPCSync, context.id = {}", context.id);
        for tg in context.iter() {
            for chunk in tg.chunks(self.chunk_size) {
                println!("syncing thread {} running local chunk {:?}", thread, chunk);
                // TODO: We need to figure out which thread we are
                chunk.execute(thread as i32, total_threads as i32);
            }
        }
        // If all the tasks for this context have been finished we're done sync'ing and can
        // clean up memory and remove the context from the TASK_LIST. Otherwise there are some
        // unfinished groups further down the the tree that were spawned by our direct tasks that
        // those are now sync'ing on and we need to help out. However since we don't know the tree
        // our best option is to just start grabbing chunks from unfinished groups in the TASK_LIST
        // and running them to at least ensure global forward progress, which will eventually get
        // the stuff we're waiting on to finish. After each chunk execution we should check if
        // our sync'ing context is done and break
        while !context.current_tasks_done() {
            println!("Not all tasks for context {} are done, thread {} helping out!", context.id, thread);
            // Get a task group to run
            let ctx = {
                // Do it in a block to limit the scope of the lock
                ::get_task_system().context_list.read().unwrap().iter()
                    .find(|c| !c.current_tasks_done()).map(|c| c.clone())
            };
            if let Some(c) = ctx {
                println!("syncing thread {} got a context {:?}", thread, c);
                for tg in c.iter() {
                    println!("syncing thread {} looking at task group {:?}", thread, tg);
                    for chunk in tg.chunks(self.chunk_size) {
                        println!("syncing thread {} running nonlocal chunk {:?}", thread, chunk);
                        chunk.execute(thread as i32, total_threads as i32);
                    }
                }
            } else {
                println!("Syncing thread didn't get a context");
            }
        }
        // Now erase this context from our vector
        let mut context_list = self.context_list.write().unwrap();
        let pos = context_list.iter().position(|c| context.id == c.id).unwrap();
        context_list.remove(pos);
    }
}

fn worker_thread(thread: usize, total_threads: usize, chunk_size: i32) {
    THREAD_ID.with(|f| *f.borrow_mut() = thread);
    let task_system = ::get_task_system();
    println!("thread {} is spawned", thread);
    loop {
        // Get a task group to run
        let ctx = {
            // Do it in a block to limit the scope of the lock
            task_system.context_list.read().unwrap().iter()
                .find(|c| !c.current_tasks_done()).map(|c| c.clone())
        };
        if let Some(c) = ctx {
            for tg in c.iter() {
                for chunk in tg.chunks(chunk_size) {
                    println!("thread {} running chunk {:?}", thread, chunk);
                    chunk.execute(thread as i32, total_threads as i32);
                }
            }
        } else {
            thread::park();
            println!("thread {} was woken up to work!", thread);
        }
    }
}

<|endoftext|>


<commit_before>use std::io::{fs, IoResult};
use std::io;

use ansi_term::{Paint, Colour, Plain, Style, Red, Green, Yellow, Blue, Purple, Cyan, Fixed};

use column::Column;
use column::Column::*;
use format::{format_metric_bytes, format_IEC_bytes};
use unix::Unix;
use sort::SortPart;
use dir::Dir;
use filetype::HasType;

pub static GREY: Colour = Fixed(244);

// Instead of working with Rust's Paths, we have our own File object
// that holds the Path and various cached information. Each file is
// definitely going to have its filename used at least once, its stat
// information queried at least once, and its file extension extracted
// at least once, so we may as well carry around that information with
// the actual path.

pub struct File<'a> {
    pub name:  String,
    pub dir:   &'a Dir<'a>,
    pub ext:   Option<String>,
    pub path:  &'a Path,
    pub stat:  io::FileStat,
    pub parts: Vec<SortPart>,
}

impl<'a> File<'a> {
    pub fn from_path(path: &'a Path, parent: &'a Dir) -> IoResult<File<'a>> {
        let v = path.filename().unwrap();  // fails if / or . or ..
        let filename = String::from_utf8(v.to_vec()).unwrap_or_else(|_| panic!("Name was not valid UTF-8"));

        // Use lstat here instead of file.stat(), as it doesn't follow
        // symbolic links. Otherwise, the stat() call will fail if it
        // encounters a link that's target is non-existent.

        fs::lstat(path).map(|stat| File {
            path:  path,
            dir:   parent,
            stat:  stat,
            name:  filename.clone(),
            ext:   File::ext(filename.clone()),
            parts: SortPart::split_into_parts(filename.clone()),
        })
    }

    fn ext(name: String) -> Option<String> {
        // The extension is the series of characters after a dot at
        // the end of a filename. This deliberately also counts
        // dotfiles - the ".git" folder has the extension "git".
        let re = regex!(r"\.([^.]+)$");
        re.captures(name.as_slice()).map(|caps| caps.at(1).to_string())
    }

    pub fn is_dotfile(&self) -> bool {
        self.name.as_slice().starts_with(".")
    }

    pub fn is_tmpfile(&self) -> bool {
        let name = self.name.as_slice();
        name.ends_with("~") || (name.starts_with("#") && name.ends_with("#"))
    }

    // Highlight the compiled versions of files. Some of them, like .o,
    // get special highlighting when they're alone because there's no
    // point in existing without their source. Others can be perfectly
    // content without their source files, such as how .js is valid
    // without a .coffee.

    pub fn get_source_files(&self) -> Vec<Path> {
        if self.ext.is_none() {
            return vec![];
        }

        let ext = self.ext.clone().unwrap();
        match ext.as_slice() {
            "class" => vec![self.path.with_extension("java")],  // Java
            "elc" => vec![self.path.with_extension("el")],  // Emacs Lisp
            "hi" => vec![self.path.with_extension("hs")],  // Haskell
            "o" => vec![self.path.with_extension("c"), self.path.with_extension("cpp")],  // C, C++
            "pyc" => vec![self.path.with_extension("py")],  // Python
            "js" => vec![self.path.with_extension("coffee"), self.path.with_extension("ts")],  // CoffeeScript, TypeScript
            "css" => vec![self.path.with_extension("sass"), self.path.with_extension("less")],  // SASS, Less

            "aux" => vec![self.path.with_extension("tex")],  // TeX: auxiliary file
            "bbl" => vec![self.path.with_extension("tex")],  // BibTeX bibliography file
            "blg" => vec![self.path.with_extension("tex")],  // BibTeX log file
            "lof" => vec![self.path.with_extension("tex")],  // TeX list of figures
            "log" => vec![self.path.with_extension("tex")],  // TeX log file
            "lot" => vec![self.path.with_extension("tex")],  // TeX list of tables
            "toc" => vec![self.path.with_extension("tex")],  // TeX table of contents

            _ => vec![],
        }
    }

    pub fn display(&self, column: &Column, unix: &mut Unix) -> String {
        match *column {
            Permissions => {
                self.permissions_string()
            },

            FileName => {
                self.file_name()
            },

            FileSize(use_iec) => {
                self.file_size(use_iec)
            },

            // A file with multiple links is interesting, but
            // directories and suchlike can have multiple links all
            // the time.
            HardLinks => {
                let style = if self.has_multiple_links() { Red.on(Yellow) } else { Red.normal() };
                style.paint(self.stat.unstable.nlink.to_string().as_slice())
            },

            Inode => {
                Purple.paint(self.stat.unstable.inode.to_string().as_slice())
            },

            Blocks => {
                if self.stat.kind == io::TypeFile || self.stat.kind == io::TypeSymlink {
                    Cyan.paint(self.stat.unstable.blocks.to_string().as_slice())
                }
                else {
                    GREY.paint("-")
                }
            },

            // Display the ID if the user/group doesn't exist, which
            // usually means it was deleted but its files weren't.
            User => {
                let uid = self.stat.unstable.uid as u32;
                unix.load_user(uid);
                let user_name = unix.get_user_name(uid).unwrap_or(uid.to_string());
                let style = if unix.uid == uid { Yellow.bold() } else { Plain };
                style.paint(user_name.as_slice())
            },

            Group => {
                let gid = self.stat.unstable.gid as u32;
                unix.load_group(gid);
                let group_name = unix.get_group_name(gid).unwrap_or(gid.to_string());
                let style = if unix.is_group_member(gid) { Yellow.normal() } else { Plain };
                style.paint(group_name.as_slice())
            },
        }
    }

    pub fn file_name(&self) -> String {
        let name = self.name.as_slice();
        let displayed_name = self.file_colour().paint(name);
        if self.stat.kind == io::TypeSymlink {
            match fs::readlink(self.path) {
                Ok(path) => {
                    let target_path = if path.is_absolute() { path } else { self.dir.path.join(path) };
                    format!("{} {}", displayed_name, self.target_file_name_and_arrow(target_path))
                }
                Err(_) => displayed_name,
            }
        }
        else {
            displayed_name
        }
    }

    pub fn file_name_width(&self) -> uint {
        self.name.as_slice().width(false)
    }

    fn target_file_name_and_arrow(&self, target_path: Path) -> String {
        let v = target_path.filename().unwrap();
        let filename = String::from_utf8_lossy(v).to_string();

        let link_target = fs::stat(&target_path).map(|stat| File {
            path:  &target_path,
            dir:   self.dir,
            stat:  stat,
            name:  filename.clone(),
            ext:   File::ext(filename.clone()),
            parts: vec![],  // not needed
        });

        // Statting a path usually fails because the file at the
        // other end doesn't exist. Show this by highlighting the
        // target file in red instead of displaying an error, because
        // the error would be shown out of context (before the
        // results, not right by the file) and it's almost always for
        // that reason anyway.

        match link_target {
            Ok(file) => format!("{} {}", GREY.paint("=>"), file.file_colour().paint(filename.as_slice())),
            Err(_)   => format!("{} {}", Red.paint("=>"), Red.underline().paint(filename.as_slice())),
        }
    }

    fn file_size(&self, use_iec_prefixes: bool) -> String {
        // Don't report file sizes for directories. I've never looked
        // at one of those numbers and gained any information from it.
        if self.stat.kind == io::TypeDirectory {
            GREY.paint("-")
        } else {
            let (size, suffix) = if use_iec_prefixes {
                format_IEC_bytes(self.stat.size)
            } else {
                format_metric_bytes(self.stat.size)
            };

            return format!("{}{}", Green.bold().paint(size.as_slice()), Green.paint(suffix.as_slice()));
        }
    }

    fn type_char(&self) -> String {
        return match self.stat.kind {
            io::TypeFile         => ".".to_string(),
            io::TypeDirectory    => Blue.paint("d"),
            io::TypeNamedPipe    => Yellow.paint("|"),
            io::TypeBlockSpecial => Purple.paint("s"),
            io::TypeSymlink      => Cyan.paint("l"),
            io::TypeUnknown      => "?".to_string(),
        }
    }

    pub fn file_colour(&self) -> Style {
        self.get_type().style()
    }

    fn has_multiple_links(&self) -> bool {
        self.stat.kind == io::TypeFile && self.stat.unstable.nlink > 1
    }

    fn permissions_string(&self) -> String {
        let bits = self.stat.perm;
        return format!("{}{}{}{}{}{}{}{}{}{}",
            self.type_char(),

            // The first three are bold because they're the ones used
            // most often.
            File::permission_bit(bits, io::USER_READ,     "r", Yellow.bold()),
            File::permission_bit(bits, io::USER_WRITE,    "w", Red.bold()),
            File::permission_bit(bits, io::USER_EXECUTE,  "x", Green.bold().underline()),
            File::permission_bit(bits, io::GROUP_READ,    "r", Yellow.normal()),
            File::permission_bit(bits, io::GROUP_WRITE,   "w", Red.normal()),
            File::permission_bit(bits, io::GROUP_EXECUTE, "x", Green.normal()),
            File::permission_bit(bits, io::OTHER_READ,    "r", Yellow.normal()),
            File::permission_bit(bits, io::OTHER_WRITE,   "w", Red.normal()),
            File::permission_bit(bits, io::OTHER_EXECUTE, "x", Green.normal()),
       );
    }

    fn permission_bit(bits: io::FilePermission, bit: io::FilePermission, character: &'static str, style: Style) -> String {
        if bits.contains(bit) {
            style.paint(character.as_slice())
        } else {
            GREY.paint("-".as_slice())
        }
    }
}
<commit_msg>Add path to symlinks<commit_after>use std::io::{fs, IoResult};
use std::io;

use ansi_term::{Paint, Colour, Plain, Style, Red, Green, Yellow, Blue, Purple, Cyan, Fixed};

use column::Column;
use column::Column::*;
use format::{format_metric_bytes, format_IEC_bytes};
use unix::Unix;
use sort::SortPart;
use dir::Dir;
use filetype::HasType;

pub static GREY: Colour = Fixed(244);

// Instead of working with Rust's Paths, we have our own File object
// that holds the Path and various cached information. Each file is
// definitely going to have its filename used at least once, its stat
// information queried at least once, and its file extension extracted
// at least once, so we may as well carry around that information with
// the actual path.

pub struct File<'a> {
    pub name:  String,
    pub dir:   &'a Dir<'a>,
    pub ext:   Option<String>,
    pub path:  &'a Path,
    pub stat:  io::FileStat,
    pub parts: Vec<SortPart>,
}

impl<'a> File<'a> {
    pub fn from_path(path: &'a Path, parent: &'a Dir) -> IoResult<File<'a>> {
        let v = path.filename().unwrap();  // fails if / or . or ..
        let filename = String::from_utf8(v.to_vec()).unwrap_or_else(|_| panic!("Name was not valid UTF-8"));

        // Use lstat here instead of file.stat(), as it doesn't follow
        // symbolic links. Otherwise, the stat() call will fail if it
        // encounters a link that's target is non-existent.

        fs::lstat(path).map(|stat| File {
            path:  path,
            dir:   parent,
            stat:  stat,
            name:  filename.clone(),
            ext:   File::ext(filename.clone()),
            parts: SortPart::split_into_parts(filename.clone()),
        })
    }

    fn ext(name: String) -> Option<String> {
        // The extension is the series of characters after a dot at
        // the end of a filename. This deliberately also counts
        // dotfiles - the ".git" folder has the extension "git".
        let re = regex!(r"\.([^.]+)$");
        re.captures(name.as_slice()).map(|caps| caps.at(1).to_string())
    }

    pub fn is_dotfile(&self) -> bool {
        self.name.as_slice().starts_with(".")
    }

    pub fn is_tmpfile(&self) -> bool {
        let name = self.name.as_slice();
        name.ends_with("~") || (name.starts_with("#") && name.ends_with("#"))
    }

    // Highlight the compiled versions of files. Some of them, like .o,
    // get special highlighting when they're alone because there's no
    // point in existing without their source. Others can be perfectly
    // content without their source files, such as how .js is valid
    // without a .coffee.

    pub fn get_source_files(&self) -> Vec<Path> {
        if self.ext.is_none() {
            return vec![];
        }

        let ext = self.ext.clone().unwrap();
        match ext.as_slice() {
            "class" => vec![self.path.with_extension("java")],  // Java
            "css"   => vec![self.path.with_extension("sass"),   self.path.with_extension("less")],  // SASS, Less
            "elc"   => vec![self.path.with_extension("el")],    // Emacs Lisp
            "hi"    => vec![self.path.with_extension("hs")],    // Haskell
            "js"    => vec![self.path.with_extension("coffee"), self.path.with_extension("ts")],  // CoffeeScript, TypeScript
            "o"     => vec![self.path.with_extension("c"),      self.path.with_extension("cpp")], // C, C++
            "pyc"   => vec![self.path.with_extension("py")],    // Python

            "aux" => vec![self.path.with_extension("tex")],  // TeX: auxiliary file
            "bbl" => vec![self.path.with_extension("tex")],  // BibTeX bibliography file
            "blg" => vec![self.path.with_extension("tex")],  // BibTeX log file
            "lof" => vec![self.path.with_extension("tex")],  // TeX list of figures
            "log" => vec![self.path.with_extension("tex")],  // TeX log file
            "lot" => vec![self.path.with_extension("tex")],  // TeX list of tables
            "toc" => vec![self.path.with_extension("tex")],  // TeX table of contents

            _ => vec![],
        }
    }

    pub fn display(&self, column: &Column, unix: &mut Unix) -> String {
        match *column {
            Permissions => {
                self.permissions_string()
            },

            FileName => {
                self.file_name()
            },

            FileSize(use_iec) => {
                self.file_size(use_iec)
            },

            // A file with multiple links is interesting, but
            // directories and suchlike can have multiple links all
            // the time.
            HardLinks => {
                let style = if self.has_multiple_links() { Red.on(Yellow) } else { Red.normal() };
                style.paint(self.stat.unstable.nlink.to_string().as_slice())
            },

            Inode => {
                Purple.paint(self.stat.unstable.inode.to_string().as_slice())
            },

            Blocks => {
                if self.stat.kind == io::TypeFile || self.stat.kind == io::TypeSymlink {
                    Cyan.paint(self.stat.unstable.blocks.to_string().as_slice())
                }
                else {
                    GREY.paint("-")
                }
            },

            // Display the ID if the user/group doesn't exist, which
            // usually means it was deleted but its files weren't.
            User => {
                let uid = self.stat.unstable.uid as u32;
                unix.load_user(uid);
                let user_name = unix.get_user_name(uid).unwrap_or(uid.to_string());
                let style = if unix.uid == uid { Yellow.bold() } else { Plain };
                style.paint(user_name.as_slice())
            },

            Group => {
                let gid = self.stat.unstable.gid as u32;
                unix.load_group(gid);
                let group_name = unix.get_group_name(gid).unwrap_or(gid.to_string());
                let style = if unix.is_group_member(gid) { Yellow.normal() } else { Plain };
                style.paint(group_name.as_slice())
            },
        }
    }

    pub fn file_name(&self) -> String {
        let name = self.name.as_slice();
        let displayed_name = self.file_colour().paint(name);
        if self.stat.kind == io::TypeSymlink {
            match fs::readlink(self.path) {
                Ok(path) => {
                    let target_path = if path.is_absolute() { path } else { self.dir.path.join(path) };
                    format!("{} {}", displayed_name, self.target_file_name_and_arrow(target_path))
                }
                Err(_) => displayed_name,
            }
        }
        else {
            displayed_name
        }
    }

    pub fn file_name_width(&self) -> uint {
        self.name.as_slice().width(false)
    }

    fn target_file_name_and_arrow(&self, target_path: Path) -> String {
        let v = target_path.filename().unwrap();
        let filename = String::from_utf8_lossy(v).to_string();

        let link_target = fs::stat(&target_path).map(|stat| File {
            path:  &target_path,
            dir:   self.dir,
            stat:  stat,
            name:  filename.clone(),
            ext:   File::ext(filename.clone()),
            parts: vec![],  // not needed
        });

        // Statting a path usually fails because the file at the
        // other end doesn't exist. Show this by highlighting the
        // target file in red instead of displaying an error, because
        // the error would be shown out of context (before the
        // results, not right by the file) and it's almost always for
        // that reason anyway.

        match link_target {
            Ok(file) => format!("{} {}{}{}", GREY.paint("=>"), Cyan.paint(target_path.dirname_str().unwrap()), Cyan.paint("/"), file.file_colour().paint(filename.as_slice())),
            Err(_)   => format!("{} {}",     Red.paint("=>"),  Red.underline().paint(filename.as_slice())),
        }
    }

    fn file_size(&self, use_iec_prefixes: bool) -> String {
        // Don't report file sizes for directories. I've never looked
        // at one of those numbers and gained any information from it.
        if self.stat.kind == io::TypeDirectory {
            GREY.paint("-")
        } else {
            let (size, suffix) = if use_iec_prefixes {
                format_IEC_bytes(self.stat.size)
            } else {
                format_metric_bytes(self.stat.size)
            };

            return format!("{}{}", Green.bold().paint(size.as_slice()), Green.paint(suffix.as_slice()));
        }
    }

    fn type_char(&self) -> String {
        return match self.stat.kind {
            io::TypeFile         => ".".to_string(),
            io::TypeDirectory    => Blue.paint("d"),
            io::TypeNamedPipe    => Yellow.paint("|"),
            io::TypeBlockSpecial => Purple.paint("s"),
            io::TypeSymlink      => Cyan.paint("l"),
            io::TypeUnknown      => "?".to_string(),
        }
    }

    pub fn file_colour(&self) -> Style {
        self.get_type().style()
    }

    fn has_multiple_links(&self) -> bool {
        self.stat.kind == io::TypeFile && self.stat.unstable.nlink > 1
    }

    fn permissions_string(&self) -> String {
        let bits = self.stat.perm;
        return format!("{}{}{}{}{}{}{}{}{}{}",
            self.type_char(),

            // The first three are bold because they're the ones used
            // most often.
            File::permission_bit(bits, io::USER_READ,     "r", Yellow.bold()),
            File::permission_bit(bits, io::USER_WRITE,    "w", Red.bold()),
            File::permission_bit(bits, io::USER_EXECUTE,  "x", Green.bold().underline()),
            File::permission_bit(bits, io::GROUP_READ,    "r", Yellow.normal()),
            File::permission_bit(bits, io::GROUP_WRITE,   "w", Red.normal()),
            File::permission_bit(bits, io::GROUP_EXECUTE, "x", Green.normal()),
            File::permission_bit(bits, io::OTHER_READ,    "r", Yellow.normal()),
            File::permission_bit(bits, io::OTHER_WRITE,   "w", Red.normal()),
            File::permission_bit(bits, io::OTHER_EXECUTE, "x", Green.normal()),
       );
    }

    fn permission_bit(bits: io::FilePermission, bit: io::FilePermission, character: &'static str, style: Style) -> String {
        if bits.contains(bit) {
            style.paint(character.as_slice())
        } else {
            GREY.paint("-".as_slice())
        }
    }
}
<|endoftext|>


<commit_before>use std::sync::Mutex;
use std::sync::atomic::{Ordering, AtomicU32};
use std::f32;

use {clamp, Point2f, Point2i, Vector2f};
use bounds::{Bounds2f, Bounds2i};
use errors::*;
use filter::Filter;
use imageio;
use paramset::ParamSet;
use spectrum::Spectrum;

const FILTER_SIZE: usize = 16;
const FILTER_TABLE_SIZE: usize = FILTER_SIZE * FILTER_SIZE;

#[derive(Default)]
struct AtomicFloat {
    bits: AtomicU32,
}

impl AtomicFloat {
    pub fn new(v: f32) -> AtomicFloat {
        AtomicFloat {
            bits: AtomicU32::new(v.to_bits()),
        }
    }

    pub fn as_float(&self) -> f32 {
        f32::from_bits(self.bits.load(Ordering::Relaxed))
    }
}

#[derive(Default)]
struct Pixel {
    xyz: [f32; 3],
    filter_weight_sum: f32,
    splat_xyz: [AtomicFloat; 3],
    _pad: f32,
}

pub struct Film {
    pub full_resolution: Point2i,
    pub _diagonal: f32,
    pub filename: String,
    pub cropped_pixel_bounds: Bounds2i,
    pixels: Mutex<Vec<Pixel>>,
    filter_table: [f32; FILTER_TABLE_SIZE],
    filter_radius: Vector2f,
    scale: f32,
}

impl Film {
    pub fn new(resolution: Point2i,
               cropwindow: Bounds2f,
               filter: Box<Filter + Sync + Send>,
               diagonal: f32,
               filename: &str,
               scale: f32)
               -> Film {
        let cropped_pixel_bounds =
            Bounds2i::from_points(&Point2i::new((resolution.x as f32 * cropwindow.p_min.x).ceil() as
                                                i32,
                                                (resolution.y as f32 * cropwindow.p_min.y).ceil() as
                                                i32),
                                  &Point2i::new((resolution.x as f32 * cropwindow.p_max.x).ceil() as
                                                i32,
                                                (resolution.y as f32 * cropwindow.p_max.y).ceil() as
                                                i32));

        let mut pixels = Vec::with_capacity(cropped_pixel_bounds.area() as usize);
        pixels.resize_default(cropped_pixel_bounds.area() as usize);
        let mut filter_table = [0f32; FILTER_TABLE_SIZE];

        let (xwidth, ywidth) = filter.width();
        // Fill in filter table
        for y in 0..FILTER_SIZE {
            let fy = (y as f32 + 0.5) * (ywidth / FILTER_SIZE as f32);
            for x in 0..FILTER_SIZE {
                let fx = (x as f32 + 0.5) * (xwidth / FILTER_SIZE as f32);
                filter_table[y * FILTER_SIZE + x] = filter.evaluate(fx, fy);
            }
        }

        Film {
            full_resolution: resolution,
            pixels: Mutex::new(pixels),
            filter_table: filter_table,
            filter_radius: Vector2f::new(xwidth, ywidth),
            cropped_pixel_bounds: cropped_pixel_bounds,
            scale: scale,
            _diagonal: diagonal * 0.001,
            filename: filename.to_owned(),
        }
    }

    pub fn create(ps: &mut ParamSet, filter: Box<Filter + Send + Sync>) -> Box<Film> {
        let mut filename = ps.find_one_string("filename", "".into());
        if filename == "" {
            filename = "image.png".into();
        } else {
            filename = String::from("rt-") + &filename;
        }
        let xres = ps.find_one_int("xresolution", 1280);
        let yres = ps.find_one_int("yresolution", 720);
        let mut crop = Bounds2f::from_points(&Point2f::new(0.0, 0.0), &Point2f::new(1.0, 1.0));
        if let Some(cr) = ps.find_float("cropwindow") {
            if cr.len() == 4 {
                crop.p_min.x = clamp(f32::min(cr[0], cr[1]), 0.0, 1.0);
                crop.p_max.x = clamp(f32::max(cr[0], cr[1]), 0.0, 1.0);
                crop.p_min.y = clamp(f32::min(cr[2], cr[3]), 0.0, 1.0);
                crop.p_max.y = clamp(f32::max(cr[2], cr[3]), 0.0, 1.0);
            } else {
                warn!("\"cropwindow\" expected 4 values");
            }
        }
        let scale = ps.find_one_float("scale", 1.0);
        let diagonal = ps.find_one_float("diagonal", 35.0);
        // TODO max_sample_luminance
        Box::new(Film::new(Point2i::new(xres, yres),
                           crop,
                           filter,
                           diagonal,
                           &filename,
                           scale))
    }

    pub fn get_film_tile(&self, sample_bounds: &Bounds2i) -> FilmTile {
        let half_pixel = Vector2f::new(0.5, 0.5);
        let float_bounds: Bounds2f = (*sample_bounds).into();
        let float_cropped_pixel_bounds: Bounds2f = self.cropped_pixel_bounds.into();

        // This is a bit clunky but we need to do all the computations as floats as the numbers can
        // temporarily be negative which would cause u32 to wrap around.
        let p0 = ceil(float_bounds.p_min - half_pixel - self.filter_radius);
        let p1 = floor(float_bounds.p_max - half_pixel + self.filter_radius +
                       Vector2f::new(1.0, 1.0));
        let sample_extent_bounds = Bounds2f::from_points(&p0, &p1);

        let tile_pixel_bounds: Bounds2i =
            Bounds2i::from(Bounds2f::intersect(&sample_extent_bounds, &float_cropped_pixel_bounds));

        FilmTile::new(&tile_pixel_bounds, &self.filter_radius, &self.filter_table)
    }

    pub fn merge_film_tile(&self, tile: FilmTile) {
        let mut pixels = self.pixels.lock().unwrap();
        for pixel in &tile.get_pixel_bounds() {
            let tile_pixel = tile.get_pixel(&pixel);
            let pidx = (pixel.y * self.full_resolution.x + pixel.x) as usize;
            let xyz = tile_pixel.contrib_sum.to_xyz();
            for i in 0..3 {
                pixels[pidx].xyz[i] += xyz[i];
            }
            pixels[pidx].filter_weight_sum += tile_pixel.filter_weight_sum;
        }
    }

    pub fn write_image(&self) -> Result<()> {
        info!("Converting image to RGB and computing final weighted pixel values");
        let splat_scale = 1.0; // TODO
        let pixels = self.pixels.lock().unwrap();
        let mut rgb = Vec::with_capacity(3 * self.cropped_pixel_bounds.area() as usize);
        for p in &self.cropped_pixel_bounds {
            // Convert pixel XYZ color to RGB
            let pixel_idx = self.get_pixel_idx(&p);
            let pixel = &pixels[pixel_idx];
            let mut rgb_pixel = Spectrum::from_xyz(&pixel.xyz);

            // Normalize pixel with weight sum
            let filter_weight_sum = pixel.filter_weight_sum;
            if filter_weight_sum != 0.0 {
                let inv_wt = 1.0 / filter_weight_sum;
                rgb_pixel[0] = f32::max(0.0, rgb_pixel[0] * inv_wt);
                rgb_pixel[1] = f32::max(0.0, rgb_pixel[1] * inv_wt);
                rgb_pixel[2] = f32::max(0.0, rgb_pixel[2] * inv_wt);
            }

            let splat_xyz = [
                pixel.splat_xyz[0].as_float(),
                pixel.splat_xyz[1].as_float(),
                pixel.splat_xyz[2].as_float(),
            ];
            let mut splat_rgb = Spectrum::from_xyz(&splat_xyz);
            rgb_pixel[0] += splat_scale * splat_rgb[0];
            rgb_pixel[1] += splat_scale * splat_rgb[1];
            rgb_pixel[2] += splat_scale * splat_rgb[2];

            // Scale pixel value by scale
            rgb_pixel[0] *= self.scale;
            rgb_pixel[1] *= self.scale;
            rgb_pixel[2] *= self.scale;

            rgb.push(rgb_pixel[0]);
            rgb.push(rgb_pixel[1]);
            rgb.push(rgb_pixel[2]);
        }

        // Write RGB image
        info!("Writing image {} with bounds {}", self.filename, self.cropped_pixel_bounds);
        imageio::write_image(&self.filename, &rgb[..], &self.cropped_pixel_bounds, &self.full_resolution)
    }

    pub fn get_sample_bounds(&self) -> Bounds2i {
        let half = Vector2f::new(0.5, 0.5);
        let float_bounds =
            Bounds2f::from_points(&floor(Point2f::from(self.cropped_pixel_bounds.p_min) + half -
                                         self.filter_radius),
                                  &ceil(Point2f::from(self.cropped_pixel_bounds.p_max) - half +
                                        self.filter_radius));

        float_bounds.into()
    }

    fn get_pixel_idx(&self, p: &Point2i) -> usize {
        assert!(self.cropped_pixel_bounds.inside_exclusive(p));
        let width = self.cropped_pixel_bounds.p_max.x - self.cropped_pixel_bounds.p_min.x;
        let offset = (p.x - self.cropped_pixel_bounds.p_min.x) + (p.y - self.cropped_pixel_bounds.p_min.y) * width;
        offset as usize
    }
}

pub struct FilmTile {
    pixel_bounds: Bounds2i,
    filter_radius: Vector2f,
    inv_filter_radius: Vector2f,
    filter_table: Box<[f32]>,
    pub pixels: Vec<FilmTilePixel>,
}

impl FilmTile {
    pub fn new(pixel_bounds: &Bounds2i, filter_radius: &Vector2f, filter: &[f32]) -> FilmTile {
        let mut filter_table = Vec::new();
        filter_table.extend_from_slice(filter);
        FilmTile {
            pixel_bounds: *pixel_bounds,
            filter_radius: *filter_radius,
            inv_filter_radius: Vector2f::new(1.0 / filter_radius.x, 1.0 / filter_radius.y),
            // Duplicating the filter table in every table is wasteful, but keeping a reference to
            // the data from Film leads to all kind of lifetime issues...
            filter_table: filter_table.into_boxed_slice(),
            pixels: vec![FilmTilePixel::default(); pixel_bounds.area() as usize],
        }
    }

    pub fn add_sample(&mut self, p_film: &Point2f, colour: Spectrum) {
        if colour.has_nan() {
            warn!("colour has NaNs! Ignoring");
            return;
        }
        let float_pixel_bounds: Bounds2f = self.pixel_bounds.into();
        // Convert to discrete pixel space
        let p_film_discrete = *p_film - Vector2f::new(0.5, 0.5);
        // compute sample raster extent (i.e. how many pixels are affected)
        // (x0, y0) -> (x1, y1) is the zone of the image affected by the sample
        let p0_f = ceil(p_film_discrete - self.filter_radius);

        let p1_f = floor(p_film_discrete + self.filter_radius + Vector2f::new(1.0, 1.0));

        let bounds: Bounds2i = Bounds2i::from(Bounds2f::intersect(&Bounds2f::from_points(&p0_f,
                                                                                &p1_f),
                                                                  &float_pixel_bounds));
        let (p0, p1) = (bounds.p_min, bounds.p_max);

        assert!(p1.x >= p0.x && p1.y >= p0.y,
                format!("p_film={}, p0={}, p1={}, pixel_bounds={:?}",
                        p_film,
                        p0,
                        p1,
                        self.pixel_bounds));

        let filter_table_size = FILTER_SIZE as f32;

        // Precompute x and y filter table offset
        let mut ifx = Vec::with_capacity(p1.x as usize - p0.x as usize);
        for x in p0.x..p1.x {
            let fx = ((x as f32 - p_film_discrete.x) * self.inv_filter_radius.x *
                      filter_table_size)
                    .abs();
            ifx.push(fx.floor().min(filter_table_size - 1.0) as usize);
        }
        let mut ify = Vec::with_capacity(p1.y as usize - p0.y as usize);
        for y in p0.y..p1.y {
            let fy = ((y as f32 - p_film_discrete.y) * self.inv_filter_radius.y *
                      filter_table_size)
                    .abs();
            ify.push(fy.floor().min(filter_table_size - 1.0) as usize);
        }

        // Add this sample's contribution to all the affected pixels
        for y in p0.y..p1.y {
            for x in p0.x..p1.x {
                let offset = ify[(y - p0.y) as usize] * FILTER_SIZE + ifx[(x - p0.x) as usize];
                let filter_weight = &self.filter_table[offset];
                let idx = self.get_pixel_index(&Point2i::new(x, y));
                let pixel = &mut self.pixels[idx];
                pixel.contrib_sum += colour * *filter_weight;
                pixel.filter_weight_sum += *filter_weight;
            }
        }
    }

    pub fn get_pixel<'a>(&'a self, p: &Point2i) -> &'a FilmTilePixel {
        &self.pixels[self.get_pixel_index(p)]
    }

    pub fn get_pixel_bounds(&self) -> Bounds2i {
        self.pixel_bounds
    }

    fn get_pixel_index(&self, p: &Point2i) -> usize {
        let width = self.pixel_bounds.p_max.x - self.pixel_bounds.p_min.x;
        let pidx = (p.y - self.pixel_bounds.p_min.y) * width + (p.x - self.pixel_bounds.p_min.x);
        pidx as usize
    }
}

#[derive(Clone, Default)]
pub struct FilmTilePixel {
    contrib_sum: Spectrum,
    filter_weight_sum: f32,
}

fn ceil(p: Point2f) -> Point2f {
    Point2f::new(p.x.ceil(), p.y.ceil())
}

fn floor(p: Point2f) -> Point2f {
    Point2f::new(p.x.floor(), p.y.floor())
}
<commit_msg>Fix use of cropwindow in Film<commit_after>use std::sync::Mutex;
use std::sync::atomic::{Ordering, AtomicU32};
use std::f32;

use {clamp, Point2f, Point2i, Vector2f};
use bounds::{Bounds2f, Bounds2i};
use errors::*;
use filter::Filter;
use imageio;
use paramset::ParamSet;
use spectrum::Spectrum;

const FILTER_SIZE: usize = 16;
const FILTER_TABLE_SIZE: usize = FILTER_SIZE * FILTER_SIZE;

#[derive(Default)]
struct AtomicFloat {
    bits: AtomicU32,
}

impl AtomicFloat {
    pub fn new(v: f32) -> AtomicFloat {
        AtomicFloat {
            bits: AtomicU32::new(v.to_bits()),
        }
    }

    pub fn as_float(&self) -> f32 {
        f32::from_bits(self.bits.load(Ordering::Relaxed))
    }
}

#[derive(Default)]
struct Pixel {
    xyz: [f32; 3],
    filter_weight_sum: f32,
    splat_xyz: [AtomicFloat; 3],
    _pad: f32,
}

pub struct Film {
    pub full_resolution: Point2i,
    pub _diagonal: f32,
    pub filename: String,
    pub cropped_pixel_bounds: Bounds2i,
    pixels: Mutex<Vec<Pixel>>,
    filter_table: [f32; FILTER_TABLE_SIZE],
    filter_radius: Vector2f,
    scale: f32,
}

impl Film {
    pub fn new(resolution: Point2i,
               cropwindow: Bounds2f,
               filter: Box<Filter + Sync + Send>,
               diagonal: f32,
               filename: &str,
               scale: f32)
               -> Film {
        let cropped_pixel_bounds =
            Bounds2i::from_points(&Point2i::new((resolution.x as f32 * cropwindow.p_min.x).ceil() as
                                                i32,
                                                (resolution.y as f32 * cropwindow.p_min.y).ceil() as
                                                i32),
                                  &Point2i::new((resolution.x as f32 * cropwindow.p_max.x).ceil() as
                                                i32,
                                                (resolution.y as f32 * cropwindow.p_max.y).ceil() as
                                                i32));

        info!("Created film with full resolution {}. Crop window of {} -> cropped_pixel_bounds {}", resolution, cropwindow, cropped_pixel_bounds);
        let mut pixels = Vec::with_capacity(cropped_pixel_bounds.area() as usize);
        pixels.resize_default(cropped_pixel_bounds.area() as usize);
        let mut filter_table = [0f32; FILTER_TABLE_SIZE];

        let (xwidth, ywidth) = filter.width();
        // Fill in filter table
        for y in 0..FILTER_SIZE {
            let fy = (y as f32 + 0.5) * (ywidth / FILTER_SIZE as f32);
            for x in 0..FILTER_SIZE {
                let fx = (x as f32 + 0.5) * (xwidth / FILTER_SIZE as f32);
                filter_table[y * FILTER_SIZE + x] = filter.evaluate(fx, fy);
            }
        }

        Film {
            full_resolution: resolution,
            pixels: Mutex::new(pixels),
            filter_table: filter_table,
            filter_radius: Vector2f::new(xwidth, ywidth),
            cropped_pixel_bounds: cropped_pixel_bounds,
            scale: scale,
            _diagonal: diagonal * 0.001,
            filename: filename.to_owned(),
        }
    }

    pub fn create(ps: &mut ParamSet, filter: Box<Filter + Send + Sync>) -> Box<Film> {
        let mut filename = ps.find_one_string("filename", "".into());
        if filename == "" {
            filename = "image.png".into();
        } else {
            filename = String::from("rt-") + &filename;
        }
        let xres = ps.find_one_int("xresolution", 1280);
        let yres = ps.find_one_int("yresolution", 720);
        let mut crop = Bounds2f::from_points(&Point2f::new(0.0, 0.0), &Point2f::new(1.0, 1.0));
        if let Some(cr) = ps.find_float("cropwindow") {
            if cr.len() == 4 {
                crop.p_min.x = clamp(f32::min(cr[0], cr[1]), 0.0, 1.0);
                crop.p_max.x = clamp(f32::max(cr[0], cr[1]), 0.0, 1.0);
                crop.p_min.y = clamp(f32::min(cr[2], cr[3]), 0.0, 1.0);
                crop.p_max.y = clamp(f32::max(cr[2], cr[3]), 0.0, 1.0);
            } else {
                warn!("\"cropwindow\" expected 4 values");
            }
        }
        let scale = ps.find_one_float("scale", 1.0);
        let diagonal = ps.find_one_float("diagonal", 35.0);
        // TODO max_sample_luminance
        Box::new(Film::new(Point2i::new(xres, yres),
                           crop,
                           filter,
                           diagonal,
                           &filename,
                           scale))
    }

    pub fn get_film_tile(&self, sample_bounds: &Bounds2i) -> FilmTile {
        let half_pixel = Vector2f::new(0.5, 0.5);
        let float_bounds: Bounds2f = (*sample_bounds).into();
        let float_cropped_pixel_bounds: Bounds2f = self.cropped_pixel_bounds.into();

        // This is a bit clunky but we need to do all the computations as floats as the numbers can
        // temporarily be negative which would cause u32 to wrap around.
        let p0 = ceil(float_bounds.p_min - half_pixel - self.filter_radius);
        let p1 = floor(float_bounds.p_max - half_pixel + self.filter_radius +
                       Vector2f::new(1.0, 1.0));
        let sample_extent_bounds = Bounds2f::from_points(&p0, &p1);

        let tile_pixel_bounds: Bounds2i =
            Bounds2i::from(Bounds2f::intersect(&sample_extent_bounds, &float_cropped_pixel_bounds));

        FilmTile::new(&tile_pixel_bounds, &self.filter_radius, &self.filter_table)
    }

    pub fn merge_film_tile(&self, tile: FilmTile) {
        let mut pixels = self.pixels.lock().unwrap();
        for pixel in &tile.get_pixel_bounds() {
            let tile_pixel = tile.get_pixel(&pixel);
            let pidx = {
                let width = self.cropped_pixel_bounds.p_max.x - self.cropped_pixel_bounds.p_min.x;
                ((pixel.y - self.cropped_pixel_bounds.p_min.y) * width + (pixel.x - self.cropped_pixel_bounds.p_min.x)) as usize
            };
            let xyz = tile_pixel.contrib_sum.to_xyz();
            for i in 0..3 {
                pixels[pidx].xyz[i] += xyz[i];
            }
            pixels[pidx].filter_weight_sum += tile_pixel.filter_weight_sum;
        }
    }

    pub fn write_image(&self) -> Result<()> {
        info!("Converting image to RGB and computing final weighted pixel values");
        let splat_scale = 1.0; // TODO
        let pixels = self.pixels.lock().unwrap();
        let mut rgb = Vec::with_capacity(3 * self.cropped_pixel_bounds.area() as usize);
        for p in &self.cropped_pixel_bounds {
            // Convert pixel XYZ color to RGB
            let pixel_idx = self.get_pixel_idx(&p);
            let pixel = &pixels[pixel_idx];
            let mut rgb_pixel = Spectrum::from_xyz(&pixel.xyz);

            // Normalize pixel with weight sum
            let filter_weight_sum = pixel.filter_weight_sum;
            if filter_weight_sum != 0.0 {
                let inv_wt = 1.0 / filter_weight_sum;
                rgb_pixel[0] = f32::max(0.0, rgb_pixel[0] * inv_wt);
                rgb_pixel[1] = f32::max(0.0, rgb_pixel[1] * inv_wt);
                rgb_pixel[2] = f32::max(0.0, rgb_pixel[2] * inv_wt);
            }

            let splat_xyz = [
                pixel.splat_xyz[0].as_float(),
                pixel.splat_xyz[1].as_float(),
                pixel.splat_xyz[2].as_float(),
            ];
            let mut splat_rgb = Spectrum::from_xyz(&splat_xyz);
            rgb_pixel[0] += splat_scale * splat_rgb[0];
            rgb_pixel[1] += splat_scale * splat_rgb[1];
            rgb_pixel[2] += splat_scale * splat_rgb[2];

            // Scale pixel value by scale
            rgb_pixel[0] *= self.scale;
            rgb_pixel[1] *= self.scale;
            rgb_pixel[2] *= self.scale;

            rgb.push(rgb_pixel[0]);
            rgb.push(rgb_pixel[1]);
            rgb.push(rgb_pixel[2]);
        }

        // Write RGB image
        info!("Writing image {} with bounds {}", self.filename, self.cropped_pixel_bounds);
        imageio::write_image(&self.filename, &rgb[..], &self.cropped_pixel_bounds, &self.full_resolution)
    }

    pub fn get_sample_bounds(&self) -> Bounds2i {
        let half = Vector2f::new(0.5, 0.5);
        let float_bounds =
            Bounds2f::from_points(&floor(Point2f::from(self.cropped_pixel_bounds.p_min) + half -
                                         self.filter_radius),
                                  &ceil(Point2f::from(self.cropped_pixel_bounds.p_max) - half +
                                        self.filter_radius));

        float_bounds.into()
    }

    fn get_pixel_idx(&self, p: &Point2i) -> usize {
        assert!(self.cropped_pixel_bounds.inside_exclusive(p));
        let width = self.cropped_pixel_bounds.p_max.x - self.cropped_pixel_bounds.p_min.x;
        let offset = (p.x - self.cropped_pixel_bounds.p_min.x) + (p.y - self.cropped_pixel_bounds.p_min.y) * width;
        offset as usize
    }
}

pub struct FilmTile {
    pixel_bounds: Bounds2i,
    filter_radius: Vector2f,
    inv_filter_radius: Vector2f,
    filter_table: Box<[f32]>,
    pub pixels: Vec<FilmTilePixel>,
}

impl FilmTile {
    pub fn new(pixel_bounds: &Bounds2i, filter_radius: &Vector2f, filter: &[f32]) -> FilmTile {
        let mut filter_table = Vec::new();
        filter_table.extend_from_slice(filter);
        FilmTile {
            pixel_bounds: *pixel_bounds,
            filter_radius: *filter_radius,
            inv_filter_radius: Vector2f::new(1.0 / filter_radius.x, 1.0 / filter_radius.y),
            // Duplicating the filter table in every table is wasteful, but keeping a reference to
            // the data from Film leads to all kind of lifetime issues...
            filter_table: filter_table.into_boxed_slice(),
            pixels: vec![FilmTilePixel::default(); pixel_bounds.area() as usize],
        }
    }

    pub fn add_sample(&mut self, p_film: &Point2f, colour: Spectrum) {
        if colour.has_nan() {
            warn!("colour has NaNs! Ignoring");
            return;
        }
        let float_pixel_bounds: Bounds2f = self.pixel_bounds.into();
        // Convert to discrete pixel space
        let p_film_discrete = *p_film - Vector2f::new(0.5, 0.5);
        // compute sample raster extent (i.e. how many pixels are affected)
        // (x0, y0) -> (x1, y1) is the zone of the image affected by the sample
        let p0_f = ceil(p_film_discrete - self.filter_radius);

        let p1_f = floor(p_film_discrete + self.filter_radius + Vector2f::new(1.0, 1.0));

        let bounds: Bounds2i = Bounds2i::from(Bounds2f::intersect(&Bounds2f::from_points(&p0_f,
                                                                                &p1_f),
                                                                  &float_pixel_bounds));
        let (p0, p1) = (bounds.p_min, bounds.p_max);

        assert!(p1.x >= p0.x && p1.y >= p0.y,
                format!("p_film={}, p0={}, p1={}, pixel_bounds={:?}",
                        p_film,
                        p0,
                        p1,
                        self.pixel_bounds));

        let filter_table_size = FILTER_SIZE as f32;

        // Precompute x and y filter table offset
        let mut ifx = Vec::with_capacity(p1.x as usize - p0.x as usize);
        for x in p0.x..p1.x {
            let fx = ((x as f32 - p_film_discrete.x) * self.inv_filter_radius.x *
                      filter_table_size)
                    .abs();
            ifx.push(fx.floor().min(filter_table_size - 1.0) as usize);
        }
        let mut ify = Vec::with_capacity(p1.y as usize - p0.y as usize);
        for y in p0.y..p1.y {
            let fy = ((y as f32 - p_film_discrete.y) * self.inv_filter_radius.y *
                      filter_table_size)
                    .abs();
            ify.push(fy.floor().min(filter_table_size - 1.0) as usize);
        }

        // Add this sample's contribution to all the affected pixels
        for y in p0.y..p1.y {
            for x in p0.x..p1.x {
                let offset = ify[(y - p0.y) as usize] * FILTER_SIZE + ifx[(x - p0.x) as usize];
                let filter_weight = &self.filter_table[offset];
                let idx = self.get_pixel_index(&Point2i::new(x, y));
                let pixel = &mut self.pixels[idx];
                pixel.contrib_sum += colour * *filter_weight;
                pixel.filter_weight_sum += *filter_weight;
            }
        }
    }

    pub fn get_pixel<'a>(&'a self, p: &Point2i) -> &'a FilmTilePixel {
        &self.pixels[self.get_pixel_index(p)]
    }

    pub fn get_pixel_bounds(&self) -> Bounds2i {
        self.pixel_bounds
    }

    fn get_pixel_index(&self, p: &Point2i) -> usize {
        let width = self.pixel_bounds.p_max.x - self.pixel_bounds.p_min.x;
        let pidx = (p.y - self.pixel_bounds.p_min.y) * width + (p.x - self.pixel_bounds.p_min.x);
        pidx as usize
    }
}

#[derive(Clone, Default)]
pub struct FilmTilePixel {
    contrib_sum: Spectrum,
    filter_weight_sum: f32,
}

fn ceil(p: Point2f) -> Point2f {
    Point2f::new(p.x.ceil(), p.y.ceil())
}

fn floor(p: Point2f) -> Point2f {
    Point2f::new(p.x.floor(), p.y.floor())
}
<|endoftext|>


<commit_before>//!Types for interpolation between multiple colors.

use std::cmp::max;

use Mix;

///A linear interpolation between colors.
///
///It's used to smoothly transition between a series of colors, that can be
///either evenly spaced or have customized positions. The gradient is
///continuous between the control points, but it's possible to iterate over a
///number of evenly spaced points using the `take` method. Any point outside
///the domain of the gradient will have the same color as the closest control
///point.
#[derive(Clone, Debug)]
pub struct Gradient<C: Mix + Clone>(Vec<(f32, C)>);

impl<C: Mix + Clone> Gradient<C> {
    ///Create a gradient of evenly spaced colors with the domain [0.0, 1.0].
    ///There must be at least one color.
    pub fn new<I: IntoIterator<Item=C>>(colors: I) -> Gradient<C> {
        let mut points: Vec<_> = colors.into_iter().map(|c| (0.0, c)).collect();
        assert!(points.len() > 0);
        let step_size = 1.0 / max(points.len() - 1, 1) as f32;

        for (i, &mut (ref mut p, _)) in points.iter_mut().enumerate() {
            *p = i as f32 * step_size;
        }

        Gradient(points)
    }

    ///Create a gradient of colors with custom spacing and domain. There must be
    ///at least one color and they are expected to be ordered by their
    ///position value.
    pub fn with_domain(colors: Vec<(f32, C)>) -> Gradient<C> {
        assert!(colors.len() > 0);

        //Maybe sort the colors?
        Gradient(colors)
    }

    ///Get a color from the gradient. The color of the closest control point
    ///will be returned if `i` is outside the domain.
    pub fn get(&self, i: f32) -> C {
        let &(mut min, ref min_color) = self.0.get(0).expect("a Gradient must contain at least one color");
        let mut min_color = min_color;
        let mut min_index = 0;

        if i <= min {
            return min_color.clone();
        }

        let &(mut max, ref max_color) = self.0.last().expect("a Gradient must contain at least one color");
        let mut max_color = max_color;
        let mut max_index = self.0.len() - 1;

        if i >= max {
            return max_color.clone();
        }

        while min_index < max_index - 1 {
            let index = min_index + (max_index - min_index) / 2;

            let (p, ref color) = self.0[index];

            if i <= p {
                max = p;
                max_color = color;
                max_index = index;
            } else {
                min = p;
                min_color = color;
                min_index = index;
            }
        }

        let factor = (i - min) / (max - min);

        min_color.mix(max_color, factor)
    }

    ///Take `n` evenly spaced colors from the gradient, as an iterator.
    pub fn take(&self, n: usize) -> Take<C> {
        let (min, max) = self.domain();

        Take {
            gradient: MaybeSlice::NotSlice(self),
            from: min,
            diff: max - min,
            len: n,
            current: 0,
        }
    }

    ///Slice this gradient to limit its domain.
    pub fn slice<R: Into<Range>>(&self, range: R) -> Slice<C> {
        Slice {
            gradient: self,
            range: range.into(),
        }
    }

    ///Get the limits of this gradient's domain.
    pub fn domain(&self) -> (f32, f32) {
        let &(min, _) = self.0.get(0).expect("a Gradient must contain at least one color");
        let &(max, _) = self.0.last().expect("a Gradient must contain at least one color");
        (min, max)
    }
}

///An iterator over interpolated colors.
pub struct Take<'a, C: Mix + Clone + 'a> {
    gradient: MaybeSlice<'a, C>,
    from: f32,
    diff: f32,
    len: usize,
    current: usize,
}

impl<'a, C: Mix + Clone> Iterator for Take<'a, C> {
    type Item = C;

    fn next(&mut self) -> Option<C> {
        if self.current < self.len {
            let i = self.from + self.current as f32 * (self.diff / self.len as f32);
            self.current += 1;
            Some(self.gradient.get(i))
        } else {
            None
        }
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        (self.len - self.current, Some(self.len - self.current))
    }
}

impl<'a, C: Mix + Clone> ExactSizeIterator for Take<'a, C> { }

///A slice of a Gradient that limits its domain.
#[derive(Clone, Debug)]
pub struct Slice<'a, C: Mix + Clone + 'a> {
    gradient: &'a Gradient<C>,
    range: Range,
}

impl<'a, C: Mix + Clone> Slice<'a, C> {
    ///Get a color from the gradient slice. The color of the closest domain
    ///limit will be returned if `i` is outside the domain.
    pub fn get(&self, i: f32) -> C {
        self.gradient.get(self.range.clamp(i))
    }

    ///Take `n` evenly spaced colors from the gradient slice, as an iterator.
    pub fn take(&self, n: usize) -> Take<C> {
        let (min, max) = self.domain();

        Take {
            gradient: MaybeSlice::Slice(self.clone()),
            from: min,
            diff: max - min,
            len: n,
            current: 0,
        }
    }

    ///Slice this gradient slice to further limit its domain. Ranges outside
    ///the domain will be clamped to the nearest domain limit.
    pub fn slice<R: Into<Range>>(&self, range: R) -> Slice<C> {
        Slice {
            gradient: self.gradient,
            range: self.range.constrain(&range.into())
        }
    }

    ///Get the limits of this gradient slice's domain.
    pub fn domain(&self) -> (f32, f32) {
        if let Range { from: Some(from), to: Some(to) } = self.range {
            (from, to)
        } else {
            let (from, to) = self.gradient.domain();
            (self.range.from.unwrap_or(from), self.range.to.unwrap_or(to))
        }
    }
}

///A domain range for gradient slices.
#[derive(Clone, Debug, PartialEq)]
pub struct Range {
    from: Option<f32>,
    to: Option<f32>,
}

impl Range {
    fn clamp(&self, mut x: f32) -> f32 {
        x = self.from.unwrap_or(x).max(x);
        self.to.unwrap_or(x).min(x)
    }

    fn constrain(&self, other: &Range) -> Range {
        if let (Some(f), Some(t)) = (other.from, self.to) {
            if f >= t {
                return Range {
                    from: self.to,
                    to: self.to,
                };
            }
        }

        if let (Some(t), Some(f)) = (other.to, self.from) {
            if t <= f {
                return Range {
                    from: self.from,
                    to: self.from,
                };
            }
        }

        Range {
            from: match (self.from, other.from) {
                (Some(s), Some(o)) => Some(s.max(o)),
                (Some(s), None) => Some(s),
                (None, Some(o)) => Some(o),
                (None, None) => None,
            },
            to: match (self.to, other.to) {
                (Some(s), Some(o)) => Some(s.min(o)),
                (Some(s), None) => Some(s),
                (None, Some(o)) => Some(o),
                (None, None) => None,
            },
        }
    }
}

impl From<::std::ops::Range<f32>> for Range {
    fn from(range: ::std::ops::Range<f32>) -> Range {
        Range {
            from: Some(range.start),
            to: Some(range.end),
        }
    }
}

impl From<::std::ops::RangeFrom<f32>> for Range {
    fn from(range: ::std::ops::RangeFrom<f32>) -> Range {
        Range {
            from: Some(range.start),
            to: None,
        }
    }
}

impl From<::std::ops::RangeTo<f32>> for Range {
    fn from(range: ::std::ops::RangeTo<f32>) -> Range {
        Range {
            from: None,
            to: Some(range.end),
        }
    }
}

impl From<::std::ops::RangeFull> for Range {
    fn from(_range: ::std::ops::RangeFull) -> Range {
        Range {
            from: None,
            to: None,
        }
    }
}

enum MaybeSlice<'a, C: Mix + Clone + 'a> {
    NotSlice(&'a Gradient<C>),
    Slice(Slice<'a, C>),
}

impl<'a, C: Mix + Clone> MaybeSlice<'a, C> {
    fn get(&self, i: f32) -> C {
        match *self {
            MaybeSlice::NotSlice(g) => g.get(i),
            MaybeSlice::Slice(ref s) => s.get(i),
        }
    }
}

#[cfg(test)]
mod test {
    use super::Range;

    #[test]
    fn range_clamp() {
        let range: Range = (0.0..1.0).into();
        assert_eq!(range.clamp(-1.0), 0.0);
        assert_eq!(range.clamp(2.0), 1.0);
        assert_eq!(range.clamp(0.5), 0.5);
    }

    #[test]
    fn range_constrain() {
        let range: Range = (0.0..1.0).into();
        assert_eq!(range.constrain(&(-3.0..-5.0).into()), (0.0..0.0).into());
        assert_eq!(range.constrain(&(-3.0..0.8).into()), (0.0..0.8).into());

        assert_eq!(range.constrain(&(3.0..5.0).into()), (1.0..1.0).into());
        assert_eq!(range.constrain(&(0.2..5.0).into()), (0.2..1.0).into());

        assert_eq!(range.constrain(&(0.2..0.8).into()), (0.2..0.8).into());
    }
}
<commit_msg>Add a simple gradient slice test<commit_after>//!Types for interpolation between multiple colors.

use std::cmp::max;

use Mix;

///A linear interpolation between colors.
///
///It's used to smoothly transition between a series of colors, that can be
///either evenly spaced or have customized positions. The gradient is
///continuous between the control points, but it's possible to iterate over a
///number of evenly spaced points using the `take` method. Any point outside
///the domain of the gradient will have the same color as the closest control
///point.
#[derive(Clone, Debug)]
pub struct Gradient<C: Mix + Clone>(Vec<(f32, C)>);

impl<C: Mix + Clone> Gradient<C> {
    ///Create a gradient of evenly spaced colors with the domain [0.0, 1.0].
    ///There must be at least one color.
    pub fn new<I: IntoIterator<Item=C>>(colors: I) -> Gradient<C> {
        let mut points: Vec<_> = colors.into_iter().map(|c| (0.0, c)).collect();
        assert!(points.len() > 0);
        let step_size = 1.0 / max(points.len() - 1, 1) as f32;

        for (i, &mut (ref mut p, _)) in points.iter_mut().enumerate() {
            *p = i as f32 * step_size;
        }

        Gradient(points)
    }

    ///Create a gradient of colors with custom spacing and domain. There must be
    ///at least one color and they are expected to be ordered by their
    ///position value.
    pub fn with_domain(colors: Vec<(f32, C)>) -> Gradient<C> {
        assert!(colors.len() > 0);

        //Maybe sort the colors?
        Gradient(colors)
    }

    ///Get a color from the gradient. The color of the closest control point
    ///will be returned if `i` is outside the domain.
    pub fn get(&self, i: f32) -> C {
        let &(mut min, ref min_color) = self.0.get(0).expect("a Gradient must contain at least one color");
        let mut min_color = min_color;
        let mut min_index = 0;

        if i <= min {
            return min_color.clone();
        }

        let &(mut max, ref max_color) = self.0.last().expect("a Gradient must contain at least one color");
        let mut max_color = max_color;
        let mut max_index = self.0.len() - 1;

        if i >= max {
            return max_color.clone();
        }

        while min_index < max_index - 1 {
            let index = min_index + (max_index - min_index) / 2;

            let (p, ref color) = self.0[index];

            if i <= p {
                max = p;
                max_color = color;
                max_index = index;
            } else {
                min = p;
                min_color = color;
                min_index = index;
            }
        }

        let factor = (i - min) / (max - min);

        min_color.mix(max_color, factor)
    }

    ///Take `n` evenly spaced colors from the gradient, as an iterator.
    pub fn take(&self, n: usize) -> Take<C> {
        let (min, max) = self.domain();

        Take {
            gradient: MaybeSlice::NotSlice(self),
            from: min,
            diff: max - min,
            len: n,
            current: 0,
        }
    }

    ///Slice this gradient to limit its domain.
    pub fn slice<R: Into<Range>>(&self, range: R) -> Slice<C> {
        Slice {
            gradient: self,
            range: range.into(),
        }
    }

    ///Get the limits of this gradient's domain.
    pub fn domain(&self) -> (f32, f32) {
        let &(min, _) = self.0.get(0).expect("a Gradient must contain at least one color");
        let &(max, _) = self.0.last().expect("a Gradient must contain at least one color");
        (min, max)
    }
}

///An iterator over interpolated colors.
pub struct Take<'a, C: Mix + Clone + 'a> {
    gradient: MaybeSlice<'a, C>,
    from: f32,
    diff: f32,
    len: usize,
    current: usize,
}

impl<'a, C: Mix + Clone> Iterator for Take<'a, C> {
    type Item = C;

    fn next(&mut self) -> Option<C> {
        if self.current < self.len {
            let i = self.from + self.current as f32 * (self.diff / self.len as f32);
            self.current += 1;
            Some(self.gradient.get(i))
        } else {
            None
        }
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        (self.len - self.current, Some(self.len - self.current))
    }
}

impl<'a, C: Mix + Clone> ExactSizeIterator for Take<'a, C> { }

///A slice of a Gradient that limits its domain.
#[derive(Clone, Debug)]
pub struct Slice<'a, C: Mix + Clone + 'a> {
    gradient: &'a Gradient<C>,
    range: Range,
}

impl<'a, C: Mix + Clone> Slice<'a, C> {
    ///Get a color from the gradient slice. The color of the closest domain
    ///limit will be returned if `i` is outside the domain.
    pub fn get(&self, i: f32) -> C {
        self.gradient.get(self.range.clamp(i))
    }

    ///Take `n` evenly spaced colors from the gradient slice, as an iterator.
    pub fn take(&self, n: usize) -> Take<C> {
        let (min, max) = self.domain();

        Take {
            gradient: MaybeSlice::Slice(self.clone()),
            from: min,
            diff: max - min,
            len: n,
            current: 0,
        }
    }

    ///Slice this gradient slice to further limit its domain. Ranges outside
    ///the domain will be clamped to the nearest domain limit.
    pub fn slice<R: Into<Range>>(&self, range: R) -> Slice<C> {
        Slice {
            gradient: self.gradient,
            range: self.range.constrain(&range.into())
        }
    }

    ///Get the limits of this gradient slice's domain.
    pub fn domain(&self) -> (f32, f32) {
        if let Range { from: Some(from), to: Some(to) } = self.range {
            (from, to)
        } else {
            let (from, to) = self.gradient.domain();
            (self.range.from.unwrap_or(from), self.range.to.unwrap_or(to))
        }
    }
}

///A domain range for gradient slices.
#[derive(Clone, Debug, PartialEq)]
pub struct Range {
    from: Option<f32>,
    to: Option<f32>,
}

impl Range {
    fn clamp(&self, mut x: f32) -> f32 {
        x = self.from.unwrap_or(x).max(x);
        self.to.unwrap_or(x).min(x)
    }

    fn constrain(&self, other: &Range) -> Range {
        if let (Some(f), Some(t)) = (other.from, self.to) {
            if f >= t {
                return Range {
                    from: self.to,
                    to: self.to,
                };
            }
        }

        if let (Some(t), Some(f)) = (other.to, self.from) {
            if t <= f {
                return Range {
                    from: self.from,
                    to: self.from,
                };
            }
        }

        Range {
            from: match (self.from, other.from) {
                (Some(s), Some(o)) => Some(s.max(o)),
                (Some(s), None) => Some(s),
                (None, Some(o)) => Some(o),
                (None, None) => None,
            },
            to: match (self.to, other.to) {
                (Some(s), Some(o)) => Some(s.min(o)),
                (Some(s), None) => Some(s),
                (None, Some(o)) => Some(o),
                (None, None) => None,
            },
        }
    }
}

impl From<::std::ops::Range<f32>> for Range {
    fn from(range: ::std::ops::Range<f32>) -> Range {
        Range {
            from: Some(range.start),
            to: Some(range.end),
        }
    }
}

impl From<::std::ops::RangeFrom<f32>> for Range {
    fn from(range: ::std::ops::RangeFrom<f32>) -> Range {
        Range {
            from: Some(range.start),
            to: None,
        }
    }
}

impl From<::std::ops::RangeTo<f32>> for Range {
    fn from(range: ::std::ops::RangeTo<f32>) -> Range {
        Range {
            from: None,
            to: Some(range.end),
        }
    }
}

impl From<::std::ops::RangeFull> for Range {
    fn from(_range: ::std::ops::RangeFull) -> Range {
        Range {
            from: None,
            to: None,
        }
    }
}

enum MaybeSlice<'a, C: Mix + Clone + 'a> {
    NotSlice(&'a Gradient<C>),
    Slice(Slice<'a, C>),
}

impl<'a, C: Mix + Clone> MaybeSlice<'a, C> {
    fn get(&self, i: f32) -> C {
        match *self {
            MaybeSlice::NotSlice(g) => g.get(i),
            MaybeSlice::Slice(ref s) => s.get(i),
        }
    }
}

#[cfg(test)]
mod test {
    use super::{Range, Gradient};
    use Rgb;

    #[test]
    fn range_clamp() {
        let range: Range = (0.0..1.0).into();
        assert_eq!(range.clamp(-1.0), 0.0);
        assert_eq!(range.clamp(2.0), 1.0);
        assert_eq!(range.clamp(0.5), 0.5);
    }

    #[test]
    fn range_constrain() {
        let range: Range = (0.0..1.0).into();
        assert_eq!(range.constrain(&(-3.0..-5.0).into()), (0.0..0.0).into());
        assert_eq!(range.constrain(&(-3.0..0.8).into()), (0.0..0.8).into());

        assert_eq!(range.constrain(&(3.0..5.0).into()), (1.0..1.0).into());
        assert_eq!(range.constrain(&(0.2..5.0).into()), (0.2..1.0).into());

        assert_eq!(range.constrain(&(0.2..0.8).into()), (0.2..0.8).into());
    }

    #[test]
    fn simple_slice() {
        let g1 = Gradient::new(vec![Rgb::linear_rgb(1.0, 0.0, 0.0), Rgb::linear_rgb(0.0, 0.0, 1.0)]);
        let g2 = g1.slice(..0.5);

        let v1: Vec<_> = g1.take(10).take(5).collect();
        let v2: Vec<_> = g2.take(5).collect();

        assert_eq!(v1, v2);
    }
}
<|endoftext|>