feat: reduce build size — replace Pillow with cv2, refactor build scripts
All checks were successful
Build Release / create-release (push) Successful in 1s
Build Release / build-docker (push) Successful in 42s
Lint & Test / test (push) Successful in 2m50s
Build Release / build-windows (push) Successful in 3m27s
Build Release / build-linux (push) Successful in 1m59s
All checks were successful
Build Release / create-release (push) Successful in 1s
Build Release / build-docker (push) Successful in 42s
Lint & Test / test (push) Successful in 2m50s
Build Release / build-windows (push) Successful in 3m27s
Build Release / build-linux (push) Successful in 1m59s
- Create utils/image_codec.py with cv2-based image helpers - Replace PIL usage across all routes, filters, and engines with cv2 - Move Pillow from core deps to [tray] optional in pyproject.toml - Extract shared build logic into build-common.sh (detect_version, cleanup, etc.) - Strip unused NumPy/PIL/zeroconf/debug files in build scripts
This commit is contained in:
91
server/src/wled_controller/utils/image_codec.py
Normal file
91
server/src/wled_controller/utils/image_codec.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""Image encoding/decoding/resizing utilities using OpenCV.
|
||||
|
||||
Replaces PIL/Pillow for JPEG encoding, image loading, and resizing operations.
|
||||
All functions work with numpy RGB arrays (H, W, 3) uint8.
|
||||
"""
|
||||
|
||||
import base64
|
||||
from pathlib import Path
|
||||
from typing import Tuple, Union
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
|
||||
def encode_jpeg(image: np.ndarray, quality: int = 85) -> bytes:
|
||||
"""Encode an RGB numpy array as JPEG bytes."""
|
||||
bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
|
||||
ok, buf = cv2.imencode(".jpg", bgr, [cv2.IMWRITE_JPEG_QUALITY, quality])
|
||||
if not ok:
|
||||
raise RuntimeError("JPEG encoding failed")
|
||||
return buf.tobytes()
|
||||
|
||||
|
||||
def encode_jpeg_data_uri(image: np.ndarray, quality: int = 85) -> str:
|
||||
"""Encode an RGB numpy array as a JPEG base64 data URI."""
|
||||
raw = encode_jpeg(image, quality)
|
||||
b64 = base64.b64encode(raw).decode("utf-8")
|
||||
return f"data:image/jpeg;base64,{b64}"
|
||||
|
||||
|
||||
def resize_image(image: np.ndarray, width: int, height: int) -> np.ndarray:
|
||||
"""Resize an image to exact dimensions.
|
||||
|
||||
Uses INTER_AREA for downscaling (better quality, faster) and
|
||||
INTER_LANCZOS4 for upscaling.
|
||||
"""
|
||||
h, w = image.shape[:2]
|
||||
shrinking = (width * height) < (w * h)
|
||||
interp = cv2.INTER_AREA if shrinking else cv2.INTER_LANCZOS4
|
||||
return cv2.resize(image, (width, height), interpolation=interp)
|
||||
|
||||
|
||||
def thumbnail(image: np.ndarray, max_width: int) -> np.ndarray:
|
||||
"""Create a thumbnail that fits within max_width, preserving aspect ratio.
|
||||
|
||||
Uses INTER_AREA (optimal for downscaling).
|
||||
"""
|
||||
h, w = image.shape[:2]
|
||||
if w <= max_width:
|
||||
return image.copy()
|
||||
scale = max_width / w
|
||||
new_w = max_width
|
||||
new_h = max(1, int(h * scale))
|
||||
return cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_AREA)
|
||||
|
||||
|
||||
def resize_down(image: np.ndarray, max_width: int) -> np.ndarray:
|
||||
"""Downscale if wider than max_width; return as-is otherwise.
|
||||
|
||||
Uses INTER_AREA (optimal for downscaling).
|
||||
"""
|
||||
h, w = image.shape[:2]
|
||||
if w <= max_width:
|
||||
return image
|
||||
scale = max_width / w
|
||||
new_w = max_width
|
||||
new_h = max(1, int(h * scale))
|
||||
return cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_AREA)
|
||||
|
||||
|
||||
def load_image_file(path: Union[str, Path]) -> np.ndarray:
|
||||
"""Load an image file and return as RGB numpy array."""
|
||||
path = str(path)
|
||||
bgr = cv2.imread(path, cv2.IMREAD_COLOR)
|
||||
if bgr is None:
|
||||
raise FileNotFoundError(f"Cannot load image: {path}")
|
||||
return cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
|
||||
|
||||
|
||||
def load_image_bytes(data: bytes) -> np.ndarray:
|
||||
"""Decode image bytes (JPEG, PNG, etc.) and return as RGB numpy array."""
|
||||
arr = np.frombuffer(data, dtype=np.uint8)
|
||||
bgr = cv2.imdecode(arr, cv2.IMREAD_COLOR)
|
||||
if bgr is None:
|
||||
raise ValueError("Cannot decode image data")
|
||||
return cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
|
||||
|
||||
|
||||
def image_size(image: np.ndarray) -> Tuple[int, int]:
|
||||
"""Return (width, height) of an image array."""
|
||||
return image.shape[1], image.shape[0]
|
||||
Reference in New Issue
Block a user