Unified Python drawing API

xxao, updated 🕥 2023-03-08 07:28:06

Pero

The main motivation behind the pero library is to provide unified API for multiple drawing backends like PyQt5, PyQt6, PySide2, PySide6, wxPython, PyCairo, PyMuPDF, Pythonista (and possibly more), which is easy to understand and use. Beside the common drawing capabilities, numerous pre-build glyphs are available, as well as an easy-to-use path, matrix transformations etc. Depending on available backend libraries, drawings can be viewed directly or exported into various image formats.

Ever since I discovered the wonderful d3js JavaScript library, I wanted to have the same amazing concept of dynamic properties within Python drawings. In fact, this has been the trigger to start working on the pero library. Finally, it is now available.

Please see the examples folder or in-code documentation of classes and functions to learn more about the pero library capabilities.

Consider also checking a small derived library providing some basic plotting functionalities, like profiles, bars, pie charts and Venn diagrams, called perrot.

```python

import pero

img = pero.Image(width=200, height=200)

img.line_cap = pero.ROUND img.line_join = pero.ROUND

fill

img.fill("w")

body

img.line_width = 2 img.line_color = pero.colors.Orange.darker(.1) img.fill_color = pero.colors.Orange img.draw_circle(100, 100, 75)

shadow

img.line_color = None img.fill_color = pero.colors.White.darker(.1) img.draw_ellipse(100, 185, 70, 10)

eyes

img.fill_color = pero.colors.Black img.draw_circle(70, 85, 15) img.draw_circle(130, 85, 15)

eyebrows

img.line_color = pero.colors.Black img.fill_color = None img.line_width = 3 img.draw_arc(70, 85, 23, pero.rads(-100), pero.rads(-20)) img.draw_arc(130, 85, 23, pero.rads(200), pero.rads(280))

mouth

img.line_width = 5 img.draw_arc(100, 100, 50, pero.rads(40), pero.rads(80))

highlight

img.line_color = pero.colors.Orange.lighter(.3) img.draw_arc(100, 100, 68, pero.rads(220), pero.rads(260))

hat

path = pero.Path(pero.WINDING) path.ellipse(100, 27, 40, 10) path.ellipse(100, 17, 30, 10) path.rect(85, 17, 30, 10)

mat = pero.Matrix().rotate(pero.rads(20), 100, 100) path.transform(mat)

img.line_color = None img.fill_color = pero.colors.Black img.draw_path(path)

show image

img.show() ```

Final Image

Requirements

Supported Backends

Installation

The pero library is fully implemented in Python. No additional compiler is necessary. After downloading the source code just run the following command from the pero folder:

$ python setup.py install

or simply use pip

$ pip install pero

Disclaimer

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Please note that the pero library is still in an alpha state. Any changes in its API may occur.

Usage

Using default backend

If you just want to draw an image using whatever the default backend is (for requested format), or show the image directly (requires PyQt5, PyQt6, PySide2, PySide6, wxPython or Pythonista iOS App), just create an image and use it as any other pero canvas:

```python

import pero

init size

width = 200 height = 200

init image

img = pero.Image(width=width, height=height)

draw graphics

img.line_color = "b" img.fill_color = "w" img.fill() img.draw_circle(100, 100, 75)

save to file

img.export('image.png')

show in viewer

img.show() ```

Using PyQt5, PyQt6, PySide2 or PySide6

Inside a QWidget you can create a QPainter and encapsulate it into the pero canvas:

```python

import pero from PyQt5.QtGui import QPainter

init size

width = 200 height = 200

init painter

qp = QPainter() qp.begin(self) qp.setRenderHint(QPainter.RenderHint.Antialiasing)

init canvas

canvas = pero.qt.QtCanvas(qp, width=width, height=height)

draw graphics

canvas.line_color = "b" canvas.fill_color = "w" canvas.fill() canvas.draw_circle(100, 100, 75)

end drawing

qp.end() ```

Using wxPython

Inside a wxApp you can use just about any wxDC you want and encapsulate it into the pero canvas:

```python

import pero import wx

init size

width = 200 height = 200

create DC

bitmap = wx.Bitmap(width, height) dc = wx.MemoryDC() dc.SelectObject(bitmap)

use GCDC

if 'wxMac' not in wx.PlatformInfo: dc = wx.GCDC(dc)

init canvas

canvas = pero.wx.WXCanvas(dc, width=width, height=height)

draw graphics

canvas.line_color = "b" canvas.fill_color = "w" canvas.fill() canvas.draw_circle(100, 100, 75) ```

Using PyCairo

Depending on the final image format, choose appropriate cairo surface, get the drawing context and encapsulate it into the pero canvas:

```python

import pero import cairo

init size

width = 200 height = 200

create cairo drawing context

surface = cairo.PSSurface('image.eps', width, height) dc = cairo.Context(surface)

init canvas

canvas = pero.cairo.CairoCanvas(dc, width=width, height=height)

draw graphics

canvas.line_color = "b" canvas.fill_color = "w" canvas.fill() canvas.draw_circle(100, 100, 75)

save to file

dc.show_page() ```

Using PyMuPDF

Create a document, add new page and encapsulate it into the pero canvas:

```python

import pero import fitz

init size

width = 200 height = 200

init document

doc = fitz.open() page = doc.newPage(width=width, height=height)

init canvas

canvas = pero.mupdf.MuPDFCanvas(page)

draw graphics

canvas.line_color = "b" canvas.fill_color = "w" canvas.fill() canvas.draw_circle(100, 100, 75)

save to file

doc.save('image.pdf') doc.close() ```

Using SVG

The pero library implements its own way to draw and save SVG files. Just create a pero canvas:

```python

import pero

init size

width = 200 height = 200

init canvas

canvas = pero.svg.SVGCanvas(width=width, height=height)

draw graphics

canvas.line_color = "b" canvas.fill_color = "w" canvas.fill() canvas.draw_circle(100, 100, 75)

save to file

with open('test.svg', 'w', encoding='utf-8') as f: f.write(canvas.get_xml()) ```

Using Pythonista

Initialize a new ui.ImageContext and create a pero canvas:

```python

import pero import ui

init size

width = 200 height = 200

open context

with ui.ImageContext(width, height) as ctx:

# init canvas
canvas = pero.pythonista.UICanvas(width=width, height=height)

# draw graphics
canvas.line_color = "b"
canvas.fill_color = "w"
canvas.fill()
canvas.draw_circle(100, 100, 75)

# show image
img = ctx.get_image()
img.show()

```

Using glyphs and dynamic properties

Similar to d3js JavaScript library, most of the properties of pre-build pero.Glyphs objects can be specified as a function, to which given data source is automatically provided. Together with pero.scales (and maybe the pero.Axis) this can be used to make simple plots easily.

```python

import pero import numpy

init size

width = 400 height = 300 padding = 50

init data

x_data = numpy.linspace(-numpy.pi, numpy.pi, 50) y_data = numpy.sin(x_data)

init scales

x_scale = pero.LinScale( in_range = (min(x_data), max(x_data)), out_range = (padding, width-padding))

y_scale = pero.LinScale( in_range = (-1, 1), out_range = (height-padding, padding))

color_scale = pero.GradientScale( in_range = (-1, 1), out_range = pero.colors.Spectral)

init marker

marker = pero.Circle( size = 8, x = lambda d: x_scale.scale(d[0]), y = lambda d: y_scale.scale(d[1]), line_color = lambda d: color_scale.scale(d[1]).darker(.2), fill_color = lambda d: color_scale.scale(d[1]))

init image

image = pero.Image(width=width, height=height)

fill

image.fill("w")

draw points

marker.draw_many(image, zip(x_data, y_data))

show image

image.show() ```

Examples

In the examples folder you will find sample codes to generate and understand all the following images. Check the image name and find corresponding python draw file.

Issues

GitHub Action to lint Python code

opened on 2023-03-08 06:29:44 by cclauss

https://beta.ruff.rs

Test results: https://github.com/cclauss/pero/actions

SVG example fails because expected fonts are missing from OS

opened on 2022-12-12 17:48:56 by villares

I'm on Manjaro Linux and I think I don't have Arial or Helvetica.

Running this example:

```python import pero

init size

width = 200 height = 200

init canvas

canvas = pero.svg.SVGCanvas(width=width, height=height)

draw graphics

canvas.line_color = "b" canvas.fill_color = "w" canvas.fill() canvas.draw_circle(100, 100, 75)

save to file

with open('test.svg', 'w', encoding='utf-8') as f: f.write(canvas.get_xml()) ```

I get the following error:

Traceback (most recent call last): File "/home/villares/GitHub/sketch-a-day/2022/sketch_2022_12_12/sketch_2022_12_12.py", line 9, in <module> canvas = pero.svg.SVGCanvas(width=width, height=height) File "/home/villares/.local/lib/python3.10/site-packages/pero/backends/svg/canvas.py", line 56, in __init__ self._update_text() File "/home/villares/.local/lib/python3.10/site-packages/pero/backends/svg/canvas.py", line 723, in _update_text font = self.get_font() File "/home/villares/.local/lib/python3.10/site-packages/pero/drawing/canvas.py", line 366, in get_font raise ValueError(message) ValueError: Cannot initialize the font! -> Arial, arial, Arial, Helvetica

Releases

pero_v0.18.0 2022-03-31 16:53:49

Full Changelog: https://github.com/xxao/pero/compare/v0.17.0...v0.18.0

pero_v0.17.0 2022-03-03 20:24:11

Full Changelog: https://github.com/xxao/pero/compare/v0.16.0...v0.17.0

pero_v0.16.0 2022-01-21 16:17:19

Added support for PyQt6, PySide6 and PySide2

Full Changelog: https://github.com/xxao/pero/compare/v0.15.2...v0.16.0

pero_v0.15.2 2021-10-01 18:31:11

All plotting extracted into separate "perrot" module.

Martin Strohalm
GitHub Repository

python wxpython pycairo pymupdf svg drawing pyqt5 pyqt6 pyside2 pyside6 visualization