图片像素错误导致显示异常
============

#### 问题现象

对图片进行处理(如编解码、相机流数据处理等)，出现显示错误或花屏等现象。

#### 背景知识

*   数据对齐：在计算机读取数据时存在数据对齐，计算机一般都为32位或者64位，故一般数据对齐都为4字节或8字节的整数倍。如果数据非4字节或8字节对齐，就会需要额外花销来处理，为了避免这种额外的开销，一般会使用0或者其它数据在未对齐的数据末尾进行填充对齐。
*   宽高：图像数据中有效的宽高。
*   宽高跨距：跨距（Stride），是图像存储在内存中，每一行数据所占空间的真实大小，它大于或等于通过图像分辨率宽度计算的字节长度。由于内存对齐的缘故，方便提取数据，每行数据的字节数可能要求为一个数的倍数，这时不足的字节数由padding填充。（Stride = width + padding）

在可编程位图中有两种表示方式为RGB和YUV，RGB的色彩空间以三原色来组合表示一个像素点，YUV格式色彩空间以亮度，色调，色饱和度来表示一个像素点。RGB色彩空间更适合图像采集和显示，YUV空间更适合编码和存储。在存储和编码之前，RGB图像要转换为YUV图像，而YUV图像在显示之前一般有必要转换回RGB。

#### 解决方案

使用图片解析软件或手动读取图片数据分析比较。

*   **场景一**：图片颜色显示不正确：
    
    问题现象：图像正常显示但图像中颜色显示错误。
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/c5/v3/KyRk3H00TVGly_dytiu3-A/zh-cn_image_0000002658911809.png?HW-CC-KV=V1&HW-CC-Date=20260709T134238Z&HW-CC-Expire=86400&HW-CC-Sign=7C229C9D88791FB966CFB64084E10C34614EFF0967B177927F083EDFF1D4ED1D)
    
    1.  在Native侧创建PixelMap传递给ArkTS的Image组件进行展示时，报出colorspace的错误，图片颜色显示错误。
    2.  在使用createPixelMap创建PixelMap发现图片变黄了。
    3.  使用readPixelsToBuffer和readPixels读取像素的排列格式不同，图片显示的颜色不同。
    
    效果预览如下：正常显示如图一，加载渲染显示如图二。
    
    图一：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/5b/v3/L7Mw4n4nTkuVxmpv3WKvaQ/zh-cn_image_0000002628392600.png?HW-CC-KV=V1&HW-CC-Date=20260709T134238Z&HW-CC-Expire=86400&HW-CC-Sign=3FE8DB9AAD7A1E5D7678AB743FCD3885BCDFC9D1C67AF6089AE82A00F520AA3F "点击放大")
    
    图二：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/f9/v3/1tO_ou5ZQeeaSITfOWE5vA/zh-cn_image_0000002658791867.png?HW-CC-KV=V1&HW-CC-Date=20260709T134238Z&HW-CC-Expire=86400&HW-CC-Sign=728D7816B72514BAD9E599CD8821F5650182595442A8DAB21CACA08A36AA6079 "点击放大")
    
    原因分析：颜色显示错误如RGB用BGR的像素格式去解析渲染，会导致原本R红色通道的值变为B蓝色通道的值，B蓝色通道的值变为R红色通道的值，导致最终颜色错误。
    
    例如原本图片的像素格式为RGB888（每个颜色通道8bit大小，一个像素3字节），其中的一个像素以16进制标识为#ff0000（红色），现在以BGR的方式去解析渲染导致该像素的红色通道和蓝色通道数值对调，变为#0000ff（蓝色）。
    
    解决方案：与场景二相同。
    
*   **场景二**：图片颜色错乱，像素点明亮可见：
    
    问题现象：图像未正常显示，颜色错乱，像素点明亮可见。
    
    效果预览如下：正常显示如图三，渲染显示后如图四。
    
    图三：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/65/v3/XY40n5pXRj2f3A3g2IakwA/zh-cn_image_0000002628552492.png?HW-CC-KV=V1&HW-CC-Date=20260709T134238Z&HW-CC-Expire=86400&HW-CC-Sign=03490B1422F175C5A0B2408EB72A82EB88F354C048277ACFA6159C3F5B6B9684 "点击放大")
    
    图四：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/ab/v3/V4Z_lQ-2TtexVIQw76Emlw/zh-cn_image_0000002658911811.png?HW-CC-KV=V1&HW-CC-Date=20260709T134238Z&HW-CC-Expire=86400&HW-CC-Sign=1F734F94AB55366E962B24A83B978250D0F028715C4B877838A621C35BC8ED88 "点击放大")
    
    原因分析：如果图像中有多种颜色，颜色显示错误错乱，无法正常显示可见图片，并有清晰可见且规则排列的点，则需要确认色彩空间的表示是否错误。
    
    *   解析图片使用的色彩空间表示错误，RGB（RGBA）与YUV通道错误表示，导致颜色错误杂乱，像素点明显，假设以RGB888像素格式的图片的其中3个像素点的数据现在以YUV格式或者BGRA8888等打乱像素点数据的方式渲染解析，会导致图片无法识别，像素点错乱。
    *   使用readPixelsToBuffer是整段buffer的读取，所以是按照原PixelMap的像素格式读的；而readPixels是区域读取，读出来的固定都是BGRA\_8888格式。
    
    YUV色彩空间格式只有图片的数据信息，无文件头等，打开时需要指定YUV的像素排列方式和宽高。可以使用相关的图片查看软件打开并尝试RGB或YUV的色彩空间的不同样点排列格式，来确认图片正确的格式。
    
    解决方案：确定好相关像素格式后，可由正确的像素格式渲染图片，如创建PixelMap时初始化缓冲区中的像素格式需要由[InitializationOptions.srcPixelFormat](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-i#initializationoptions8)指定（默认值为BGRA\_8888）。
    
    获取PixelMap图片信息可通过[getImageInfo(): Promise<ImageInfo>](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-pixelmap#getimageinfo7)函数或者[getImageInfo(callback: AsyncCallback<ImageInfo>): void](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-pixelmap#getimageinfo7-1)函数来得到图像像素信息，前者以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%')
      }
    }
    

*   **场景三**：图片出现撕裂条纹：
    
    问题现象：图片呈明显的斜条纹撕裂：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/3b/v3/ZUkiHMFJR8iw4DzobdOCGQ/zh-cn_image_0000002628392604.png?HW-CC-KV=V1&HW-CC-Date=20260709T134238Z&HW-CC-Expire=86400&HW-CC-Sign=23432010DE79BCD4DC1B83BF7D39268C4C553FDA23D139C76A78CF97899B3460)
    
    常见跨距花屏场景：
    
    *   相机预览中发生跨距异常。
    *   编解码跨距异常花屏。
    
    原因分析：在图像渲染时，若宽高和padding被错误的对齐，会导致图片出现斜条纹：
    
    假设图片对齐为8，宽为6，跨距为8，填充为2，正常显示如下图：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/f9/v3/HRe_oqNcSyO3Dpp36aFCDA/zh-cn_image_0000002658791869.png?HW-CC-KV=V1&HW-CC-Date=20260709T134238Z&HW-CC-Expire=86400&HW-CC-Sign=B929E1462FAE014EF3C6CADF6F620580A515EDD9FFF867BC6D7C15F0D4AB70D4)
    
    当跨距设置错误时会出现下面两种情况：
    
    *   当padding填充不够时（假设为1），因为对齐为8此时会读取8个像素数据，现在只有1个padding，此时就会把第二行的像素数据多读取一个如下图所示：
        
        ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/bf/v3/jzanSq8iR4aapB8XM87xtg/zh-cn_image_0000002628552496.png?HW-CC-KV=V1&HW-CC-Date=20260709T134238Z&HW-CC-Expire=86400&HW-CC-Sign=99A45F4CAE27D324886F25E53C88AF36DCF4D339CF87B3FCDE3E058CFBDEAA2E)
        
    
    此时画面因为像素的错位显示呈现出左下往右上的撕裂斜条纹。
    
    *   同理此时跨距过大，即padding填充过多时（假设为3）会导致多的一个填充被下一行像素数据读取如下图所示：
        
        ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/8d/v3/U_8_4THzTqqWejbq7ulVFQ/zh-cn_image_0000002658911813.png?HW-CC-KV=V1&HW-CC-Date=20260709T134238Z&HW-CC-Expire=86400&HW-CC-Sign=6776B45F7A2FC0D755BF93A417CF168A9019F316EEA4C48D3735F8C697C9235F)
        
    
    此时画面因为像素的错位显示呈现出左上往右下的撕裂斜条纹。
    
    解决方案：
    
    *   相机预览中发生跨距异常：[相机预览花屏解决方案](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-deal-stride-solution)。
    *   编解码跨距异常花屏（[编码文档](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/video-encoding#buffer模式)/[解码文档](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/video-decoding#buffer模式)拷贝YUV图像时设置跨距）。

#### 常见FAQ

Q：编解码的对齐字节是多少（如何获取编码图像跨距/步长）？

A：根据不同的硬件限制，编解码器自动对齐的字节不同，可以使用[OH\_VideoEncoder\_GetInputDescription](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-native-avcodec-videoencoder-h#oh_videoencoder_getinputdescription)查询得到AVformat，再用[OH\_AVFormat\_GetIntValue](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-native-avformat-h#oh_avformat_getintvalue)获取[OH\_MD\_KEY\_VIDEO\_STRIDE](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-codecbase#视频专有的键值对)的值进行处理。详细处理方式参考视频编码文档中的[调用OH\_VideoEncoder\_PushInputBuffer写入编码图像](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/video-encoding#buffer模式)步骤。

Q：HarmonyOS中PixelMap支持什么像素格式？

A：PixelMap的像素格式参考[PixelMapFormat](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-e#pixelmapformat7)。

#### 总结

渲染图片错误花屏通常由对图片数据的错误处理导致。

| 
现象

 | 

像素错误情况

 | 

常见原因

 | 

处理方式

 |
| :-- | :-- | :-- | :-- |
| 

图片颜色错误

 | 

像素点内颜色通道错误

 | 

像素中颜色通道顺序错误（如图片像素以BGR的方式储存但以RGB的方式读取渲染）

 | 

更换图片像素通道的解析顺序

 |
| 

图片颜色杂乱且像素点清晰可见

 | 

每个像素点的数据异常

 | 

图片色彩空间/图片像素格式错误（如RGB的色彩空间采用YUV解析/RGB类型的像素格式使用RGBA的像素解析）

 | 

以正确的色彩空间和像素格式解析渲染图片

 |
| 

图片撕裂有规则的斜条纹

 | 

图片上像素点的个数错误

 | 

图片的宽高跨距处理不正确

 | 

确认字节对齐大小，添加或删除填充使图片跨距对齐

 |