Git Product home page Git Product logo

vason's Introduction

vason - simple 2D rasterizer

WARNING: This crate is in very early stages. Anything can change anytime. Use at your own risk.

This crate enables you to render simple 2D shapes to a buffer of pixels. After creating a Canvas from a buffer, you have access to methods to fill in, or draw the outline of shapes.

This crate has no runtime dependencies.

A ppm module is included that lets you save your buffer as an image (that can be displayed by some major image viewers).

The crate also works well together with libraries such as minifb, thus you can even use it for small games / demos / visualizations.

Current and planned features:

  • basic shape rendering:
    • fill_rect, outline_rect, thick_outline_rect
    • fill_circle, outline_circle,
    • fill_ellipse, outline_ellipse,
    • line, hline, vline, thick_hline, thick_vline
  • Save buffer to a primitive image format (ppm)
  • more shapes:
    • fill_triangle, outline_triangle, thick_outline_triangle
    • thick_outline_circle
    • thick_outline_ellipse
    • thick_line
    • bezier_curve
  • flood fill
  • copy regions over from other buffer (sprites)
  • Pen-API: "Turtle Geometry"
  • Descriptor-API: A higher level helper API that can make your code more readable (but a tad less efficient).
  • alpha compositing (transparency)
  • built-in monospaced font rendering
  • further optimizations...
  • and more...

Example

use std::fs::File;
use vason::{ppm::encode_canvas, Canvas, Color};

fn main() {
    let mut buffer = vec![0u32; 256*256];
    let mut canvas = Canvas::new(&mut buffer, 256, 256);
    canvas.clear((180, 255, 100));
    canvas.fill_rect(80, 40, 128, 192, Color::GREEN);
    canvas.fill_circle(-40, -40, 128, Color::BLUE);
    canvas.outline_circle(-40, -40, 178, Color::RED);
    canvas.line(256, 0, 0, 256, Color::MAGENTA);

    let mut f = File::create("test.ppm").expect("could not create file");
    encode_canvas(&canvas, &mut f).expect("could not write image to file");
}

You may use the Pen-API to easily draw to the buffer images such as this:

use std::fs::File;
use vason::{ppm::encode_canvas, Canvas, Color, Pen};

fn tree(pen: &mut Pen, size: f32, depth: i32) {
    let state = pen.get_state();
    let green = ((20 - depth) * 15).clamp(0, u8::MAX as i32) as u8;
    pen.set_color((0, green, 0));

    if depth <= 0 || size < 5.0 {
        pen.forward(size).backward(size);
        return;
    }
    pen.forward(size / 3.0).turn_left(30.0);
    tree(pen, (size * 2.0) / 3.0, depth - 1);
    pen.turn_right(30.0).forward(size / 6.0).turn_right(25.0);
    tree(pen, size / 2.0, depth - 1);
    pen.turn_left(25.0).forward(size / 3.0).turn_right(25.0);
    tree(pen, size / 2.0, depth - 1);
    pen.turn_left(25.0).forward(size / 6.0).set_state(state);
}

fn sun(pen: &mut Pen, scale: f32) {
    pen.repeat(18, |pen| {
        pen.forward(0.5 * scale)
            .turn_right(150.0)
            .forward(0.6 * scale)
            .turn_right(100.0)
            .forward(0.3 * scale)
            .turn_right(90.0);
    });
}

fn main() {
    let mut buffer = vec![0u32; 1024 * 1024];
    let mut canvas = Canvas::new(&mut buffer, 1024, 1024);
    canvas.clear((15, 15, 35));
    let mut pen = canvas.pen();

    pen.set_position(512.0, 1024.0).set_direction(-90.0);
    tree(&mut pen, 650.0, 15);

    pen.set_color(Color::YELLOW).set_position(80.0, 120.0);
    sun(&mut pen, 175.0);

    let mut f = File::create("pen.ppm").expect("couldn't create file");
    encode_canvas(&canvas, &mut f).expect("couldn't write image to file");
}

This piece of code produces the following image: rendered image

vason's People

Contributors

vismate avatar

Watchers

 avatar

vason's Issues

Rasterization of outlined shapes

  • outline_rect(x, y, w, h, color)
  • outline_circle(x, y, r, color)
  • thick_outline_rect(x, y, w, h, thickness, color)
  • thick_hline(y, x1, x2, thickness, color)
  • thick_vline(x, y1, y2, thickness, color)
  • outline_ellipse(x, y, a, b, color)
    This list will expand

Rasterization tests

It is difficult to test rasterizing methods, since it's difficult to tell if rendering is valid or not.

One method would be to generate test images with an external app. This may be more problematic than what it's worth, because there can be slight differences in how the external app renders a shape vs how we render one. Slight differences does not invalidate our results, since there are many algorithms the one can use to render a certain shape. Implementation details can also change a few pixels (truncation, rounding and casting errors, accumulation of small inaccuracies)...

An other method is to simply generate our own test images and test against them when we make a change. This method is used by the olive.c project.

Performance of saving ppm

Writing a ppm file is currently painfully slow. An obvious improvement on the user-side is to use a BufWriter, but even then saving an image feels really sluggish.

The main problem is the sheer number of individual writes (this is why using a BufWriter helps a ton).
The data needs to be written to the writer at once (or in larger chunks). The easiest way to do this is preparing the data in ram before writing.

Unit tests

Unit tests should be written where we mainly test for whether a method panics or not.
Since most of methods/functions implemented does not return anything we cannot test their validity.

In a following issue I'll contemplate the idea of implement a kind of testing that olive.c has.

Descriptor API

A higher level helper API that can make code more readable (but a tad less efficient).

By providing types such as Rect, Line, Circle ... that may implement a Draw trait, the users code could be made more readable.

The philosophy behind the methods of Canvas is generality. No Point, Vec, ... structs nor the use of tuples, just plain values.
This can be great, because these values can come from anywhere.

For example:

use vason::{Canvas, Color};

// User's own struct or one from an other lib
struct Point {
  x: i32,
  y: i32,
}

fn main() {
  let mut buffer = vec![0u32; 256*256];
  let mut canvas = Canvas::new(&mut buffer, 256, 256);

  let p1 = Point {x: 10, y: 23 };
  let p2 = Point {x: 102, y: 78 };
  let p3 = Point {x: 16, y: 53 };

  canvas.thick_outline_triangle(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, 5, Color::BLUE);
  ...
}

As you can see, the canvas's method can be called without transforming the data. (You just need to "explode" it)

But let's consider an other case:

use vason::{Canvas, Color};

fn main() {
  let mut buffer = vec![0u32; 256*256];
  let mut canvas = Canvas::new(&mut buffer, 256, 256);

  canvas.thick_outline_triangle(10, 23, 102, 78, 16, 53, 5, Color::BLUE);
  ...
}

In cases like these (especially if you write a lot more of these lines) it can be hard to tell which number represents what.

Descriptor API

With the descriptor API we could make code more readable, when the user doesn't need to interact with foreign types too uch, and when the performance impact does not matter as much.

use vason::{Canvas, Color, descriptor::Triangle};

fn main() {
  let mut buffer = vec![0u32; 256*256];
  let mut canvas = Canvas::new(&mut buffer, 256, 256);

  let tri = Triangle {
    p1: (10, 23),
    p2: (102, 78),
    p3: (16, 53).
    thickness: Some(5),
    color: Color::BLUE,
  }

// Maxbe with a builder-api
let tri = Triangle::new( (10, 23), (102, 78), (16, 53) ).with_thickness(5).with_color(Color::BLUE);

 canvas.draw(&tri);
 // Or
 tri.draw_to(&mut canvas);
  ...
}

This API besides making the code more readable also serves as an abstraction over the numerous method variants of canvas (fill_, outline_, thick_, thick_outline_).

This API could also motivate the canvas methods to be even more specialized thus more performant.
The user could have a lower and also a higher level access to the library's features.

Implement basic 2D rasterization

Canvas should provide at least the following methods:

  • fill_rectangle(x, y, w, h, color)
  • fill_circle(x, y, r, color)
  • line(x1, y1, x2, y2, color)

This list will be expanded later

Coordinates should be represented with i32, sizes with u32 i32 as well and colors with generic types that implement Into<Color>.

I've been editing this issue a lot. (why comment? I'm alone...) Trying to come up with a simple way to represent coordinates and sizes.

First, I though it would be nice to use signed types for coordinates and unsigned ones for sizes. It makes sense after all. The problem is with the implementation. It's really janky full of type conversions and thus warnings. The more intuitive and "proper" (with regards to parameter types) the API is, the more janky is the implementation.

The default behavior of drawing methods in case of invalid argument is not drawing anything (or not drawing a specific part of the shape). This makes total sense, so it is way easier to expand upon these cases rather than to restrict the API and do a messy implementation.

Better examples

The crate should provide better, clearer and more impressive examples.

Logo-like stateful drawing API

I'd like to introduce a Logo-like "turtle geometry" API to the library.

This would be kind of an unique way to use the library in contrast with other similar rasterizers. It could possibly make for a more fun experience.
After all, this entire project is just for fun...

Let this API be known as the pen API.

Features

As the feature set grows, I'm thinking about splitting this crate into multiple features.

Features:

  • Pen-API
  • Descriptor-API
  • PPM

This would be due by the time the Descriptor-API is ready.

Color formats

Add color formats to play nicely with various buffers .
Color formats must be still representable with u32's

Documentation

Methods, functions and types should be properly documented.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.