HarmonyOS APP实战-基于Image Kit的图像处理APP - 第7篇:图片色彩调整(亮度/对比度/饱和度)
HarmonyOS APP实战-基于Image Kit的图像处理APP - 第7篇亮度对比度饱和度调整1. 开篇在第6篇中我们完成了滤镜与色彩矩阵功能通过PixelMap.colorMatrix属性我们预定义了灰度、怀旧、冷色三种色彩矩阵并构建了FilterPage让用户点击即可切换滤镜效果。该篇产出的ColorMatrixUtils模块已整齐地集成在工程中。但色彩矩阵虽然能实现整体色调转换却无法单独控制亮度、对比度、饱和度这三个最基础的图像属性。用户可能希望让灰暗的照片更明亮或者让色彩更鲜艳。本篇将使用两种方式实现这些调整对于亮度和饱和度我们使用经典的像素点操作通过PixelMap.readPixelsToBuffer读取像素数据、算术处理后调用writeBufferToPixels写回对于对比度我们复用colorMatrix的矩阵变换能力。最终产出可滑动的三轴调节面板并实时显示数值。2. 核心实现2.1 基础配置导入模块与工具函数初始化在工程pages/目录下新建AdjustPage.ets并确保已安装ohos.multimedia.image和ohos.file.fs。我们需要从 PixelMap 中读取像素数据处理后再写回。// AdjustPage.ets — 顶部导入与类型定义importimagefromohos.multimedia.image;import{BusinessError}fromohos.base;// 定义调整参数类型exportinterfaceAdjustParams{brightness:number;// -100 ~ 1000为原始contrast:number;// -100 ~ 1000为原始saturation:number;// -100 ~ 1000为原始}// 默认参数无调整exportconstdefaultParams:AdjustParams{brightness:0,contrast:0,saturation:0};关键点说明image模块提供了PixelMap的全部操作能力。AdjustParams定义了三个调整值的范围亮度明亮偏移、对比度增强或减弱反差、饱和度色彩鲜艳程度。默认值均为 0表示不产生任何变化。2.2 核心逻辑三个属性的像素级调整运算ColorAdjustUtils模块包含实际的像素算术逻辑。对于亮度和饱和度我们遍历每个像素的 RGBA 分量做直接算术对于对比度通过构建色彩矩阵使用colorMatrix属性实现。// ColorAdjustUtils.ets — 核心调整运算importimagefromohos.multimedia.image;exportclassColorAdjustUtils{// 在 PixelMap 中应用亮度、对比度、饱和度调整staticasyncapplyAdjustments(pixelMap:image.PixelMap,params:AdjustParams):Promisevoid{// 1. 先应用对比度通过 colorMatrixawaitthis.applyContrast(pixelMap,params.contrast);// 2. 再应用亮度和饱和度通过像素点操作awaitthis.applyBrightnessAndSaturation(pixelMap,params.brightness,params.saturation);}// 对比度使用 colorMatrix 的矩阵变换privatestaticasyncapplyContrast(pixelMap:image.PixelMap,contrast:number):Promisevoid{if(contrast0)return;// 将对比度值 -100~100 映射为矩阵系数1 contrast/100constfactor1contrast/100;// 平移量用于保持中点亮度不变consttranslate(1-factor)*128;// 构建色彩矩阵RGBA 各通道乘 factor translateconstmatrix:number[][factor,0,0,0,translate,0,factor,0,0,translate,0,0,factor,0,translate,0,0,0,1,0];// 应用矩阵pixelMap.colorMatrixmatrix;}// 亮度和饱和度逐像素操作privatestaticasyncapplyBrightnessAndSaturation(pixelMap:image.PixelMap,brightness:number,// -100~100saturation:number// -100~100):Promisevoid{if(brightness0saturation0)return;// 3.1 读取所有像素数据到 ArrayBufferconstpixelBytespixelMap.getPixelBytesNumber();// 获取总字节数constbuffer:ArrayBufferawaitpixelMap.readPixelsToBuffer(pixelBytes);// 3.2 将 ArrayBuffer 转为 Uint8Array 方便逐个字节操作constdatanewUint8Array(buffer);// 3.3 遍历每个像素每个像素占4字节R, G, B, AconstbrightnessOffsetbrightness*2.55;// 映射到 0~255 范围constsaturationFactor1saturation/100;for(leti0;idata.length;i4){// 原像素值letrdata[i];letgdata[i1];letbdata[i2];// A 通道保持不变// 3.3.1 应用亮度直接加偏移量rthis.clampPixel(rbrightnessOffset);gthis.clampPixel(gbrightnessOffset);bthis.clampPixel(bbrightnessOffset);// 3.3.2 应用饱和度计算灰度值后混合// 灰度公式0.299R 0.587G 0.114Bconstgray0.299*r0.587*g0.114*b;rthis.clampPixel(gray(r-gray)*saturationFactor);gthis.clampPixel(gray(g-gray)*saturationFactor);bthis.clampPixel(gray(b-gray)*saturationFactor);// 写回缓冲区data[i]r;data[i1]g;data[i2]b;}// 3.4 将处理后的数据写回 PixelMapawaitpixelMap.writeBufferToPixels(buffer);}// 辅助方法将像素值钳制到 0~255privatestaticclampPixel(value:number):number{returnMath.min(255,Math.max(0,Math.round(value)));}}关键点说明pixelMap.readPixelsToBuffer()返回PromiseArrayBuffer需要await。参数pixelBytes可通过pixelMap.getPixelBytesNumber()获得。写回时使用writeBufferToPixels()传入的ArrayBuffer大小必须与读取时一致。饱和度算法先计算灰度值再对 RGB 分别加权混合saturationFactor 1时颜色更鲜艳 1时更暗淡。对比度使用colorMatrix属性实现矩阵乘法的核心是新像素 原值 * factor translate使中值128保持不变而高低值被拉伸或压缩。每次调整前需确保pixelMap未被释放调整顺序为先矩阵变换后像素操作避免冲突。2.3 完整页面带滑块的 AdjustPage现在构建AdjustPage包含三个滑块Slider分别调节亮度、对比度、饱和度并实时显示数值。页面内含预览图和“应用”按钮。// AdjustPage.ets — 完整页面组件importimagefromohos.multimedia.image;import{ColorAdjustUtils}from./ColorAdjustUtils;import{AdjustParams,defaultParams}from./AdjustPage;// 注意循环引用处理实际可抽离为单独模块Componentexportstruct AdjustPage{// 接收外部传入的 PixelMapprivatepixelMap:image.PixelMap;// 状态变量StatebrightnessValue:number0;StatecontrastValue:number0;StatesaturationValue:number0;StatepreviewMap:image.PixelMap|nullnull;// 用于预览的副本aboutToAppear(){// 创建 PixelMap 副本避免修改原图深拷贝简单示例可通过 imagePacker 编解码实现此处简化为直接引用this.previewMapthis.pixelMap;// 生产环境需深拷贝此处聚焦主流程}build(){Column(){// 图片预览区域if(this.previewMap){Image(this.previewMap).width(100%).height(300).objectFit(ImageFit.Contain).margin({top:10})}// 控制面板Column(){// 亮度滑块Row(){Text(亮度)Slider({value:this.brightnessValue,min:-100,max:100,step:1,style:SliderStyle.OutSet}).width(70%).onChange((val:number){this.brightnessValueval;this.applyAdjustments();})Text(${this.brightnessValue}).width(50).textAlign(TextAlign.End)}.padding(10)// 对比度滑块Row(){Text(对比度)Slider({value:this.contrastValue,min:-100,max:100,step:1,style:SliderStyle.OutSet}).width(70%).onChange((val:number){this.contrastValueval;this.applyAdjustments();})Text(${this.contrastValue}).width(50).textAlign(TextAlign.End)}.padding(10)// 饱和度滑块Row(){Text(饱和度)Slider({value:this.saturationValue,min:-100,max:100,step:1,style:SliderStyle.OutSet}).width(70%).onChange((val:number){this.saturationValueval;this.applyAdjustments();})Text(${this.saturationValue}).width(50).textAlign(TextAlign.End)}.padding(10)}.width(100%).padding(20)// 应用按钮Button(应用调整).margin({top:20}).onClick(async(){// 将调整后的 PixelMap 回传给父组件// 此处假设调用了父组件回调保存})}.width(100%).height(100%).padding(10)}// 实时应用调整预览privateasyncapplyAdjustments(){if(!this.previewMap)return;// 因为预览是实时需要基于原始副本重新计算否则会累积误差// 此处简化每次从 this.pixelMap 深拷贝后应用实际需缓存原始constparams:AdjustParams{brightness:this.brightnessValue,contrast:this.contrastValue,saturation:this.saturationValue};// 深拷贝示例使用 packer 编解码省略细节// const copyMap await PixelMapUtils.copyPixelMap(this.pixelMap);awaitColorAdjustUtils.applyAdjustments(this.previewMap,params);}}关键点说明Slider组件的value双向绑定到State变量onChange触发applyAdjustments。previewMap用于显示当前调整效果实际生产环境需要深拷贝原始PixelMap以避免每次调整在原有上叠加。页面提供“应用”按钮在真实场景中应将调整结果保存到原始数据或导出。3. 运行验证在IndexPage中选择一张图片进入编辑。点击“亮度/对比度/饱和度”菜单项跳转到AdjustPage。拖动亮度滑块至50预览图亮度提升拖动对比度至30图像反差增强饱和度滑至40花朵颜色更鲜艳。所有数值在滑块右侧实时显示调整过程流畅无卡顿。若调整后图像出现色块或异常请检查clampPixel是否钳制正确以及readPixelsToBuffer后是否未等待写入完成。4. 小结与预告本篇实现了三个最常用的图像属性调整分别通过像素点操作和色彩矩阵两种方式完成。ColorAdjustUtils模块已完整可用配合AdjustPage的滑块交互用户可以直观地控制图像的亮部、暗部平衡与色彩鲜艳度。该功能直接补全了图像处理 APP 的核心调节能力是第6篇滤镜功能的自然延伸。第8篇将聚焦“图片编码导出至相册”使用image.createImagePacker将处理后的PixelMap编码为 JPEG 或 PNG并调用PhotoAccessHelper保存到系统相册真正完成“编辑-导出”的闭环。敬请期待

相关新闻