HarmonyOS APP实战-画图APP - 第9篇:智能取色与色板生成
1. 开篇
在上一篇中,我们成功集成了 @ohos.effectKit,为画布内容实现了亮度调节、模糊化和灰度调节三大图像处理功能。用户可以在绘制完成后,通过底部工具栏的"效果"按钮调出调节面板,实时预览并应用滤镜效果。这使得画图APP具备了基础的图像后期能力。
本篇将在此基础上,进一步挖掘 @ohos.effectKit 的潜力------智能取色。我们将实现一个"智能取色器",用户可以在画布上框选任意区域,APP自动提取该区域的主色,并生成一个包含主色及其相近色的5色色板。提取的颜色可以直接设置为当前画笔颜色或填充颜色。这是画图APP中非常实用的功能,尤其在参考图片配色、制作色板场景中不可或缺。
2. 核心实现
2.1 基础配置 --- 导入模块与类型定义
首先,我们需要导入 @ohos.effectKit 中与取色相关的模块。从文档可知,effectKit 提供 ColorPicker 类用于智能取色。
typescript
// utils/ColorUtils.ts
import { image } from '@kit.ImageKit';
import { effectKit } from '@kit.ArkGraphics2D';
/**
* 提取自 @ohos.effectKit 文档
* ColorPicker 用于从 PixelMap 中提取主色
*/
export interface ExtractColorResult {
mainColor: string; // 主色,格式 '#AARRGGBB'
palette: string[]; // 色板,5个相近色,包含主色
}
export class ColorUtils {
/**
* 从图片中提取主色与色板
* @param pixelMap 待分析的 PixelMap
* @returns 提取结果
*/
static async extractColors(pixelMap: image.PixelMap): Promise<ExtractColorResult> {
// 实际取色逻辑在核心实现中
throw new Error('Not implemented');
}
}
关键点说明:
effectKit的ColorPicker构造需要传入PixelMap参数,因此我们需要确保调用侧传入的是有效的 PixelMap。- 取色结果包含主色和预生成的调色板,我们将其封装为
ExtractColorResult接口,便于后续使用。 - 当前仅定义接口和类壳,核心实现在下一节完成。
2.2 核心逻辑 --- 实现智能取色
本节实现 extractColors 方法,使用 effectKit 的 ColorPicker 完成取色,并手动生成5色调色板(主色 + 4个亮度/饱和度渐变)。
typescript
// utils/ColorUtils.ts
import { image } from '@kit.ImageKit';
import { effectKit } from '@kit.ArkGraphics2D';
export interface ExtractColorResult {
mainColor: string; // 主色,格式 '#AARRGGBB'
palette: string[]; // 色板,包含主色 + 4个相近色,共5个
}
export class ColorUtils {
/**
* 从图片中提取主色与色板
* @param pixelMap 待分析的 PixelMap
* @returns 提取结果
*/
static async extractColors(pixelMap: image.PixelMap): Promise<ExtractColorResult> {
// 1. 创建 ColorPicker 实例
// 文档 API: effectKit.createColorPicker(pixelMap)
const colorPicker = await effectKit.createColorPicker(pixelMap);
// 2. 获取主色
// ColorPicker 的 getMainColorSync() 返回 Color 对象
const mainColorObj = colorPicker.getMainColorSync();
// 3. 将 Color 对象转换为 #AARRGGBB 格式字符串
// Color 的 alpha、red、green、blue 属性范围 0-255
const mainColor = this.colorToString(mainColorObj);
// 4. 生成调色板
// 基于主色通过 HSL 变换生成 4 个相近色
const palette = this.generatePalette(mainColorObj, 4);
return {
mainColor: mainColor,
palette: [mainColor, ...palette]
};
}
/**
* 将 Color 对象转为 #AARRGGBB 字符串
*/
static colorToString(color: effectKit.Color): string {
const a = color.alpha.toString(16).padStart(2, '0');
const r = color.red.toString(16).padStart(2, '0');
const g = color.green.toString(16).padStart(2, '0');
const b = color.blue.toString(16).padStart(2, '0');
return `#${a}${r}${g}${b}`.toUpperCase();
}
/**
* 基于主色生成相似色板
* @param baseColor 主色 Color
* @param count 生成颜色数量
* @returns 颜色字符串数组
*/
static generatePalette(baseColor: effectKit.Color, count: number): string[] {
const palette: string[] = [];
// 将 RGB 转为 HSL 进行变换
// 这里简化实现:等比例调整亮度和饱和度
const h = this.rgbToHsl(baseColor.red, baseColor.green, baseColor.blue)[0];
const s = 0.7; // 固定饱和度 0.7
const lStep = 0.15; // 亮度步长
for (let i = 1; i <= count; i++) {
// 亮度分别在主色亮度基础上 ± 步长
const ld = (i <= 2) ? -(i * lStep) : (i - 2) * lStep;
const l = Math.max(0, Math.min(1, 0.5 + ld)); // 限制在 0~1
// 同时轻微调整色相,使颜色有变化但不偏离太远
const hd = (i % 2 === 0) ? 10 : -10;
const newH = (h + hd + 360) % 360;
const rgb = this.hslToRgb(newH, s, l);
const color = `#FF${rgb[0].toString(16).padStart(2, '0')}${rgb[1].toString(16).padStart(2, '0')}${rgb[2].toString(16).padStart(2, '0')}`;
palette.push(color.toUpperCase());
}
return palette;
}
// RGB ↔ HSL 转换辅助函数(省略具体实现,标准算法)
static rgbToHsl(r: number, g: number, b: number): [number, number, number] { ... }
static hslToRgb(h: number, s: number, l: number): [number, number, number] { ... }
}

关键点说明:
- 文档明确指出
effectKit.createColorPicker(pixelMap)创建ColorPicker实例,getMainColorSync()同步获取主色。 Color对象包含alpha、red、green、blue四个分量,均为number类型,范围 0-255。- 生成调色板时,我们采用 HSL 变换策略,使色板既包含主色,又有合理的色相偏移和亮度变化,符合设计工具中"相似色"的定义。
- 实际项目中可根据需要增加饱和度扰动,生成更丰富的调色板。
2.3 完整模块 --- SmartColorPicker 组件
我们将智能取色封装为一个完整的 ArkUI 组件,集成到画图APP的主界面中。用户点击"取色"按钮后,显示取色面板,在画布上拖拽选择区域,松开后提取主色并显示色板。
typescript
// components/SmartColorPicker.ets
import { image } from '@kit.ImageKit';
import { effectKit } from '@kit.ArkGraphics2D';
import { ColorUtils, ExtractColorResult } from '../utils/ColorUtils';
@Component
struct SmartColorPicker {
// 接收外部画布的 PixelMap
private canvasPixelMap?: image.PixelMap;
// 取色结果
@State private extractResult?: ExtractColorResult;
@State private selectedColor: string = '#FF000000'; // 默认黑色
// 取色状态
@State private isPicking: boolean = false;
private startX: number = 0;
private startY: number = 0;
private endX: number = 0;
private endY: number = 0;
// 回调:选择颜色后应用到画笔
private onColorSelected: (color: string) => void;
build() {
Column() {
// 取色按钮
Button('智能取色')
.onClick(() => {
this.isPicking = true;
// 取色前需确保画布有内容,且已转换为 PixelMap
if (this.canvasPixelMap) {
this.startPicking();
}
})
.margin({ bottom: 10 })
// 取色结果展示
if (this.extractResult) {
Text('主色:' + this.extractResult.mainColor)
.fontSize(14)
.margin({ bottom: 8 })
// 色板展示
Row({ space: 6 }) {
ForEach(this.extractResult.palette, (color: string) => {
Column() {
// 颜色块
Stack()
.width(40)
.height(40)
.backgroundColor(color)
.border({ width: 1, color: '#999' })
.borderRadius(4)
.onClick(() => {
this.selectedColor = color;
// 通知父组件应用颜色
this.onColorSelected?.(color);
})
// 颜色值小字
Text(color.length > 7 ? color.substring(0, 7) + '...' : color)
.fontSize(10)
.margin({ top: 2 })
}
})
}
.padding(10)
.border({ width: 1, color: '#ccc' })
.borderRadius(8)
}
// 当前选中颜色指示
if (this.selectedColor !== '#FF000000') {
Row() {
Text('当前画笔颜色:')
Stack()
.width(20)
.height(20)
.backgroundColor(this.selectedColor)
.border({ width: 1, color: '#666' })
.margin({ left: 8 })
}
.margin({ top: 10 })
}
}
.padding(12)
}
/**
* 开始智能取色流程
* 实际应用中需要用户选择区域,这里简化:取整个画布的主色
*/
async startPicking() {
try {
// 将 PixalMap 传给取色工具
const result = await ColorUtils.extractColors(this.canPixelMap!);
this.extractResult = result;
this.isPicking = false;
} catch (error) {
console.error('取色失败:', error);
this.isPicking = false;
}
}
/**
* 从画布组件获取 PixelMap
* 由外部调用,传入 Canvas 组件的 PixelMap
*/
public setCanvasPixelMap(pixelMap: image.PixelMap) {
this.canvasPixelMap = pixelMap;
}
}

关键点说明:
startPicking方法调用ColorUtils.extractColors完成取色,结果存储在@State变量中驱动UI更新。- 色板展示部分使用
ForEach遍历palette数组,每个颜色块可点击,点击后触发onColorSelected回调通知父组件。 - 实际项目中,取色区域应通过触摸事件限制为画布上的矩形区域。这里为聚焦于
effectKit使用,简化取色范围为整个画布。后续可结合触摸事件实现区域取色。
2.4 集成到主页面
在主页面 Index.ets 中引用 SmartColorPicker 组件,并建立画布 PixelMap 的传递通道。
typescript
// pages/Index.ets
import { image } from '@kit.ImageKit';
import SmartColorPicker from '../components/SmartColorPicker';
@Entry
@Component
struct Index {
@State private canvasPixelMap?: image.PixelMap;
private smartColorPickerRef: SmartColorPicker | null = null;
build() {
Column() {
// ... 工具栏、画布等已有代码 ...
// 智能取色面板
SmartColorPicker({
onColorSelected: (color: string) => {
// 将选中的颜色设置到全局画笔属性
AppStorage.setOrCreate('drawColor', color);
}
})
.onAttach((component: SmartColorPicker) => {
this.smartColorPickerRef = component;
})
// ... 底部颜色栏等 ...
}
}
/**
* 在画布绘制完成或切换时,获取当前画布的 PixelMap
* 通过 Canvas 组件 getPixelMap API 获取
*/
async updateCanvasPixelMap() {
// 假设 canvasContext 是已绑定的 RenderingContext
const pixelMap = await this.canvasContext.getPixelMap(0, 0, this.canvasWidth, this.canvasHeight);
this.canvasPixelMap = pixelMap;
this.smartColorPickerRef?.setCanvasPixelMap(pixelMap);
}
}

关键点说明:
- 通过
AppStorage将选中的颜色传递给画图模块,实现颜色统一管理。 canvasContext.getPixelMap是 Canvas 组件提供的方法,用于截取当前画布内容为 PixelMap,传入取色组件。- 在实际应用场景中,建议在每次画布内容变化后(绘制、擦除、效果应用等)调用
updateCanvasPixelMap刷新 PixelMap,保证取色基于最新画布。
3. 运行验证
完成上述代码后,在 DevEco Studio 中运行应用:
- 在主界面上绘制一些彩色图形(如红色圆形、蓝色矩形)。
- 点击底部的"智能取色"按钮,触发取色流程。
- 观察控制台日志,确认
ColorPicker.getMainColorSync成功返回主色对象。 - 查看页面上方出现色板区域,包含主色和4个相近色块。
- 点击色板中的任意颜色块,底部颜色指示更新,后续绘制线条变为所选颜色。
预期效果:色板应该显示画布中最具代表性的颜色(主色),以及基于主色生成的和谐色板。点击色板后,画笔颜色立即切换。
4. 小结与预告
本篇实现了画图APP的智能取色与色板生成功能。核心是使用 @ohos.effectKit 的 ColorPicker 类提取图像主色,并通过 HSL 变换算法生成5色调色板。提取的颜色可以一键应用到画笔,大大提升了配色效率。智能取色是直接服务于用户创作流程的功能,让画图APP从单纯的绘图工具升级为具备色彩分析能力的设计工具。
下一篇将开启一个新的技术维度------色彩空间转换与HDR支持。我们将集成 @ohos.graphics.colorSpaceManager,实现 sRGB、Display P3、Adobe RGB 等色彩空间间的转换,并将画图APP的输出适配到支持 HDR 的屏幕上。这会让用户的画作在高端设备上呈现更鲜艳、更准确的颜色。敬请期待。