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