Files
Android-pyApi/android_pyapi/android/media_image.py
T
shuiyu1123 a51854455b feat: 大规模扩充至212个模块/175K行代码 — 覆盖android/java/javax/org/dalvik全包
- android/ 167个模块 (app/content/os/view/widget/graphics/media/hardware/net/telephony/provider等)
- java/ 35个模块 (lang/io/util/net/nio/security/crypto/sql/beans/rmi/awt等)
- javax/ 4个模块 (net/crypto/security/sql)
- org/ 5个模块 (json/w3c_dom/xml_sax/xml_pull/apache_http)
- dalvik/ 1个模块 (system)
- ~3053个类/接口,方法签名完全对齐Android SDK
- 桌面跨平台运行 (Windows/macOS/Linux)
- 修复所有导入错误和Python关键字冲突
2026-06-24 14:15:56 +00:00

649 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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)