Working with Images
PyXA uses the XAImage
class to provide a standard, convenient way to interact with images stored in files, images created by other PyXA operations, and images managed by applications such as Photos or Image Events. Using this class, you can:
Load and save images
Access information about images, such as whether they contain transparent pixels
Copy images to the clipboard
Set image attributes such as vibrance and temperature
Apply filters and distortions
Create composite images or blend images together using composition filters
Stitch images together
Extract text contained in images
The XAImage
class aims to simplify complex Objective-C implementations of image manipulation down to just a few lines of code for PyXA users. As a result, creating scripts to operate on images can be done quickly and without needing to understand the underlying mechanisms as work. You can then intuitively utilize images elsewhere in your automation scripts. Some ideas for automation include:
Download images from an online API, extract text from them, and store the text in a note
Find images containing a certain word or phrase and sort them into an album in Photos
Create a mosaic of all images in a particular photo album, or of all images taken in the last year
Auto-enhance all images stored in a particular folder
Store images files sent to you in a particular folder immediately after receiving them
Overlay the songs of a Music playlist onto a mosaic of all the songs’ album artworks
…and many more! What will you come up with?
The rest of this page describes the various features of the XAImage
class and provides several examples for you to reference.
Loading Images
To initialize an XAImage
object, you provide some kind of reference to an image via the image_reference parameter (the first parameter of XAImage’s init method). This parameter accepts data in many forms, including: a raw string file path (with any image file extension), a web or filesystem URL, an XAPath
or XAURL
object, another XAImage
object, and any object whose class implements the XAImageLike
protocol, such as XAPhotosMediaItem
. The code below shows how to use these different kinds of image references.
import PyXA
# Load a single image file
img = PyXA.XAImage("/Users/exampleUser/Desktop/PyXALogoTransparent.png")
print(img)
# <PyXA.XABase.XAImage object at 0x104b385b0>
# Load an image from a web URL
img = PyXA.XAImage("https://raw.githubusercontent.com/SKaplanOfficial/PyXA/main/docs/_static/assets/PyXALogoTransparent.png")
img.show_in_preview()
# Load images from a XAURL or XAPath
path = PyXA.XAPath("/Users/exampleUser/Desktop/PyXALogoTransparent.png")
url = PyXA.XAURL("https://raw.githubusercontent.com/SKaplanOfficial/PyXA/main/docs/_static/assets/PyXALogoTransparent.png")
img1 = PyXA.XAImage(path)
img2 = PyXA.XAImage(url)
# Initialize an image from another image
img1 = PyXA.XAImage("/Users/exampleUser/Desktop/PyXALogoTransparent.png")
img2 = PyXA.XAImage(img1)
# Get an image from Photos
photos = PyXA.Application("Photos")
img = PyXA.XAImage(photos.media_items().by_title("PyXA Logo"))
Additionally, some apps such as Image Events utilize subclasses of XAImage
to afford a more direct connection between their content and the functionality of XAImages. In the case of Image Events, an image opened using PyXA.apps.ImageEvents.XAImageEventsApplication.open()
is an instance of PyXA.apps.ImageEvents()
.XAImageEventsImage`, a subclass of :class:`~PyXA.XABase.XAImage, and has access to both XAImage’s methods as well as methods specific to the Image Events context.
import PyXA
ie = PyXA.Application("Image Events")
ie_img = ie.open("/Users/steven/Desktop/PyXALogoTransparent.png")
# Use XAImageEventsImage methods
profile = ie.profiles().by_name("Generic Gray Profile")
ie_img.embed_profile(profile)
# Use XAList methods
ie_img.pad(50, 50, PyXA.XAColor.black()).rotate(45)
ie_img.gaussian_blur().bloom().show_in_preview()
You can also use the open()
method to open one or more image files or URLs. When opening multiple images at a time, this method returns an XAImageList
object – more on that later.
Accessing Image Information
import PyXA
image = PyXA.XAImage.open("/Users/steven/Desktop/PyXALogoTransparent.png")
# Check if image has alpha channel
if image.has_alpha_channel:
# Check if image contains transparent pixels
if image.is_opaque:
print("Image is capable of having transparent pixels, but does not have any right now")
else:
print("Image contains transparent pixels")
else:
print("Image cannot contain transparent pixels in its current format")
# Print other attributes
print(image.color_space_name)
print(image.size)
print(image.data)
Modifying Image Attributes
In addition to accessing information attributes, you can use PyXA to concisely set image attributes such as the vibrance, tint, and temperature of an image. The mutable attributes are:
gamma
vibrance
tint
temperature
white_point
highlight
shadow
import PyXA
image = PyXA.XAImage.open("/Users/steven/Downloads/Owl.png")
image.vibrance = 0.1
image.highlight = 0
image.shadow = 1
image.tint = 200
image.temperature = 12000
image.gamma = 10
image.show_in_preview()
Basics of Image Manipulation
In addition to modifiable attributes, PyXA provides several method for basic image manipulation, including: flip_horizontally()
, flip_vertically()
, rotate()
, scale()
, crop()
, and pad()
.
import PyXA
# Apply individual modifications
image = PyXA.XAImage("/Users/steven/Desktop/cat2.jpeg")
image.crop((600, 600))
image.scale(2, 2)
image.show_in_preview()
# Apply modifications using method chaining
image.pad(pad_color=PyXA.XAColor.red()).rotate(45).flip_horizontally()
image.show_in_preview()
Applying Filters
PyXA provides easy-access to several common image filters that might be useful for automation workflows; for more advanced use cases, a dedicated image manipulation library is recommended. The provided filter methods are:
import PyXA
image = PyXA.XAImage("/Users/steven/Desktop/cat2.jpeg")
image.pixellate().sepia().vignette(5).show_in_preview()
Adding Distortions
In addition to filters, PyXA provided a few methods for adding distortions to images. These methods include bump()
, pinch()
, and twirl()
.
import PyXA
image = PyXA.XAImage("/Users/steven/Downloads/Owl.png")
image.bump(radius=800, curvature=1).pinch((871, 871), intensity=1).twirl().show_in_preview()
Composite Operations
PyXA currently supports four composite operations (operations that merge many images into a single, composite image): horizontal_stitch()
, vertical_stitch()
, additive_composition()
, and subtractive_composition()
. The first two are image concatenation operations which “stitch” together a series of images either vertically or horizontally, one on top of or next to another, to form a single combined imaged, while the other two produce an image by overlaying images and applying a specific blend filter.
The composite image operations noted above are methods of the XAImageList
class.
import PyXA
images = PyXA.XAImage.open("/Users/steven/Downloads/Owl.png", "/Users/steven/Desktop/PyXALogoTransparent.png")
images.additive_composition().show_in_preview()
images.subtractive_composition().show_in_preview()
images.horizontal_stitch().show_in_preview()
images.vertical_stitch().show_in_preview()
Text Extraction
With PyXA, you can extract text from images using just one method call. When working with a single XAImage
object , calling the object’s extract_text()
method will return a list of all text contained within the image, separated by newline characters. Likewise, when calling extract_text()
on an XAImageList
object, you will get a list of lists of strings, with each image’s text organized into its own entry.
import PyXA
# Extract text from one image
image = PyXA.XAImage("/Users/steven/Desktop/handwritingImage.png")
print(images.extract_text())
# ["This is a handwritten note"]
# Extract text from multiple images at a time
images = PyXA.XAImage.open("/Users/steven/Desktop/codeImage.png", "/Users/steven/Desktop/handwritingImage.png", "/Users/steven/Desktop/signImage.jpeg")
texts = images.extract_text()
print(texts)
# [
# ["import PyXA", 'PyXA.Application("Music").play()'],
# ["This is a handwritten note"],
# ["KEEP", "RIGHT"],
# ]
This functionality allows you to quickly and easily obtain the text within an image, then use that text elsewhere in your automation scripts. For example, the code below rotates or scales images according to the text found within them:
import PyXA
import os
sample_folder = "/Users/steven/Desktop/samples/"
output_folder = "/Users/steven/Desktop/output/"
# Create output folder if necessary
if not os.path.exists(output_folder):
os.makedirs(output_folder, exist_ok=True)
# Loop through source images
for index, sample in enumerate(os.listdir(sample_folder)):
print(f"Analyzing sample {index + 1}...")
image = PyXA.XAImage(sample_folder + sample)
# Extract image text -- each image source is known to have two lines
image_text = image.extract_text()
operation = image_text[0]
arg = int(image_text[1])
# Apply appropriate operation
if operation == "rotate":
image.rotate(arg)
elif operation == "scale":
image.scale(arg, arg)
# Save modified image to file in output folder
print("\tWriting to disk...")
image.save(output_folder + sample)
Working with Lists of Images
As with many other PyXA classes, XAImage
has an associated XAImageList
class that provides both convenience and performance improvements over standard lists. For example, PyXA.XABase.XAImageList.flip_horizontally()
method performs over twice as fast as iterating over the same size list of individual PyXA.XABase.XAImage
objects and calling the PyXA.XABase.XAImage.flip_horizontally()
method on each list item, as seen in the example code below:
import PyXA
from timeit import timeit
def without_xalist():
images = [PyXA.XAImage("/Users/steven/Desktop/dog1.JPG"), PyXA.XAImage("/Users/steven/Desktop/dog1.JPG"), PyXA.XAImage("/Users/steven/Desktop/dog1.JPG")]
flipped_images = []
for image in images:
flipped_images.append(image.flip_vertically())
return flipped_images
def with_xalist():
images = PyXA.XAImage.open("/Users/steven/Desktop/dog1.JPG", "/Users/steven/Desktop/dog1.JPG", "/Users/steven/Desktop/dog1.JPG")
flipped_images = images.flip_vertically()
return flipped_images
t1 = timeit(without_xalist, number=100)
t2 = timeit(with_xalist, number=100)
print("Non-XAList avg over 100 trials:", t1 / 50.0)
print("XAList avg over 100 trials:", t2 / 50.0 )
# Non-XAList avg over 100 trials: 1.53993887584
# XAList avg over 100 trials: 0.6404187591799999
A relationship exists for all of the XAImage
and XAImageList
methods. Thus, generally speaking, you’ll want to make use of XAImageList
objects any time you work with several or more images.