1
0
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:
Clyde Stubbs
2026-01-15 14:27:26 +11:00
committed by Jonathan Swoboda
parent 0de91e6648
commit 3c63ff5e36
3 changed files with 63 additions and 10 deletions

View File

@@ -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))

View 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

View File

@@ -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}"
)