# 如何正确处理高像素图片

#### 概述

随着相机传感器技术的发展，越来越多设备支持高像素（如2亿像素）照片的拍摄。高像素图片意味着更高的图像分辨率，放大查看时图像细节更清晰，但文件体积、解码内存、渲染内存、网络传输成本和处理耗时也更高。应用在上传、分享、备份、编辑、预览和大图浏览等场景处理高像素图片时，应先确认业务是否需要原始分辨率，再选择获取高像素原图、降采样解码、区域解码或像素格式转换等方案。

#### 处理原则

1.  **判断业务是否需要高像素原图**。当用于列表、封面、聊天消息、分享预览等场景时，优先使用降采样图或缩略图。
2.  **以最终获取到的文件为准**。通过PhotoPicker、URI或媒体库接口获取图片后，应重新读取真实宽高、编码格式和元数据，不要仅依赖文件名、URI后缀或设备型号获取图像信息。
3.  **解码时控制输出规模**。根据显示尺寸设置desiredSize，根据可视区域设置desiredRegion，根据业务链路设置desiredPixelFormat。
4.  **及时释放图片资源**。图片占用内存较大，切换图片、重新解码或页面销毁时应释放不再使用的PixelMap或ImageSource。

#### 常见问题

#### 适用高像素原图的场景

**适用场景**

1.  图库、相册、影像查看器等需要放大查看高像素原图细节的应用。
2.  网盘、备份、文件传输等需要保存或同步高像素原图的应用。
3.  图片编辑、裁剪、打印、专业创作等对原始分辨率有要求的应用。
4.  文档扫描、工业质检等需要查看局部纹理、文字或缺陷细节的应用。

**通常不需要直接使用高像素原图的场景**

1.  聊天消息、头像、信息流、商品列表等仅需做小尺寸预览的场景。
2.  分享封面、列表卡片、普通页面配图等只需要适配屏幕显示尺寸的场景。
3.  对上传耗时、流量消耗或存储空间敏感，且业务不要求保留原始分辨率的场景。

**处理方式**

1.  如果业务只需要全图展示，使用降采样解码。
2.  如果业务需要放大查看细节，在降采样全图预览基础上结合区域解码。
3.  如果业务需要上传、备份或编辑高像素原图，可在页面里声明具备高像素原图处理能力。因高像素图片处理时间相对较长，需设计相应的内存、网络和存储策略。

#### 上传、分享等场景使用高像素图片失败或异常退出

**可能原因**

1.  应用直接按照高像素图片的原始分辨率解码，解码峰值内存占用过高。
2.  解码后生成的PixelMap对象内存占用较大，或同一页面同时持有多个高像素PixelMap对象。
3.  应用将高像素图片直接送显，超出渲染链路可承载的内存范围，导致应用异常退出或页面卡顿。
4.  应用在上传、分享前进行了压缩、旋转、水印、滤镜等处理，导致短时间内同时存在高像素原图、解码结果（PixelMap）和处理中间图，峰值内存占用过高导致异常。

**解决措施**

1.  仅用于预览、列表、封面等场景时，使用降采样解码，按实际显示尺寸生成PixelMap。需要查看局部细节时，使用区域解码，仅解码当前可视区域或用户放大后的目标区域。
2.  避免在同一页面长期持有多个高像素PixelMap对象；不再使用PixelMap对象时及时释放图片资源。
3.  上传或分享场景按业务需要选择高像素原图或降采样图。若业务不要求保留原始分辨率，建议上传或分享降采样后的图片。
4.  对编辑、压缩、加水印等链路，尽量串行处理并复用中间结果，避免同时持有多份大图数据。

#### 为什么通过PhotoPicker或URI获取高像素图片时得到约1200万像素或约1250万像素的JPEG图片？

**可能原因**

系统在部分访问链路中会将高像素图片转换为兼容性更好的JPEG图片，以降低应用处理成本并提升兼容性。转换后的图片通常约为1200万像素或约1250万像素，实际结果以系统实现和图片来源为准。

**解决措施**

API version 24开始提供AssetCompatibleCapability接口，支持配置应用是否兼容高像素图片资源。

如果应用需要获取高像素原图，应显式声明应用具备处理高像素图片的能力，并在后续处理流程中做好降采样、区域解码和内存管理。

通过PhotoPicker获取图片时，可在PhotoSelectOptions中配置assetCompatibleCapability以声明具备高像素原图的处理能力。

```
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { BusinessError } from '@kit.BasicServicesKit';

async function pickerExample(): Promise<void> {
  try {
    const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
    photoSelectOptions.assetCompatibleCapability = {
      supportedHighResolution: true
    } as photoAccessHelper.AssetCompatibleCapability;

    const photoPicker = new photoAccessHelper.PhotoViewPicker();
    const photoSelectResult: photoAccessHelper.PhotoSelectResult =
      await photoPicker.select(photoSelectOptions);
    console.info(`PhotoViewPicker.select successfully: ${JSON.stringify(photoSelectResult)}`);
  } catch (error) {
    const err = error as BusinessError;
    console.error(`PhotoViewPicker.select failed. code: ${err.code}, message: ${err.message}`);
  }
}
```

对于网盘、备份等需要按高像素原图处理媒体文件的应用，可使用setAssetCompatibleCapability接口配置AssetCompatibleCapability。

```
import { dataSharePredicates } from '@kit.ArkData';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { fileIo as fs } from '@kit.CoreFileKit';

async function enableHighResolution(
  phAccessHelper: photoAccessHelper.PhotoAccessHelper
): Promise<void> {
  const capability: photoAccessHelper.AssetCompatibleCapability = {
    supportedHighResolution: true
  };
  await phAccessHelper.setAssetCompatibleCapability(capability);
}

async function getHighResolutionAsset(
  phAccessHelper: photoAccessHelper.PhotoAccessHelper
): Promise<void> {
  try {
    await enableHighResolution(phAccessHelper);

    const predicates = new dataSharePredicates.DataSharePredicates();
    const fetchOptions: photoAccessHelper.FetchOptions = {
      fetchColumns: [photoAccessHelper.PhotoKeys.URI],
      predicates
    };
    const fetchResult: photoAccessHelper.FetchResult<photoAccessHelper.PhotoAsset> =
      await phAccessHelper.getAssets(fetchOptions);
    const asset: photoAccessHelper.PhotoAsset = await fetchResult.getFirstObject();
    const srcFile = fs.openSync(asset.uri, fs.OpenMode.READ_ONLY);

    // 获取到fd后，可按业务需要进行解码、上传或备份。
    console.info(`Open source file successfully. fd: ${srcFile.fd}`);
  } catch (error) {
    const err = error as BusinessError;
    console.error(`Failed get high resolution asset: code: ${err.code}, message: ${err.message}`);
  }
}
```

#### 获取到图片后，格式、尺寸或元数据判断异常

**可能原因**

1.  应用根据URI后缀判断图片格式，但URI后缀不一定等同于图片真实编码格式。
2.  应用直接使用媒体库记录的文件属性进行业务判断，但最终获取到的图片文件可能经过兼容性转换。
3.  应用假定获取前后的文件格式、分辨率或元数据完全一致，导致上传校验、格式分支、尺寸判断等逻辑异常。

**解决措施**

1.  获取图片URI或fd后，使用Image Kit接口读取真实的图片格式、宽高等信息。
2.  避免仅根据URI后缀、文件名或媒体库记录判断图片文件属性。
3.  对上传、分享、编辑等关键链路，使用最终拿到的图片文件重新校验格式、尺寸和元数据。

**示例（ArkTS）：**

解决获取图片格式异常问题，获取真实MIMEType。

```
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';

function getImageMimeType(filePath: string): string {
  try {
    const source: image.ImageSource = image.createImageSource(filePath);
    const info: image.ImageInfo = source.getImageInfoSync();
    const mime: string = info.mimeType;
    source.release();
    return mime;
  } catch (error) {
    return "";
  }
}
```

**示例（C/C++）：**

解决获取图片格式异常问题，获取真实MIMEType。

在target\_link\_libraries依赖中添加libimage\_source.so和libimage\_common.so。

```
target_link_libraries(entry PUBLIC libimage_source.so libimage_common.so)
```

```
#include <cstdlib>
#include <cstring>
#include <multimedia/image_framework/image_source_native.h>
#include <multimedia/image_framework/image_common.h>

/**
 * @brief 通过文件描述符获取图片MIME类型。
 *
 * 返回的字符串由调用者负责释放（使用 free()）。
 *
 * @param fd 图片文件描述符，调用者负责打开和关闭fd。
 * @return 成功时返回动态分配的MIME类型字符串；失败时返回nullptr。
 */
static char* GetImageMimeType(int32_t fd)
{
    if (fd < 0) {
        return nullptr;
    }

    OH_ImageSourceNative *source = nullptr;
    Image_ErrorCode ret = OH_ImageSourceNative_CreateFromFd(fd, &source);
    if (ret != IMAGE_SUCCESS || source == nullptr) {
        return nullptr;
    }

    OH_ImageSource_Info *info = nullptr;
    ret = OH_ImageSourceInfo_Create(&info);
    if (ret != IMAGE_SUCCESS || info == nullptr) {
        OH_ImageSourceNative_Release(source);
        return nullptr;
    }

    ret = OH_ImageSourceNative_GetImageInfo(source, 0, info);
    if (ret != IMAGE_SUCCESS) {
        OH_ImageSourceInfo_Release(info);
        OH_ImageSourceNative_Release(source);
        return nullptr;
    }

    Image_MimeType mime = {nullptr, 0};
    ret = OH_ImageSourceInfo_GetMimeType(info, &mime);
    if (ret != IMAGE_SUCCESS || mime.data == nullptr) {
        OH_ImageSourceInfo_Release(info);
        OH_ImageSourceNative_Release(source);
        return nullptr;
    }

    char *result = static_cast<char *>(malloc(mime.size + 1));
    if (result == nullptr) {
        OH_ImageSourceInfo_Release(info);
        OH_ImageSourceNative_Release(source);
        return nullptr;
    }

    memcpy(result, mime.data, mime.size);
    result[mime.size] = '\0';

    OH_ImageSourceInfo_Release(info);
    OH_ImageSourceNative_Release(source);
    return result;
}
```

#### 如何判断图片是否为高像素图片？

**判断方式**

应用可通过图片宽高计算像素总数，并结合业务阈值判断图片是否属于高像素图片。本文示例以width \* height >= 100000000作为高像素图片判断条件，即图片像素总数不小于1亿。

该阈值不是固定标准，主要用于识别带来较高解码和渲染内存压力的图片。应用可根据目标设备内存、页面显示尺寸、性能指标和业务体验要求调整阈值。

需要注意，只有接入高像素原图获取后拿到的图片宽高才是原始图片的宽高，否则获取到的是转换后兼容性文件的宽高。接入方式可参考为什么通过PhotoPicker或URI获取高像素图片时得到约1200万像素或约1250万像素的JPEG图片？。

**示例（ArkTS）：**

```
import { image } from '@kit.ImageKit';

const HIGH_PIXEL_THRESHOLD: number = 100000000;

function isHighPixelImage(fd: number): boolean {
  let imageSource: image.ImageSource | undefined = undefined;
  try {
    imageSource = image.createImageSource(fd);
    const imageInfo: image.ImageInfo = imageSource.getImageInfoSync();
    const width: number = imageInfo.size.width;
    const height: number = imageInfo.size.height;
    const pixelCount: number = width * height;
    return pixelCount >= HIGH_PIXEL_THRESHOLD;
  } catch (error) {
    return false;
  } finally {
    imageSource?.release();
  }
}
```

**示例（C/C++）：**

在target\_link\_libraries依赖中添加libimage\_source.so。

```
target_link_libraries(entry PUBLIC libimage_source.so)
```

```
#include <cstdint>
#include <multimedia/image_framework/image_common.h>
#include <multimedia/image_framework/image_source_native.h>

static constexpr uint64_t HIGH_PIXEL_THRESHOLD = 100000000ULL;

/**
 * @brief 判断图片是否为高像素图。
 *
 * 判断规则：
 * width * height >= 100000000。
 *
 * @param fd 图片文件fd，调用者负责打开和关闭fd。
 * @return true 表示高像素图。
 * @return false 表示不是高像素图，或获取图片信息失败。
 */
static bool IsHighPixelImage(int32_t fd)
{
    if (fd < 0) {
        return false;
    }

    OH_ImageSourceNative *imageSource = nullptr;
    Image_ErrorCode ret = OH_ImageSourceNative_CreateFromFd(fd, &imageSource);
    if (ret != IMAGE_SUCCESS || imageSource == nullptr) {
        return false;
    }

    OH_ImageSource_Info *imageInfo = nullptr;
    ret = OH_ImageSourceInfo_Create(&imageInfo);
    if (ret != IMAGE_SUCCESS || imageInfo == nullptr) {
        OH_ImageSourceNative_Release(imageSource);
        return false;
    }

    ret = OH_ImageSourceNative_GetImageInfo(imageSource, 0, imageInfo);
    if (ret != IMAGE_SUCCESS) {
        OH_ImageSourceInfo_Release(imageInfo);
        OH_ImageSourceNative_Release(imageSource);
        return false;
    }

    uint32_t width = 0;
    uint32_t height = 0;
    ret = OH_ImageSourceInfo_GetWidth(imageInfo, &width);
    if (ret != IMAGE_SUCCESS) {
        OH_ImageSourceInfo_Release(imageInfo);
        OH_ImageSourceNative_Release(imageSource);
        return false;
    }

    ret = OH_ImageSourceInfo_GetHeight(imageInfo, &height);
    if (ret != IMAGE_SUCCESS) {
        OH_ImageSourceInfo_Release(imageInfo);
        OH_ImageSourceNative_Release(imageSource);
        return false;
    }

    uint64_t pixelCount = static_cast<uint64_t>(width) * static_cast<uint64_t>(height);
    bool isHighPixel = pixelCount >= HIGH_PIXEL_THRESHOLD;
    OH_ImageSourceInfo_Release(imageInfo);
    OH_ImageSourceNative_Release(imageSource);
    return isHighPixel;
}
```

#### 如何选择解码像素格式？

**适用场景**

图片解码接口在未指定desiredPixelFormat时，默认会按RGBA\_8888显示链路生成PixelMap。RGBA\_8888适合图片显示、编辑和通用图像处理，但高像素图片按RGBA\_8888解码时，内存通常按width \* height \* 4估算。

如果业务不需要透明通道，可根据后续处理链路选择更合适的格式：

| 解码像素格式 | 适用场景 | 内存估算（受硬件对齐影响，实际内存占用可能更高） |
| --- | --- | --- |
| image.PixelMapFormat.RGBA_8888 | 通用显示、编辑，或需要透明通道与高色彩精度的场景。 | 约4字节/像素。 |
| image.PixelMapFormat.RGB_565 | 无透明通道需求且色彩精度要求较低的预览、缩略图、列表图片等场景。 | 约2字节/像素。 |
| image.PixelMapFormat.NV21、image.PixelMapFormat.NV12 | 无透明通道需求，且后续需接入相机、视频、算法或硬件编解码等 YUV 处理链路的场景。 | 约2字节/像素。 |

**处理方式**

1.  用于常规显示或编辑时，优先使用RGBA\_8888，兼容性和处理便利性更好。
2.  仅用于无透明度预览，且可以接受较低色彩精度时，选择RGB\_565降低内存。
3.  进入相机、视频、算法或某些硬件编解码链路时，可优先选择NV21或NV12，避免不必要的RGBA与YUV转换。
4.  在指定目标像素格式时，需要确认目标处理链路和显示链路是否支持对应格式，做好充分验证。

**示例：**

```
import { image } from '@kit.ImageKit';

const decodingOptions: image.DecodingOptions = {
  desiredSize: { width: 0, height: 0 }, // 这是desiredSize的默认值，在该配置下会按照原图尺寸解码。
  desiredPixelFormat: image.PixelMapFormat.RGB_565 // 可根据具体需求配置不同解码格式。
};
```

#### 如何使用降采样或区域解码优化高像素图片内存占用？

**适用场景**

针对列表预览、详情页首屏、分享预览等仅需屏幕展示的场景，建议优先使用降采样解码。当业务涉及大图浏览、手势缩放或局部细节查看（如自定义图片查看器）时，应在降采样基础上叠加区域解码，以兼顾显示效果与内存安全。

**处理方式**

1.  使用降采样解码根据目标显示尺寸、屏幕分辨率和业务体验要求设计目标尺寸，例如8K、4K或2K。
2.  使用降采样解码时设置desiredSize，按目标尺寸生成PixelMap。注意，建议按照等比缩放原则配置该参数，避免图片变形；原图尺寸较小时，建议不配置该参数，避免冗余的缩放性能开销。
3.  用户放大或拖动图片时，区域解码根据当前交互状态计算需要显示的原图区域。
4.  区域解码使用desiredRegion只加载目标区域，并在交互状态变化后更新解码区域。
5.  区域解码会释放不再显示的区域图像，避免缓存过多PixelMap对象。
6.  （可选）如果业务需要控制内存或接入特定处理链路，可同时设置desiredPixelFormat。

降采样解码与区域解码示例请参考图片区域解码与下采样(C/C++)或图片区域解码与下采样(ArkTS)。

#### 高像素图片处理总结

1.  读取图片后先获取真实宽高，估算解码后内存，再决定处理策略。
2.  仅全图展示时请使用降采样解码，不建议直接按原始尺寸解码送显。
3.  需要局部高清时使用区域解码，建议避免因为查看局部细节一次性解码整张原图。
4.  根据业务选择像素格式。无透明度预览可考虑RGB\_565；YUV处理链路可考虑NV21或NV12。
5.  控制同一页面同时存在的PixelMap数量；切换图片、重新解码或页面销毁时释放资源。
6.  对上传、编辑、压缩、滤镜等链路，及时释放资源，避免同时保留多份大尺寸中间图。