Image
Image 是 tgfx 中代表二维像素数组的最高层级抽象,也是绘图流水线中最核心的内容载体。
在现代图形引擎中,像素数据的流动极其频繁。Image 的设计动机在于屏蔽底层存储的多样性(无论像素是在磁盘、CPU 内存还是 GPU 显存中),并提供高性能的并发能力。
- 不可变性 (Immutable):一旦创建,其尺寸、内容、色彩空间均不可更改。这种设计消除了状态竞争,使得同一个
Image对象可以被多个Canvas同时绘制。 - 线程安全 (Thread-safe):由于不可变特性,Image 可以在任何线程被传递和访问,无需外部加锁。
- 延迟解码 (Lazy Decoding):Image 在创建时通常只是一个"元数据记录",真正的解码动作(IO 读取、压缩包解压)会推迟到该图像首次被提交给 GPU 绘制或显式调用
makeDecoded()时。
1. 图像创建方式
根据应用场景的性能和内存需求,你可以从以下路径创建 Image:
1.1 从文件路径 / 编码数据 (推荐用于 UI)
MakeFromFile(path):建立一个文件引用。MakeFromEncoded(data):包装内存中的 PNG/JPG/WebP 字节流。- 协作说明:这些 API 调用是轻量的,因为它们不会立即触发像素解码。
- 性能注意:若在渲染帧循环中直接调用此方法并立即绘制,IO 和解码开销可能会导致首帧掉帧。建议在预加载阶段提前准备。
代码示例:
// Load from file path (lazy decoding)
auto image = Image::MakeFromFile("photo.png");
// Load from encoded memory data
auto data = Data::MakeWithCopy(buffer, size);
auto image2 = Image::MakeFromEncoded(data);
1.2 从 Bitmap / NativeImage (内存共享)
MakeFrom(Bitmap):包装 CPU 端像素数据。MakeFrom(NativeImage):包装系统原生对象(AndroidBitmap或 AppleCGImage)。- 内存逻辑:Image 会共享底层的像素内存。若原始
Bitmap随后被修改,Image 会通过 写时拷贝 (COW) 机制自动克隆一份内存副本,以确保持有内容的一致性。
代码示例:
// Wrap a Bitmap (COW shared)
Bitmap bitmap(200, 200);
// ... fill pixels ...
auto image = Image::MakeFrom(bitmap);
1.3 从原始像素数据
MakeFrom(ImageInfo, Data):直接从原始像素字节数据创建 Image。调用者需要确保像素数据在 Image 的整个生命周期内保持不变。- 应用场景:适用于已经在内存中准备好像素数据的场景,例如从自定义解码器或算法生成的像素。
代码示例:
auto info = ImageInfo::Make(256, 256, ColorType::RGBA_8888);
auto pixels = Data::MakeWithCopy(rawPixelBuffer, info.byteSize());
auto image = Image::MakeFrom(info, pixels);
1.4 从 HardwareBuffer (高性能零拷贝)
MakeFrom(HardwareBuffer, ColorSpace):从单平面硬件缓冲区创建(RGB 格式)。MakeFrom(HardwareBuffer, YUVColorSpace):从多平面硬件缓冲区创建(YUV 格式)。- 动机:这是处理视频帧和相机预览的最高效方式,数据直接在显存中流动,规避了昂贵的 CPU-GPU 搬运成本。
- 平台映射:Android
AHardwareBuffer、iOS/macOSCVPixelBuffer、WindowsID3D11Texture2D。
1.5 从 ImageGenerator (自定义数据源)
MakeFrom(Generator):通过自定义的ImageGenerator子类控制每一帧的像素生成逻辑。- 延迟特性:与文件源类似,像素生成会推迟到首次绘制时。适合包装自定义解码器或过程化生成器。
1.6 从 BackendTexture (GPU 纹理包装)
MakeFrom(Context*, BackendTexture, origin, colorSpace):直接包装现有的 GPU 纹理 ID。调用者必须确保原始纹理在 Image 生命周期内保持有效,Image 不会管理纹理的生命周期。MakeAdopted(Context*, BackendTexture, origin, colorSpace):同样包装 GPU 纹理,但 Image 接管所有权——当 Image 销毁时,底层纹理会被自动释放。- origin 参数:
ImageOrigin::TopLeft(默认)或ImageOrigin::BottomLeft,用于指示纹理坐标原点。
代码示例:
// Wrap an existing GPU texture (caller manages lifetime)
auto image = Image::MakeFrom(context, backendTexture, ImageOrigin::TopLeft);
// Adopt an existing GPU texture (Image takes ownership)
auto image2 = Image::MakeAdopted(context, backendTexture);
1.7 从 Picture (矢量绘制命令)
MakeFrom(Picture, width, height, matrix, colorSpace):将已录制的绘制命令流封装为图像源。- 矢量特性:PictureImage 不会立即栅格化,只在绘制时按需渲染所需的区域到临时离屏图像。支持无限缩放而不损失精度。
- 缓存建议:如果需要缓存全尺寸内容,可对返回的 Image 调用
makeRasterized()。
代码示例:
// Record drawing commands
PictureRecorder recorder;
auto* recordCanvas = recorder.beginRecording();
recordCanvas->drawCircle(200, 150, 100, paint);
auto picture = recorder.finishRecordingAsPicture();
// Create Image from Picture
auto image = Image::MakeFrom(picture, 400, 300);
1.8 从 ImageBuffer
MakeFrom(ImageBuffer):包装一个已准备好的ImageBuffer对象。通常用于从ImageReader获取的视频帧或从Bitmap::makeBuffer()生成的缓冲区。
2. 协作流程:从加载到渲染
tgfx 的图像处理遵循从资源引用到硬件缓存的清晰链路:
数据源 (File/Encoded) → Image (属性识别) → makeDecoded (预热解码) → Canvas (绘制合成)
3. 图像变换 (make...)
变换操作不会修改原图,而是返回一个新的"逻辑视图"。
3.1 内容裁剪与方向校正
makeSubset(Rect):逻辑裁剪,返回指定区域的视图。- 内存注意:子集图像仍会维持对原图全部数据的引用。若需从超大图中提取极小块并释放原图内存,建议配合
makeRasterized()使用。
- 内存注意:子集图像仍会维持对原图全部数据的引用。若需从超大图中提取极小块并释放原图内存,建议配合
makeOriented(Orientation):基于 EXIF 标签自动校正图像的旋转与镜像方向。如果方向为TopLeft(默认),则返回原 Image。
3.2 解码与 GPU 优化
makeDecoded(Context*):核心优化 API。显式触发解码并将纹理上传至特定 GPU 上下文,同时立即调度一个异步解码任务,不会阻塞调用线程。- 应用场景:在后台线程预热图像,确保在 UI 线程绘制时能够立即可见。
- 智能跳过:如果 Image 已完全解码(
isFullyDecoded()为 true)或已有对应纹理缓存,则直接返回原 Image。
makeMipmapped(enabled):启用或禁用多级纹理(Mipmap)。开启后,当图像缩小到极小尺寸时能有效抑制闪烁和视觉噪声。makeTextureImage(Context*):获取一个由 GPU 纹理支撑的 Image。如果已存在纹理缓存则直接包装返回;否则立即创建纹理。返回后可安全释放原 Image 以减少 CPU 内存占用。
3.3 缩放与栅格化
makeScaled(newWidth, newHeight, sampling):返回指定尺寸的缩放视图。新 Image 保留原图的 Mipmap 和栅格化状态。- 如果原 Image 是已栅格化的,缩放后的 Image 也会被栅格化并缓存在新尺寸上。
- 如果原 Image 未栅格化,缩放后的 Image 不会被缓存,每次绘制只渲染所需部分。需要缓存时请调用
makeRasterized()。
makeRasterized():将逻辑视图(如子集、缩放、Picture 源)栅格化为独立的 GPU 缓存。栅格化后的 Image 拥有独立的纹理资源,不再依赖原图数据。- 已栅格化的 Image(如直接由 ImageBuffer、ImageGenerator 或 GPU 纹理创建的)调用此方法会直接返回自身。
3.4 滤镜与特殊布局
makeWithFilter(filter, offset, clipRect):应用ImageFilter(如模糊、色彩矩阵等)并返回结果图像。滤镜有可能改变图像边界,offset参数用于记录平移偏移。makeRGBAAA(displayWidth, displayHeight, alphaStartX, alphaStartY):特殊布局合成。将同一张图的不同象限合并为带 Alpha 通道的图像,常用于透明视频动效。
4. 绘制方式与采样控制
4.1 绘制 API
drawImage(image, x, y):最基础的位移绘制。将图像左上角定位到(x, y)。drawImage(image, sampling, paint):支持自定义采样选项的绘制方式。drawImageRect(image, dstRect):将整个图像拉伸到目标矩形。drawImageRect(image, srcRect, dstRect, sampling, paint, constraint):精准的区域拉伸绘制。从srcRect采样,绘制到dstRect。
代码示例:
auto image = Image::MakeFromFile("photo.png");
// Simple draw at (50, 50)
canvas->drawImage(image, 50, 50);
// Draw with source-to-destination mapping
auto src = Rect::MakeXYWH(0, 0, 100, 100);
auto dst = Rect::MakeXYWH(50, 50, 200, 200);
canvas->drawImageRect(image, src, dst);
4.2 drawAtlas (图集绘制)
drawAtlas 利用图集技术,通过 单次绘制调用 (Draw Call) 渲染大量精灵 (Sprites),是粒子系统和高性能 UI 组件的首选。
void drawAtlas(std::shared_ptr<Image> atlas,
const Matrix matrix[], // Each sprite's transform
const Rect tex[], // Source rectangle in atlas for each sprite
const Color colors[], // Optional per-sprite tint color (nullptr to skip)
size_t count, // Number of sprites
const SamplingOptions& sampling = {},
const Paint* paint = nullptr);
atlas:包含所有精灵帧的图集图像。matrix[]:每个精灵的独立变换矩阵,控制位置、旋转、缩放。tex[]:每个精灵在图集中的源矩形区域。colors[]:可选的逐精灵着色数组。传入nullptr时不着色。- 适用场景:粒子特效、弹幕动画、游戏精灵渲染等需要批量绘制大量小图的场景。
4.3 SamplingOptions (采样控制)
SamplingOptions 通过 FilterMode 和 MipmapMode 两个维度控制采样质量:
FilterMode:纹理过滤模式
| 模式 | 算法 | 特点 |
|---|---|---|
Nearest | 邻近采样(最近邻) | 性能最高,非整数缩放时产生锯齿,适合像素艺术风格。 |
Linear | 双线性过滤(2×2 插值) | 默认选择,缩放效果平滑,适合大多数 UI 场景。 |
Min / Mag 分离控制
SamplingOptions 支持独立设置缩小(min)和放大(mag)时的过滤模式:
// Default: both Linear
SamplingOptions sampling; // min = Linear, mag = Linear
// Same filter for both
SamplingOptions sampling(FilterMode::Nearest); // min = Nearest, mag = Nearest
// Different filters for min and mag
SamplingOptions sampling(FilterMode::Linear, // minFilterMode (shrink)
FilterMode::Nearest, // magFilterMode (enlarge)
MipmapMode::Linear); // mipmapMode
MipmapMode:多级渐远纹理模式
| 模式 | 行为 | 适用场景 |
|---|---|---|
None | 禁用 Mipmap,仅从基础层采样。 | 图像始终以原始尺寸或接近原始尺寸绘制时。 |
Nearest | 从最近的 Mipmap 层级采样。 | 性能优先的缩小场景。 |
Linear | 在两个最近层级之间插值。默认值。 | 图像缩至极小尺寸时,有效抑制闪烁和锯齿。 |
注意:Mipmap 模式仅在 Image 启用了 makeMipmapped(true) 后才生效。否则,无论 MipmapMode 设置为何值,实际行为等同于 None。
4.4 SrcRectConstraint (源矩形约束)
在 drawImageRect 中,SrcRectConstraint 控制源矩形边缘的采样行为:
| 模式 | 行为 |
|---|---|
Fast | 默认。允许在源矩形边界外采样,性能更高。 |
Strict | 严格限制在源矩形内采样,禁用 Mipmap。适用于图集拼接场景,防止相邻精灵的像素渗透。 |
5. 作为 Shader 使用
通过 Shader::MakeImageShader 可将 Image 转化为动态"画笔纹理",在填充任意形状时提供图像采样。
5.1 TileMode (平铺模式)
| 模式 | 行为 |
|---|---|
Clamp | 默认。拉伸边缘像素填充超出部分。 |
Repeat | 重复平铺图像。 |
Mirror | 镜像翻转重复,相邻图像自然无缝衔接。 |
Decal | 超出边界返回透明(transparent-black)。 |
5.2 基础用法
auto image = Image::MakeFromFile("pattern.png");
// Create a repeating image shader
auto shader = Shader::MakeImageShader(image, TileMode::Repeat, TileMode::Repeat);
Paint paint;
paint.setShader(shader);
// Fill any path with the image texture
canvas->drawPath(path, paint);
5.3 局部变换
通过 shader->makeWithMatrix(matrix) 为 Shader 设置独立的变换矩阵,例如在路径填充时对纹理进行旋转或缩放:
auto shader = Shader::MakeImageShader(image, TileMode::Repeat, TileMode::Repeat);
// Rotate the texture pattern by 45 degrees
Matrix matrix = Matrix::MakeRotate(45);
auto rotatedShader = shader->makeWithMatrix(matrix);
Paint paint;
paint.setShader(rotatedShader);
canvas->drawRect(rect, paint);
6. 渲染效果参考
基础变换效果
从大图中提取中心 400×400 子集,应用高斯模糊滤镜后绘制:
auto image = Image::MakeFromFile("bridge.jpg");
// Crop center 400x400 region
float x = static_cast<float>(image->width() - 400) / 2.0f;
float y = static_cast<float>(image->height() - 400) / 2.0f;
auto subset = image->makeSubset(Rect::MakeXYWH(x, y, 400.0f, 400.0f));
// Apply blur filter
auto filter = ImageFilter::Blur(10.0f, 10.0f);
auto filteredImage = subset->makeWithFilter(filter);
// Draw
auto surface = Surface::Make(context, 400, 400);
auto canvas = surface->getCanvas();
canvas->drawImage(filteredImage, 0, 0, SamplingOptions(FilterMode::Linear));

Shader TileMode 对比
将同一张小图作为 Shader 源,分别使用四种 TileMode 填充 200×200 区域(左上 Repeat、右上 Mirror、左下 Clamp、右下 Decal):
auto tile = image->makeScaled(80, 80, SamplingOptions(FilterMode::Linear));
// Repeat: tiles the image seamlessly
auto repeatShader = Shader::MakeImageShader(tile, TileMode::Repeat, TileMode::Repeat);
paint.setShader(repeatShader);
canvas->drawRect(Rect::MakeWH(200, 200), paint);
// Mirror: alternating flip for seamless edges
auto mirrorShader = Shader::MakeImageShader(tile, TileMode::Mirror, TileMode::Mirror);
// Clamp: stretches edge pixels beyond bounds
auto clampShader = Shader::MakeImageShader(tile, TileMode::Clamp, TileMode::Clamp);
// Decal: transparent beyond bounds
auto decalShader = Shader::MakeImageShader(tile, TileMode::Decal, TileMode::Decal);

Picture + drawAtlas
通过 PictureRecorder 录制矢量绘制命令,创建为 Image,再使用 drawAtlas 批量绘制 4×3 精灵网格,每个精灵带有不同的着色:
// 1. Record drawing commands into a Picture
PictureRecorder recorder;
auto* recordCanvas = recorder.beginRecording();
Paint circlePaint = {};
circlePaint.setColor(Color::FromRGBA(60, 120, 240, 255));
recordCanvas->drawCircle(40, 40, 35, circlePaint);
circlePaint.setColor(Color::FromRGBA(240, 80, 60, 200));
recordCanvas->drawCircle(60, 60, 25, circlePaint);
auto picture = recorder.finishRecordingAsPicture();
// 2. Create Image from Picture (100x100)
auto pictureImage = Image::MakeFrom(picture, 100, 100);
// 3. Batch draw 12 sprites with drawAtlas
size_t count = 12;
std::vector<Matrix> matrices(count);
std::vector<Rect> texRects(count);
std::vector<Color> colors(count);
auto texRect = Rect::MakeWH(100, 100);
for (size_t i = 0; i < count; ++i) {
int col = static_cast<int>(i) % 4;
int row = static_cast<int>(i) / 4;
matrices[i] = Matrix::MakeTrans(col * 100.0f, row * 100.0f);
texRects[i] = texRect;
float t = static_cast<float>(i) / static_cast<float>(count - 1);
colors[i] = Color::FromRGBA(static_cast<uint8_t>(255 * (1 - t)),
static_cast<uint8_t>(200 * t),
static_cast<uint8_t>(100 + 155 * t), 255);
}
canvas->drawAtlas(pictureImage, matrices.data(), texRects.data(), colors.data(), count);

