Loading...
No commits yet
Not committed History
Blame
test_optimize_figure.py • 9.2 KB
#!/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"])