Pillow Advanced Image Processing
Image Filters and Enhancement
Built-in Filter Application (filter())
Pillow provides a variety of built-in filters, using the ImageFilter module:
Example
from PIL import Image, ImageFilter
# Open the image
image = Image.open("input.jpg")
# Apply blur filter
blurred = image.filter(ImageFilter.BLUR)
blurred.save("blurred.jpg")
# Apply contour filter
contour = image.filter(ImageFilter.CONTOUR)
contour.save("contour.jpg")
# Apply emboss filter
emboss = image.filter(ImageFilter.EMBOSS)
emboss.save("emboss.jpg")
# Find edges
edges = image.filter(ImageFilter.FIND_EDGES)
edges.save("edges.jpg")
# Sharpen filter
sharpen = image.filter(ImageFilter.SHARPEN)
sharpen.save("sharpen.jpg")
# Smooth filter
smooth = image.filter(ImageFilter.SMOOTH)
smooth.save("smooth.jpg")
# Detail enhancement
detail = image.filter(ImageFilter.DETAIL)
detail.save("detail.jpg")
# Open the image
image = Image.open("input.jpg")
# Apply blur filter
blurred = image.filter(ImageFilter.BLUR)
blurred.save("blurred.jpg")
# Apply contour filter
contour = image.filter(ImageFilter.CONTOUR)
contour.save("contour.jpg")
# Apply emboss filter
emboss = image.filter(ImageFilter.EMBOSS)
emboss.save("emboss.jpg")
# Find edges
edges = image.filter(ImageFilter.FIND_EDGES)
edges.save("edges.jpg")
# Sharpen filter
sharpen = image.filter(ImageFilter.SHARPEN)
sharpen.save("sharpen.jpg")
# Smooth filter
smooth = image.filter(ImageFilter.SMOOTH)
smooth.save("smooth.jpg")
# Detail enhancement
detail = image.filter(ImageFilter.DETAIL)
detail.save("detail.jpg")
Image Enhancement (ImageEnhance Module)
The ImageEnhance module provides tools for controlling brightness, contrast, color, and sharpness:
Example
from PIL import Image, ImageEnhance
# Open the image
image = Image.open("input.jpg")
# Enhance brightness (factor > 1 increases brightness, < 1 reduces brightness)
enhancer = ImageEnhance.Brightness(image)
brightened = enhancer.enhance(1.5) # Increase brightness by 50%
brightened.save("brightened.jpg")
# Enhance contrast
enhancer = ImageEnhance.Contrast(image)
contrast = enhancer.enhance(1.8) # Increase contrast by 80%
contrast.save("contrast.jpg")
# Enhance color saturation
enhancer = ImageEnhance.Color(image)
saturated = enhancer.enhance(1.5) # Increase saturation by 50%
saturated.save("saturated.jpg")
# Enhance sharpness
enhancer = ImageEnhance.Sharpness(image)
sharpened = enhancer.enhance(2.0) # Increase sharpness by 100%
sharpened.save("sharpened.jpg")
Custom Convolution Kernel
Pillow allows you to use custom convolution kernels to create special effects:
pythonimport numpy as np
from PIL import Image, ImageFilter
# Open the image
image = Image.open("input.jpg")
# Create a custom convolution kernel
# This is a sharpening convolution kernel
kernel = ImageFilter.Kernel(
size=(3, 3),
kernel=[-1, -1, -1, -1, 9, -1, -1, -1, -1],
scale=1
)
sharpened = image.filter(kernel)
sharpened.save("custom_sharpen.jpg")
# Emboss effect convolution kernel
emboss_kernel = ImageFilter.Kernel(
size=(3, 3),
kernel=[-2, -1, 0, -1, 1, 1, 0, 1, 2],
scale=1,
offset=128
)
embossed = image.filter(emboss_kernel)
embossed.save("custom_emboss.jpg")
# Gaussian blur convolution kernel
gaussian_kernel = ImageFilter.Kernel(
size=(5, 5),
kernel=[1, 4, 6, 4, 1, 4, 16, 24, 16, 4, 6, 24, 36, 24, 6, 4, 16, 24, 16, 4, 1, 4, 6, 4, 1],
scale=256
)
gaussian_blur = image.filter(gaussian_kernel)
gaussian_blur.save("custom_gaussian.jpg")
# Open the image
image = Image.open("input.jpg")
# Enhance brightness (factor > 1 increases brightness, < 1 reduces brightness)
enhancer = ImageEnhance.Brightness(image)
brightened = enhancer.enhance(1.5) # Increase brightness by 50%
brightened.save("brightened.jpg")
# Enhance contrast
enhancer = ImageEnhance.Contrast(image)
contrast = enhancer.enhance(1.8) # Increase contrast by 80%
contrast.save("contrast.jpg")
# Enhance color saturation
enhancer = ImageEnhance.Color(image)
saturated = enhancer.enhance(1.5) # Increase saturation by 50%
saturated.save("saturated.jpg")
# Enhance sharpness
enhancer = ImageEnhance.Sharpness(image)
sharpened = enhancer.enhance(2.0) # Increase sharpness by 100%
sharpened.save("sharpened.jpg")
Custom Convolution Kernel
Pillow allows you to use custom convolution kernels to create special effects:
pythonimport numpy as np
from PIL import Image, ImageFilter
# Open the image
image = Image.open("input.jpg")
# Create a custom convolution kernel
# This is a sharpening convolution kernel
kernel = ImageFilter.Kernel(
size=(3, 3),
kernel=[-1, -1, -1, -1, 9, -1, -1, -1, -1],
scale=1
)
sharpened = image.filter(kernel)
sharpened.save("custom_sharpen.jpg")
# Emboss effect convolution kernel
emboss_kernel = ImageFilter.Kernel(
size=(3, 3),
kernel=[-2, -1, 0, -1, 1, 1, 0, 1, 2],
scale=1,
offset=128
)
embossed = image.filter(emboss_kernel)
embossed.save("custom_emboss.jpg")
# Gaussian blur convolution kernel
gaussian_kernel = ImageFilter.Kernel(
size=(5, 5),
kernel=[1, 4, 6, 4, 1, 4, 16, 24, 16, 4, 6, 24, 36, 24, 6, 4, 16, 24, 16, 4, 1, 4, 6, 4, 1],
scale=256
)
gaussian_blur = image.filter(gaussian_kernel)
gaussian_blur.save("custom_gaussian.jpg")
Edge Detection and Sharpening
In addition to using built-in filters, you can also implement more advanced edge detection and sharpening effects:
Example
from PIL import Image, ImageFilter, ImageChops, ImageOps
# Open the image
image = Image.open("input.jpg")
# Sobel edge detection
# Horizontal Sobel filter
h_sobel = ImageFilter.Kernel(
size=(3, 3),
kernel=[-1, 0, 1, -2, 0, 2, -1, 0, 1],
scale=1
)
# Vertical Sobel filter
v_sobel = ImageFilter.Kernel(
size=(3, 3),
kernel=[-1, -2, -1, 0, 0, 0, 1, 2, 1],
scale=1
)
h_edges = image.filter(h_sobel)
v_edges = image.filter(v_sobel)
# Merge horizontal and vertical edges
edges = ImageChops.add(
ImageChops.multiply(h_edges, h_edges),
ImageChops.multiply(v_edges, v_edges)
)
edges.save("sobel_edges.jpg")
# Use high-frequency enhancement for sharpening (USM - Unsharp Masking)
def unsharp_mask(image, radius=2, percent=150, threshold=3):
"""Apply USM sharpening filter
Parameters:
radius: radius of Gaussian blur
percent: sharpening strength percentage
threshold: minimum brightness change threshold for applying sharpening
"""
blurred = image.filter(ImageFilter.GaussianBlur(radius=radius))
sharpened = Image.blend(image, ImageChops.subtract(image, blurred), percent/100)
return sharpened
usm_image = unsharp_mask(image, radius=2, percent=200, threshold=5)
usm_image.save("usm_sharpened.jpg")
# Open the image
image = Image.open("input.jpg")
# Sobel edge detection
# Horizontal Sobel filter
h_sobel = ImageFilter.Kernel(
size=(3, 3),
kernel=[-1, 0, 1, -2, 0, 2, -1, 0, 1],
scale=1
)
# Vertical Sobel filter
v_sobel = ImageFilter.Kernel(
size=(3, 3),
kernel=[-1, -2, -1, 0, 0, 0, 1, 2, 1],
scale=1
)
h_edges = image.filter(h_sobel)
v_edges = image.filter(v_sobel)
# Merge horizontal and vertical edges
edges = ImageChops.add(
ImageChops.multiply(h_edges, h_edges),
ImageChops.multiply(v_edges, v_edges)
)
edges.save("sobel_edges.jpg")
# Use high-frequency enhancement for sharpening (USM - Unsharp Masking)
def unsharp_mask(image, radius=2, percent=150, threshold=3):
"""Apply USM sharpening filter
Parameters:
radius: radius of Gaussian blur
percent: sharpening strength percentage
threshold: minimum brightness change threshold for applying sharpening
"""
blurred = image.filter(ImageFilter.GaussianBlur(radius=radius))
sharpened = Image.blend(image, ImageChops.subtract(image, blurred), percent/100)
return sharpened
usm_image = unsharp_mask(image, radius=2, percent=200, threshold=5)
usm_image.save("usm_sharpened.jpg")
Drawing and Text Addition
Basic Shape Drawing (ImageDraw Module)
Example
from PIL import Image, ImageDraw
# Create a blank canvas
width, height = 800, 600
image = Image.new("RGB", (width, height), color="white")
draw = ImageDraw.Draw(image)
# Draw a line
draw.line([(100, 100), (700, 500)], fill="black", width=5)
# Draw a rectangle
draw.rectangle([(200, 200), (600, 400)], outline="red", width=3, fill="yellow")
# Draw an ellipse
draw.ellipse([(300, 150), (500, 350)], outline="blue", width=3, fill="lightblue")
# Draw a circle
draw.ellipse([(550, 50), (650, 150)], outline="green", width=2, fill="lightgreen")
# Draw a polygon
draw.polygon([(100, 500), (300, 450), (500, 550), (250, 600)],
outline="purple", fill="lavender")
# Draw an arc
draw.arc([(400, 400), (600, 500)], start=0, end=180, fill="orange", width=3)
# Draw a point
for i in range(50):
import random
x = random.randint(0, width)
y = random.randint(0, height)
draw.point((x, y), fill="black")
image.save("drawings.png")
# Create a blank canvas
width, height = 800, 600
image = Image.new("RGB", (width, height), color="white")
draw = ImageDraw.Draw(image)
# Draw a line
draw.line([(100, 100), (700, 500)], fill="black", width=5)
# Draw a rectangle
draw.rectangle([(200, 200), (600, 400)], outline="red", width=3, fill="yellow")
# Draw an ellipse
draw.ellipse([(300, 150), (500, 350)], outline="blue", width=3, fill="lightblue")
# Draw a circle
draw.ellipse([(550, 50), (650, 150)], outline="green", width=2, fill="lightgreen")
# Draw a polygon
draw.polygon([(100, 500), (300, 450), (500, 550), (250, 600)],
outline="purple", fill="lavender")
# Draw an arc
draw.arc([(400, 400), (600, 500)], start=0, end=180, fill="orange", width=3)
# Draw a point
for i in range(50):
import random
x = random.randint(0, width)
y = random.randint(0, height)
draw.point((x, y), fill="black")
image.save("drawings.png")
Adding Text (text())
Example
from PIL import Image, ImageDraw, ImageFont
# Create a blank canvas
image = Image.new("RGB", (800, 600), color="white")
draw = ImageDraw.Draw(image)
# Use the default font
draw.text((100, 100), "Hello, Pillow!", fill="black")
# Load a custom TrueType font
try:
# Try to load a system font
# Windows: "arial.ttf", "simhei.ttf"
# Mac: "Arial.ttf", "STHeiti Light.ttc"
# Linux: "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
font = ImageFont.truetype("arial.ttf", size=36)
except IOError:
# If the system font is not found, use the default font
font = ImageFont.load_default()
# Draw text with a custom font
draw.text((100, 200), "Custom font text", font=font, fill="blue")
# Draw text with a stroke
def draw_text_with_outline(draw, text, position, font, text_color, outline_color):
"""Draw text with outline"""
x, y = position
# Draw outline
draw.text((x-1, y-1), text, font=font, fill=outline_color)
draw.text((x+1, y-1), text, font=font, fill=outline_color)
draw.text((x-1, y+1), text, font=font, fill=outline_color)
draw.text((x+1, y+1), text, font=font, fill=outline_color)
# Draw main text
draw.text((x, y), text, font=font, fill=text_color)
draw_text_with_outline(draw, "Outline text effect", (100, 300), font, "red", "black")
# Get text size and draw centered
text = "Centered text"
text_width, text_height = draw.textsize(text, font=font)
position = ((800 - text_width) // 2, 400)
draw.text(position, text, font=font, fill="purple")
# Draw multi-line text
multiline_text = """This is
a multi-line text example
using the Pillow library
for drawing"""
draw.multiline_text((100, 450), multiline_text, font=font, fill="green", spacing=10, align="center")
image.save("text_drawings.png")
# Create a blank canvas
image = Image.new("RGB", (800, 600), color="white")
draw = ImageDraw.Draw(image)
# Use the default font
draw.text((100, 100), "Hello, Pillow!", fill="black")
# Load a custom TrueType font
try:
# Try to load a system font
# Windows: "arial.ttf", "simhei.ttf"
# Mac: "Arial.ttf", "STHeiti Light.ttc"
# Linux: "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
font = ImageFont.truetype("arial.ttf", size=36)
except IOError:
# If the system font is not found, use the default font
font = ImageFont.load_default()
# Draw text with a custom font
draw.text((100, 200), "Custom font text", font=font, fill="blue")
# Draw text with a stroke
def draw_text_with_outline(draw, text, position, font, text_color, outline_color):
"""Draw text with outline"""
x, y = position
# Draw outline
draw.text((x-1, y-1), text, font=font, fill=outline_color)
draw.text((x+1, y-1), text, font=font, fill=outline_color)
draw.text((x-1, y+1), text, font=font, fill=outline_color)
draw.text((x+1, y+1), text, font=font, fill=outline_color)
# Draw main text
draw.text((x, y), text, font=font, fill=text_color)
draw_text_with_outline(draw, "Outline text effect", (100, 300), font, "red", "black")
# Get text size and draw centered
text = "Centered text"
text_width, text_height = draw.textsize(text, font=font)
position = ((800 - text_width) // 2, 400)
draw.text(position, text, font=font, fill="purple")
# Draw multi-line text
multiline_text = """This is
a multi-line text example
using the Pillow library
for drawing"""
draw.multiline_text((100, 450), multiline_text, font=font, fill="green", spacing=10, align="center")
image.save("text_drawings.png")
Drawing Complex Shapes
Example
from PIL import Image, ImageDraw
import math
# Create a blank canvas
width, height = 800, 800
image = Image.new("RGB", (width, height), color="white")
draw = ImageDraw.Draw(image)
# Draw a star
def draw_star(draw, center, points=5, outer_radius=100, inner_radius=50, rotation=0, **kwargs):
"""Draw a star
Parameters:
center: center point coordinate tuple (x, y)
points: number of points of the star
outer_radius: outer circle radius
inner_radius: inner circle radius
rotation: rotation angle (degrees)
**kwargs: parameters passed to polygon()
"""
cx, cy = center
angle = math.pi / points
rotation_rad = math.radians(rotation)
vertices = []
for i in range(2 * points):
radius = outer_radius if i % 2 == 0 else inner_radius
theta = i * angle + rotation_rad
x = cx + radius * math.sin(theta)
y = cy - radius * math.cos(theta)
vertices.append((x, y))
draw.polygon(vertices, **kwargs)
# Draw the star
draw_star(draw, center=(200, 200), fill="gold", outline="orange", width=2)
draw_star(draw, center=(500, 200), points=8, rotation=22.5,
outer_radius=120, inner_radius=40, fill="blue", outline="navy", width=2)
# Draw a heart
def draw_heart(draw, center, size=100, **kwargs):
"""Draw a heart
Parameters:
center: center point coordinate tuple (x, y)
size: size of the heart
**kwargs: parameters passed to polygon()
"""
cx, cy = center
vertices = []
for t in range(100):
angle = t / 100 * 2 * math.pi
x = 16 * math.sin(angle) ** 3
y = 13 * math.cos(angle) - 5 * math.cos(2*angle) - 2 * math.cos(3*angle) - math.cos(4*angle)
# Scale and translate
vertices.append((cx + x * size / 16, cy - y * size / 16))
draw.polygon(vertices, **kwargs)
draw_heart(draw, center=(200, 500), size=150, fill="red", outline="darkred", width=3)
# Draw a spiral
def draw_spiral(draw, center, loops=3, radius_start=5, radius_end=100, points=500, **kwargs):
"""Draw a spiral
Parameters:
center: center point coordinate tuple (x, y)
loops: number of loops in the spiral
radius_start: starting radius
radius_end: ending radius
points: number of points
**kwargs: parameters passed to line()
"""
cx, cy = center
vertices = []
for i in range(points + 1):
# Calculate current angle and radius
angle = i / points * loops * 2 * math.pi
radius = radius_start + (radius_end - radius_start) * i / points
x = cx + radius * math.cos(angle)
y = cy + radius * math.sin(angle)
vertices.append((x, y))
# Draw a polyline
for i in range(len(vertices) - 1):
draw.line([vertices[i], vertices[i+1]], **kwargs)
draw_spiral(draw, center=(500, 500), loops=5, fill="purple", width=2)
image.save("complex_shapes.png")
import math
# Create a blank canvas
width, height = 800, 800
image = Image.new("RGB", (width, height), color="white")
draw = ImageDraw.Draw(image)
# Draw a star
def draw_star(draw, center, points=5, outer_radius=100, inner_radius=50, rotation=0, **kwargs):
"""Draw a star
Parameters:
center: center point coordinate tuple (x, y)
points: number of points of the star
outer_radius: outer circle radius
inner_radius: inner circle radius
rotation: rotation angle (degrees)
**kwargs: parameters passed to polygon()
"""
cx, cy = center
angle = math.pi / points
rotation_rad = math.radians(rotation)
vertices = []
for i in range(2 * points):
radius = outer_radius if i % 2 == 0 else inner_radius
theta = i * angle + rotation_rad
x = cx + radius * math.sin(theta)
y = cy - radius * math.cos(theta)
vertices.append((x, y))
draw.polygon(vertices, **kwargs)
# Draw the star
draw_star(draw, center=(200, 200), fill="gold", outline="orange", width=2)
draw_star(draw, center=(500, 200), points=8, rotation=22.5,
outer_radius=120, inner_radius=40, fill="blue", outline="navy", width=2)
# Draw a heart
def draw_heart(draw, center, size=100, **kwargs):
"""Draw a heart
Parameters:
center: center point coordinate tuple (x, y)
size: size of the heart
**kwargs: parameters passed to polygon()
"""
cx, cy = center
vertices = []
for t in range(100):
angle = t / 100 * 2 * math.pi
x = 16 * math.sin(angle) ** 3
y = 13 * math.cos(angle) - 5 * math.cos(2*angle) - 2 * math.cos(3*angle) - math.cos(4*angle)
# Scale and translate
vertices.append((cx + x * size / 16, cy - y * size / 16))
draw.polygon(vertices, **kwargs)
draw_heart(draw, center=(200, 500), size=150, fill="red", outline="darkred", width=3)
# Draw a spiral
def draw_spiral(draw, center, loops=3, radius_start=5, radius_end=100, points=500, **kwargs):
"""Draw a spiral
Parameters:
center: center point coordinate tuple (x, y)
loops: number of loops in the spiral
radius_start: starting radius
radius_end: ending radius
points: number of points
**kwargs: parameters passed to line()
"""
cx, cy = center
vertices = []
for i in range(points + 1):
# Calculate current angle and radius
angle = i / points * loops * 2 * math.pi
radius = radius_start + (radius_end - radius_start) * i / points
x = cx + radius * math.cos(angle)
y = cy + radius * math.sin(angle)
vertices.append((x, y))
# Draw a polyline
for i in range(len(vertices) - 1):
draw.line([vertices[i], vertices[i+1]], **kwargs)
draw_spiral(draw, center=(500, 500), loops=5, fill="purple", width=2)
image.save("complex_shapes.png")
Using Different Fonts and Styles
Example
from PIL import Image, ImageDraw, ImageFont
import os
# Create a blank canvas
image = Image.new("RGB", (800, 600), color="white")
draw = ImageDraw.Draw(image)
# Try to load multiple fonts
fonts = {
"default": ImageFont.load_default(),
}
# Try to load system fonts
try:
fonts["arial"] = ImageFont.truetype("arial.ttf", size=36)
except IOError:
pass
try:
fonts["times"] = ImageFont.truetype("times.ttf", size=36)
except IOError:
pass
try:
fonts["simhei"] = ImageFont.truetype("simhei.ttf", size=36) # Chinese font
except IOError:
pass
# Draw different font styles
y_position = 50
for font_name, font in fonts.items():
draw.text((50, y_position), f"Font: {font_name}", font=font, fill="black")
y_position += 60
# Create artistic text effect
def draw_gradient_text(draw, text, position, font, start_color, end_color, steps=10):
"""Draw gradient text
Parameters:
draw: ImageDraw object
text: the text to draw
position: text position (x, y)
font: font object
start_color: start color (r, g, b)
end_color: end color (r, g, b)
steps: number of gradient steps
"""
x, y = position
width, height = draw.textsize(text, font=font)
# Calculate the width of each character
char_width = width / len(text)
for i, char in enumerate(text):
# Calculate the color of the current character
progress = i / (len(text) - 1) if len(text) > 1 else 0
r = int(start_color[0] + (end_color[0] - start_color[0]) * progress)
g = int(start_color[1] + (end_color[1] - start_color[1]) * progress)
b = int(start_color[2] + (end_color[2] - start_color[2]) * progress)
color = (r, g, b)
# Draw the current character
draw.text((x + i * char_width, y), char, font=font, fill=color)
# Apply gradient text
if "arial" in fonts:
draw_gradient_text(
draw,
"Gradient text effect",
(50, 300),
fonts["arial"],
start_color=(255, 0, 0), # Red
end_color=(0, 0, 255) # Blue
)
# Apply shadow text
def draw_shadow_text(draw, text, position, font, text_color, shadow_color, offset=(3, 3)):
"""Draw text with shadow"""
x, y = position
dx, dy = offset
# Draw shadow
draw.text((x + dx, y + dy), text, font=font, fill=shadow_color)
# Draw main text
draw.text((x, y), text, font=font, fill=text_color)
# Apply shadow effect
if "arial" in fonts:
draw_shadow_text(
draw,
"Text with shadow effect",
(50, 400),
fonts["arial"],
text_color=(255, 255, 255), # White
shadow_color=(100, 100, 100), # Gray
offset=(3, 3)
)
image.save("font_styles.png")
import os
# Create a blank canvas
image = Image.new("RGB", (800, 600), color="white")
draw = ImageDraw.Draw(image)
# Try to load multiple fonts
fonts = {
"default": ImageFont.load_default(),
}
# Try to load system fonts
try:
fonts["arial"] = ImageFont.truetype("arial.ttf", size=36)
except IOError:
pass
try:
fonts["times"] = ImageFont.truetype("times.ttf", size=36)
except IOError:
pass
try:
fonts["simhei"] = ImageFont.truetype("simhei.ttf", size=36) # Chinese font
except IOError:
pass
# Draw different font styles
y_position = 50
for font_name, font in fonts.items():
draw.text((50, y_position), f"Font: {font_name}", font=font, fill="black")
y_position += 60
# Create artistic text effect
def draw_gradient_text(draw, text, position, font, start_color, end_color, steps=10):
"""Draw gradient text
Parameters:
draw: ImageDraw object
text: the text to draw
position: text position (x, y)
font: font object
start_color: start color (r, g, b)
end_color: end color (r, g, b)
steps: number of gradient steps
"""
x, y = position
width, height = draw.textsize(text, font=font)
# Calculate the width of each character
char_width = width / len(text)
for i, char in enumerate(text):
# Calculate the color of the current character
progress = i / (len(text) - 1) if len(text) > 1 else 0
r = int(start_color[0] + (end_color[0] - start_color[0]) * progress)
g = int(start_color[1] + (end_color[1] - start_color[1]) * progress)
b = int(start_color[2] + (end_color[2] - start_color[2]) * progress)
color = (r, g, b)
# Draw the current character
draw.text((x + i * char_width, y), char, font=font, fill=color)
# Apply gradient text
if "arial" in fonts:
draw_gradient_text(
draw,
"Gradient text effect",
(50, 300),
fonts["arial"],
start_color=(255, 0, 0), # Red
end_color=(0, 0, 255) # Blue
)
# Apply shadow text
def draw_shadow_text(draw, text, position, font, text_color, shadow_color, offset=(3, 3)):
"""Draw text with shadow"""
x, y = position
dx, dy = offset
# Draw shadow
draw.text((x + dx, y + dy), text, font=font, fill=shadow_color)
# Draw main text
draw.text((x, y), text, font=font, fill=text_color)
# Apply shadow effect
if "arial" in fonts:
draw_shadow_text(
draw,
"Text with shadow effect",
(50, 400),
fonts["arial"],
text_color=(255, 255, 255), # White
shadow_color=(100, 100, 100), # Gray
offset=(3, 3)
)
image.save("font_styles.png")
Image Composition and Blending
Image Overlay (paste())
Example
from PIL import Image
# Open background image and foreground image
try:
background = Image.open("background.jpg")
foreground = Image.open("foreground.png")
except IOError:
# If there is no image file, create a sample image
background = Image.new("RGB", (800, 600), color="lightblue")
foreground = Image.new("RGBA", (200, 200), color=(255, 0, 0, 128)) # Semi-transparent red square
# Resize the foreground image (optional)
foreground = foreground.resize((300, 300))
# Calculate paste position (centered)
paste_position = (
(background.width - foreground.width) // 2,
(background.height - foreground.height) // 2
)
# Simple paste (ignoring transparency)
simple_paste = background.copy()
simple_paste.paste(foreground, paste_position)
simple_paste.save("simple_paste.png")
# Paste with transparency (if the foreground has an alpha channel)
if foreground.mode == 'RGBA':
alpha_paste = background.copy()
alpha_paste.paste(foreground, paste_position, foreground)
alpha_paste.save("alpha_paste.png")
# Open background image and foreground image
try:
background = Image.open("background.jpg")
foreground = Image.open("foreground.png")
except IOError:
# If there is no image file, create a sample image
background = Image.new("RGB", (800, 600), color="lightblue")
foreground = Image.new("RGBA", (200, 200), color=(255, 0, 0, 128)) # Semi-transparent red square
# Resize the foreground image (optional)
foreground = foreground.resize((300, 300))
# Calculate paste position (centered)
paste_position = (
(background.width - foreground.width) // 2,
(background.height - foreground.height) // 2
)
# Simple paste (ignoring transparency)
simple_paste = background.copy()
simple_paste.paste(foreground, paste_position)
simple_paste.save("simple_paste.png")
# Paste with transparency (if the foreground has an alpha channel)
if foreground.mode == 'RGBA':
alpha_paste = background.copy()
alpha_paste.paste(foreground, paste_position, foreground)
alpha_paste.save("alpha_paste.png")
Transparency Handling (alpha channel)
Example
from PIL import Image
# Create an image with alpha channel
width, height = 500, 500
image = Image.new("RGBA", (width, height), color=(0, 0, 0, 0)) # Fully transparent
# Generate gradient transparency effect
for y in range(height):
for x in range(width):
# Transparency decreases from left to right (from fully opaque to fully transparent)
alpha = 255 - int(255 * x / width)
# Color gradient from top to bottom
r = int(255 * y / height)
g = int(255 * (1 - y / height))
b = 128
image.putpixel((x, y), (r, g, b, alpha))
image.save("alpha_gradient.png")
# Convert RGB image to an image with alpha channel
def add_alpha_channel(image, alpha_value=128):
"""Add alpha channel to RGB image
Args:
image: PIL Image object
alpha_value: transparency value (0-255)
"""
if image.mode != 'RGBA':
# Convert to RGBA mode
rgba_image = image.convert("RGBA")
# Get pixel data
data = rgba_image.getdata()
# Set transparency
new_data = []
for item in data:
# Modify pixel alpha value
if len(item) == 4: # Already RGBA
new_data.append((item[0], item[1], item[2], alpha_value))
else: # RGB
new_data.append((item[0], item[1], item[2], alpha_value))
rgba_image.putdata(new_data)
return rgba_image
return image
# Create an example RGB image
rgb_image = Image.new("RGB", (300, 300), color="blue")
# Add alpha channel
rgba_image = add_alpha_channel(rgb_image, alpha_value=128)
rgba_image.save("with_alpha.png")
# Create a circular mask
def create_circular_mask(image, radius=None):
"""Create a circular mask
Args:
image: PIL Image object
radius: circle radius, defaults to half the shorter side of the image
"""
width, height = image.width, image.height
center_x, center_y = width // 2, height // 2
if radius is None:
radius = min(center_x, center_y)
# Create a new transparent image
mask = Image.new('L', (width, height), 0)
# Create a circular mask
for y in range(height):
for x in range(width):
# Calculate distance to center
distance = ((x - center_x) ** 2 + (y - center_y) ** 2) ** 0.5
# If inside circle, set to opaque
if distance <= radius:
mask.putpixel((x, y), 255)
# Apply mask to original image
if image.mode != 'RGBA':
image = image.convert('RGBA')
result = Image.new('RGBA', (width, height), (0, 0, 0, 0))
result.paste(image, (0, 0), mask)
return result
# Create an example image
square_image = Image.new("RGB", (300, 300), color="green")
# Apply circular mask
circular_image = create_circular_mask(square_image)
circular_image.save("circular_image.png")
# Create an image with alpha channel
width, height = 500, 500
image = Image.new("RGBA", (width, height), color=(0, 0, 0, 0)) # Fully transparent
# Generate gradient transparency effect
for y in range(height):
for x in range(width):
# Transparency decreases from left to right (from fully opaque to fully transparent)
alpha = 255 - int(255 * x / width)
# Color gradient from top to bottom
r = int(255 * y / height)
g = int(255 * (1 - y / height))
b = 128
image.putpixel((x, y), (r, g, b, alpha))
image.save("alpha_gradient.png")
# Convert RGB image to an image with alpha channel
def add_alpha_channel(image, alpha_value=128):
"""Add alpha channel to RGB image
Args:
image: PIL Image object
alpha_value: transparency value (0-255)
"""
if image.mode != 'RGBA':
# Convert to RGBA mode
rgba_image = image.convert("RGBA")
# Get pixel data
data = rgba_image.getdata()
# Set transparency
new_data = []
for item in data:
# Modify pixel alpha value
if len(item) == 4: # Already RGBA
new_data.append((item[0], item[1], item[2], alpha_value))
else: # RGB
new_data.append((item[0], item[1], item[2], alpha_value))
rgba_image.putdata(new_data)
return rgba_image
return image
# Create an example RGB image
rgb_image = Image.new("RGB", (300, 300), color="blue")
# Add alpha channel
rgba_image = add_alpha_channel(rgb_image, alpha_value=128)
rgba_image.save("with_alpha.png")
# Create a circular mask
def create_circular_mask(image, radius=None):
"""Create a circular mask
Args:
image: PIL Image object
radius: circle radius, defaults to half the shorter side of the image
"""
width, height = image.width, image.height
center_x, center_y = width // 2, height // 2
if radius is None:
radius = min(center_x, center_y)
# Create a new transparent image
mask = Image.new('L', (width, height), 0)
# Create a circular mask
for y in range(height):
for x in range(width):
# Calculate distance to center
distance = ((x - center_x) ** 2 + (y - center_y) ** 2) ** 0.5
# If inside circle, set to opaque
if distance <= radius:
mask.putpixel((x, y), 255)
# Apply mask to original image
if image.mode != 'RGBA':
image = image.convert('RGBA')
result = Image.new('RGBA', (width, height), (0, 0, 0, 0))
result.paste(image, (0, 0), mask)
return result
# Create an example image
square_image = Image.new("RGB", (300, 300), color="green")
# Apply circular mask
circular_image = create_circular_mask(square_image)
circular_image.save("circular_image.png")
Blend Modes (blend())
Example
from PIL import Image, ImageChops
# Create two example images
width, height = 500, 500
image1 = Image.new("RGB", (width, height), color="blue")
image2 = Image.new("RGB", (width, height), color="yellow")
# Draw gradients on the images
for y in range(height):
for x in range(width):
# Image 1: from blue to black left to right
blue_value = max(0, 255 - int(255 * x / width))
image1.putpixel((x, y), (0, 0, blue_value))
# Image 2: from yellow to red top to bottom
red_value = int(255 * y / height)
green_value = max(0, 255 - int(255 * y / height))
image2.putpixel((x, y), (red_value, green_value, 0))
image1.save("gradient1.png")
image2.save("gradient2.png")
# Different blending modes
# 1. Normal blend (blend)
blend_result = Image.blend(image1, image2, alpha=0.5)
blend_result.save("blend.png")
# 2. Add (add)
add_result = ImageChops.add(image1, image2)
add_result.save("add.png")
# 3. Overlay (screen)
screen_result = ImageChops.screen(image1, image2)
screen_result.save("screen.png")
# 4. Multiply (multiply)
multiply_result = ImageChops.multiply(image1, image2)
multiply_result.save("multiply.png")
# 5. Difference (difference)
difference_result = ImageChops.difference(image1, image2)
difference_result.save("difference.png")
# 6. Exclude (invert)
invert_result = ImageChops.invert(image1)
invert_result.save("invert.png")
# 7. Brightness (lighter)
lighter_result = ImageChops.lighter(image1, image2)
lighter_result.save("lighter.png")
# 8. Darkness (darker)
darker_result = ImageChops.darker(image1, image2)
darker_result.save("darker.png")
# Create a custom blend function
def custom_blend(image1, image2, mode="soft_light"):
"""Custom blending mode
Args:
image1, image2: Two images to blend
mode: Blending mode name
"""
if image1.mode != image2.mode:
raise ValueError("Both images must have the same mode")
width, height = image1.size
result = Image.new(image1.mode, (width, height))
for y in range(height):
for x in range(width):
r1, g1, b1 = image1.getpixel((x, y))
r2, g2, b2 = image2.getpixel((x, y))
if mode == "soft_light":
# Implement soft light blending mode
r = (r1 * (255 - r2) + r2 * r2 // 255) // 255
g = (g1 * (255 - g2) + g2 * g2 // 255) // 255
b = (b1 * (255 - b2) + b2 * b2 // 255) // 255
elif mode == "hard_light":
# Implement hard light blending mode
if r2 < 128:
r = (2 * r1 * r2) // 255
else:
r = 255 - (2 * (255 - r1) * (255 - r2)) // 255
if g2 < 128:
g = (2 * g1 * g2) // 255
else:
g = 255 - (2 * (255 - g1) * (255 - g2)) // 255
if b2 < 128:
b = (2 * b1 * b2) // 255
else:
b = 255 - (2 * (255 - b1) * (255 - b2)) // 255
elif mode == "overlay":
# Implement overlay blending mode
if r1 < 128:
r = (2 * r1 * r2) // 255
else:
r = 255 - (2 * (255 - r1) * (255 - r2)) // 255
if g1 < 128:
g = (2 * g1 * g2) // 255
else:
g = 255 - (2 * (255 - g1) * (255 - g2)) // 255
if b1 < 128:
b = (2 * b1 * b2) // 255
else:
b = 255 - (2 * (255 - b1) * (255 - b2)) // 255
else:
# Default: use normal blend
r = (r1 + r2) // 2
g = (g1 + g2) // 2
b = (b1 + b2) // 2
result.putpixel((x, y), (r, g, b))
return result
# Apply custom blending mode
soft_light = custom_blend(image1, image2, mode="soft_light")
soft_light.save("soft_light.png")
hard_light = custom_blend(image1, image2, mode="hard_light")
hard_light.save("hard_light.png")
overlay = custom_blend(image1, image2, mode="overlay")
overlay.save("overlay.png")
# Create two example images
width, height = 500, 500
image1 = Image.new("RGB", (width, height), color="blue")
image2 = Image.new("RGB", (width, height), color="yellow")
# Draw gradients on the images
for y in range(height):
for x in range(width):
# Image 1: from blue to black left to right
blue_value = max(0, 255 - int(255 * x / width))
image1.putpixel((x, y), (0, 0, blue_value))
# Image 2: from yellow to red top to bottom
red_value = int(255 * y / height)
green_value = max(0, 255 - int(255 * y / height))
image2.putpixel((x, y), (red_value, green_value, 0))
image1.save("gradient1.png")
image2.save("gradient2.png")
# Different blending modes
# 1. Normal blend (blend)
blend_result = Image.blend(image1, image2, alpha=0.5)
blend_result.save("blend.png")
# 2. Add (add)
add_result = ImageChops.add(image1, image2)
add_result.save("add.png")
# 3. Overlay (screen)
screen_result = ImageChops.screen(image1, image2)
screen_result.save("screen.png")
# 4. Multiply (multiply)
multiply_result = ImageChops.multiply(image1, image2)
multiply_result.save("multiply.png")
# 5. Difference (difference)
difference_result = ImageChops.difference(image1, image2)
difference_result.save("difference.png")
# 6. Exclude (invert)
invert_result = ImageChops.invert(image1)
invert_result.save("invert.png")
# 7. Brightness (lighter)
lighter_result = ImageChops.lighter(image1, image2)
lighter_result.save("lighter.png")
# 8. Darkness (darker)
darker_result = ImageChops.darker(image1, image2)
darker_result.save("darker.png")
# Create a custom blend function
def custom_blend(image1, image2, mode="soft_light"):
"""Custom blending mode
Args:
image1, image2: Two images to blend
mode: Blending mode name
"""
if image1.mode != image2.mode:
raise ValueError("Both images must have the same mode")
width, height = image1.size
result = Image.new(image1.mode, (width, height))
for y in range(height):
for x in range(width):
r1, g1, b1 = image1.getpixel((x, y))
r2, g2, b2 = image2.getpixel((x, y))
if mode == "soft_light":
# Implement soft light blending mode
r = (r1 * (255 - r2) + r2 * r2 // 255) // 255
g = (g1 * (255 - g2) + g2 * g2 // 255) // 255
b = (b1 * (255 - b2) + b2 * b2 // 255) // 255
elif mode == "hard_light":
# Implement hard light blending mode
if r2 < 128:
r = (2 * r1 * r2) // 255
else:
r = 255 - (2 * (255 - r1) * (255 - r2)) // 255
if g2 < 128:
g = (2 * g1 * g2) // 255
else:
g = 255 - (2 * (255 - g1) * (255 - g2)) // 255
if b2 < 128:
b = (2 * b1 * b2) // 255
else:
b = 255 - (2 * (255 - b1) * (255 - b2)) // 255
elif mode == "overlay":
# Implement overlay blending mode
if r1 < 128:
r = (2 * r1 * r2) // 255
else:
r = 255 - (2 * (255 - r1) * (255 - r2)) // 255
if g1 < 128:
g = (2 * g1 * g2) // 255
else:
g = 255 - (2 * (255 - g1) * (255 - g2)) // 255
if b1 < 128:
b = (2 * b1 * b2) // 255
else:
b = 255 - (2 * (255 - b1) * (255 - b2)) // 255
else:
# Default: use normal blend
r = (r1 + r2) // 2
g = (g1 + g2) // 2
b = (b1 + b2) // 2
result.putpixel((x, y), (r, g, b))
return result
# Apply custom blending mode
soft_light = custom_blend(image1, image2, mode="soft_light")
soft_light.save("soft_light.png")
hard_light = custom_blend(image1, image2, mode="hard_light")
hard_light.save("hard_light.png")
overlay = custom_blend(image1, image2, mode="overlay")
overlay.save("overlay.png")
Mask Application and Advanced Compositing Techniques
Mask Application
Example
from PIL import Image, ImageDraw
# Create an example image
width, height = 500, 500
image = Image.new("RGB", (width, height), color="purple")
# Create a gradient
for y in range(height):
for x in range(width):
# Gradient from top-left to bottom-right
r = int(255 * (x + y) / (width + height))
g = int(128 * x / width)
b = int(255 * (1 - y / height))
image.putpixel((x, y), (r, g, b))
# Create mask image (L mode represents grayscale)
mask = Image.new('L', (width, height), 0)
draw = ImageDraw.Draw(mask)
# Draw shapes on the mask
# Center radial gradient mask
center_x, center_y = width // 2, height // 2
max_distance = (width**2 + height**2)**0.5 / 2
for y in range(height):
for x in range(width):
# Calculate distance from point to center
distance = ((x - center_x)**2 + (y - center_y)**2)**0.5
# Set opacity based on distance (the farther, the more transparent)
alpha = max(0, int(255 * (1 - distance / max_distance)))
mask.putpixel((x, y), alpha)
# Save mask for inspection
mask.save("radial_mask.png")
# Create blank target image
result = Image.new("RGBA", (width, height), (0, 0, 0, 0))
# Apply mask to original image
result.paste(image, (0, 0), mask)
result.save("radial_masked.png")
# Create custom shape mask
shape_mask = Image.new('L', (width, height), 0)
draw = ImageDraw.Draw(shape_mask)
# Draw polygon mask
polygon_points = [
(width//4, height//4),
(3*width//4, height//4),
(width, height//2),
(3*width//4, 3*height//4),
(width//4, 3*height//4),
(0, height//2)
]
draw.polygon(polygon_points, fill=255)
shape_mask.save("polygon_mask.png")
# Apply polygon mask
poly_result = Image.new("RGBA", (width, height), (0, 0, 0, 0))
poly_result.paste(image, (0, 0), shape_mask)
poly_result.save("polygon_masked.png")
# Create text mask
text_mask = Image.new('L', (width, height), 0)
draw = ImageDraw.Draw(text_mask)
# Try to load a custom font, fall back to default font if failed
try:
from PIL import ImageFont
font = ImageFont.truetype("arial.ttf", 120)
except IOError:
font = ImageFont.load_default()
# Draw text on mask
text = "PILLOW"
text_width, text_height = draw.textsize(text, font=font)
position = ((width - text_width) // 2, (height - text_height) // 2)
draw.text(position, text, fill=255, font=font)
text_mask.save("text_mask.png")
# Apply text mask
text_result = Image.new("RGBA", (width, height), (0, 0, 0, 0))
text_result.paste(image, (0, 0), text_mask)
text_result.save("text_masked.png")
# Create an example image
width, height = 500, 500
image = Image.new("RGB", (width, height), color="purple")
# Create a gradient
for y in range(height):
for x in range(width):
# Gradient from top-left to bottom-right
r = int(255 * (x + y) / (width + height))
g = int(128 * x / width)
b = int(255 * (1 - y / height))
image.putpixel((x, y), (r, g, b))
# Create mask image (L mode represents grayscale)
mask = Image.new('L', (width, height), 0)
draw = ImageDraw.Draw(mask)
# Draw shapes on the mask
# Center radial gradient mask
center_x, center_y = width // 2, height // 2
max_distance = (width**2 + height**2)**0.5 / 2
for y in range(height):
for x in range(width):
# Calculate distance from point to center
distance = ((x - center_x)**2 + (y - center_y)**2)**0.5
# Set opacity based on distance (the farther, the more transparent)
alpha = max(0, int(255 * (1 - distance / max_distance)))
mask.putpixel((x, y), alpha)
# Save mask for inspection
mask.save("radial_mask.png")
# Create blank target image
result = Image.new("RGBA", (width, height), (0, 0, 0, 0))
# Apply mask to original image
result.paste(image, (0, 0), mask)
result.save("radial_masked.png")
# Create custom shape mask
shape_mask = Image.new('L', (width, height), 0)
draw = ImageDraw.Draw(shape_mask)
# Draw polygon mask
polygon_points = [
(width//4, height//4),
(3*width//4, height//4),
(width, height//2),
(3*width//4, 3*height//4),
(width//4, 3*height//4),
(0, height//2)
]
draw.polygon(polygon_points, fill=255)
shape_mask.save("polygon_mask.png")
# Apply polygon mask
poly_result = Image.new("RGBA", (width, height), (0, 0, 0, 0))
poly_result.paste(image, (0, 0), shape_mask)
poly_result.save("polygon_masked.png")
# Create text mask
text_mask = Image.new('L', (width, height), 0)
draw = ImageDraw.Draw(text_mask)
# Try to load a custom font, fall back to default font if failed
try:
from PIL import ImageFont
font = ImageFont.truetype("arial.ttf", 120)
except IOError:
font = ImageFont.load_default()
# Draw text on mask
text = "PILLOW"
text_width, text_height = draw.textsize(text, font=font)
position = ((width - text_width) // 2, (height - text_height) // 2)
draw.text(position, text, fill=255, font=font)
text_mask.save("text_mask.png")
# Apply text mask
text_result = Image.new("RGBA", (width, height), (0, 0, 0, 0))
text_result.paste(image, (0, 0), text_mask)
text_result.save("text_masked.png")
Advanced Image Blending and Compositing Effects
Let's explore some more advanced image blending and compositing effects:
Example
from PIL import Image, ImageFilter, ImageChops, ImageOps
import math
# Create two example images
width, height = 500, 500
image1 = Image.new("RGB", (width, height), color="blue")
image2 = Image.new("RGB", (width, height), color="yellow")
# Draw gradients on the images
for y in range(height):
for x in range(width):
# Image 1: from blue to black left to right
blue_value = max(0, 255 - int(255 * x / width))
image1.putpixel((x, y), (0, 0, blue_value))
# Image 2: from yellow to red top to bottom
red_value = int(255 * y / height)
green_value = max(0, 255 - int(255 * y / height))
image2.putpixel((x, y), (red_value, green_value, 0))
# Transition blending effect
def transition_blend(image1, image2, direction="horizontal", steps=10):
"""Create a smooth transition effect between two images
Args:
image1, image2: Two images to blend
direction: Transition direction, "horizontal" or "vertical"
steps: Number of transition steps
"""
if image1.size != image2.size:
raise ValueError("Both images must have the same size")
width, height = image1.size
result = Image.new(image1.mode, (width, height))
for y in range(height):
for x in range(width):
if direction == "horizontal":
# Horizontal transition: left to right
blend_factor = x / width
elif direction == "vertical":
# Vertical transition: top to bottom
blend_factor = y / height
elif direction == "radial":
# Radial transition: from center outward
center_x, center_y = width // 2, height // 2
distance = ((x - center_x)**2 + (y - center_y)**2)**0.5
max_distance = max((width**2 + height**2)**0.5 / 2, 1)
blend_factor = min(1.0, distance / max_distance)
else:
blend_factor = 0.5 # Default is uniform blending
# Get pixels of both images
r1, g1, b1 = image1.getpixel((x, y))
r2, g2, b2 = image2.getpixel((x, y))
# Compute blended pixel
r = int(r1 * (1 - blend_factor) + r2 * blend_factor)
g = int(g1 * (1 - blend_factor) + g2 * blend_factor)
b = int(b1 * (1 - blend_factor) + b2 * blend_factor)
result.putpixel((x, y), (r, g, b))
return result
# Create horizontal transition
horizontal_transition = transition_blend(image1, image2, direction="horizontal")
horizontal_transition.save("horizontal_transition.png")
# Create vertical transition
vertical_transition = transition_blend(image1, image2, direction="vertical")
vertical_transition.save("vertical_transition.png")
# Create radial transition
radial_transition = transition_blend(image1, image2, direction="radial")
radial_transition.save("radial_transition.png")
# Create checkerboard blending effect
def checkerboard_blend(image1, image2, grid_size=50):
"""Create checkerboard blending effect
Args:
image1, image2: Two images to blend
grid_size: Size of the checkerboard
"""
if image1.size != image2.size:
raise ValueError("Both images must have the same size")
width, height = image1.size
result = Image.new(image1.mode, (width, height))
for y in range(height):
for x in range(width):
# Determine which grid cell the current pixel is in
grid_x = x // grid_size
grid_y = y // grid_size
# Select image based on checkerboard pattern
if (grid_x + grid_y) % 2 == 0:
result.putpixel((x, y), image1.getpixel((x, y)))
else:
result.putpixel((x, y), image2.getpixel((x, y)))
return result
# Create checkerboard blend
checkerboard = checkerboard_blend(image1, image2, grid_size=50)
checkerboard.save("checkerboard_blend.png")
# Noise blending effect
def noise_blend(image1, image2, noise_level=0.5):
"""Blend using noise
Args:
image1, image2: Two images to blend
noise_level: Noise level (0.0-1.0)
"""
if image1.size != image2.size:
raise ValueError("Both images must have the same size")
import random
width, height = image1.size
result = Image.new(image1.mode, (width, height))
for y in range(height):
for x in range(width):
# Generate random values
random_value = random.random()
# Select image based on random value and noise level
if random_value < noise_level:
result.putpixel((x, y), image1.getpixel((x, y)))
else:
result.putpixel((x, y), image2.getpixel((x, y)))
return result
# Create noise blend
noise_blended = noise_blend(image1, image2, noise_level=0.5)
noise_blended.save("noise_blend.png")
# Create fluid blending effect
def fluid_blend(image1, image2, turbulence=10.0, seed=42):
"""Create a fluid-like blending effect
Args:
image1, image2: Two images to blend
turbulence: Turbulence intensity
seed: Random seed
"""
if image1.size != image2.size:
raise ValueError("Both images must have the same size")
import random
random.seed(seed)
width, height = image1.size
result = Image.new(image1.mode, (width, height))
# Create a simplified Perlin noise implementation
def noise(x, y):
# Simplified noise function
n = x + y * 57
n = (n << 13) ^ n
return (1.0 - ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0)
for y in range(height):
for x in range(width):
# Use distorted coordinates to generate blending factor
noise_value = noise(x / turbulence, y / turbulence)
blend_factor = (noise_value + 1.0) / 2.0 # Normalize to 0-1 range
# Get pixels of both images
r1, g1, b1 = image1.getpixel((x, y))
r2, g2, b2 = image2.getpixel((x, y))
# Compute blended pixel
r = int(r1 * (1 - blend_factor) + r2 * blend_factor)
g = int(g1 * (1 - blend_factor) + g2 * blend_factor)
b = int(b1 * (1 - blend_factor) + b2 * blend_factor)
result.putpixel((x, y), (r, g, b))
return result
# Create fluid blend
fluid_blended = fluid_blend(image1, image2, turbulence=20.0)
fluid_blended.save("fluid_blend.png")
import math
# Create two example images
width, height = 500, 500
image1 = Image.new("RGB", (width, height), color="blue")
image2 = Image.new("RGB", (width, height), color="yellow")
# Draw gradients on the images
for y in range(height):
for x in range(width):
# Image 1: from blue to black left to right
blue_value = max(0, 255 - int(255 * x / width))
image1.putpixel((x, y), (0, 0, blue_value))
# Image 2: from yellow to red top to bottom
red_value = int(255 * y / height)
green_value = max(0, 255 - int(255 * y / height))
image2.putpixel((x, y), (red_value, green_value, 0))
# Transition blending effect
def transition_blend(image1, image2, direction="horizontal", steps=10):
"""Create a smooth transition effect between two images
Args:
image1, image2: Two images to blend
direction: Transition direction, "horizontal" or "vertical"
steps: Number of transition steps
"""
if image1.size != image2.size:
raise ValueError("Both images must have the same size")
width, height = image1.size
result = Image.new(image1.mode, (width, height))
for y in range(height):
for x in range(width):
if direction == "horizontal":
# Horizontal transition: left to right
blend_factor = x / width
elif direction == "vertical":
# Vertical transition: top to bottom
blend_factor = y / height
elif direction == "radial":
# Radial transition: from center outward
center_x, center_y = width // 2, height // 2
distance = ((x - center_x)**2 + (y - center_y)**2)**0.5
max_distance = max((width**2 + height**2)**0.5 / 2, 1)
blend_factor = min(1.0, distance / max_distance)
else:
blend_factor = 0.5 # Default is uniform blending
# Get pixels of both images
r1, g1, b1 = image1.getpixel((x, y))
r2, g2, b2 = image2.getpixel((x, y))
# Compute blended pixel
r = int(r1 * (1 - blend_factor) + r2 * blend_factor)
g = int(g1 * (1 - blend_factor) + g2 * blend_factor)
b = int(b1 * (1 - blend_factor) + b2 * blend_factor)
result.putpixel((x, y), (r, g, b))
return result
# Create horizontal transition
horizontal_transition = transition_blend(image1, image2, direction="horizontal")
horizontal_transition.save("horizontal_transition.png")
# Create vertical transition
vertical_transition = transition_blend(image1, image2, direction="vertical")
vertical_transition.save("vertical_transition.png")
# Create radial transition
radial_transition = transition_blend(image1, image2, direction="radial")
radial_transition.save("radial_transition.png")
# Create checkerboard blending effect
def checkerboard_blend(image1, image2, grid_size=50):
"""Create checkerboard blending effect
Args:
image1, image2: Two images to blend
grid_size: Size of the checkerboard
"""
if image1.size != image2.size:
raise ValueError("Both images must have the same size")
width, height = image1.size
result = Image.new(image1.mode, (width, height))
for y in range(height):
for x in range(width):
# Determine which grid cell the current pixel is in
grid_x = x // grid_size
grid_y = y // grid_size
# Select image based on checkerboard pattern
if (grid_x + grid_y) % 2 == 0:
result.putpixel((x, y), image1.getpixel((x, y)))
else:
result.putpixel((x, y), image2.getpixel((x, y)))
return result
# Create checkerboard blend
checkerboard = checkerboard_blend(image1, image2, grid_size=50)
checkerboard.save("checkerboard_blend.png")
# Noise blending effect
def noise_blend(image1, image2, noise_level=0.5):
"""Blend using noise
Args:
image1, image2: Two images to blend
noise_level: Noise level (0.0-1.0)
"""
if image1.size != image2.size:
raise ValueError("Both images must have the same size")
import random
width, height = image1.size
result = Image.new(image1.mode, (width, height))
for y in range(height):
for x in range(width):
# Generate random values
random_value = random.random()
# Select image based on random value and noise level
if random_value < noise_level:
result.putpixel((x, y), image1.getpixel((x, y)))
else:
result.putpixel((x, y), image2.getpixel((x, y)))
return result
# Create noise blend
noise_blended = noise_blend(image1, image2, noise_level=0.5)
noise_blended.save("noise_blend.png")
# Create fluid blending effect
def fluid_blend(image1, image2, turbulence=10.0, seed=42):
"""Create a fluid-like blending effect
Args:
image1, image2: Two images to blend
turbulence: Turbulence intensity
seed: Random seed
"""
if image1.size != image2.size:
raise ValueError("Both images must have the same size")
import random
random.seed(seed)
width, height = image1.size
result = Image.new(image1.mode, (width, height))
# Create a simplified Perlin noise implementation
def noise(x, y):
# Simplified noise function
n = x + y * 57
n = (n << 13) ^ n
return (1.0 - ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0)
for y in range(height):
for x in range(width):
# Use distorted coordinates to generate blending factor
noise_value = noise(x / turbulence, y / turbulence)
blend_factor = (noise_value + 1.0) / 2.0 # Normalize to 0-1 range
# Get pixels of both images
r1, g1, b1 = image1.getpixel((x, y))
r2, g2, b2 = image2.getpixel((x, y))
# Compute blended pixel
r = int(r1 * (1 - blend_factor) + r2 * blend_factor)
g = int(g1 * (1 - blend_factor) + g2 * blend_factor)
b = int(b1 * (1 - blend_factor) + b2 * blend_factor)
result.putpixel((x, y), (r, g, b))
return result
# Create fluid blend
fluid_blended = fluid_blend(image1, image2, turbulence=20.0)
fluid_blended.save("fluid_blend.png")
Creating Gradient Masks and Complex Compositing Effects
Example
from PIL import Image, ImageDraw, ImageFilter, ImageOps
import math
# Create a demo image
width, height = 600, 400
original = Image.new("RGB", (width, height), color=(30, 30, 30))
# Draw some shapes
draw = ImageDraw.Draw(original)
draw.rectangle([(50, 50), (width-50, height-50)], outline="white", width=2)
draw.ellipse([(100, 100), (width-100, height-100)], outline="blue", width=3)
draw.line([(width//2, 50), (width//2, height-50)], fill="red", width=5)
draw.line([(50, height//2), (width-50, height//2)], fill="green", width=5)
# Save original image
original.save("original_composite.png")
# Create gradient mask
def create_gradient_mask(size, direction="horizontal", start=0, end=255):
"""Create gradient mask
Args:
size: Mask size (width, height)
direction: Gradient direction "horizontal", "vertical", "radial", "diagonal"
start: Starting brightness value (0-255)
end: Ending brightness value (0-255)
"""
width, height = size
mask = Image.new('L', size, 0)
for y in range(height):
for x in range(width):
if direction == "horizontal":
# Horizontal gradient
value = start + int((end - start) * x / width)
elif direction == "vertical":
# Vertical gradient
value = start + int((end - start) * y / height)
elif direction == "radial":
# Radial gradient
center_x, center_y = width // 2, height // 2
max_dist = math.sqrt((width/2)**2 + (height/2)**2)
dist = math.sqrt((x - center_x)**2 + (y - center_y)**2)
value = start + int((end - start) * (dist / max_dist))
elif direction == "diagonal":
# Diagonal gradient
value = start + int((end - start) * (x + y) / (width + height))
else:
value = 0
# Ensure values are within 0-255 range
value = max(0, min(255, value))
mask.putpixel((x, y), value)
return mask
# Create gradient masks in different directions
horizontal_mask = create_gradient_mask((width, height), direction="horizontal")
horizontal_mask.save("horizontal_mask.png")
vertical_mask = create_gradient_mask((width, height), direction="vertical")
vertical_mask.save("vertical_mask.png")
radial_mask = create_gradient_mask((width, height), direction="radial")
radial_mask.save("radial_mask.png")
diagonal_mask = create_gradient_mask((width, height), direction="diagonal")
diagonal_mask.save("diagonal_mask.png")
# Create a new image for blending
new_image = Image.new("RGB", (width, height))
for y in range(height):
for x in range(width):
# Create a complex color pattern
r = int(255 * (0.5 + 0.5 * math.sin(x / 30)))
g = int(255 * (0.5 + 0.5 * math.cos(y / 20)))
b = int(255 * (0.5 + 0.5 * math.sin((x + y) / 40)))
new_image.putpixel((x, y), (r, g, b))
new_image.save("pattern_image.png")
# Apply different mask effects
def apply_mask_blend(image1, image2, mask):
"""Blend two images using a mask
Args:
image1, image2: Two images to blend
mask: Grayscale mask image
"""
if image1.size != image2.size or image1.size != mask.size:
raise ValueError("All images must have the same size")
width, height = image1.size
result = Image.new(image1.mode, (width, height))
for y in range(height):
for x in range(width):
# Get mask value (0-255)
mask_value = mask.getpixel((x, y))
blend_factor = mask_value / 255.0
# Get pixels of both images
pixel1 = image1.getpixel((x, y))
pixel2 = image2.getpixel((x, y))
# Blend based on pixel depth
if len(pixel1) == 3: # RGB
r1, g1, b1 = pixel1
r2, g2, b2 = pixel2
r = int(r1 * (1 - blend_factor) + r2 * blend_factor)
g = int(g1 * (1 - blend_factor) + g2 * blend_factor)
b = int(b1 * (1 - blend_factor) + b2 * blend_factor)
result.putpixel((x, y), (r, g, b))
elif len(pixel1) == 4: # RGBA
r1, g1, b1, a1 = pixel1
r2, g2, b2, a2 = pixel2
r = int(r1 * (1 - blend_factor) + r2 * blend_factor)
g = int(g1 * (1 - blend_factor) + g2 * blend_factor)
b = int(b1 * (1 - blend_factor) + b2 * blend_factor)
a = int(a1 * (1 - blend_factor) + a2 * blend_factor)
result.putpixel((x, y), (r, g, b, a))
return result
# Apply different mask blends
horizontal_blend = apply_mask_blend(original, new_image, horizontal_mask)
horizontal_blend.save("horizontal_blend.png")
vertical_blend = apply_mask_blend(original, new_image, vertical_mask)
vertical_blend.save("vertical_blend.png")
radial_blend = apply_mask_blend(original, new_image, radial_mask)
radial_blend.save("radial_blend.png")
diagonal_blend = apply_mask_blend(original, new_image, diagonal_mask)
diagonal_blend.save("diagonal_blend.png")
# Create pattern mask
def create_pattern_mask(size, pattern_type="grid", scale=40):
"""Create pattern mask
Args:
size: Mask size (width, height)
pattern_type: Pattern type "grid", "dots", "stripes", "waves"
scale: Scale factor of the pattern
"""
width, height = size
mask = Image.new('L', size, 0)
for y in range(height):
for x in range(width):
value = 0
if pattern_type == "grid":
# Grid Pattern
if x % scale < scale // 2 or y % scale < scale // 2:
value = 255
elif pattern_type == "dots":
# Dot Matrix Pattern
dist_to_center = math.sqrt(((x % scale) - scale/2)**2 +
((y % scale) - scale/2)**2)
if dist_to_center < scale // 4:
value = 255
elif pattern_type == "stripes":
# Stripe Pattern
if (x + y) % scale < scale // 2:
value = 255
elif pattern_type == "waves":
# Wave Pattern
value = int(127.5 + 127.5 * math.sin(x / scale) * math.cos(y / scale))
else:
value = 0
mask.putpixel((x, y), value)
return mask
# Create different pattern masks
grid_mask = create_pattern_mask((width, height), pattern_type="grid", scale=30)
grid_mask.save("grid_mask.png")
dots_mask = create_pattern_mask((width, height), pattern_type="dots", scale=30)
dots_mask.save("dots_mask.png")
stripes_mask = create_pattern_mask((width, height), pattern_type="stripes", scale=20)
stripes_mask.save("stripes_mask.png")
waves_mask = create_pattern_mask((width, height), pattern_type="waves", scale=40)
waves_mask.save("waves_mask.png")
# Apply pattern mask blending
grid_blend = apply_mask_blend(original, new_image, grid_mask)
grid_blend.save("grid_blend.png")
dots_blend = apply_mask_blend(original, new_image, dots_mask)
dots_blend.save("dots_blend.png")
stripes_blend = apply_mask_blend(original, new_image, stripes_mask)
stripes_blend.save("stripes_blend.png")
waves_blend = apply_mask_blend(original, new_image, waves_mask)
waves_blend.save("waves_blend.png")
import math
# Create a demo image
width, height = 600, 400
original = Image.new("RGB", (width, height), color=(30, 30, 30))
# Draw some shapes
draw = ImageDraw.Draw(original)
draw.rectangle([(50, 50), (width-50, height-50)], outline="white", width=2)
draw.ellipse([(100, 100), (width-100, height-100)], outline="blue", width=3)
draw.line([(width//2, 50), (width//2, height-50)], fill="red", width=5)
draw.line([(50, height//2), (width-50, height//2)], fill="green", width=5)
# Save original image
original.save("original_composite.png")
# Create gradient mask
def create_gradient_mask(size, direction="horizontal", start=0, end=255):
"""Create gradient mask
Args:
size: Mask size (width, height)
direction: Gradient direction "horizontal", "vertical", "radial", "diagonal"
start: Starting brightness value (0-255)
end: Ending brightness value (0-255)
"""
width, height = size
mask = Image.new('L', size, 0)
for y in range(height):
for x in range(width):
if direction == "horizontal":
# Horizontal gradient
value = start + int((end - start) * x / width)
elif direction == "vertical":
# Vertical gradient
value = start + int((end - start) * y / height)
elif direction == "radial":
# Radial gradient
center_x, center_y = width // 2, height // 2
max_dist = math.sqrt((width/2)**2 + (height/2)**2)
dist = math.sqrt((x - center_x)**2 + (y - center_y)**2)
value = start + int((end - start) * (dist / max_dist))
elif direction == "diagonal":
# Diagonal gradient
value = start + int((end - start) * (x + y) / (width + height))
else:
value = 0
# Ensure values are within 0-255 range
value = max(0, min(255, value))
mask.putpixel((x, y), value)
return mask
# Create gradient masks in different directions
horizontal_mask = create_gradient_mask((width, height), direction="horizontal")
horizontal_mask.save("horizontal_mask.png")
vertical_mask = create_gradient_mask((width, height), direction="vertical")
vertical_mask.save("vertical_mask.png")
radial_mask = create_gradient_mask((width, height), direction="radial")
radial_mask.save("radial_mask.png")
diagonal_mask = create_gradient_mask((width, height), direction="diagonal")
diagonal_mask.save("diagonal_mask.png")
# Create a new image for blending
new_image = Image.new("RGB", (width, height))
for y in range(height):
for x in range(width):
# Create a complex color pattern
r = int(255 * (0.5 + 0.5 * math.sin(x / 30)))
g = int(255 * (0.5 + 0.5 * math.cos(y / 20)))
b = int(255 * (0.5 + 0.5 * math.sin((x + y) / 40)))
new_image.putpixel((x, y), (r, g, b))
new_image.save("pattern_image.png")
# Apply different mask effects
def apply_mask_blend(image1, image2, mask):
"""Blend two images using a mask
Args:
image1, image2: Two images to blend
mask: Grayscale mask image
"""
if image1.size != image2.size or image1.size != mask.size:
raise ValueError("All images must have the same size")
width, height = image1.size
result = Image.new(image1.mode, (width, height))
for y in range(height):
for x in range(width):
# Get mask value (0-255)
mask_value = mask.getpixel((x, y))
blend_factor = mask_value / 255.0
# Get pixels of both images
pixel1 = image1.getpixel((x, y))
pixel2 = image2.getpixel((x, y))
# Blend based on pixel depth
if len(pixel1) == 3: # RGB
r1, g1, b1 = pixel1
r2, g2, b2 = pixel2
r = int(r1 * (1 - blend_factor) + r2 * blend_factor)
g = int(g1 * (1 - blend_factor) + g2 * blend_factor)
b = int(b1 * (1 - blend_factor) + b2 * blend_factor)
result.putpixel((x, y), (r, g, b))
elif len(pixel1) == 4: # RGBA
r1, g1, b1, a1 = pixel1
r2, g2, b2, a2 = pixel2
r = int(r1 * (1 - blend_factor) + r2 * blend_factor)
g = int(g1 * (1 - blend_factor) + g2 * blend_factor)
b = int(b1 * (1 - blend_factor) + b2 * blend_factor)
a = int(a1 * (1 - blend_factor) + a2 * blend_factor)
result.putpixel((x, y), (r, g, b, a))
return result
# Apply different mask blends
horizontal_blend = apply_mask_blend(original, new_image, horizontal_mask)
horizontal_blend.save("horizontal_blend.png")
vertical_blend = apply_mask_blend(original, new_image, vertical_mask)
vertical_blend.save("vertical_blend.png")
radial_blend = apply_mask_blend(original, new_image, radial_mask)
radial_blend.save("radial_blend.png")
diagonal_blend = apply_mask_blend(original, new_image, diagonal_mask)
diagonal_blend.save("diagonal_blend.png")
# Create pattern mask
def create_pattern_mask(size, pattern_type="grid", scale=40):
"""Create pattern mask
Args:
size: Mask size (width, height)
pattern_type: Pattern type "grid", "dots", "stripes", "waves"
scale: Scale factor of the pattern
"""
width, height = size
mask = Image.new('L', size, 0)
for y in range(height):
for x in range(width):
value = 0
if pattern_type == "grid":
# Grid Pattern
if x % scale < scale // 2 or y % scale < scale // 2:
value = 255
elif pattern_type == "dots":
# Dot Matrix Pattern
dist_to_center = math.sqrt(((x % scale) - scale/2)**2 +
((y % scale) - scale/2)**2)
if dist_to_center < scale // 4:
value = 255
elif pattern_type == "stripes":
# Stripe Pattern
if (x + y) % scale < scale // 2:
value = 255
elif pattern_type == "waves":
# Wave Pattern
value = int(127.5 + 127.5 * math.sin(x / scale) * math.cos(y / scale))
else:
value = 0
mask.putpixel((x, y), value)
return mask
# Create different pattern masks
grid_mask = create_pattern_mask((width, height), pattern_type="grid", scale=30)
grid_mask.save("grid_mask.png")
dots_mask = create_pattern_mask((width, height), pattern_type="dots", scale=30)
dots_mask.save("dots_mask.png")
stripes_mask = create_pattern_mask((width, height), pattern_type="stripes", scale=20)
stripes_mask.save("stripes_mask.png")
waves_mask = create_pattern_mask((width, height), pattern_type="waves", scale=40)
waves_mask.save("waves_mask.png")
# Apply pattern mask blending
grid_blend = apply_mask_blend(original, new_image, grid_mask)
grid_blend.save("grid_blend.png")
dots_blend = apply_mask_blend(original, new_image, dots_mask)
dots_blend.save("dots_blend.png")
stripes_blend = apply_mask_blend(original, new_image, stripes_mask)
stripes_blend.save("stripes_blend.png")
waves_blend = apply_mask_blend(original, new_image, waves_mask)
waves_blend.save("waves_blend.png")