mirror of
https://github.com/esphome/esphome.git
synced 2026-02-08 00:31:58 +00:00
[image] Correctly handle dimensions in physical units (#13209)
This commit is contained in:
committed by
Jonathan Swoboda
parent
0de91e6648
commit
3c63ff5e36
@@ -665,15 +665,10 @@ async def write_image(config, all_frames=False):
|
|||||||
if is_svg_file(path):
|
if is_svg_file(path):
|
||||||
import resvg_py
|
import resvg_py
|
||||||
|
|
||||||
if resize:
|
resize = resize or (None, None)
|
||||||
width, height = resize
|
image_data = resvg_py.svg_to_bytes(
|
||||||
# resvg-py allows rendering by width/height directly
|
svg_path=str(path), width=resize[0], height=resize[1], dpi=100
|
||||||
image_data = resvg_py.svg_to_bytes(
|
)
|
||||||
svg_path=str(path), width=int(width), height=int(height)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Default size
|
|
||||||
image_data = resvg_py.svg_to_bytes(svg_path=str(path))
|
|
||||||
|
|
||||||
# Convert bytes to Pillow Image
|
# Convert bytes to Pillow Image
|
||||||
image = Image.open(io.BytesIO(image_data))
|
image = Image.open(io.BytesIO(image_data))
|
||||||
|
|||||||
5
tests/component_tests/image/config/mm_dimensions.svg
Normal file
5
tests/component_tests/image/config/mm_dimensions.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="10mm" height="10mm" viewBox="0 0 100 100">
|
||||||
|
<rect x="0" y="0" width="100" height="100" fill="#00FF00"/>
|
||||||
|
<circle cx="50" cy="50" r="30" fill="#0000FF"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 248 B |
@@ -5,17 +5,21 @@ from __future__ import annotations
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from esphome import config_validation as cv
|
from esphome import config_validation as cv
|
||||||
from esphome.components.image import (
|
from esphome.components.image import (
|
||||||
|
CONF_INVERT_ALPHA,
|
||||||
|
CONF_OPAQUE,
|
||||||
CONF_TRANSPARENCY,
|
CONF_TRANSPARENCY,
|
||||||
CONFIG_SCHEMA,
|
CONFIG_SCHEMA,
|
||||||
get_all_image_metadata,
|
get_all_image_metadata,
|
||||||
get_image_metadata,
|
get_image_metadata,
|
||||||
|
write_image,
|
||||||
)
|
)
|
||||||
from esphome.const import CONF_ID, CONF_RAW_DATA_ID, CONF_TYPE
|
from esphome.const import CONF_DITHER, CONF_FILE, CONF_ID, CONF_RAW_DATA_ID, CONF_TYPE
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
|
||||||
|
|
||||||
@@ -350,3 +354,52 @@ def test_get_all_image_metadata_empty() -> None:
|
|||||||
"get_all_image_metadata should always return a dict"
|
"get_all_image_metadata should always return a dict"
|
||||||
)
|
)
|
||||||
# Length could be 0 or more depending on what's in CORE at test time
|
# Length could be 0 or more depending on what's in CORE at test time
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_progmem_array():
|
||||||
|
"""Mock progmem_array to avoid needing a proper ID object in tests."""
|
||||||
|
with patch("esphome.components.image.cg.progmem_array") as mock_progmem:
|
||||||
|
mock_progmem.return_value = MagicMock()
|
||||||
|
yield mock_progmem
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_svg_with_mm_dimensions_succeeds(
|
||||||
|
component_config_path: Callable[[str], Path],
|
||||||
|
mock_progmem_array: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test that SVG files with dimensions in mm are successfully processed."""
|
||||||
|
# Create a config for write_image without CONF_RESIZE
|
||||||
|
config = {
|
||||||
|
CONF_FILE: component_config_path("mm_dimensions.svg"),
|
||||||
|
CONF_TYPE: "BINARY",
|
||||||
|
CONF_TRANSPARENCY: CONF_OPAQUE,
|
||||||
|
CONF_DITHER: "NONE",
|
||||||
|
CONF_INVERT_ALPHA: False,
|
||||||
|
CONF_RAW_DATA_ID: "test_raw_data_id",
|
||||||
|
}
|
||||||
|
|
||||||
|
# This should succeed without raising an error
|
||||||
|
result = await write_image(config)
|
||||||
|
|
||||||
|
# Verify that write_image returns the expected tuple
|
||||||
|
assert isinstance(result, tuple), "write_image should return a tuple"
|
||||||
|
assert len(result) == 6, "write_image should return 6 values"
|
||||||
|
|
||||||
|
prog_arr, width, height, image_type, trans_value, frame_count = result
|
||||||
|
|
||||||
|
# Verify the dimensions are positive integers
|
||||||
|
# At 100 DPI, 10mm = ~39 pixels (10mm * 100dpi / 25.4mm_per_inch)
|
||||||
|
assert isinstance(width, int), "Width should be an integer"
|
||||||
|
assert isinstance(height, int), "Height should be an integer"
|
||||||
|
assert width > 0, "Width should be positive"
|
||||||
|
assert height > 0, "Height should be positive"
|
||||||
|
assert frame_count == 1, "Single image should have frame_count of 1"
|
||||||
|
# Verify we got reasonable dimensions from the mm-based SVG
|
||||||
|
assert 30 < width < 50, (
|
||||||
|
f"Width should be around 39 pixels for 10mm at 100dpi, got {width}"
|
||||||
|
)
|
||||||
|
assert 30 < height < 50, (
|
||||||
|
f"Height should be around 39 pixels for 10mm at 100dpi, got {height}"
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user