Skip to content

Commit

Permalink
Docs: Start some documentation.
Browse files Browse the repository at this point in the history
  • Loading branch information
Gadgetoid committed Dec 11, 2024
1 parent 4d54686 commit 57a9c7a
Showing 1 changed file with 220 additions and 0 deletions.
220 changes: 220 additions & 0 deletions docs/picovector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
# PicoVector

PicoVector is a vector graphics library for the Raspberry Pi Pico.

Instead of painting individual pixels, it deals with points and paths which
make up shapes from simple rectangles to complex UI elements or typography.


## Getting Started

To get started with PicoVector on Presto you must first import and set it up
by passing in your PicoGraphics surface:

```python
from presto import Presto
from picovector import PicoVector, Polygon, Transform, ANTIALIAS_BEST

presto = Presto()
vector = PicoVector(presto.display)

transform = Transform()
vector.set_transform(transform)

vector.set_antialiasing(ANTIALIAS_BEST)
```

## Transforms

Scaling and rotating vectors is accomplished with a `Transform` and happens
when one is drawn. In most cases it will suffice to create one `Transform`
and then apply rotation and scale as needed.

* `rotate(angle, (x, y))` - rotate polygons by `angle` in degrees
around point `(x, y)`.
* `scale(scale_x, scale_t)` - apply a scale, change the size of polygons
* `translate(x, y)` - apply a translation, change the position of polygons
* `reset()` - reset the transform object back to the default (no transform)

Internally transforms are matrix operations, so the order you apply them
both matters and may be counter-intuitive.

If, for example, you want to rotate something in the middle of the screen
you'll find that you need to rotate it *first* and then translate it into
position. Eg:

```python
shape = Polygon()
shape.path((-10, -20), (10, 0), (-10, 20))

transform = Transform()
transform.translate(presto.width // 2, presto.height // 2)
transform.rotate(a, (0, 0))

vector.set_transform(transform)
vector.draw(shape)
```

## Antialiasing

Behind the scenes all of PicoVector's drawing is done by PicoGraphics- by
setting pixels. Unlike just directly drawing shapes with pixels PicoVector
includes anti-aliasing, a smoothing technique that turns diagonal lines
into the crisp, blended edges we'd expect from computers today.

Available options are:

* `ANTIALIAS_NONE` - turns off anti-aliasing for best performance
* `ANTIALIAS_FAST` - 4x anti-aliasing, a good balance between speed & quality
* `ANTIALIAS_BEST` - 16x anti-aliasing, best quality at the expense of speed

## Polygons & Primitives

The basis of all drawing operations in PicoVector is the `Polygon` object.

A `Polygon` is a collection of one or more paths to be drawn together. This
allows for shapes with holes - letters, for example - or more complicated
designs - logos or icons - to be scaled, rotated and drawn at once.

If paths overlap then the top-most path will "punch" out the one underneath.

To use any of the primitives or path drawing methods you must first create
a `Polygon`, for example here's a simple triangle:

```python
from picovector import Polygon
my_shape = Polygon()
my_shape.path((10, 0), (0, 10), (20, 10))
```

### Path

* `path((x, y), (x2, y2), (x3, y3), ...)`

A path is simply an arbitrary list of points that produce a complete closed
shape. It's ideal for drawing complex shapes such as logos or icons.

If you have a list of points you can use Python's spread operator to pass it
into `path`, eg:

```python
my_points = [(10, 0), (0, 10), (20, 10)]
my_shape = Polygon()
my_shape.path(*my_points)
```

### Rectangle

* `rectangle(x, y, w, h, corners=(r1, r2, r3, r4), stroke=0)`

A rectangle is a plain old rectangular shape with optional rounded corners.

If `stroke` is greater than zero then the rectangle outline will be produced.

If any of the corner radii are greater than zero then that corner will be created.

### Regular

* `regular(x, y, radius, sides, stroke=0)`

Creates a regular polygon with the given radius and number of sides. Needs at
least 3 sides (an equilateral triangle) and converges on a circle.

If `stroke` is greater than zero then the regular polygon outline will be created.

### Circle

* `circle(x, y, radius, stroke=0)`

Effectively a regular polygon, approximates a circle by automatically picking
a number of sides that will look smooth for a given radius.

If `stroke` is greater than zero then the circle outline will be created.

### Arc

* `arc(x, y, radius, from, to, stroke=0)`

Create an arc at x, y with radius, from and to angle (degrees).

Great for radial graphs.

If `stroke` is greater than zero then the arc outline will be created.

### Star

* `star(x, y, points, inner_radius, outer_radius, stroke=0)`

Create a star at x, y with given number of points.

The inner and outer radius (in pixels) define where the points start and end.

If `stroke` is greater than zero then the arc outline will be created.

## Fonts & Text

Under the hood PicoVector uses [Alright Fonts](https://github.com/lowfatcode/alright-fonts)
a font-format for embedded and low resource platforms.

Alright Fonts supports converting TTF and OTF fonts into .af format which can
then be displayed using PicoVector. Most of your favourite fonts should work
- including silly fonts like [Jokerman](https://en.wikipedia.org/wiki/Jokerman_(typeface)) - but there are some limitations to their complexity.

### Converting

Converting from an OTF or TTF font is done with the `afinate` utility. It's a
Python script that handles decomposing the font into a simple list of points.

Right now you'll need the `port-to-c17` branch:

```
git clone https://github.com/lowfatcode/alright-fonts --branch port-to-c17
```

And you'll need to set up/activate a virtual env and install some dependencies:

```
cd alright-fonts
python3 -m venv .venv
source .venv/bin/activate
pip install freetype.py simplification
```

And, finally, convert a font with `afinate`:

```
./afinate --font jokerman.ttf --quality medium jokerman.af
```

This will output two things- a wall of text detailing which characters have
been converted and how many points/contours they consist of, and the font
file itself. You'll then need to upload the font to your board, this could
be via the file explorer in Thonny or your preferred method.

### Loading & Configuring

```python
vector.set_font("jokerman.af", 24)
```

### Spacing & Alignment

* `set_font_size()`
* `set_font_word_spacing()`
* `set_font_letter_spacing()`
* `set_font_line_height()`
* `set_font_align()`

### Measuring Text

* `x, y, w, h = measure_text(text, x=0, y=0, angle=None)`

Returns a four tuple with the x, y position, width and height in pixels.

### Drawing Text

* `text(text, x, y, angle=None, max_width=0, max_height=0)`

When you draw text the x, y position and angle are used to create a new
Transform to control the position and rotation of your text. The transform
set with `.set_transform()` is ignored.

0 comments on commit 57a9c7a

Please sign in to comment.