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.