pyPhotoAlbum/tests/test_image_utils_styling.py
Duncan Tourolle 54cc78783a
All checks were successful
Python CI / test (push) Successful in 1m44s
Lint / lint (push) Successful in 1m29s
Tests / test (3.11) (push) Successful in 1m49s
Tests / test (3.12) (push) Successful in 1m52s
Tests / test (3.13) (push) Successful in 1m45s
Tests / test (3.14) (push) Successful in 1m28s
Added styling
Improved pdf generation speed
2026-01-01 13:37:14 +01:00

335 lines
12 KiB
Python

"""
Unit tests for image_utils styling functions
"""
import pytest
from PIL import Image
from pyPhotoAlbum.image_utils import (
apply_rounded_corners,
apply_drop_shadow,
create_border_image,
)
class TestApplyRoundedCorners:
"""Tests for apply_rounded_corners function"""
def test_zero_radius_returns_same_image(self):
"""Test that 0% radius returns unchanged image"""
img = Image.new("RGB", (100, 100), color="red")
result = apply_rounded_corners(img, 0.0)
assert result.size == img.size
def test_negative_radius_returns_same_image(self):
"""Test that negative radius returns unchanged image"""
img = Image.new("RGB", (100, 100), color="blue")
result = apply_rounded_corners(img, -10.0)
assert result.size == img.size
def test_returns_rgba_image(self):
"""Test that result is RGBA mode"""
img = Image.new("RGB", (100, 100), color="green")
result = apply_rounded_corners(img, 10.0)
assert result.mode == "RGBA"
def test_preserves_size(self):
"""Test that image size is preserved"""
img = Image.new("RGBA", (200, 150), color="yellow")
result = apply_rounded_corners(img, 15.0)
assert result.size == (200, 150)
def test_corners_are_transparent(self):
"""Test that corners become transparent"""
img = Image.new("RGB", (100, 100), color="red")
result = apply_rounded_corners(img, 25.0)
# Top-left corner should be transparent
pixel = result.getpixel((0, 0))
assert pixel[3] == 0, "Top-left corner should be transparent"
# Top-right corner should be transparent
pixel = result.getpixel((99, 0))
assert pixel[3] == 0, "Top-right corner should be transparent"
# Bottom-left corner should be transparent
pixel = result.getpixel((0, 99))
assert pixel[3] == 0, "Bottom-left corner should be transparent"
# Bottom-right corner should be transparent
pixel = result.getpixel((99, 99))
assert pixel[3] == 0, "Bottom-right corner should be transparent"
def test_center_is_opaque(self):
"""Test that center remains opaque"""
img = Image.new("RGB", (100, 100), color="blue")
result = apply_rounded_corners(img, 10.0)
# Center pixel should be fully opaque
pixel = result.getpixel((50, 50))
assert pixel[3] == 255, "Center should be opaque"
def test_50_percent_radius_creates_ellipse(self):
"""Test that 50% radius creates elliptical result"""
img = Image.new("RGB", (100, 100), color="purple")
result = apply_rounded_corners(img, 50.0)
# Corner should be transparent
assert result.getpixel((0, 0))[3] == 0
def test_radius_clamped_to_50(self):
"""Test that radius > 50 is clamped"""
img = Image.new("RGB", (100, 100), color="orange")
result = apply_rounded_corners(img, 100.0) # Should be clamped to 50
# Should still work and not crash
assert result.size == (100, 100)
assert result.mode == "RGBA"
def test_preserves_existing_rgba(self):
"""Test that existing RGBA image alpha is preserved"""
img = Image.new("RGBA", (100, 100), (255, 0, 0, 128)) # Semi-transparent red
result = apply_rounded_corners(img, 10.0)
# Center should still be semi-transparent (original alpha combined with mask)
center_pixel = result.getpixel((50, 50))
assert center_pixel[3] == 128 # Original alpha preserved
class TestApplyDropShadow:
"""Tests for apply_drop_shadow function"""
def test_returns_rgba_image(self):
"""Test that result is RGBA mode"""
img = Image.new("RGB", (50, 50), color="red")
result = apply_drop_shadow(img)
assert result.mode == "RGBA"
def test_expand_increases_size(self):
"""Test that expand=True increases canvas size"""
img = Image.new("RGBA", (50, 50), color="blue")
result = apply_drop_shadow(img, offset=(5, 5), blur_radius=3, expand=True)
assert result.width > 50
assert result.height > 50
def test_no_expand_preserves_size(self):
"""Test that expand=False preserves size"""
img = Image.new("RGBA", (50, 50), color="green")
result = apply_drop_shadow(img, offset=(2, 2), blur_radius=3, expand=False)
assert result.size == (50, 50)
def test_shadow_has_correct_color(self):
"""Test that shadow uses specified color"""
# Create image with transparent background and opaque center
img = Image.new("RGBA", (20, 20), (0, 0, 0, 0))
img.paste((255, 0, 0, 255), (5, 5, 15, 15))
result = apply_drop_shadow(
img, offset=(10, 10), blur_radius=0, shadow_color=(0, 255, 0, 255), expand=True
)
# Shadow should be visible in the offset area
# The shadow color should be green
assert result.mode == "RGBA"
def test_zero_blur_radius(self):
"""Test that blur_radius=0 works"""
img = Image.new("RGBA", (30, 30), (255, 0, 0, 255))
result = apply_drop_shadow(img, blur_radius=0, expand=True)
assert result.mode == "RGBA"
assert result.width >= 30
assert result.height >= 30
def test_large_offset(self):
"""Test shadow with large offset"""
img = Image.new("RGBA", (50, 50), (0, 0, 255, 255))
result = apply_drop_shadow(img, offset=(20, 20), blur_radius=5, expand=True)
assert result.width > 50 + 20
assert result.height > 50 + 20
def test_negative_offset(self):
"""Test shadow with negative offset (shadow above/left of image)"""
img = Image.new("RGBA", (50, 50), (255, 255, 0, 255))
result = apply_drop_shadow(img, offset=(-10, -10), blur_radius=2, expand=True)
assert result.width > 50
assert result.height > 50
class TestCreateBorderImage:
"""Tests for create_border_image function"""
def test_returns_rgba_image(self):
"""Test that result is RGBA mode"""
result = create_border_image(100, 100, 5)
assert result.mode == "RGBA"
def test_correct_size(self):
"""Test that result has correct size"""
result = create_border_image(200, 150, 10)
assert result.size == (200, 150)
def test_zero_border_returns_transparent(self):
"""Test that 0 border width returns fully transparent image"""
result = create_border_image(100, 100, 0)
assert result.mode == "RGBA"
# All pixels should be transparent
for x in range(100):
for y in range(100):
assert result.getpixel((x, y))[3] == 0
def test_border_color_applied(self):
"""Test that border color is applied correctly"""
result = create_border_image(50, 50, 5, border_color=(255, 0, 0))
# Edge pixel should be red
edge_pixel = result.getpixel((0, 25)) # Left edge, middle
assert edge_pixel[:3] == (255, 0, 0)
assert edge_pixel[3] == 255 # Opaque
def test_center_is_transparent(self):
"""Test that center is transparent"""
result = create_border_image(100, 100, 10)
# Center pixel should be transparent
center_pixel = result.getpixel((50, 50))
assert center_pixel[3] == 0
def test_border_surrounds_image(self):
"""Test that border covers all edges"""
result = create_border_image(50, 50, 5, border_color=(0, 255, 0))
# Top edge
assert result.getpixel((25, 0))[3] == 255 # Opaque
# Bottom edge
assert result.getpixel((25, 49))[3] == 255
# Left edge
assert result.getpixel((0, 25))[3] == 255
# Right edge
assert result.getpixel((49, 25))[3] == 255
def test_with_corner_radius(self):
"""Test border with rounded corners"""
result = create_border_image(100, 100, 10, border_color=(0, 0, 255), corner_radius=20)
assert result.mode == "RGBA"
assert result.size == (100, 100)
def test_corner_radius_affects_transparency(self):
"""Test that corner radius creates rounded border"""
result = create_border_image(100, 100, 10, corner_radius=25)
# Outer corner should be transparent (outside the rounded border)
outer_corner = result.getpixel((0, 0))
assert outer_corner[3] == 0
def test_large_border_width(self):
"""Test with large border width"""
result = create_border_image(100, 100, 45) # Very thick border
assert result.mode == "RGBA"
# Center should still be transparent (just a small area)
center = result.getpixel((50, 50))
assert center[3] == 0
class TestStylingIntegration:
"""Integration tests combining multiple styling functions"""
def test_rounded_corners_then_shadow(self):
"""Test applying rounded corners then shadow"""
img = Image.new("RGB", (100, 100), color="red")
rounded = apply_rounded_corners(img, 15.0)
result = apply_drop_shadow(rounded, offset=(5, 5), blur_radius=3, expand=True)
assert result.mode == "RGBA"
assert result.width > 100
assert result.height > 100
def test_preserve_quality_through_chain(self):
"""Test that chaining operations preserves image quality"""
# Create a simple pattern
img = Image.new("RGB", (80, 80), color="blue")
img.paste((255, 0, 0), (20, 20, 60, 60)) # Red square in center
# Apply styling chain
result = apply_rounded_corners(img, 10.0)
result = apply_drop_shadow(result, expand=True)
assert result.mode == "RGBA"
def test_small_image_styling(self):
"""Test styling on small images"""
img = Image.new("RGB", (10, 10), color="green")
rounded = apply_rounded_corners(img, 20.0)
shadow = apply_drop_shadow(rounded, offset=(1, 1), blur_radius=1, expand=True)
assert shadow.mode == "RGBA"
assert shadow.width >= 10
assert shadow.height >= 10
def test_large_image_styling(self):
"""Test styling on larger images"""
img = Image.new("RGB", (1000, 800), color="purple")
rounded = apply_rounded_corners(img, 5.0)
assert rounded.mode == "RGBA"
assert rounded.size == (1000, 800)
# Corners should be transparent
assert rounded.getpixel((0, 0))[3] == 0
assert rounded.getpixel((999, 0))[3] == 0
def test_non_square_image_styling(self):
"""Test styling on non-square images"""
img = Image.new("RGB", (200, 50), color="orange")
rounded = apply_rounded_corners(img, 30.0)
assert rounded.mode == "RGBA"
assert rounded.size == (200, 50)
# Radius should be based on shorter side (50)
# 30% of 50 = 15 pixels radius
assert rounded.getpixel((0, 0))[3] == 0
class TestEdgeCases:
"""Tests for edge cases and boundary conditions"""
def test_1x1_image_rounded_corners(self):
"""Test rounded corners on 1x1 image"""
img = Image.new("RGB", (1, 1), color="white")
result = apply_rounded_corners(img, 50.0)
assert result.size == (1, 1)
def test_very_small_radius(self):
"""Test with very small radius percentage"""
img = Image.new("RGB", (100, 100), color="cyan")
result = apply_rounded_corners(img, 0.1)
assert result.mode == "RGBA"
def test_shadow_with_transparent_image(self):
"""Test shadow on fully transparent image"""
img = Image.new("RGBA", (50, 50), (0, 0, 0, 0))
result = apply_drop_shadow(img, expand=True)
assert result.mode == "RGBA"
def test_border_on_small_image(self):
"""Test border on small image (larger than border)"""
# Use a 10x10 image with 2px border (edge case with very small image)
result = create_border_image(10, 10, 2)
assert result.size == (10, 10)
assert result.mode == "RGBA"
def test_styling_preserves_pixel_data(self):
"""Test that styling preserves underlying pixel data"""
# Create image with known pattern
img = Image.new("RGB", (50, 50), (0, 0, 0))
img.putpixel((25, 25), (255, 255, 255))
result = apply_rounded_corners(img, 5.0)
# Center white pixel should still be white (with alpha)
pixel = result.getpixel((25, 25))
assert pixel[0] == 255
assert pixel[1] == 255
assert pixel[2] == 255
assert pixel[3] == 255 # Opaque in center