#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Test file for: optimize_figure.py
import os
import sys
from pathlib import Path
import pytest
# Add scripts/python to path for imports
ROOT_DIR = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(ROOT_DIR / "scripts" / "python"))
# Check for PIL dependency
try:
from optimize_figure import crop_whitespace, enhance_image_quality, optimize_figure
from PIL import Image
HAS_PIL = True
except ImportError:
HAS_PIL = False
# compute_optimal_size is pure logic - try to import separately
try:
from optimize_figure import compute_optimal_size
HAS_COMPUTE = True
except ImportError:
HAS_COMPUTE = False
# Tests for compute_optimal_size (pure logic - no dependencies)
@pytest.mark.skipif(not HAS_COMPUTE, reason="compute_optimal_size not available")
def test_compute_optimal_size_within_limits():
"""Test that image already within limits returns same dimensions."""
# Use larger dimensions that are above 80% of publication_width_px threshold
width, height = 2000, 1600
max_width, max_height = 3000, 3000
new_w, new_h = compute_optimal_size(width, height, max_width, max_height)
# Should not change since within limits and good resolution
assert new_w == 2000
assert new_h == 1600
@pytest.mark.skipif(not HAS_COMPUTE, reason="compute_optimal_size not available")
def test_compute_optimal_size_width_limit():
"""Test that wide image gets scaled to fit max_width."""
width, height = 3000, 1000
max_width, max_height = 2000, 2000
new_w, new_h = compute_optimal_size(width, height, max_width, max_height)
assert new_w == 2000 # Width limited
assert new_h < 1000 # Height scaled proportionally
@pytest.mark.skipif(not HAS_COMPUTE, reason="compute_optimal_size not available")
def test_compute_optimal_size_height_limit():
"""Test that tall image gets scaled to fit max_height."""
width, height = 1000, 3000
max_width, max_height = 2000, 2000
new_w, new_h = compute_optimal_size(width, height, max_width, max_height)
assert new_h == 2000 # Height limited
assert new_w < 1000 # Width scaled proportionally
@pytest.mark.skipif(not HAS_COMPUTE, reason="compute_optimal_size not available")
def test_compute_optimal_size_even_dimensions():
"""Test that result has even width and height."""
width, height = 2501, 1501
max_width, max_height = 2000, 2000
new_w, new_h = compute_optimal_size(width, height, max_width, max_height)
assert new_w % 2 == 0 # Even width
assert new_h % 2 == 0 # Even height
@pytest.mark.skipif(not HAS_COMPUTE, reason="compute_optimal_size not available")
def test_compute_optimal_size_aspect_ratio_preserved():
"""Test that aspect ratio is approximately preserved."""
width, height = 1600, 1000
max_width, max_height = 800, 800
new_w, new_h = compute_optimal_size(width, height, max_width, max_height)
original_ratio = width / height
new_ratio = new_w / new_h
# Allow small difference due to rounding
assert abs(original_ratio - new_ratio) < 0.01
# Tests for crop_whitespace (requires PIL)
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_crop_whitespace_removes_padding():
"""Test that crop_whitespace removes white borders."""
# Create image with white border
img = Image.new("RGB", (200, 200), color="white")
# Add colored content in center
pixels = img.load()
for i in range(50, 150):
for j in range(50, 150):
pixels[j, i] = (255, 0, 0) # Red square
cropped = crop_whitespace(img, padding=5)
# Should be smaller than original
assert cropped.width < 200
assert cropped.height < 200
# Should be around 100x100 + padding
assert 100 <= cropped.width <= 120
assert 100 <= cropped.height <= 120
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_crop_whitespace_no_content_returns_original():
"""Test that all-white image returns original."""
img = Image.new("RGB", (100, 100), color="white")
cropped = crop_whitespace(img)
assert cropped.size == img.size
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_crop_whitespace_padding_parameter():
"""Test that padding parameter is respected."""
img = Image.new("RGB", (200, 200), color="white")
pixels = img.load()
for i in range(80, 120):
for j in range(80, 120):
pixels[j, i] = (0, 0, 255) # Blue square
cropped_small = crop_whitespace(img, padding=5)
cropped_large = crop_whitespace(img, padding=20)
# Larger padding should result in larger image
assert cropped_large.width > cropped_small.width
assert cropped_large.height > cropped_small.height
# Tests for enhance_image_quality (requires PIL)
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_enhance_image_quality_returns_rgb():
"""Test that RGBA image is converted to RGB."""
img = Image.new("RGBA", (100, 100), color=(255, 0, 0, 128))
enhanced = enhance_image_quality(img)
assert enhanced.mode == "RGB"
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_enhance_image_quality_preserves_dimensions():
"""Test that enhancement preserves image dimensions."""
img = Image.new("RGB", (150, 200), color="blue")
enhanced = enhance_image_quality(img)
assert enhanced.size == img.size
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_enhance_image_quality_returns_image():
"""Test that enhancement returns a PIL Image."""
img = Image.new("RGB", (100, 100), color="green")
enhanced = enhance_image_quality(img)
assert isinstance(enhanced, Image.Image)
# Tests for optimize_figure (full pipeline - requires PIL)
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_optimize_figure_creates_output(tmp_path):
"""Test that optimize_figure creates output file."""
# Create test image
img = Image.new("RGB", (300, 200), color="red")
input_path = tmp_path / "input.jpg"
output_path = tmp_path / "output.jpg"
img.save(str(input_path))
result = optimize_figure(str(input_path), str(output_path), no_crop=True)
assert result == str(output_path)
assert output_path.exists()
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_optimize_figure_missing_input_returns_none(tmp_path):
"""Test that optimize_figure returns None for missing file."""
output_path = tmp_path / "output.jpg"
result = optimize_figure("/nonexistent/input.jpg", str(output_path))
assert result is None
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_optimize_figure_auto_output_path(tmp_path):
"""Test that optimize_figure generates output path automatically."""
img = Image.new("RGB", (200, 200), color="blue")
input_path = tmp_path / "test.jpg"
img.save(str(input_path))
result = optimize_figure(str(input_path), output_path=None)
# Should create test_optimized.jpg
expected_path = tmp_path / "test_optimized.jpg"
assert result == str(expected_path)
assert expected_path.exists()
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_optimize_figure_with_crop(tmp_path):
"""Test that optimize_figure crops whitespace when enabled."""
# Create image with white border
img = Image.new("RGB", (400, 400), color="white")
pixels = img.load()
for i in range(100, 300):
for j in range(100, 300):
pixels[j, i] = (0, 255, 0) # Green square
input_path = tmp_path / "bordered.jpg"
output_path = tmp_path / "cropped.jpg"
img.save(str(input_path))
result = optimize_figure(
str(input_path),
str(output_path),
no_crop=False,
max_width=5000,
max_height=5000,
)
assert result is not None
assert output_path.exists()
# The function successfully runs with cropping enabled
# Actual dimensions depend on scaling logic, so just verify file was created
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_optimize_figure_respects_max_dimensions(tmp_path):
"""Test that optimize_figure respects max width/height."""
# Create large image
img = Image.new("RGB", (3000, 2000), color="yellow")
input_path = tmp_path / "large.jpg"
output_path = tmp_path / "resized.jpg"
img.save(str(input_path))
result = optimize_figure(
str(input_path), str(output_path), max_width=1000, max_height=1000, no_crop=True
)
result_img = Image.open(result)
assert result_img.width <= 1000
assert result_img.height <= 1000
@pytest.mark.skipif(not HAS_PIL, reason="PIL (Pillow) not available")
def test_optimize_figure_different_formats(tmp_path):
"""Test that optimize_figure handles different image formats."""
# Test with PNG
img = Image.new("RGB", (200, 200), color="purple")
input_path = tmp_path / "test.png"
output_path = tmp_path / "test_out.png"
img.save(str(input_path))
result = optimize_figure(str(input_path), str(output_path))
assert result is not None
assert output_path.exists()
if __name__ == "__main__":
import pytest
pytest.main([os.path.abspath(__file__), "-v"])