generated from pimoroni/pga
-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
220 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |