HarmonyOS APP实战-画图APP - 第9篇:图像效果应用(智能取色)

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');
  }
}

关键点说明:

  • effectKitColorPicker 构造需要传入 PixelMap 参数,因此我们需要确保调用侧传入的是有效的 PixelMap。
  • 取色结果包含主色和预生成的调色板,我们将其封装为 ExtractColorResult 接口,便于后续使用。
  • 当前仅定义接口和类壳,核心实现在下一节完成。

2.2 核心逻辑 --- 实现智能取色

本节实现 extractColors 方法,使用 effectKitColorPicker 完成取色,并手动生成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 对象包含 alpharedgreenblue 四个分量,均为 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 中运行应用:

  1. 在主界面上绘制一些彩色图形(如红色圆形、蓝色矩形)。
  2. 点击底部的"智能取色"按钮,触发取色流程。
  3. 观察控制台日志,确认 ColorPicker.getMainColorSync 成功返回主色对象。
  4. 查看页面上方出现色板区域,包含主色和4个相近色块。
  5. 点击色板中的任意颜色块,底部颜色指示更新,后续绘制线条变为所选颜色。

预期效果:色板应该显示画布中最具代表性的颜色(主色),以及基于主色生成的和谐色板。点击色板后,画笔颜色立即切换。

4. 小结与预告

本篇实现了画图APP的智能取色与色板生成功能。核心是使用 @ohos.effectKitColorPicker 类提取图像主色,并通过 HSL 变换算法生成5色调色板。提取的颜色可以一键应用到画笔,大大提升了配色效率。智能取色是直接服务于用户创作流程的功能,让画图APP从单纯的绘图工具升级为具备色彩分析能力的设计工具。

下一篇将开启一个新的技术维度------色彩空间转换与HDR支持。我们将集成 @ohos.graphics.colorSpaceManager,实现 sRGB、Display P3、Adobe RGB 等色彩空间间的转换,并将画图APP的输出适配到支持 HDR 的屏幕上。这会让用户的画作在高端设备上呈现更鲜艳、更准确的颜色。敬请期待。

相关推荐
星释2 小时前
鸿蒙智能体开发实战:41.智能体上架审核规范与合规指南
华为·ai·harmonyos·鸿蒙
ZZZMMM.zip3 小时前
基于鸿蒙PC与鸿蒙Flutter框架的AI紧急求助应用开发实践
人工智能·flutter·华为·harmonyos·鸿蒙
不才难以繁此生3 小时前
NavPathStack 返回错页怎么排查:中式美食搜索、收藏和推荐入口怎么带来源
harmonyos·arkts·arkui·中式美食·navpathstack·路由状态
解局易否结局3 小时前
ArkUI-X 跨平台开发实战:从工程搭建到平台差异化适配
华为·harmonyos
不羁的木木3 小时前
HarmonyOS APP实战-画图APP - 第7篇:文本绘制功能
华为·harmonyos
●VON3 小时前
HarmonyKit | 鸿蒙新特性:HDS 沉浸光感与空间化视觉设计
华为·性能优化·单元测试·harmonyos·鸿蒙
解局易否结局4 小时前
鸿蒙原生开发实战:用 Native C++ 与 OpenGL ES 构建高性能图形渲染管线
c++·elasticsearch·华为·harmonyos
ZZZMMM.zip4 小时前
鸿蒙生态新征程:基于鸿蒙Flutter框架的AI菜谱营养应用深度解析
人工智能·flutter·华为·harmonyos·鸿蒙·鸿蒙系统
用户0934077735144 小时前
HarmonyOS WPS Open SDK 二开答疑:ToB/ToC 差异与接入误区
harmonyos