目录

如何正确处理高像素图片

概述

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

处理原则

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

常见问题

[h2]适用高像素原图的场景

适用场景

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

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

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

处理方式

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

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

可能原因

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

解决措施

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

[h2]为什么通过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 to get high resolution asset: code: ${err.code}, message: ${err.message}`);
  }
}

[h2]获取到图片后,格式、尺寸或元数据判断异常

可能原因

  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/image_source_native.h>
#include <multimedia/image_framework/image/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;
}

[h2]如何判断图片是否为高像素图片?

判断方式

应用可通过图片宽高计算像素总数,并结合业务阈值判断图片是否属于高像素图片。本文示例以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/image_common.h>
#include <multimedia/image_framework/image/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;
}

[h2]如何选择解码像素格式?

适用场景

图片解码接口在未指定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 // 可根据具体需求配置不同解码格式。
};

[h2]如何使用降采样或区域解码优化高像素图片内存占用?

适用场景

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

处理方式

  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. 对上传、编辑、压缩、滤镜等链路,及时释放资源,避免同时保留多份大尺寸中间图。