Tristram Gräbener
Tristram Gräbener

Reputation: 9711

Draw a line in a bitmap (possibly with piston)

I want to draw a line in a bitmap, e.g. from pixel (10, 10) to pixel (90, 90). The line must have a specific width.

Using piston image, I am able to draw a single pixel:

let mut image = ImageBuffer::<image::Rgb<u8>>::new(100, 100);
image.get_pixel_mut(5, 5).data = [255, 255, 255];
image.save("output.png");

However there is no method to draw a line.

I suppose I have to use piston::graphics for that, but I can’t find any ressource how to do it (any example involves a window that provides a context on which graphics works on).

Upvotes: 5

Views: 3657

Answers (2)

Michael
Michael

Reputation: 5062

In addition to the great answer above: there is now direct support for drawing lines and many more shapes (even texts) in the imageproc library (see also the examples there):

extern crate image;
extern crate imageproc;

use image::{Rgb, RgbImage};
use imageproc::drawing::draw_line_segment_mut;

fn main() {
    let mut img = RgbImage::new(100, 100);
    draw_line_segment_mut(
        &mut img,
        (5f32, 5f32),              // start point
        (95f32, 95f32),            // end point
        Rgb([69u8, 203u8, 133u8]), // RGB colors
    );
    img.save("output.png").unwrap();
}

Upvotes: 5

Danilo Bargen
Danilo Bargen

Reputation: 19452

If you drop the width requirement and don't need antialiasing either, you could use something like Bresenham's line algorithm (also on Rosetta Code):

extern crate image;

use image::RgbImage;

fn draw_line(img: &mut RgbImage, x0: i64, y0: i64, x1: i64, y1: i64) {

    // Create local variables for moving start point
    let mut x0 = x0;
    let mut y0 = y0;

    // Get absolute x/y offset
    let dx = if x0 > x1 { x0 - x1 } else { x1 - x0 };
    let dy = if y0 > y1 { y0 - y1 } else { y1 - y0 };

    // Get slopes
    let sx = if x0 < x1 { 1 } else { -1 };
    let sy = if y0 < y1 { 1 } else { -1 };

    // Initialize error
    let mut err = if dx > dy { dx } else {-dy} / 2;
    let mut err2;

    loop {
        // Set pixel
        img.get_pixel_mut(x0 as u32, y0 as u32).data = [255, 255, 255];

        // Check end condition
        if x0 == x1 && y0 == y1 { break };

        // Store old error
        err2 = 2 * err;

        // Adjust error and start position
        if err2 > -dx { err -= dy; x0 += sx; }
        if err2 < dy { err += dx; y0 += sy; }
    }

}

fn main() {
    let mut img = RgbImage::new(256, 256);

    draw_line(&mut img, 10, 10, 246, 128);
    draw_line(&mut img, 128, 10, 10, 246);

    img.save("output.png").unwrap();
}

Output:

enter image description here

As a primitive form of adding thickness, you could repeat drawing the line with some offset. Alternatively, draw a filled rectangle where the height of the rectangle corresponds to the thickness of the desired line.

There's a ticket open in the imageproc project to add anti aliased line drawing support: https://github.com/PistonDevelopers/imageproc/issues/97

Upvotes: 5

Related Questions