1. 为什么会在一款RN应用里动PixelFormat
1.1 你手里的是"图片",不是"像素"
先讲个很典型的现场。拿着一个OpenHarmony设备做React Native开发,从相册选了一张照片,要传到一个人脸识别SDK里。SDK要求输入RGBA字节流,你在JS里把uri传给接口,结果对方返回一个"format not supported",或者图片显示出来整体发绿、颜色错乱。常见的直觉是"图片格式转一下不就行了",但你在ArkTS/JS里翻了一圈,发现根本没有现成的convertPixelFormat()这种万能接口。问题就卡在:你手里的是一个被封装好的PixelMap对象,而不是一堆可以直接理解的内存字节。
很多做RN开发的同事一开始不理解,为什么图片这种"基础能力",在OpenHarmony上会变成必须仔细处理的环节。原因其实不复杂:**OpenHarmony的媒体框架处理的是PixelMap,RN组件要显示的是Image,而底层编码器、摄像头、显示控制器各自约定的是PixelFormat。**这三层之间不会自动替你转换,一旦某个环节的格式约定不匹配,轻则花屏,重则整个链路直接报错。
1.2 RN框架挡在中间,但不能不碰格式
React Native for OpenHarmony(RNOH)把原生能力封装成了JS接口,这是好事,因为它屏蔽了大量底层差异。但也带来了一个问题:当你要把图片字节流交给某个原生算法库、硬件编码器或者第三方SDK时,RN框架并不知道你对端要的是什么PixelFormat,它只会把PixelMap原样交给你。所以你必须在某个节点主动去做一次格式协商和转换。
举个更直白的例子:你用RN写的App要调用一个C++实现的图像处理模块,这个模块只接受NV12或者NV21的YUV数据,而RN Image组件从相册读出来的照片,默认解出来多半是RGBA_8888。这时候你没有别的路可走,只能做一次从RGBA到NV12的转换。转换前必须搞清楚RGBA_8888的内存排列、NV12的Y平面和UV平面的位置、宽高对齐规则,这些东西任何一个点弄错,结果都是一帧不能用的数据。
1.3 哪些项目尤其逃不掉
如果你只是把网络图片显示在页面上,那PixelFormat基本不用操心,RNOH内部已经处理好了。但如果你的项目踩中下面任一条,我建议你把这篇文章读完:
- 调用人脸识别、OCR、图像质量检测等三方算法SDK,且SDK要求裸数据输入;
- 做视频流处理或低延迟拍照,需要把相机预览帧直接喂给编码器;
- 要在ArkTS/JS层做图像裁剪、缩放、水印,还要保证输出数据能被原生模块直接使用;
- 在RK3568、RK3588这类开发板上做硬件编解码,需要判断输出帧格式到底是NV12还是NV21,是TILE还是LINEAR。
这些问题在纯RN项目里可能一辈子遇不到,但一旦遇到,网上资料零零散散,尤其RNOH相关的几乎没有。所以我把这一路的经验整理成文,后面每一节都对应一个真实会踩的坑。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. PixelFormat是个什么黑盒:从RGBA到YUV的编码差异
2.1 主流格式与内存排布
先说最基础的。PixelFormat描述的是每个像素在内存里怎么存储,它比"图片格式是jpg还是png"低一个层次。jpg/png是容器格式,解码之后才会得到PixelFormat层面的数据。OpenHarmony里常见的PixelMap格式包括:RGBA_8888、BGRA_8888、RGB_565、NV12、NV21,部分地方还会出现ARGB_8888和灰度格式。
这几种格式的内存排布是理解所有问题的地基:
| 格式 | 每像素位数 | 内存排列说明 | 典型用途 |
|---|---|---|---|
| RGBA_8888 | 32位 | 每像素R、G、B、A各占1字节 | 通用图片、UI绘制 |
| BGRA_8888 | 32位 | 每像素B、G、R、A各占1字节 | 部分GPU/硬件加速接口 |
| RGB_565 | 16位 | 高5位R、中间6位G、低5位B,无Alpha | 低成本屏幕、LCD |
| NV12 | 12位/像素 | Y平面连续,接着UV交错平面,先U后V | 视频编码、摄像头输出 |
| NV21 | 12位/像素 | Y平面连续,接着VU交错平面,先V后U | 部分摄像头、旧编码器 |
一个容易忽略的点:RGB_565虽然省内存,但缺失Alpha通道,而且绿色通道占6位导致颜色精度不均匀,图像里如果有渐变色或肤色,RGBA转RGB_565之后会出现肉眼可见的色带。所以只要不是屏幕或硬件强制要求,我一般不建议把图像转成RGB_565保存。
2.2 YUV家族:NV12/NV21为什么这么常见
很多做RN的人第一次接触YUV会非常不习惯,因为它的思路和RGB完全不一样。RGB是直接用三原色表示颜色,而YUV把亮度(Y)和色度(UV)分开。这样做的好处是:人眼对亮度敏感、对色度不敏感,所以可以降低色度的采样率来压缩数据量,这就是所谓YUV420的由来。
NV12和NV21都属于YUV420的存储变种。它们把一帧图像分成两个平面:
- 第一个平面:Y数据,大小为
width * height,每个像素一个字节; - 第二个平面:UV数据,大小为
width * height / 2,每两个像素共享一组U和V。
区别只在于UV交错排列的顺序,NV12是U、V、U、V……,NV21是V、U、V、U……。这个顺序一旦搞反,图像不会变灰,而是会出现严重的颜色失真,典型表现是整个画面像被上了色偏滤镜,红色和蓝色互换。摄像头和硬件编码器对NV12/NV21有各自的偏好,所以做相机采集和视频编码时,我们要先明确设备到底输出哪种格式。
2.3 对齐和色彩范围:两个容易翻车的小细节
第一个细节是行对齐(stride)。很多初接触裸数据的开发者默认地把"宽640"理解成"每行640字节",这在RGBA_8888下是对的,因为每像素4字节,一行就是2560字节。但有些硬件在分配内存时,会对每行做16字节、32字节或64字节的对齐,导致实际每行字节数大于理论值。如果你按"宽*高"去计算数据长度,最后取到的数据尾部会串行,画面出现斜
