在可编程位图中有两种表示方式为RGB和YUV,RGB的色彩空间以三原色来组合表示一个像素点,YUV格式色彩空间以亮度,色调,色饱和度来表示一个像素点。RGB色彩空间更适合图像采集和显示,YUV空间更适合编码和存储。在存储和编码之前,RGB图像要转换为YUV图像,而YUV图像在显示之前一般有必要转换回RGB。
使用图片解析软件或手动读取图片数据分析比较。
问题现象:图像正常显示但图像中颜色显示错误。

效果预览如下:正常显示如图一,加载渲染显示如图二。
图一:

图二:

原因分析:颜色显示错误如RGB用BGR的像素格式去解析渲染,会导致原本R红色通道的值变为B蓝色通道的值,B蓝色通道的值变为R红色通道的值,导致最终颜色错误。
例如原本图片的像素格式为RGB888(每个颜色通道8bit大小,一个像素3字节),其中的一个像素以16进制标识为#ff0000(红色),现在以BGR的方式去解析渲染导致该像素的红色通道和蓝色通道数值对调,变为#0000ff(蓝色)。
解决方案:与场景二相同。
问题现象:图像未正常显示,颜色错乱,像素点明亮可见。
效果预览如下:正常显示如图三,渲染显示后如图四。
图三:

图四:

原因分析:如果图像中有多种颜色,颜色显示错误错乱,无法正常显示可见图片,并有清晰可见且规则排列的点,则需要确认色彩空间的表示是否错误。
YUV色彩空间格式只有图片的数据信息,无文件头等,打开时需要指定YUV的像素排列方式和宽高。可以使用相关的图片查看软件打开并尝试RGB或YUV的色彩空间的不同样点排列格式,来确认图片正确的格式。
解决方案:确定好相关像素格式后,可由正确的像素格式渲染图片,如创建PixelMap时初始化缓冲区中的像素格式需要由InitializationOptions.srcPixelFormat指定(默认值为BGRA_8888)。
获取PixelMap图片信息可通过getImageInfo(): Promise<ImageInfo>函数或者getImageInfo(callback: AsyncCallback<ImageInfo>): void函数来得到图像像素信息,前者以Promise形式返回获取的图像像素信息,后者以callback形式返回获取的图像像素信息。
pixelMap.getImageInfo().then( (value:image.ImageInfo) => { this.pixelmapSize = pixelMap.getPixelBytesNumber(); console.info(TAG + 'pixelmapBytes: '+ pixelMap.getPixelBytesNumber()); console.info(TAG + 'width: ' + value.size.width + ' height: '+value.size.height+' stride: '+value.stride+' format: '+ value.pixelFormat+ ' MIME '+value.mimeType); });
createPixelMap创建PixelMap,像素格式默认为BGRA排列,将排列RGBA改为BGRA即可,若想在读取图片数据后进行其他操作,需要在图片解析后判断下一步操作需要哪种像素排列格式,将格式转换后进行操作,最后重新创建PixelMap需要将格式更改为BGRA再用createPixelMap创建。
rgbaToBgra(src: Uint8Array, dst: Uint8Array, length: number) { for (let i = 0; i < length; i += 4) { // 取出R、G、B、A四个字节 const r = src[i]; const g = src[i + 1]; const b = src[i + 2]; const a = src[i + 3]; // 将B、G、R、A写入dst dst[i] = b; dst[i + 1] = g; dst[i + 2] = r; dst[i + 3] = a; } }
完整示例参考如下:
import { image } from '@kit.ImageKit'; import { BusinessError } from '@kit.BasicServicesKit'; const TAG = 'pixelTest'; @Entry @Component struct pixelTest { message: string = 'pixelTest'; @State bitmap: image.PixelMap | undefined = undefined; pixelmapSize: number = 0; // 单个像素的RGBA变换 rgbaToBgra(src: Uint8Array, dst: Uint8Array, length: number) { for (let i = 0; i < length; i += 4) { // 取出R、G、B、A四个字节 const r = src[i]; const g = src[i + 1]; const b = src[i + 2]; const a = src[i + 3]; // 将B、G、R、A写入dst dst[i] = b; dst[i + 1] = g; dst[i + 2] = r; dst[i + 3] = a; } } // pixelmap转换格式 build() { Row() { Column() { Image(this.bitmap) Text(this.message) .fontSize($r('app.float.page_text_font_size')) .fontWeight(FontWeight.Bold) .onClick(async () => { // xxx.png替换为rawfile目录下的图像 let imageRawFileDescriptor = await this.getUIContext().getHostContext()?.resourceManager.getRawFd('xxx.png'); const imageSource: image.ImageSource = image.createImageSource(imageRawFileDescriptor); let decodeingOptions: image.DecodingOptions = { editable: true, desiredPixelFormat: image.PixelMapFormat.RGBA_8888, }; await imageSource.createPixelMap(decodeingOptions).then( (pixelMap: image.PixelMap) => { console.info('Succeeded in creating PixelMap'); this.bitmap = pixelMap; pixelMap.getImageInfo().then( (value:image.ImageInfo) => { this.pixelmapSize = pixelMap.getPixelBytesNumber(); console.info(`${TAG} pixelmapBytes ${pixelMap.getPixelBytesNumber()}`); console.info(`${TAG} width: ${value.size.width} height: ${value.size.height} stride: ${value.stride} format: ${value.pixelFormat} MIME: ${value.mimeType}`); }); // 改换pixelmap的format const readBuffer:ArrayBuffer = new ArrayBuffer(400000); // 读取的字节数需根据图片调整 如果使用pixelmapSize初始化buffer 需要确保pixelmapSize在此之前已被赋值 if(this.bitmap !== undefined){ this.bitmap.readPixelsToBuffer(readBuffer, (error: BusinessError) => { if (error) { console.error(`Failed to read image pixel data. code is ${error.code}, message is ${error.message}`); // 不符合条件则进入。 return; } else { console.info('Succeeded in reading image pixel data.'); // 符合条件则进入。 } }); } else { console.error('this bitmap is undefined'); return; } let srcUint8Array = new Uint8Array(readBuffer); console.info(`${TAG} srcbuffer: ${srcUint8Array}`); let dstUint8Array = new Uint8Array(400000); // 读取的字节数需根据图片调整 this.rgbaToBgra(srcUint8Array, dstUint8Array, 400000); // 读取的字节数需根据图片调整 console.info(`${TAG} dstbuffer: ${dstUint8Array}`); }); }) } .width('100%') } .height('100%') } }
问题现象:图片呈明显的斜条纹撕裂:

原因分析:在图像渲染时,若宽高和padding被错误的对齐,会导致图片出现斜条纹:
假设图片对齐为8,宽为6,跨距为8,填充为2,正常显示如下图:

当跨距设置错误时会出现下面两种情况:
此时画面因为像素的错位显示呈现出左下往右上的撕裂斜条纹。
此时画面因为像素的错位显示呈现出左上往右下的撕裂斜条纹。
解决方案:
Q:编解码的对齐字节是多少(如何获取编码图像跨距/步长)?
A:根据不同的硬件限制,编解码器自动对齐的字节不同,可以使用OH_VideoEncoder_GetInputDescription查询得到AVformat,再用OH_AVFormat_GetIntValue获取OH_MD_KEY_VIDEO_STRIDE的值进行处理。详细处理方式参考视频编码文档中的调用OH_VideoEncoder_PushInputBuffer写入编码图像步骤。
Q:HarmonyOS中PixelMap支持什么像素格式?
A:PixelMap的像素格式参考PixelMapFormat。