Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: CI

on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"

- name: Install system dependencies (Cairo)
run: |
sudo apt-get update
sudo apt-get install -y libcairo2-dev pkg-config

- name: Install project and test dependencies
run: |
python -m pip install --upgrade pip
pip install -e .
pip install pytest Pillow

- name: Run tests
run: |
pytest -q
69 changes: 69 additions & 0 deletions .github/workflows/version-bump-and-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Version Bump & Release

on:
push:
branches: [ main, master ]

permissions:
contents: write
id-token: write

jobs:
bump-and-release:
if: ${{ !startsWith(github.event.head_commit.message, '[SKIP]') }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Configure Git author
run: |
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"

- name: Install bump and build tooling
run: |
python -m pip install --upgrade pip
pip install bump2version build

# Bump logic controlled by commit message prefix:
# [MAJOR] -> major, [FEATURE] -> minor, default -> patch
- name: Bump Major Version
if: startsWith(github.event.head_commit.message, '[MAJOR]')
run: |
bump2version major
echo "BUMPED=1" >> $GITHUB_ENV

- name: Bump Minor Version
if: startsWith(github.event.head_commit.message, '[FEATURE]')
run: |
bump2version minor
echo "BUMPED=1" >> $GITHUB_ENV

- name: Bump Patch Version
if: env.BUMPED != '1'
run: |
bump2version patch

- name: Push version tag
run: |
git push --follow-tags

- name: Build distributions
run: |
python -m build

# Prefer Trusted Publisher via OIDC if configured on PyPI.
# If not yet configured, set PYPI_API_TOKEN secret and uncomment 'password:' below.
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
# password: ${{ secrets.PYPI_API_TOKEN }}
skip-existing: true
19 changes: 19 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.4
hooks:
# Run the linter
- id: ruff
args: [--fix]
# Run the formatter
- id: ruff-format

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-merge-conflict
- id: check-toml
14 changes: 0 additions & 14 deletions .travis.yml

This file was deleted.

3 changes: 1 addition & 2 deletions LICENCE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The MIT License (MIT)

The MIT License (MIT)

Copyright (c) 2014 Zulko
Copyright (c) 2025 Zulko

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand All @@ -23,4 +23,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

190 changes: 190 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
<p align="center">
<img src="https://raw.githubusercontent.com/Zulko/gizeh/master/logo.jpeg" alt="[logo]" />
</p>

# Gizeh — Cairo for tourists

Gizeh is a Python library for vector graphics, built on top of [`cairocffi`](https://cairocffi.readthedocs.io/) (Cairo bindings for Python). Cairo is powerful but low-level; Gizeh adds a small set of intuitive primitives to make drawing easier.

```python
# Let's draw a red circle!
import gizeh

surface = gizeh.Surface(width=320, height=260) # pixels
circle = gizeh.circle(r=30, xy=[40, 40], fill=(1, 0, 0))
circle.draw(surface) # draw the circle on the surface
surface.write_to_png("circle.png") # export as PNG
```

See Gizeh in action (combined with MoviePy for animations) in
[this blog post](http://zulko.github.io/blog/2014/09/20/vector-animations-with-python/).

Gizeh supports Python 3.7+.

## Installation

Gizeh requires the Cairo graphics library to be installed on your system.

- macOS (Homebrew):
- `brew install cairo pkg-config`
- Debian/Ubuntu:
- `sudo apt-get install -y libcairo2-dev pkg-config`

Then install the Python package:

```bash
pip install gizeh
```

Alternatively, from source (editable/development install):

```bash
pip install -e .
```

## User Guide

This guide, along with the examples in `gizeh/examples`, should give you everything you need to get started. Check docstrings for details.

### Surfaces

A Surface is a rectangle of fixed dimensions (in pixels) on which you draw elements, and which you can save/export as an image:

```python
import gizeh

# initialize surface
surface = gizeh.Surface(width=320, height=260) # in pixels

# make a shape and draw it on the surface
circle = gizeh.circle(r=30, xy=[40, 40], fill=(1, 1, 1))
circle.draw(surface)

# export the surface
surface.get_npimage() # returns a (width x height x 3) numpy array
surface.write_to_png("circle.png")
```

### Elements

Basic elements are circles, rectangles, lines, text, etc., that you draw on a surface using `my_element.draw(surface)`. Common parameters:

- `xy`: center coordinates of the object. At render time (`surface.write_to_png`) you can set `y_origin` to `"top"` (default) or `"bottom"`. With `"top"`, `(0, 0)` is the upper-left of the final image; the bottom-right is `(width, height)`. With `"bottom"`, `(0, 0)` is the bottom-left (like plots), and `(width, height)` is the top-right.
- `angle`: rotation (radians) around `xy`.
- `fill`: interior fill (default: none). Can be a color `(R, G, B)`, gradient, image pattern, etc.
- `stroke`: contour fill; same rules as `fill`.
- `stroke_width`: contour width (pixels). Default is `0` (no stroke).

Examples:

```python
import numpy as np
Pi = np.pi

circ = gizeh.circle(r=30, xy=(50, 50), fill=(1, 1, 1))
rect = gizeh.rectangle(lx=60.3, ly=45, xy=(60, 70), fill=(0, 1, 0), angle=Pi/8)
sqr = gizeh.square(l=20, stroke=(1, 1, 1), stroke_width=1.5)
arc = gizeh.arc(r=20, a1=Pi/4, a2=3*Pi/4, fill=(1, 1, 1))
text = gizeh.text("Hello world", fontfamily="Impact", fontsize=40,
fill=(1, 1, 1), xy=(100, 100), angle=Pi/12)
poly = gizeh.regular_polygon(r=40, n=5, angle=np.pi/4, xy=[40, 50], fill=(1, 0, 1))
line = gizeh.polyline(points=[(0, 0), (20, 30), (40, 40), (0, 10)],
stroke_width=3, stroke=(1, 0, 0), fill=(0, 1, 0))
```

### Fill and stroke

`fill` and `stroke` can be:

- RGB `(r, g, b)` with values in `[0, 1]`
- RGBA `(r, g, b, a)` with `a` in `[0, 1]`
- `gizeh.ColorGradient`
- `gizeh.ImagePattern` (an image)
- A NumPy RGB/RGBA image array (where implemented)

### Transformations

Elements can be transformed (translated, rotated, scaled). All transformations are out-of-place: they return a modified copy.

```python
square_1 = gizeh.square(l=20, xy=[30, 35], fill=(1, 0, 0))
square_2 = square_1.rotate(np.pi/8) # rotation around [0, 0] by default
square_3 = square_2.rotate(np.pi/4, center=[10, 15]) # rotation around a center
square_4 = square_1.scale(2) # two times bigger
square_5 = square_1.scale(sx=2, sy=3) # width x2, height x3
square_6 = square_1.scale(2, center=[30, 30]) # zoom around a center
square_7 = square_1.translate(xy=[5, 15]) # translation
```

### Groups

A `Group` is a collection of elements (including nested groups) transformed and drawn together:

```python
square = gizeh.square(l=20, fill=(1, 0, 0), xy=(40, 40))
circle = gizeh.circle(r=20, fill=(1, 1, 0), xy=(50, 30))
group_1 = gizeh.Group([square, circle])
group_2 = group_1.translate(xy=[30, 30]).rotate(np.pi/4)
group_3 = gizeh.Group([circle, group_1])

surface = gizeh.Surface(width=300, height=200)
group_1.draw(surface)
group_2.draw(surface)
group_3.draw(surface)
surface.write_to_png("my_masterwork.png")
```

## Troubleshooting installation

If pip installation fails, ensure you have the system Cairo development packages and `pkg-config` installed (see “Installation” above).

- macOS (Homebrew): `brew install cairo pkg-config`
- Debian/Ubuntu: `sudo apt-get install -y libcairo2-dev pkg-config`

## Developers

Development uses [`uv`](https://github.com/astral-sh/uv) for fast Python packaging and workflows.

Prerequisites:

- Install system packages
- macOS: `brew install uv cairo pkg-config`
- Debian/Ubuntu: `sudo apt-get install -y libcairo2-dev pkg-config` and install `uv` from its docs

Setup and install deps:

```bash
uv python install 3.12
uv venv --python 3.12
uv sync --group dev
```

Run tests:

```bash
uv run pytest -q
```

Run a specific test:

```bash
uv run pytest -q tests/test_samples.py::test_random_squares
```

Quick import check:

```bash
uv run python -c "import gizeh, numpy, cairocffi; print('ok')"
```

Install and run pre-commit hooks:

```bash
uv run pre-commit install
uv run pre-commit run --all-files
```

## Contributing

Gizeh is open-source (MIT) by [Zulko](https://github.com/Zulko). Contributions are welcome—issues and pull requests on
[GitHub](https://github.com/Zulko/gizeh).
Loading