""" Android media.image 组件 — Image, ImageReader, ImageWriter 等 方法签名完全对齐 Android SDK """ from __future__ import annotations import os import io import math import time import struct import threading from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union from .._core import Stub, AndroidOnlyError from ..java.lang import RuntimeException # ═══════════════════════════════════════════════════════════════ # Image 格式常量 # ═══════════════════════════════════════════════════════════════ FORMAT_NV21 = 17 FORMAT_NV16 = 16 FORMAT_YUY2 = 20 FORMAT_JPEG = 256 FORMAT_RAW_PRIVATE = 36 FORMAT_RAW_SENSOR = 32 FORMAT_RAW10 = 37 FORMAT_RAW12 = 38 FORMAT_RAW_DEPTH = 4098 FORMAT_RAW_DEPTH10 = 4099 FORMAT_YUV_420_888 = 35 FORMAT_YUV_422_888 = 39 FORMAT_YUV_444_888 = 40 FORMAT_FLEX_RGB_888 = 41 FORMAT_FLEX_RGBA_8888 = 42 FORMAT_PRIVATE = 34 FORMAT_UNKNOWN = 0 FORMAT_RGB_565 = 4 FORMAT_RGBA_8888 = 1 FORMAT_RGBX_8888 = 2 FORMAT_BGRA_8888 = 5 FORMAT_Y8 = 538982489 FORMAT_Y16 = 540422489 FORMAT_DEPTH16 = 1144402265 FORMAT_DEPTH_POINT_CLOUD = 257 FORMAT_HEIC = 1212500294 TRANSFORM_NONE = 0 TRANSFORM_FLIP_H = 1 TRANSFORM_FLIP_V = 2 TRANSFORM_ROTATE_90 = 4 TRANSFORM_ROTATE_180 = 3 TRANSFORM_ROTATE_270 = 7 SCALER_ROTATE_90 = 1 SCALER_ROTATE_180 = 2 SCALER_ROTATE_270 = 3 class Image(Stub): """Image — 图像数据容器""" class Plane(Stub): """Image.Plane — 图像平面(YUV 各个平面)""" def __init__(self, buffer: Optional[bytes] = None, row_stride: int = 0, pixel_stride: int = 0): super().__init__() self._buffer = buffer or b"" self._row_stride = row_stride self._pixel_stride = pixel_stride def getBuffer(self) -> Optional[bytes]: return self._buffer def getRowStride(self) -> int: return self._row_stride def getPixelStride(self) -> int: return self._pixel_stride def setBuffer(self, buf: bytes) -> None: self._buffer = buf def setRowStride(self, stride: int) -> None: self._row_stride = stride def setPixelStride(self, stride: int) -> None: self._pixel_stride = stride def toString(self) -> str: return (f"Plane(rowStride={self._row_stride}, " f"pixelStride={self._pixel_stride}, " f"bufferSize={len(self._buffer) if self._buffer else 0})") # Image 格式常量 FORMAT_NV21 = FORMAT_NV21 FORMAT_NV16 = FORMAT_NV16 FORMAT_YUY2 = FORMAT_YUY2 FORMAT_JPEG = FORMAT_JPEG FORMAT_RAW_PRIVATE = FORMAT_RAW_PRIVATE FORMAT_RAW_SENSOR = FORMAT_RAW_SENSOR FORMAT_RAW10 = FORMAT_RAW10 FORMAT_RAW12 = FORMAT_RAW12 FORMAT_RAW_DEPTH = FORMAT_RAW_DEPTH FORMAT_RAW_DEPTH10 = FORMAT_RAW_DEPTH10 FORMAT_YUV_420_888 = FORMAT_YUV_420_888 FORMAT_YUV_422_888 = FORMAT_YUV_422_888 FORMAT_YUV_444_888 = FORMAT_YUV_444_888 FORMAT_FLEX_RGB_888 = FORMAT_FLEX_RGB_888 FORMAT_FLEX_RGBA_8888 = FORMAT_FLEX_RGBA_8888 FORMAT_PRIVATE = FORMAT_PRIVATE FORMAT_UNKNOWN = FORMAT_UNKNOWN FORMAT_RGB_565 = FORMAT_RGB_565 FORMAT_RGBA_8888 = FORMAT_RGBA_8888 FORMAT_RGBX_8888 = FORMAT_RGBX_8888 FORMAT_BGRA_8888 = FORMAT_BGRA_8888 FORMAT_Y8 = FORMAT_Y8 FORMAT_Y16 = FORMAT_Y16 FORMAT_DEPTH16 = FORMAT_DEPTH16 FORMAT_DEPTH_POINT_CLOUD = FORMAT_DEPTH_POINT_CLOUD FORMAT_HEIC = FORMAT_HEIC def __init__(self, width: int = 0, height: int = 0, format: int = FORMAT_UNKNOWN, timestamp: int = 0): super().__init__() self._width = width self._height = height self._format = format self._timestamp = timestamp self._planes: List[Image.Plane] = [] self._crop: Optional[Tuple[int, int, int, int]] = None self._fence: Optional[Any] = None self._hardware_buffer: Optional[Any] = None self._released = False self._owner: Optional[Any] = None self._source: Optional[Any] = None self._lock = threading.Lock() def getWidth(self) -> int: return self._width def getHeight(self) -> int: return self._height def getFormat(self) -> int: return self._format def getTimestamp(self) -> int: return self._timestamp def setTimestamp(self, timestamp: int) -> None: self._timestamp = timestamp def getPlanes(self) -> List[Plane]: return self._planes def getCropRect(self) -> Optional[Tuple[int, int, int, int]]: return self._crop def setCropRect(self, crop: Tuple[int, int, int, int]) -> None: self._crop = crop def getFence(self) -> Optional[Any]: return self._fence def setFence(self, fence: Any) -> None: self._fence = fence def getHardwareBuffer(self) -> Optional[Any]: return self._hardware_buffer def close(self) -> None: with self._lock: if self._released: return self._released = True self._planes.clear() if self._source: self._source._notify_image_closed(self) def isClosed(self) -> bool: return self._released def toString(self) -> str: return (f"Image(width={self._width}, height={self._height}, " f"format={self._format}, planes={len(self._planes)}, " f"timestamp={self._timestamp})") # ═══════════════════════════════════════════════════════════════ # ImageReader # ═══════════════════════════════════════════════════════════════ class ImageReader(Stub): """ImageReader — 图像读取器""" def __init__(self, width: int, height: int, format: int, max_images: int = 1, usage: int = 0): super().__init__() self._width = width self._height = height self._format = format self._max_images = max_images self._usage = usage self._images: List[Image] = [] self._available_images: List[Image] = [] self._on_image_available: Optional[Callable[[ImageReader], None]] = None self._lock = threading.Lock() self._released = False self._surface: Optional[Any] = None @staticmethod def newInstance(width: int, height: int, format: int, max_images: int = 1, usage: int = 0) -> "ImageReader": return ImageReader(width, height, format, max_images, usage) def acquireLatestImage(self) -> Optional[Image]: with self._lock: if not self._available_images: return None latest = self._available_images[-1] self._available_images.clear() return latest def acquireNextImage(self) -> Optional[Image]: with self._lock: if not self._available_images: return None return self._available_images.pop(0) def getWidth(self) -> int: return self._width def getHeight(self) -> int: return self._height def getFormat(self) -> int: return self._format def getMaxImages(self) -> int: return self._max_images def getUsage(self) -> int: return self._usage def getSurface(self) -> Optional[Any]: return self._surface def setOnImageAvailableListener(self, listener: Optional[Callable], handler: Optional[Any] = None) -> None: self._on_image_available = listener def getOnImageAvailableListener(self) -> Optional[Callable]: return self._on_image_available def close(self) -> None: with self._lock: if self._released: return self._released = True for img in self._images: img.close() self._images.clear() self._available_images.clear() def isClosed(self) -> bool: return self._released def _notify_image_closed(self, image: Image) -> None: with self._lock: if image in self._images: self._images.remove(image) def _add_image(self, image: Image) -> None: with self._lock: image._source = self self._images.append(image) self._available_images.append(image) if self._on_image_available: self._on_image_available(self) def toString(self) -> str: return (f"ImageReader(width={self._width}, height={self._height}, " f"format={self._format}, maxImages={self._max_images})") # ═══════════════════════════════════════════════════════════════ # ImageWriter # ═══════════════════════════════════════════════════════════════ class ImageWriter(Stub): """ImageWriter — 图像写入器""" def __init__(self, surface: Any, max_images: int = 1): super().__init__() self._surface = surface self._max_images = max_images self._queued_images: List[Image] = [] self._on_image_released: Optional[Callable] = None self._lock = threading.Lock() self._released = False @staticmethod def newInstance(surface: Any, max_images: int = 1) -> "ImageWriter": return ImageWriter(surface, max_images) def getSurface(self) -> Optional[Any]: return self._surface def getMaxImages(self) -> int: return self._max_images def queueInputImage(self, image: Image) -> None: with self._lock: if self._released: raise RuntimeException("ImageWriter 已关闭") self._queued_images.append(image) if self._on_image_released: self._on_image_released(self) def setOnImageReleasedListener(self, listener: Optional[Callable], handler: Optional[Any] = None) -> None: self._on_image_released = listener def getOnImageReleasedListener(self) -> Optional[Callable]: return self._on_image_released def close(self) -> None: with self._lock: if self._released: return self._released = True self._queued_images.clear() def isClosed(self) -> bool: return self._released def toString(self) -> str: return (f"ImageWriter(maxImages={self._max_images}, " f"queued={len(self._queued_images)})") # ═══════════════════════════════════════════════════════════════ # MediaImage # ═══════════════════════════════════════════════════════════════ class MediaImage(Image): """MediaImage — 媒体图像(扩展 Image)""" def __init__(self, width: int = 0, height: int = 0, format: int = FORMAT_UNKNOWN, timestamp: int = 0, sequence_number: int = 0): super().__init__(width, height, format, timestamp) self._sequence_number = sequence_number self._metadata: Dict[str, Any] = {} self._transform: int = TRANSFORM_NONE def getSequenceNumber(self) -> int: return self._sequence_number def setSequenceNumber(self, seq: int) -> None: self._sequence_number = seq def getTransform(self) -> int: return self._transform def setTransform(self, transform: int) -> None: self._transform = transform def getMetadata(self, key: str) -> Any: return self._metadata.get(key) def setMetadata(self, key: str, value: Any) -> None: self._metadata[key] = value def getAllMetadata(self) -> Dict[str, Any]: return dict(self._metadata) def toString(self) -> str: return (f"MediaImage(width={self.getWidth()}, height={self.getHeight()}, " f"format={self.getFormat()}, seq={self._sequence_number})") # ═══════════════════════════════════════════════════════════════ # SurfaceImage / ImageTransform # ═══════════════════════════════════════════════════════════════ class SurfaceImage(Stub): """SurfaceImage — 表面图像(用于 SurfaceView/TextureView 捕获)""" def __init__(self): super().__init__() self._image: Optional[Image] = None self._transform: int = TRANSFORM_NONE self._timestamp: int = 0 self._lock = threading.Lock() def getImage(self) -> Optional[Image]: with self._lock: return self._image def setImage(self, image: Image) -> None: with self._lock: self._image = image self._timestamp = image.getTimestamp() def getTransform(self) -> int: return self._transform def setTransform(self, transform: int) -> None: self._transform = transform def getTimestamp(self) -> int: return self._timestamp def release(self) -> None: with self._lock: if self._image: self._image.close() self._image = None class ImageTransform(Stub): """ImageTransform — 图像变换""" SCALER_ROTATE_90 = SCALER_ROTATE_90 SCALER_ROTATE_180 = SCALER_ROTATE_180 SCALER_ROTATE_270 = SCALER_ROTATE_270 TRANSFORM_NONE = TRANSFORM_NONE TRANSFORM_FLIP_H = TRANSFORM_FLIP_H TRANSFORM_FLIP_V = TRANSFORM_FLIP_V TRANSFORM_ROTATE_90 = TRANSFORM_ROTATE_90 TRANSFORM_ROTATE_180 = TRANSFORM_ROTATE_180 TRANSFORM_ROTATE_270 = TRANSFORM_ROTATE_270 def __init__(self, rotation: int = 0, flip_h: bool = False, flip_v: bool = False): super().__init__() self._rotation = rotation self._flip_h = flip_h self._flip_v = flip_v def getRotation(self) -> int: return self._rotation def setRotation(self, rotation: int) -> None: self._rotation = rotation def isFlippedHorizontally(self) -> bool: return self._flip_h def setFlipHorizontal(self, flip: bool) -> None: self._flip_h = flip def isFlippedVertically(self) -> bool: return self._flip_v def setFlipVertical(self, flip: bool) -> None: self._flip_v = flip @staticmethod def fromTransformBits(transform: int) -> "ImageTransform": flip_h = bool(transform & TRANSFORM_FLIP_H) flip_v = bool(transform & TRANSFORM_FLIP_V) rotation = 0 if transform & TRANSFORM_ROTATE_90: rotation = 90 elif transform & TRANSFORM_ROTATE_180: rotation = 180 elif transform & TRANSFORM_ROTATE_270: rotation = 270 return ImageTransform(rotation, flip_h, flip_v) def getTransformBits(self) -> int: bits = 0 if self._rotation == 90: bits |= TRANSFORM_ROTATE_90 elif self._rotation == 180: bits |= TRANSFORM_ROTATE_180 elif self._rotation == 270: bits |= TRANSFORM_ROTATE_270 if self._flip_h: bits |= TRANSFORM_FLIP_H if self._flip_v: bits |= TRANSFORM_FLIP_V return bits def toString(self) -> str: return (f"ImageTransform(rotation={self._rotation}, " f"flipH={self._flip_h}, flipV={self._flip_v})") class ImageConfig(Stub): """ImageConfig — 图像配置""" def __init__(self, width: int = 0, height: int = 0, format: int = FORMAT_UNKNOWN, usage: int = 0): super().__init__() self._width = width self._height = height self._format = format self._usage = usage def getWidth(self) -> int: return self._width def setWidth(self, width: int) -> None: self._width = width def getHeight(self) -> int: return self._height def setHeight(self, height: int) -> None: self._height = height def getFormat(self) -> int: return self._format def setFormat(self, fmt: int) -> None: self._format = fmt def getUsage(self) -> int: return self._usage def setUsage(self, usage: int) -> None: self._usage = usage def toString(self) -> str: return (f"ImageConfig(width={self._width}, height={self._height}, " f"format={self._format})") class ImagePool(Stub): """ImagePool — 图像池""" def __init__(self, max_images: int = 4): super().__init__() self._max_images = max_images self._pool: List[Image] = [] self._lock = threading.Lock() def acquireImage(self, width: int, height: int, format: int) -> Optional[Image]: with self._lock: for img in self._pool: if (img.getWidth() == width and img.getHeight() == height and img.getFormat() == format and not img.isClosed()): self._pool.remove(img) return img if len(self._pool) < self._max_images: img = Image(width, height, format) return img return None def releaseImage(self, image: Image) -> None: with self._lock: if len(self._pool) < self._max_images and not image.isClosed(): self._pool.append(image) def close(self) -> None: with self._lock: for img in self._pool: img.close() self._pool.clear() def getAvailableCount(self) -> int: with self._lock: return len(self._pool) def getMaxImages(self) -> int: return self._max_images def toString(self) -> str: return f"ImagePool(max={self._max_images}, available={len(self._pool)})" class ImageUtils(Stub): """ImageUtils — 图像工具类""" @staticmethod def getFormatName(format: int) -> str: names = { FORMAT_NV21: "NV21", FORMAT_NV16: "NV16", FORMAT_YUY2: "YUY2", FORMAT_JPEG: "JPEG", FORMAT_RAW_PRIVATE: "RAW_PRIVATE", FORMAT_RAW_SENSOR: "RAW_SENSOR", FORMAT_RAW10: "RAW10", FORMAT_RAW12: "RAW12", FORMAT_RAW_DEPTH: "RAW_DEPTH", FORMAT_YUV_420_888: "YUV_420_888", FORMAT_FLEX_RGB_888: "FLEX_RGB_888", FORMAT_FLEX_RGBA_8888: "FLEX_RGBA_8888", FORMAT_PRIVATE: "PRIVATE", FORMAT_RGB_565: "RGB_565", FORMAT_RGBA_8888: "RGBA_8888", FORMAT_BGRA_8888: "BGRA_8888", FORMAT_DEPTH16: "DEPTH16", FORMAT_DEPTH_POINT_CLOUD: "DEPTH_POINT_CLOUD", FORMAT_HEIC: "HEIC", } return names.get(format, f"UNKNOWN({format})") @staticmethod def isYuvFormat(format: int) -> bool: return format in (FORMAT_NV21, FORMAT_NV16, FORMAT_YUY2, FORMAT_YUV_420_888, FORMAT_YUV_422_888, FORMAT_YUV_444_888) @staticmethod def getPlaneCount(format: int) -> int: if format in (FORMAT_NV21, FORMAT_NV16): return 2 if format in (FORMAT_YUV_420_888, FORMAT_YUV_422_888, FORMAT_YUV_444_888): return 3 if format in (FORMAT_RGBA_8888, FORMAT_RGBX_8888, FORMAT_BGRA_8888, FORMAT_RGB_565, FORMAT_JPEG): return 1 if format == FORMAT_PRIVATE: return 1 return 1 @staticmethod def getSizeInBytes(width: int, height: int, format: int) -> int: bpp = { FORMAT_RGBA_8888: 4, FORMAT_RGBX_8888: 4, FORMAT_BGRA_8888: 4, FORMAT_RGB_565: 2, FORMAT_YUY2: 2, } if format == FORMAT_JPEG: return width * height * 3 if format == FORMAT_NV21: return width * height * 3 // 2 if format == FORMAT_NV16: return width * height * 2 if format == FORMAT_YUV_420_888: return width * height * 3 // 2 if format in bpp: return width * height * bpp[format] return width * height * 4 @staticmethod def isDepthFormat(format: int) -> bool: return format in (FORMAT_DEPTH16, FORMAT_DEPTH_POINT_CLOUD, FORMAT_RAW_DEPTH, FORMAT_RAW_DEPTH10)