5个不稳定版本

0.3.0 2024年8月11日
0.2.0 2023年12月18日
0.1.3 2023年12月7日
0.1.2 2023年11月2日
0.1.1 2023年10月30日

#14 in 渲染

Download history 98/week @ 2024-05-03 52/week @ 2024-05-10 51/week @ 2024-05-17 76/week @ 2024-05-24 60/week @ 2024-05-31 39/week @ 2024-06-07 48/week @ 2024-06-14 57/week @ 2024-06-21 19/week @ 2024-06-28 46/week @ 2024-07-05 40/week @ 2024-07-12 29/week @ 2024-07-19 55/week @ 2024-07-26 39/week @ 2024-08-02 155/week @ 2024-08-09 30/week @ 2024-08-16

282 每月下载量
用于 7 个crate(2直接)

MIT/Apache

135KB
2.5K SLoC

clipline

CI crates.io docs.rs downloads

使用像素完美的裁剪高效渲染线段。

概述

  • 提供裁剪和未裁剪的渲染线段迭代器。
    • 消除边界检查:裁剪的线段保证在区域内。
    • 保证裁剪的线段与其未裁剪版本匹配。
  • 支持大多数大小的有符号和无符号整数坐标。
    • 仅使用整数算术。
    • 防止溢出和除以零,禁止 clippy::arithmetic_side_effects
    • 在底层数值类型的整个域上定义迭代器。
  • 可在 const 上下文和 #![no_std] 环境中使用。

clipline in action

用法

clipline 添加到 Cargo.toml

[dependencies]
clipline = "0.3.0"

功能标志

  • octant_64
    • 为所有目标启用 OctantAnyOctanti64/u64 上,以及为64位目标在 isize/usize 上。
    • 仅在使用完整的64位范围时使用此标志,因为 Octant 将使用 u128i128 进行某些计算。
  • try_foldis_empty (nightly-only)
    • 启用优化的 Iterator::try_foldExactSizeIterator::is_empty 实现。

示例

use clipline::{AnyOctant, Clip, Diagonal0, Point};

/// Width of the pixel buffer.
const WIDTH: usize = 64;
/// Height of the pixel buffer.
const HEIGHT: usize = 48;

/// Pixel color value.
const RGBA: u32 = 0xFFFFFFFF;

/// A function that operates on a single pixel in a pixel buffer.
///
/// ## Safety
/// `(x, y)` must be inside the `buffer`.
unsafe fn draw(buffer: &mut [u32], (x, y): Point<i8>, rgba: u32) {
    let index = y as usize * WIDTH + x as usize;
    debug_assert!(index < buffer.len());
    *buffer.get_unchecked_mut(index) = rgba;
}

fn main() {
    let mut buffer = [0_u32; WIDTH * HEIGHT];

    // The clipping region is closed/inclusive, thus 1 needs to be subtracted from the size.
    let clip = Clip::<i8>::new((0, 0), (WIDTH as i8 - 1, HEIGHT as i8 - 1)).unwrap();

    // `Clip` has convenience methods for the general iterators.
    clip.any_octant((-128, -100), (100, 80))
        // None if the line segment is completely invisible.
        // You might want to handle that case differently.
        .unwrap()
        // clipped to [(0, 1), ..., (58, 47)]
        .for_each(|xy| {
            // SAFETY: (x, y) has been clipped to the buffer.
            unsafe { draw(&mut buffer, xy, RGBA) }
        });

    // Alternatively, use the iterator constructors.
    AnyOctant::<i8>::clip((12, 0), (87, 23), &clip)
        .into_iter()
        .flatten()
        // clipped to [(12, 0), ..., (63, 16)]
        .for_each(|xy| {
            // SAFETY: (x, y) has been clipped to the buffer.
            unsafe { draw(&mut buffer, xy, RGBA) }
        });

    // Horizontal and vertical line segments.
    clip.axis_0(32, 76, -23)
        .unwrap()
        // clipped to [(63, 32), ..., (0, 32)]
        .for_each(|xy| {
            // SAFETY: (x, y) has been clipped to the buffer.
            unsafe { draw(&mut buffer, xy, RGBA) }
        });

    clip.axis_1(32, -23, 76)
        .unwrap()
        // clipped to [(32, 0), ..., (32, 47)]
        .for_each(|xy| {
            // SAFETY: (x, y) has been clipped to the buffer.
            unsafe { draw(&mut buffer, xy, RGBA) }
        });

    // Unclipped iterators are also available.
    // (-2, -2) -> (12, 12) is covered by Diagonal0, we can construct it directly.
    Diagonal0::<i8>::new((-2, -2), (12, 12))
        .unwrap()
        // Need to check every pixel to avoid going out of bounds.
        .filter(|&xy| clip.point(xy))
        .for_each(|xy| {
            // SAFETY: (x, y) is inside the buffer.
            unsafe { draw(&mut buffer, xy, RGBA) }
        });
}

限制

  • 为了支持在 const 上下文中的使用,类型必须为每个支持的数值类型都有一个固有的实现,而不是依赖于特质。这一点加上 Rust 对函数重载的支持不足,意味着数值类型参数必须始终指定。
  • 目前,只能迭代半开线段。这允许为所有类型实现 ExactSizeIterator。包含迭代器在 #1 中跟踪。

基准测试

  • divan 用于 基准测试 clipline 的不同版本,以及 line_drawing。使用 cargo bench 运行基准测试。
  • 实际上,对于未裁剪的线段,在索引到网格时需要进行边界检查,因此 draw_pixel_checkeddraw_pixel_unchecked 函数之间存在差异。

参考文献

clipline 启发于以下论文

无运行时依赖