Skip to content

nxsaken/clipline

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

clipline

CI crates.io docs.rs downloads

This crate provides iterators over the rasterized points of directed, half-open line segments with pixel-perfect clipping to rectangular regions.

See the documentation for details, or the example below.

Features

  • Supports unsigned and signed coordinates of most sizes.
    • Defines the iterators on the entire domains of the underlying numeric types.
    • Avoids integer overflow without overhead.
  • Guarantees that clipped segments match the unclipped versions of themselves.
  • Usable in const contexts and #![no_std] environments.

clipline in action

Usage

use clipline::*;

/// Width of the pixel buffer.
const WIDTH: u16 = 256;
/// Height of the pixel buffer.
const HEIGHT: u16 = 240;

/// A function that operates on a single pixel in a pixel buffer.
///
/// # Safety
///
/// `i < WIDTH`, `j < HEIGHT`.
unsafe fn draw(pixels: &mut [bool], i: u16, j: u16, pixel: bool) {
    let index = j as usize * WIDTH as usize + i as usize;
    debug_assert!(index < pixels.len());
    unsafe { *pixels.get_unchecked_mut(index) = pixel; }
}

fn main() {
    let mut pixels = [false; WIDTH as usize * HEIGHT as usize];
  
    // This defines a clipping region (0, 0, WIDTH-1, HEIGHT-1),
    // which covers all valid indices of the pixel buffer.
    let clip = Clip::<i16>::from_size(WIDTH, HEIGHT).unwrap();
  
    // For Clip, *_proj involves a simple cast from i16 to u16.
    clip.line_b_proj(-32, -64, 320, 256)
        // This will panic if the line segment is completely outside the region.
        .unwrap()
        // This iterates over all points inside the region, relative to that region.
        // Effectively this allows to safely index into the underlying buffer.
        .for_each(|(i, j)| {
            // SAFETY: i < WIDTH, j < HEIGHT.
            unsafe { draw(&mut pixels, i, j, true) }
        });
  
    // This is how you can construct an unclipped line segment iterator.
    LineD::<u16>::new(1, 2, 31, 32)
        // This will panic if the segment is not diagonal.
        .unwrap()
        // By construction, all points of this line segment lie inside the region, thus
        // clipping can be skipped. Do this if you are sure your line segments are inside.
        .for_each(|(i, j)| {
            // SAFETY: i < WIDTH, j < HEIGHT.
            unsafe { draw(&mut pixels, i, j, true) }
        });
  
    // (-32, 16) -> (64, 16)
    LineAx::<i16>::new(16, -32, 64)
        // This is a naive pointwise clip-projection.
        // It's much slower than clipping the segment as a whole.
        .filter_map(|(x, y)| clip.point_proj(x, y))
        // But it gets the job done.
        .for_each(|(i, j)| {
            // SAFETY: i < WIDTH, j < HEIGHT.
            unsafe { draw(&mut pixels, i, j, true) }
        });
  
    // This defines a clipping region (16, 32, 16 + WIDTH - 1, 32 + HEIGHT - 1),
    // which covers all valid indices of the pixel buffer *after projection*.
    let clip = Viewport::<i16>::from_min_size(16, 32, WIDTH, HEIGHT).unwrap();
  
    // For Viewport, *_proj involves subtracting the minimum corner of the Viewport
    // from all the clipped coordinates.
    clip.line_b_proj(-16, -32, 336, 288)
        // This is equivalent to the first example (we just shifted the original line segment).
        .unwrap()
        // This iterates over all points inside the region, relative to that region.
        // Effectively this allows to safely index into the underlying buffer.
        .for_each(|(i, j)| {
            // SAFETY: i < WIDTH, j < HEIGHT.
            unsafe { draw(&mut pixels, i, j, true) }
        });
  
    fn do_at_world_pos(x: i16, y: i16) {
        println!("doing something at world position {x}, {y}")
    }
  
    // Both Clip and Viewport support clipping without projection.
    // This could be useful if you want to iterate over a line segment
    // in "world-space" (represented by signed or unsigned coordinates),
    // restricted to a region. It's not safe to use this to index into a grid.
    clip.line_b(-16, -32, 336, 288)
        .unwrap()
        .for_each(|(x, y)| do_at_world_pos(x, y));
  
    // Unsigned Clips have infallible from_max constructors
    // and do not provide *_proj methods (no need).
    let mut line = Clip::<u16>::from_max(WIDTH - 1, HEIGHT - 1)
        .line_b(1, 2, 320, 256)
        .unwrap();
  
    // custom iteration APIs are available in const contexts
    while let Some((i, j)) = line.pop_head() {
        // SAFETY: i < WIDTH, j < HEIGHT.
        unsafe { draw(&mut pixels, i, j, true) }
    }
}

References

clipline synthesizes the algorithms from the following papers:

About

Line segment rasterization with pixel-perfect clipping.

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Contributors

Languages