HarmonyOS APP实战-画图APP - 第9篇:智能取色与色板生成
1. 开篇
在上一篇中,我们成功集成了@ohos.effectKit,为画布内容实现了亮度调节、模糊化和灰度调节三大图像处理功能。用户可以在绘制完成后,通过底部工具栏的“效果”按钮调出调节面板,实时预览并应用滤镜效果。这使得画图APP具备了基础的图像后期能力。
本篇将在此基础上,进一步挖掘@ohos.effectKit的潜力——智能取色。我们将实现一个“智能取色器”,用户可以在画布上框选任意区域,APP自动提取该区域的主色,并生成一个包含主色及其相近色的5色色板。提取的颜色可以直接设置为当前画笔颜色或填充颜色。这是画图APP中非常实用的功能,尤其在参考图片配色、制作色板场景中不可或缺。
2. 核心实现
2.1 基础配置 — 导入模块与类型定义
首先,我们需要导入@ohos.effectKit中与取色相关的模块。从文档可知,effectKit提供ColorPicker类用于智能取色。
// utils/ColorUtils.tsimport{image}from'@kit.ImageKit';import{effectKit}from'@kit.ArkGraphics2D';/** * 提取自 @ohos.effectKit 文档 * ColorPicker 用于从 PixelMap 中提取主色 */exportinterfaceExtractColorResult{mainColor:string;// 主色,格式 '#AARRGGBB'palette:string[];// 色板,5个相近色,包含主色}exportclassColorUtils{/** * 从图片中提取主色与色板 * @param pixelMap 待分析的 PixelMap * @returns 提取结果 */staticasyncextractColors(pixelMap:image.PixelMap):Promise<ExtractColorResult>{// 实际取色逻辑在核心实现中thrownewError('Not implemented');}}关键点说明:
effectKit的ColorPicker构造需要传入PixelMap参数,因此我们需要确保调用侧传入的是有效的 PixelMap。- 取色结果包含主色和预生成的调色板,我们将其封装为
ExtractColorResult接口,便于后续使用。 - 当前仅定义接口和类壳,核心实现在下一节完成。
2.2 核心逻辑 — 实现智能取色
本节实现extractColors方法,使用effectKit的ColorPicker完成取色,并手动生成5色调色板(主色 + 4个亮度/饱和度渐变)。
// utils/ColorUtils.tsimport{image}from'@kit.ImageKit';import{effectKit}from'@kit.ArkGraphics2D';exportinterfaceExtractColorResult{mainColor:string;// 主色,格式 '#AARRGGBB'palette:string[];// 色板,包含主色 + 4个相近色,共5个}exportclassColorUtils{/** * 从图片中提取主色与色板 * @param pixelMap 待分析的 PixelMap * @returns 提取结果 */staticasyncextractColors(pixelMap:image.PixelMap):Promise<ExtractColorResult>{// 1. 创建 ColorPicker 实例// 文档 API: effectKit.createColorPicker(pixelMap)constcolorPicker=awaiteffectKit.createColorPicker(pixelMap);// 2. 获取主色// ColorPicker 的 getMainColorSync() 返回 Color 对象constmainColorObj=colorPicker.getMainColorSync();// 3. 将 Color 对象转换为 #AARRGGBB 格式字符串// Color 的 alpha、red、green、blue 属性范围 0-255constmainColor=this.colorToString(mainColorObj);// 4. 生成调色板// 基于主色通过 HSL 变换生成 4 个相近色constpalette=this.generatePalette(mainColorObj,4);return{mainColor:mainColor,palette:[mainColor,...palette]};}/** * 将 Color 对象转为 #AARRGGBB 字符串 */staticcolorToString(color:effectKit.Color):string{consta=color.alpha.toString(16).padStart(2,'0');constr=color.red.toString(16).padStart(2,'0');constg=color.green.toString(16).padStart(2,'0');constb=color.blue.toString(16).padStart(2,'0');return`#${a}${r}${g}${b}`.toUpperCase();}/** * 基于主色生成相似色板 * @param baseColor 主色 Color * @param count 生成颜色数量 * @returns 颜色字符串数组 */staticgeneratePalette(baseColor:effectKit.Color,count:number):string[]{constpalette:string[]=[];// 将 RGB 转为 HSL 进行变换// 这里简化实现:等比例调整亮度和饱和度consth=this.rgbToHsl(baseColor.red,baseColor.green,baseColor.blue)[0];consts=0.7;// 固定饱和度 0.7constlStep=0.15;// 亮度步长for(leti=1;i<=count;i++){// 亮度分别在主色亮度基础上 ± 步长constld=(i<=2)?-(i*lStep):(i-2)*lStep;constl=Math.max(0,Math.min(1,0.5+ld));// 限制在 0~1// 同时轻微调整色相,使颜色有变化但不偏离太远consthd=(i%2===0)?10:-10;constnewH=(h+hd+360)%360;constrgb=this.hslToRgb(newH,s,l);constcolor=`#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());}returnpalette;}// RGB ↔ HSL 转换辅助函数(省略具体实现,标准算法)staticrgbToHsl(r:number,g:number,b:number):[number,number,number]{...}statichslToRgb(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的主界面中。用户点击“取色”按钮后,显示取色面板,在画布上拖拽选择区域,松开后提取主色并显示色板。
// components/SmartColorPicker.etsimport{image}from'@kit.ImageKit';import{effectKit}from'@kit.ArkGraphics2D';import{ColorUtils,ExtractColorResult}from'../utils/ColorUtils';@Componentstruct SmartColorPicker{// 接收外部画布的 PixelMapprivatecanvasPixelMap?:image.PixelMap;// 取色结果@StateprivateextractResult?:ExtractColorResult;@StateprivateselectedColor:string='#FF000000';// 默认黑色// 取色状态@StateprivateisPicking:boolean=false;privatestartX:number=0;privatestartY:number=0;privateendX:number=0;privateendY:number=0;// 回调:选择颜色后应用到画笔privateonColorSelected:(color:string)=>void;build(){Column(){// 取色按钮Button('智能取色').onClick(()=>{this.isPicking=true;// 取色前需确保画布有内容,且已转换为 PixelMapif(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)}/** * 开始智能取色流程 * 实际应用中需要用户选择区域,这里简化:取整个画布的主色 */asyncstartPicking(){try{// 将 PixalMap 传给取色工具constresult=awaitColorUtils.extractColors(this.canPixelMap!);this.extractResult=result;this.isPicking=false;}catch(error){console.error('取色失败:',error);this.isPicking=false;}}/** * 从画布组件获取 PixelMap * 由外部调用,传入 Canvas 组件的 PixelMap */publicsetCanvasPixelMap(pixelMap:image.PixelMap){this.canvasPixelMap=pixelMap;}}关键点说明:
startPicking方法调用ColorUtils.extractColors完成取色,结果存储在@State变量中驱动UI更新。- 色板展示部分使用
ForEach遍历palette数组,每个颜色块可点击,点击后触发onColorSelected回调通知父组件。 - 实际项目中,取色区域应通过触摸事件限制为画布上的矩形区域。这里为聚焦于
effectKit使用,简化取色范围为整个画布。后续可结合触摸事件实现区域取色。
2.4 集成到主页面
在主页面Index.ets中引用SmartColorPicker组件,并建立画布 PixelMap 的传递通道。
// pages/Index.etsimport{image}from'@kit.ImageKit';importSmartColorPickerfrom'../components/SmartColorPicker';@Entry@Componentstruct Index{@StateprivatecanvasPixelMap?:image.PixelMap;privatesmartColorPickerRef:SmartColorPicker|null=null;build(){Column(){// ... 工具栏、画布等已有代码 ...// 智能取色面板SmartColorPicker({onColorSelected:(color:string)=>{// 将选中的颜色设置到全局画笔属性AppStorage.setOrCreate('drawColor',color);}}).onAttach((component:SmartColorPicker)=>{this.smartColorPickerRef=component;})// ... 底部颜色栏等 ...}}/** * 在画布绘制完成或切换时,获取当前画布的 PixelMap * 通过 Canvas 组件 getPixelMap API 获取 */asyncupdateCanvasPixelMap(){// 假设 canvasContext 是已绑定的 RenderingContextconstpixelMap=awaitthis.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 的屏幕上。这会让用户的画作在高端设备上呈现更鲜艳、更准确的颜色。敬请期待。