Scripting Labeled Intentions With Python PIL
Date: 2021-07-11 | labeled-intentions | python | pil | art |
Overview
- I released new art project Labeled Intentions last week
- I built the design with a script written in Python, leveraging the PIL library
- In this post, I'll go through why I chose Python for this and walk through the code I used to build it
Context
Labeled Intentions
Labeled Intentions is my newest art project which explores the meaning of labels. It does this by creating text labels (which we'll be talking about here) and then producing shirts with those labels on them (which we won't talk about here though you can read how I do this in: How I make and sell clothes online).
Python PIL
I chose Python over a traditional image editor (like GIMP, Inkscape, Photoshop, etc.) for this project because I saw that it would be way more efficient. Labeled Intentions is a set of several labels, each requiring several outputs. Doing this by hand, with an image editor, would require me to do the same operation multiple times. Plus if I had to change something (the font type, the size, the background) etc. it would require lots of changes to all the different designs.
With code, I can just set new rules and make the computer regenerate everything to the new specs. I wanted to reduce the cost of iteration so I chose code over a traditional image editor.
For those that don't know PIL is an image manipulation library for Python. There are many image manipulation libraries out there but I like PIL because:
- Good documentation
- Widely used -> battle hardened
- Wide functionality
- Continuously supported and updated
Code
Code Overview
Essentially the code flows like this:
- A set of labels to be created
- Foreach label
- Create 3 images
- transparent
- white
- black
- Add centered text on each image
- Save the image
- Create 3 images
Source Code
Here's the source code.
Note: I run this snippet of code in a generation framework I created so there are a few imports that won't make sense by itself but this still has all the important logic for how I created this. generate_async
returns a list of images which I then save using PIL.
File: LabeledIntentionsGenerationRunner.py
- This is the driver for Labeled Intentions generation
rom typing import Any, List
from PIL import Image
from Domain.GenerationRunners.IGenerationRunner import IGenerationRunner
from Domain.GenerationRunners.IGenerationResult import IGenerationResult
from Domain.GenerationRunners.GenerationResult import GenerationResult
from Domain.Models.Point import Point
from Domain.Models.RGBA import RGBA
from Domain.Utilities.TextUtilities import draw_centered_text_on_image
class LabeledIntentionsGenerationRunner(IGenerationRunner):
image_size_px: int = 2000
font_path: str = './resources/HelveticaNeue-Bold.ttf'
font_size: int = 200
def __init__(self):
self.labeled_intentions: List[str] = [
'HAMBLOH',
'LABEL',
'INTENTION',
# NY
'HEALING',
'!DEAD',
'THRIVING',
# Purpose
'ART',
'BUSINESS',
'PLEASURE',
# Color
'BLACK',
'RED',
'WHITE'
]
async def generate_async(
self,
) -> List[IGenerationResult]:
all_results: List[IGenerationResult] = []
transparent = RGBA((0,0,0), 0)
black = RGBA((0,0,0))
white = RGBA((255, 255, 255))
center_point = Point(self.image_size_px / 2, self.image_size_px /2)
for label in self.labeled_intentions:
transparent_image = Image.new(
'RGBA',
(LabeledIntentionsGenerationRunner.image_size_px, LabeledIntentionsGenerationRunner.image_size_px),
transparent.to_tuple_alpha())
black_image = Image.new(
'RGBA',
(LabeledIntentionsGenerationRunner.image_size_px, LabeledIntentionsGenerationRunner.image_size_px),
black.to_tuple_alpha())
white_image = Image.new(
'RGBA',
(LabeledIntentionsGenerationRunner.image_size_px, LabeledIntentionsGenerationRunner.image_size_px),
white.to_tuple_alpha())
transparent_words_image = draw_centered_text_on_image(
transparent_image,
center_point,
LabeledIntentionsGenerationRunner._get_hambloh_string_from_label(label),
LabeledIntentionsGenerationRunner.font_path,
LabeledIntentionsGenerationRunner.font_size,
white
)
black_words_image = draw_centered_text_on_image(
black_image,
center_point,
LabeledIntentionsGenerationRunner._get_hambloh_string_from_label(label),
LabeledIntentionsGenerationRunner.font_path,
LabeledIntentionsGenerationRunner.font_size,
white
)
white_words_image = draw_centered_text_on_image(
white_image,
center_point,
LabeledIntentionsGenerationRunner._get_hambloh_string_from_label(label),
LabeledIntentionsGenerationRunner.font_path,
LabeledIntentionsGenerationRunner.font_size,
black
)
all_results.append(
GenerationResult(
LabeledIntentionsGenerationRunner._get_file_name(label, 'transparent'),
transparent_words_image
)
)
all_results.append(
GenerationResult(
LabeledIntentionsGenerationRunner._get_file_name(label, 'white'),
black_words_image
)
)
all_results.append(
GenerationResult(
LabeledIntentionsGenerationRunner._get_file_name(label, 'black'),
white_words_image
)
)
return all_results
@staticmethod
def _get_file_name(
label: str,
descriptor: str
) -> str:
return f'{label}-{descriptor}'
@staticmethod
def _get_hambloh_string_from_label(
label: str
) -> str:
return f'\"{label}\"'
File: TextUtilities.py
- This is how I draw the centered text on each image
from Domain.Models.Point import Point
from Domain.Models.RGBA import RGBA
from PIL import Image, ImageDraw, ImageFont
def draw_centered_text_on_image(
image: Image,
center_point: Point,
text: str,
font_path: str,
font_size: int,
color_rgb: RGBA
) -> Image:
"""
Draws centered text on image
"""
text_image = Image.new('RGBA', image.size, (255,255,255,0))
font = ImageFont.truetype(
font=font_path,
size=font_size,
)
image_draw = ImageDraw.Draw(text_image)
text_width, text_height = image_draw.textsize(text, font)
draw_point = Point(
(center_point.x - (text_width / 2)),
(center_point.y - (text_height / 2))
)
image_draw.text(
draw_point.to_tuple_int(),
text,
font=font,
fill=color_rgb.to_bgr_tuple_alpha()
)
out_image = Image.alpha_composite(image, text_image)
return out_image
How I save images:
async def export_image_async(
self,
original_image: Image,
image_name: str
) -> None:
print('exporting image')
if not os.path.isdir(self.folder_path):
os.mkdir(self.folder_path)
save_file_path = FileImageExporter._construct_output_file_path(
self.folder_path,
image_name,
self._get_file_ending_for_file_image_type(self.file_image_type)
)
original_image.save(save_file_path)
print(f'image saved to: {save_file_path}')
Next Steps
[] Let me know what you create in the comments below!
Further Reading
- Learn more about Labeled Intentions and shop the collection: Labeled Intentions project page
- Read How I make and sell clothes online
Happy generating!
-HAMY.OUT
Want more like this?
The best / easiest way to support my work is by subscribing for future updates and sharing with your network.