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 — 顶部导入与类型定义
import image from '@ohos.multimedia.image';
import { BusinessError } from '@ohos.base';
// 定义调整参数类型
export interface AdjustParams {
brightness: number; // -100 ~ 100,0为原始
contrast: number; // -100 ~ 100,0为原始
saturation: number; // -100 ~ 100,0为原始
}
// 默认参数(无调整)
export const defaultParams: AdjustParams = {
brightness: 0,
contrast: 0,
saturation: 0
};
关键点说明
image模块提供了PixelMap的全部操作能力。AdjustParams定义了三个调整值的范围:亮度(明亮偏移)、对比度(增强或减弱反差)、饱和度(色彩鲜艳程度)。- 默认值均为 0,表示不产生任何变化。
2.2 核心逻辑:三个属性的像素级调整运算
ColorAdjustUtils 模块包含实际的像素算术逻辑。对于亮度和饱和度,我们遍历每个像素的 RGBA 分量做直接算术;对于对比度,通过构建色彩矩阵使用 colorMatrix 属性实现。
// ColorAdjustUtils.ets — 核心调整运算
import image from '@ohos.multimedia.image';
export class ColorAdjustUtils {
// 在 PixelMap 中应用亮度、对比度、饱和度调整
static async applyAdjustments(
pixelMap: image.PixelMap,
params: AdjustParams
): Promise<void> {
// 1. 先应用对比度(通过 colorMatrix)
await this.applyContrast(pixelMap, params.contrast);
// 2. 再应用亮度和饱和度(通过像素点操作)
await this.applyBrightnessAndSaturation(pixelMap, params.brightness, params.saturation);
}
// 对比度:使用 colorMatrix 的矩阵变换
private static async applyContrast(pixelMap: image.PixelMap, contrast: number): Promise<void> {
if (contrast === 0) return;
// 将对比度值 -100~100 映射为矩阵系数:1 + contrast/100
const factor = 1 + contrast / 100;
// 平移量:用于保持中点亮度不变
const translate = (1 - factor) * 128;
// 构建色彩矩阵:RGBA 各通道乘 factor + translate
const matrix: number[] = [
factor, 0, 0, 0, translate,
0, factor, 0, 0, translate,
0, 0, factor, 0, translate,
0, 0, 0, 1, 0
];
// 应用矩阵
pixelMap.colorMatrix = matrix;
}
// 亮度和饱和度:逐像素操作
private static async applyBrightnessAndSaturation(
pixelMap: image.PixelMap,
brightness: number, // -100~100
saturation: number // -100~100
): Promise<void> {
if (brightness === 0 && saturation === 0) return;
// 3.1 读取所有像素数据到 ArrayBuffer
const pixelBytes = pixelMap.getPixelBytesNumber(); // 获取总字节数
const buffer: ArrayBuffer = await pixelMap.readPixelsToBuffer(pixelBytes);
// 3.2 将 ArrayBuffer 转为 Uint8Array 方便逐个字节操作
const data = new Uint8Array(buffer);
// 3.3 遍历每个像素(每个像素占4字节:R, G, B, A)
const brightnessOffset = brightness * 2.55; // 映射到 0~255 范围
const saturationFactor = 1 + saturation / 100;
for (let i = 0; i < data.length; i += 4) {
// 原像素值
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// A 通道保持不变
// 3.3.1 应用亮度:直接加偏移量
r = this.clampPixel(r + brightnessOffset);
g = this.clampPixel(g + brightnessOffset);
b = this.clampPixel(b + brightnessOffset);
// 3.3.2 应用饱和度:计算灰度值后混合
// 灰度公式:0.299R + 0.587G + 0.114B
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
r = this.clampPixel(gray + (r - gray) * saturationFactor);
g = this.clampPixel(gray + (g - gray) * saturationFactor);
b = this.clampPixel(gray + (b - gray) * saturationFactor);
// 写回缓冲区
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
}
// 3.4 将处理后的数据写回 PixelMap
await pixelMap.writeBufferToPixels(buffer);
}
// 辅助方法:将像素值钳制到 0~255
private static clampPixel(value: number): number {
return Math.min(255, Math.max(0, Math.round(value)));
}
}
关键点说明
pixelMap.readPixelsToBuffer()返回Promise<ArrayBuffer>,需要await。参数pixelBytes可通过pixelMap.getPixelBytesNumber()获得。- 写回时使用
writeBufferToPixels(),传入的ArrayBuffer大小必须与读取时一致。 - 饱和度算法:先计算灰度值,再对 RGB 分别加权混合,
saturationFactor > 1时颜色更鲜艳,< 1时更暗淡。 - 对比度使用
colorMatrix属性实现,矩阵乘法的核心是:新像素 = 原值 * factor + translate,使中值(128)保持不变,而高低值被拉伸或压缩。 - 每次调整前需确保
pixelMap未被释放,调整顺序为先矩阵变换后像素操作,避免冲突。
2.3 完整页面:带滑块的 AdjustPage
现在构建 AdjustPage,包含三个滑块(Slider)分别调节亮度、对比度、饱和度,并实时显示数值。页面内含预览图和“应用”按钮。
// AdjustPage.ets — 完整页面组件
import image from '@ohos.multimedia.image';
import { ColorAdjustUtils } from './ColorAdjustUtils';
import { AdjustParams, defaultParams } from './AdjustPage'; // 注意循环引用处理,实际可抽离为单独模块
@Component
export struct AdjustPage {
// 接收外部传入的 PixelMap
private pixelMap: image.PixelMap;
// 状态变量
@State brightnessValue: number = 0;
@State contrastValue: number = 0;
@State saturationValue: number = 0;
@State previewMap: image.PixelMap | null = null; // 用于预览的副本
aboutToAppear() {
// 创建 PixelMap 副本避免修改原图(深拷贝简单示例:可通过 imagePacker 编解码实现,此处简化为直接引用)
this.previewMap = this.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.brightnessValue = val;
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.contrastValue = val;
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.saturationValue = val;
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)
}
// 实时应用调整(预览)
private async applyAdjustments() {
if (!this.previewMap) return;
// 因为预览是实时,需要基于原始副本重新计算,否则会累积误差
// 此处简化:每次从 this.pixelMap 深拷贝后应用(实际需缓存原始)
const params: AdjustParams = {
brightness: this.brightnessValue,
contrast: this.contrastValue,
saturation: this.saturationValue
};
// 深拷贝示例(使用 packer 编解码,省略细节)
// const copyMap = await PixelMapUtils.copyPixelMap(this.pixelMap);
await ColorAdjustUtils.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 保存到系统相册,真正完成“编辑-导出”的闭环。敬请期待!
更多推荐



所有评论(0)