SDR 图像怎么变成 HDR?HarmonyOS 色彩空间转换全流程拆解
文章目录SDR 和 HDR 傻傻分不清楚色彩空间转换路径图整体执行流程核心代码详解第一步加载图片并自动识别 HDR/SDR第二步读取色彩空间并决定可转换目标第三步执行色彩空间转换两个核心 Native 接口的区别踩坑记录写在最后第一次搞色彩空间转换的时候我被几个问题搞懵了SDR 和 HDR 到底区别在哪为什么有的图能转有的不能转hdrAIHDR和getNativeImgFromPixelMap什么区别搞完UsingImageProcessingToProcessImages这个 demo 之后基本都搞明白了写下来备忘。SDR 和 HDR 傻傻分不清楚先说人话SDRStandard Dynamic Range普通图片就是我们日常看到的 JPEG/PNG亮度范围有限最亮和最暗差不多差 1000:1HDRHigh Dynamic Range高动态范围图能显示更高的亮度、更暗的暗部差距可以到 10000:1 甚至更高色彩空间描述一张图能表达多少种颜色。sRGB 是最普通的Display P3 比 sRGB 大约大 25%BT.2020 覆盖范围更广HDR 图通常用 BT.2020换个比喻理解SDR 就像普通 LED 电视HDR 就像 OLED同一个场景 HDR 能看到更多细节。色彩空间转换路径图不是所有色彩空间之间都可以互相转换官方有限制。这张图总结了可以转换的路径关键规律SDR ↔ SDR用getNativeImgFromPixelMap直接像素搬运色彩映射SDR → HDRBT2020_HLG用hdrAIHDRAI 算法推断高光细节门槛高一些HDR → SDR也是getNativeImgFromPixelMap但要注意 SDR 源图需要先复制像素整体执行流程核心代码详解第一步加载图片并自动识别 HDR/SDRimport{image}fromkit.ImageKit;import{photoAccessHelper}fromkit.MediaLibraryKit;import{fileIo}fromkit.CoreFileKit;pickerPicture():void{constphotoSelectOptionsnewphotoAccessHelper.PhotoSelectOptions();photoSelectOptions.MIMETypephotoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;photoSelectOptions.maxSelectNumber1;constphotoViewPickernewphotoAccessHelper.PhotoViewPicker();photoViewPicker.select(photoSelectOptions).then((result:photoAccessHelper.PhotoSelectResult){// 通过 fd 而非 URI 创建 ImageSource性能更好letfdfileIo.openSync(result.photoUris[0],fileIo.OpenMode.READ_ONLY);constimageSourceimage.createImageSource(fd.fd);letoption:image.DecodingOptions{};option.index0;// 关键AUTO 模式让系统自动判断是 SDR 还是 HDRoption.desiredDynamicRangeimage.DecodingDynamicRange.AUTO;this.pixelMapSrcimageSource.createPixelMapSync(option);this.getColorSpace();});}第二步读取色彩空间并决定可转换目标import{colorSpaceManager}fromkit.ArkGraphics2D;getColorSpace():void{// 判断 HDR/SDRconstimageInfo:image.ImageInfothis.pixelMapSrc!.getImageInfoSync();if(imageInfo.isHdr){this.textHDR;// 存入全局其他页面元数据生成、HDR层转换可以直接用this.hdrPixelMapthis.pixelMapSrc;}else{this.textSDR;}// 读取当前色彩空间this.srcColorSpacethis.pixelMapSrc!.getColorSpace().getColorSpaceName();// 根据色彩空间决定哪些目标可用switch(this.srcColorSpace){casecolorSpaceManager.ColorSpace.SRGB:// sRGB 可以转 DisplayP3SDR→SDR 或 BT2020_HLGSDR→HDRthis.canConvertP3true;this.canConvertHLGtrue;this.isCanConvertingtrue;break;casecolorSpaceManager.ColorSpace.DISPLAY_P3:// DisplayP3 可以转 sRGB 或 HDRthis.canConvertSRGBtrue;this.canConvertHLGtrue;this.isCanConvertingtrue;break;casecolorSpaceManager.ColorSpace.BT2020_HLG:// HDR 可以转回 SDRthis.canConvertSRGBtrue;this.canConvertP3true;this.isCanConvertingtrue;break;default:this.isCanConvertingfalse;// 不支持的色彩空间}}第三步执行色彩空间转换importnativePixfromlibentry.so;colorspaceConvert():void{this.isShowfalse;this.aiHdrErrorfalse;consttargetNamethis.targetColorSpace?.getColorSpaceName();if(targetName!colorSpaceManager.ColorSpace.BT2020_HLG){// SDR ↔ SDR 转换 // 创建目标 PixelMapRGBA_8888 格式letoutPutPixelMapnativePix.createPixelMap(this.inputHeight,this.inputWidth);// 设置元数据类型为 NONE非 HDRoutPutPixelMap.setMetadata(image.HdrMetadataKey.HDR_METADATA_TYPE,image.HdrMetadataType.NONE);// 指定目标色彩空间outPutPixelMap.setColorSpace(this.targetColorSpace);if(this.textSDR){// SDR 源图需要先把像素数据复制到新 PixelMap再转换letsrcPixelMapnativePix.createPixelMap(this.inputHeight,this.inputWidth);letbufferthis.inputHeight*this.inputWidth*4;constreadBuffer:ArrayBuffernewArrayBuffer(buffer);this.pixelMapSrc!.readPixelsToBufferSync(readBuffer);srcPixelMap.writeBufferToPixelsSync(readBuffer);nativePix.getNativeImgFromPixelMap(srcPixelMap,outPutPixelMap);}else{// HDR 源图可以直接传入nativePix.getNativeImgFromPixelMap(this.pixelMapSrc,outPutPixelMap);}this.pixelMapDstoutPutPixelMap;this.isShowtrue;}else{// SDR → HDRAI 超分// 目标格式必须是 RGBA_1010102HDR 专用格式每通道 10 bitletopts:image.InitializationOptions{editable:true,pixelFormat:image.PixelMapFormat.RGBA_1010102,size:{height:this.inputHeight,width:this.inputWidth}};letoutPutPixelMapimage.createPixelMapSync(opts);// 设置 HDR 元数据类型outPutPixelMap.setMetadata(image.HdrMetadataKey.HDR_METADATA_TYPE,image.HdrMetadataType.ALTERNATE// 单层 HDRAI 生成);outPutPixelMap.setColorSpace(this.targetColorSpace);// 同样需要先复制源图像素letsrcPixelMapnativePix.createPixelMap(this.inputHeight,this.inputWidth);constreadBuffer:ArrayBuffernewArrayBuffer(this.inputHeight*this.inputWidth*4);this.pixelMapSrc!.readPixelsToBufferSync(readBuffer);srcPixelMap.writeBufferToPixelsSync(readBuffer);leterrorCode:numbernativePix.hdrAIHDR(srcPixelMap,outPutPixelMap);if(errorCode0){this.pixelMapDstoutPutPixelMap;// 同时更新全局 HDR 图让元数据生成页可用this.hdrPixelMapoutPutPixelMap;this.isShowtrue;}else{// AI 超分可能因为图片内容不够丰富而失败this.aiHdrErrortrue;}}}两个核心 Native 接口的区别接口用途输入格式输出格式备注getNativeImgFromPixelMap(src, dst)SDR↔SDR、HDR→SDRRGBA_8888RGBA_8888直接色域映射确定成功hdrAIHDR(src, dst)SDR→HDRRGBA_8888 复制体RGBA_1010102AI 推断可能返回非 0 错误码踩坑记录坑1SDR 源图必须先复制像素再传入不能直接把pixelMapSrc传给 Native 层的转换接口原因是 Native 层会修改传入的 PixelMap 数据导致原图被污染。正确做法是先readPixelsToBufferSync读出来再写到新建的 PixelMap 里。坑2HDR 目标必须用 RGBA_1010102SDR 用 RGBA_8888每通道 8 bitHDR 必须用 RGBA_1010102每通道 10 bit。搞错格式 Native 层会直接返回错误。坑3hdrAIHDR 不保证成功AI 超分对源图内容有要求如果图片是全黑、全白或者内容太简单算法推断不出高光细节会返回非 0 错误码。UI 上要做好错误提示不能假设转换一定成功。坑4desiredDynamicRange: AUTO很重要解码时如果不设置AUTOHDR 图会被降级为 SDR 解码imageInfo.isHdr将永远是false。写在最后色彩空间转换这块刚开始确实比较绕主要是概念太多。记住核心逻辑就好SDR ↔ SDR 走getNativeImgFromPixelMapSDR → HDR 走hdrAIHDR两个接口对应的像素格式不一样。StorageLink跨页面共享 HDR 图的设计思路也很值得借鉴比传统回调方便多了。