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")

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")

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")

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")

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")

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")

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")

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")

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")

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")

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")

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")

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")
other extensions