Custom Integrations#
The methods convert_shape() and convert_image() are customizable. In addition to the built-in integrations, you can create new integrations that the convert_shape() and convert_image() methods will use to convert Python objects into objects that are compatible with py5.
This page documents how to create custom integrations for both methods. Both methods have similar customization designs.
This page will explain the customization design in detail, but if you are going to build a custom integration, it is recommended you also have a look at the source code for the built-in image and shape customizations for additional insight and examples. You can also ask for help in GitHub Discussions.
Your custom integration will require you to create two functions. The first function is a
predicate function that accepts a Python object as its parameter and returns True
or False
to indicate if the object is convertible by your custom integration. The second function
must accept the same Python object and **kwargs
parameters, and return an object compatible
with py5.
Your custom integrations will always take precedence over py5’s builtin integrations.
We will begin with some imports needed for our examples.
import numpy as np
from PIL import Image
from shapely import Point
import py5_tools
import py5
Custom Integrations for Image Conversion#
For our image conversion example, we will create a custom integration that converts PIL Image objects and by default rotates them 180 degrees. We will also support a keyword argument to rotate the image by a different angle.
First we create our predicate function. It will simply check if an object is a PIL
Image object and return True
or False
. This function is identical to py5’s built-in
customization for PIL Image objects.
def pillow_image_to_ndarray_precondition(obj):
return isinstance(obj, Image.Image)
The second function’s parameters will be a sketch instance, the object to be converted,
and **kwargs
parameters. You do not have to return a Py5Image
object. Instead, it is
simpler and less coding for you to return a special NumpyImageArray
object or just
return the path (as a string or a pathlib.Path
instance) to an image saved to disk that
can be read and loaded by py5. If for some reason these options are not ideal for what
you want to do, you can create the Py5Image
object anyway and return that.
This NumpyImageArray
class is a special class py5 uses internally to manage image data in
numpy arrays. When creating an instance of NumpyImageArray
, the first parameter should be
the numpy array with three dimensions. The first two dimensions should be the image’s vertical
and horizontal dimensions, respectively. The third should be the image’s color channels.
The second parameter should be a string indicating the color channel sequence of the
array’s third dimension. Any value that the create_image_from_numpy() method’s
bands
parameter accepts is acceptable here. If the color channel sequence is L
, the array’s
third dimension is optional.
If it is more convenient, your code can save the converted image to disk in a temporary file. The Python library tempfile can help with this.
The conversion function below is very similar to py5’s built-in customization for PIL Image objects.
def pillow_image_to_ndarray_converter(sketch, img, **kwargs):
rotate = kwargs.get('rotate', 180)
if img.mode not in ["RGB", "RGBA"]:
img = img.convert(mode="RGB")
img = img.rotate(rotate)
return py5.NumpyImageArray(np.asarray(img), img.mode)
The last step is to register the pair of functions with py5. After registering, when the convert_image() method is called, it will use this new conversion function we wrote to convert PIL Image objects.
py5.register_image_conversion(
pillow_image_to_ndarray_precondition, pillow_image_to_ndarray_converter
)
Now that everything is configured correctly, let’s create a Sketch that uses our new PIL image conversion functionality.
def setup():
py5.size(300, 300)
pil_img = Image.open('images/rockies.jpg')
img1 = py5.convert_image(pil_img)
img2 = py5.convert_image(pil_img, rotate=45)
py5.image(img1, 25, 100)
py5.image(img2, 175, 100)
py5.run_sketch()
The result looks as we would expect. The left image is upside down and the right image is rotated 45 degrees.
py5_tools.screenshot()
Custom Integrations for Shape Conversion#
For our shape conversion example, we will create a custom integration that
converts shapely Point
objects into clouds of gaussian distributed points.
We will support keyword arguments for the standard deviation and count of
the points in the cloud.
First we create our predicate function. It will check if an object is a shapely
Point object and return True
or False
.
def shapely_point_precondition(obj):
return isinstance(obj, Point)
The second function’s parameters will be a sketch instance, the object to be converted,
and **kwargs
parameters. The conversion function must return a new Py5Shape
object
to be returned by convert_shape().
def shapely_point_converter(sketch, obj, **kwargs):
count = kwargs.get('count', 2500)
stdev = kwargs.get('stdev', 25)
points = stdev * np.random.randn(count, 2) + [obj.x, obj.y]
s = sketch.create_shape()
with s.begin_shape(sketch.POINTS):
s.vertices(points)
return s
We again register the pair of functions with py5. When
the convert_shape() method is called, it will
use the conversion function we wrote to convert shapely
Point
objects.
py5.register_shape_conversion(
shapely_point_precondition, shapely_point_converter
)
Now let’s create a Sketch that uses our shapely Point
conversion functionality.
def setup():
py5.size(300, 300)
point1 = Point(130, 210)
point2 = Point(210, 130)
points1 = py5.convert_shape(point1)
points2 = py5.convert_shape(point2, count=5000, stdev=10)
py5.shape(points1)
py5.shape(points2)
py5.run_sketch()
As expected, each point is rendered as a scattered field of individual points.
py5_tools.screenshot()