鸿蒙 ArkTS 颜色工具类封装:从 hex 到 colorFilter 矩阵,

支持 hex / RGB / HSL / CMYK / colorValue 五种颜色格式互转,以及任意格式一键生成 colorFilter 矩阵,含透明度通道。


一、主要用途?

ColorMatrixUtil 颜色工具类,核心能力有两块:

  1. 颜色矩阵生成:将任意颜色格式(hex、RGB、HSL、CMYK、鸿蒙 colorValue)转换为 colorFilter 所需的 4×5 矩阵,用于给图片/图标染色。
  2. 颜色格式全互通:在 hex、RGB、HSL、CMYK、colorValue 五种格式之间任意互转,一个工具类覆盖所有场景。

二、解决了什么问题?

痛点 1:鸿蒙 colorFilter 矩阵难写

在鸿蒙中给图片染色的标准方式是设置 colorFilter

Image($r('app.media.icon'))
  .colorFilter([
    0, 0, 0, 0, r,  // R 通道
    0, 0, 0, 0, g,  // G 通道
    0, 0, 0, 0, b,  // B 通道
    0, 0, 0, a, 0   // A 通道
  ])

这个 20 元素的矩阵对开发者极不友好——你有一个 #F5F5F5 的颜色,怎么填?每次都要手动算一遍?

ColorMatrixUtil 一行搞定:

.colorFilter(ColorMatrixUtil.hex2Matrix('#F5F5F5'))

痛点 2:透明度算不对

很多颜色工具类默认只处理不透明颜色(alpha=1),遇到 #99F5F5F5 这种带透明度的就跪了。

本工具类按 ARGB 顺序解析,A 通道矩阵公式为 [0, 0, 0, a, 0](即 A' = a × 源A),让半透明图标染色后依然保持正确的透明度层次。

痛点 3:颜色格式来回转,到处找工具

实际开发中经常遇到:

  • 设计稿给的是 hex,但某个组件需要 RGB
  • 后端返回的是 colorValue0xAARRGGBB),需要转成 hex 展示
  • 要做 HSL 取色器,需要 RGB ↔ HSL 互转
  • 打印场景需要 CMYK 值

以前这些转换散落在各处,现在一个工具类全部打通。


三、颜色格式全互通矩阵

ColorMatrixUtil 实现了 hex ↔ RGB ↔ HSL ↔ CMYK ↔ colorValue 五种格式之间的 20 条转换路径全部打通:

从 \ 到 hex RGB HSL CMYK colorValue Matrix
hex ✅ parseHexColor ✅ hex2Hsl ✅ hex2Cmyk ✅ hex2ColorValue ✅ hex2Matrix
RGB ✅ rgb2Hex ✅ rgb2Hsl ✅ rgb2Cmyk ✅ rgb2ColorValue ✅ rgb2Matrix
HSL ✅ hsl2Hex ✅ hsl2Rgb ✅ hsl2Cmyk ✅ hsl2ColorValue ✅ hsl2Matrix
CMYK ✅ cmyk2Hex ✅ cmyk2Rgb ✅ cmyk2Hsl ✅ cmyk2ColorValue ✅ cmyk2Matrix
colorValue ✅ colorValue2Hex ✅ colorValue2Rgb ✅ colorValue2Hsl ✅ colorValue2Cmyk ✅ rcv2Matrix

方法命名规范

所有方法采用 {源类型}2{目标类型} 命名,一眼看出方向:

  • hex2Matrix — hex 转到 Matrix
  • rgb2Hex — RGB 转到 hex
  • hsl2Rgb — HSL 转到 RGB
  • colorValue2Cmyk — colorValue 转到 CMYK

四、核心用法示例

4.1 图片染色(最常用)

// 将图标染成主题色
Image($r('app.media.icon_home'))
  .colorFilter(ColorMatrixUtil.hex2Matrix('#F5F5F5'))

// 半透明图标染色
Image($r('app.media.icon_watermark'))
  .colorFilter(ColorMatrixUtil.hex2Matrix('#66FFFFFF'))

// 用资源颜色染色
Image($r('app.media.icon_menu'))
  .colorFilter(ColorMatrixUtil.rcv2Matrix(
    getContext().resourceManager.getColorSync($r('app.color.primary'))
  ))

// 用 HSL 染色
Image($r('app.media.icon'))
  .colorFilter(ColorMatrixUtil.hsl2Matrix(210, 80, 60))

4.2 颜色格式互转

// hex → RGB
const rgb = ColorMatrixUtil.parseHexColor('#FF6B35')
// { r: 1, g: 0.4196, b: 0.2078, a: 1 }

// RGB → hex
const hex = ColorMatrixUtil.rgb2Hex(255, 107, 53)
// '#ff6b35'

// hex → HSL
const hsl = ColorMatrixUtil.hex2Hsl('#FF6B35')
// { h: 16, s: 100, l: 60, a: 1 }

// colorValue → hex
const hex2 = ColorMatrixUtil.colorValue2Hex(0xFF0000FF)
// '#0000ff'

// HSL → hex
const hex3 = ColorMatrixUtil.hsl2Hex(120, 100, 50)
// '#00ff00'

// CMYK → RGB
const rgb2 = ColorMatrixUtil.cmyk2Rgb(0, 100, 100, 0)
// { r: 255, g: 0, b: 0, a: 1 }

4.3 从后台数据直接染色

// 场景:后台返回颜色值 0xAARRGGBB,需要给图标染色
const themeColor = response.data.themeColor // 例如 0xFFF5F5F5
Image($r('app.media.icon'))
  .colorFilter(ColorMatrixUtil.rcv2Matrix(themeColor))

4.4 动态生成半透明遮罩

// 用 RGB + 透明度动态生成矩阵
Image($r('app.media.banner'))
  .colorFilter(ColorMatrixUtil.rgb2Matrix(0, 0, 0, 102)) // 40% 黑色遮罩

五、技术细节

5.1 colorFilter 矩阵原理

矩阵是 4 行 × 5 列,每行对应一个颜色通道的输出计算:

R' = r0·R + r1·G + r2·B + r3·A + r4
G' = g0·R + g1·G + g2·B + g3·A + g4
B' = b0·R + b1·G + b2·B + b3·A + b4
A' = a0·R + a1·G + a2·B + a3·A + a4

本工具生成的矩阵设计为纯色映射

  • RGB 三通道:前 4 个系数为 0,偏移量 r4/g4/b4 设为目标颜色值(归一化到 0-1)
  • A 通道:a3 = 目标透明度a4 = 0,即 A' = a × 源A

这样图片的明暗层次保留,仅颜色被替换为目标颜色。

5.2 hex 解析:ARGB 顺序

支持四种 hex 格式,统一解析为 ARGB:

输入 含义 内部展开
#F5F5F5 6 位 RGB #FFF5F5F5(alpha 补 FF)
#99F5F5F5 8 位 ARGB 直接解析
#FFF 3 位 RGB #FFFFFFFF
#8F00 4 位 RGBA #88FF0000(alpha 在前)

5.3 透明度全链路支持

所有转换方法都支持透明度参数 a(0-255),默认 255(不透明):

rgb2Matrix(255, 0, 0, 128)      // 半透明红色
hsl2Matrix(0, 100, 50, 128)     // 半透明红色(HSL)
cmyk2Matrix(0, 100, 100, 0, 128) // 半透明红色(CMYK)

六、自带自测

工具类内置 testColorMatrixUtil() 静态方法,覆盖 50+ 测试用例:

  • hex 全部格式解析(#RGB、#RGBA、#RRGGBB、#RRGGBBAA)
  • 各颜色格式 → Matrix 生成
  • 颜色格式互转(单向 + 闭环往返)
  • 矩阵格式校验(长度=20、系数范围、偏移量范围)
  • 交叉验证(hex ≈ RGB ≈ HSL ≈ CMYK)
ColorMatrixUtil.testColorMatrixUtil()
// 输出所有 [PASS]/[FAIL] 结果及汇总

七、完整 API 速查

Matrix 生成(5 个)

方法 参数 返回
hex2Matrix(hex) '#F5F5F5' number[20]
rgb2Matrix(r,g,b,a?) 255,245,245,153 number[20]
hsl2Matrix(h,s,l,a?) 0,0,96,153 number[20]
cmyk2Matrix(c,m,y,k,a?) 0,0,0,4,153 number[20]
rcv2Matrix(colorValue) 0x99F5F5F5 number[20]

颜色互转(20 个)

类别 方法
hex → parseHexColor, hex2Hsl, hex2Cmyk, hex2ColorValue
RGB → rgb2Hex, rgb2Hsl, rgb2Cmyk, rgb2ColorValue
HSL → hsl2Hex, hsl2Rgb, hsl2Cmyk, hsl2ColorValue
CMYK → cmyk2Hex, cmyk2Rgb, cmyk2Hsl, cmyk2ColorValue
colorValue → colorValue2Hex, colorValue2Rgb, colorValue2Hsl, colorValue2Cmyk

八、使用方式

ColorMatrixUtil.ets 放到项目的 utils 目录,直接 import 即可:

import { ColorMatrixUtil } from '../utils/ColorMatrixUtil'

纯静态方法,无需实例化。


九、完整源码

/**
 * @author dawn
 * @Description 颜色转 colorFilter 矩阵工具类
 *
 * colorFilter 矩阵格式:
 * [r0, r1, r2, r3, r4,
 *  g0, g1, g2, g3, g4,
 *  b0, b1, b2, b3, b4,
 *  a0, a1, a2, a3, a4]
 * 其中 r4, g4, b4 是颜色偏移量,用于将图片映射为目标颜色
 */

export class ColorMatrixUtil {
  static hex2Matrix(hexColor: string): number[] {
    return hexToColorMatrix(hexColor)
  }

  static rcv2Matrix(colorValue: number): number[] {
    return colorValueToColorMatrix(colorValue)
  }

  static rgb2Matrix(r: number, g: number, b: number, a: number = 255): number[] {
    return rgbToColorMatrix(r, g, b, a)
  }

  static hsl2Matrix(h: number, s: number, l: number, a: number = 255): number[] {
    return hslToColorMatrix(h, s, l, a)
  }

  static cmyk2Matrix(c: number, m: number, y: number, k: number, a: number = 255): number[] {
    return cmykToColorMatrix(c, m, y, k, a)
  }

  static parseHexColor(hexColor: string): RgbColor {
    return parseHexColor(hexColor)
  }

  static hsl2Rgb(h: number, s: number, l: number): RgbColor {
    return hslToRgb(h, s, l)
  }

  static cmyk2Rgb(c: number, m: number, y: number, k: number): RgbColor {
    return cmykToRgb(c, m, y, k)
  }

  // RGB → 各种
  static rgb2Hex(r: number, g: number, b: number, a: number = 255): string {
    return rgbToHex(r, g, b, a)
  }

  static rgb2ColorValue(r: number, g: number, b: number, a: number = 255): number {
    return rgbToColorValue(r, g, b, a)
  }

  static rgb2Hsl(r: number, g: number, b: number): HslColor {
    return rgbToHsl(r, g, b)
  }

  static rgb2Cmyk(r: number, g: number, b: number): CmykColor {
    return rgbToCmyk(r, g, b)
  }

  // hex → 各种
  static hex2Hsl(hexColor: string): HslColor {
    return hexToHsl(hexColor)
  }

  static hex2Cmyk(hexColor: string): CmykColor {
    return hexToCmyk(hexColor)
  }

  static hex2ColorValue(hexColor: string): number {
    return hexToColorValue(hexColor)
  }

  // HSL → 各种
  static hsl2Hex(h: number, s: number, l: number, a: number = 255): string {
    return hslToHex(h, s, l, a)
  }

  static hsl2Cmyk(h: number, s: number, l: number): CmykColor {
    return hslToCmyk(h, s, l)
  }

  static hsl2ColorValue(h: number, s: number, l: number, a: number = 255): number {
    return hslToColorValue(h, s, l, a)
  }

  // CMYK → 各种
  static cmyk2Hex(c: number, m: number, y: number, k: number, a: number = 255): string {
    return cmykToHex(c, m, y, k, a)
  }

  static cmyk2Hsl(c: number, m: number, y: number, k: number): HslColor {
    return cmykToHsl(c, m, y, k)
  }

  static cmyk2ColorValue(c: number, m: number, y: number, k: number, a: number = 255): number {
    return cmykToColorValue(c, m, y, k, a)
  }

  // colorValue → 各种
  static colorValue2Hex(colorValue: number): string {
    return colorValueToHex(colorValue)
  }

  static colorValue2Rgb(colorValue: number): RgbColor {
    return colorValueToRgb(colorValue)
  }

  static colorValue2Hsl(colorValue: number): HslColor {
    return colorValueToHsl(colorValue)
  }

  static colorValue2Cmyk(colorValue: number): CmykColor {
    return colorValueToCmyk(colorValue)
  }

  static testColorMatrixUtil(): void {
    testColorMatrixUtil()
  }
}

interface RgbColor {
  r: number;
  g: number;
  b: number;
  a: number;
}

interface HslColor {
  h: number;
  s: number;
  l: number;
  a: number;
}

interface CmykColor {
  c: number;
  m: number;
  y: number;
  k: number;
  a: number;
}

// ==================== 底层转换 ====================

function rgbToHex(r: number, g: number, b: number, a: number = 255): string {
  const toHex = (v: number): string => {
    const h = Math.round(Math.max(0, Math.min(255, v))).toString(16)
    return h.length === 1 ? '0' + h : h
  }
  const hex = '#' + toHex(r) + toHex(g) + toHex(b)
  if (a < 255) {
    return hex + toHex(a)
  }
  return hex
}

function rgbToColorValue(r: number, g: number, b: number, a: number = 255): number {
  const ra = Math.round(Math.max(0, Math.min(255, a)))
  const rr = Math.round(Math.max(0, Math.min(255, r)))
  const rg = Math.round(Math.max(0, Math.min(255, g)))
  const rb = Math.round(Math.max(0, Math.min(255, b)))
  return ((ra & 0xFF) << 24) | ((rr & 0xFF) << 16) | ((rg & 0xFF) << 8) | (rb & 0xFF)
}

function rgbToHsl(r: number, g: number, b: number): HslColor {
  const rn = r / 255
  const gn = g / 255
  const bn = b / 255
  const max = Math.max(rn, gn, bn)
  const min = Math.min(rn, gn, bn)
  const delta = max - min

  let h = 0
  let s = 0
  const l = (max + min) / 2

  if (delta !== 0) {
    s = l > 0.5 ? delta / (2 - max - min) : delta / (max + min)
    if (max === rn) {
      h = ((gn - bn) / delta + (gn < bn ? 6 : 0)) * 60
    } else if (max === gn) {
      h = ((bn - rn) / delta + 2) * 60
    } else {
      h = ((rn - gn) / delta + 4) * 60
    }
  }

  return { h: Math.round(h), s: Math.round(s * 100), l: Math.round(l * 100), a: 1 }
}

function rgbToCmyk(r: number, g: number, b: number): CmykColor {
  const rn = r / 255
  const gn = g / 255
  const bn = b / 255
  const k = 1 - Math.max(rn, gn, bn)
  if (k === 1) {
    return { c: 0, m: 0, y: 0, k: 100, a: 1 }
  }
  const c = (1 - rn - k) / (1 - k)
  const m = (1 - gn - k) / (1 - k)
  const y = (1 - bn - k) / (1 - k)
  return {
    c: Math.round(c * 100),
    m: Math.round(m * 100),
    y: Math.round(y * 100),
    k: Math.round(k * 100),
    a: 1
  }
}

function hexToHsl(hexColor: string): HslColor {
  const rgb = parseHexColor(hexColor)
  const hsl = rgbToHsl(rgb.r * 255, rgb.g * 255, rgb.b * 255)
  hsl.a = rgb.a
  return hsl
}

function hexToCmyk(hexColor: string): CmykColor {
  const rgb = parseHexColor(hexColor)
  const cmyk = rgbToCmyk(rgb.r * 255, rgb.g * 255, rgb.b * 255)
  cmyk.a = rgb.a
  return cmyk
}

function hexToColorValue(hexColor: string): number {
  const rgb = parseHexColor(hexColor)
  return rgbToColorValue(rgb.r * 255, rgb.g * 255, rgb.b * 255, rgb.a * 255)
}

function hslToHex(h: number, s: number, l: number, a: number = 255): string {
  const rgb = hslToRgb(h, s, l)
  return rgbToHex(rgb.r, rgb.g, rgb.b, a)
}

function hslToCmyk(h: number, s: number, l: number): CmykColor {
  const rgb = hslToRgb(h, s, l)
  return rgbToCmyk(rgb.r, rgb.g, rgb.b)
}

function hslToColorValue(h: number, s: number, l: number, a: number = 255): number {
  const rgb = hslToRgb(h, s, l)
  return rgbToColorValue(rgb.r, rgb.g, rgb.b, a)
}

function cmykToHex(c: number, m: number, y: number, k: number, a: number = 255): string {
  const rgb = cmykToRgb(c, m, y, k)
  return rgbToHex(rgb.r, rgb.g, rgb.b, a)
}

function cmykToHsl(c: number, m: number, y: number, k: number): HslColor {
  const rgb = cmykToRgb(c, m, y, k)
  return rgbToHsl(rgb.r, rgb.g, rgb.b)
}

function cmykToColorValue(c: number, m: number, y: number, k: number, a: number = 255): number {
  const rgb = cmykToRgb(c, m, y, k)
  return rgbToColorValue(rgb.r, rgb.g, rgb.b, a)
}

function colorValueToHex(colorValue: number): string {
  const a = (colorValue >> 24) & 0xFF
  const r = (colorValue >> 16) & 0xFF
  const g = (colorValue >> 8) & 0xFF
  const b = colorValue & 0xFF
  return rgbToHex(r, g, b, a)
}

function colorValueToRgb(colorValue: number): RgbColor {
  const a = ((colorValue >> 24) & 0xFF) / 255
  const r = ((colorValue >> 16) & 0xFF) / 255
  const g = ((colorValue >> 8) & 0xFF) / 255
  const b = (colorValue & 0xFF) / 255
  return { r, g, b, a }
}

function colorValueToHsl(colorValue: number): HslColor {
  const rgb = colorValueToRgb(colorValue)
  const hsl = rgbToHsl(rgb.r * 255, rgb.g * 255, rgb.b * 255)
  hsl.a = rgb.a
  return hsl
}

function colorValueToCmyk(colorValue: number): CmykColor {
  const rgb = colorValueToRgb(colorValue)
  const cmyk = rgbToCmyk(rgb.r * 255, rgb.g * 255, rgb.b * 255)
  cmyk.a = rgb.a
  return cmyk
}

// ==================== 颜色矩阵生成 ====================

function hexToColorMatrix(hexColor: string): number[] {
  let hex = hexColor.trim()
  if (hex.startsWith('#')) {
    hex = hex.substring(1)
  }
  // 补全为 8 位 ARGB
  if (hex.length === 6) {
    hex = 'ff' + hex
  } else if (hex.length === 3) {
    hex = 'ff' + hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]
  } else if (hex.length === 4) {
    hex = hex[3] + hex[3] + hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]
  } else if (hex.length !== 8) {
    console.error(`Invalid hex color format: ${hexColor}`)
    return [0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0]
  }
  const a = parseInt(hex.substring(0, 2), 16) / 255
  const r = parseInt(hex.substring(2, 4), 16) / 255
  const g = parseInt(hex.substring(4, 6), 16) / 255
  const b = parseInt(hex.substring(6, 8), 16) / 255
  return [
    0, 0, 0, 0, r,
    0, 0, 0, 0, g,
    0, 0, 0, 0, b,
    0, 0, 0, a, 0
  ]
}

function colorValueToColorMatrix(colorValue: number): number[] {
  const a = (colorValue >> 24) & 0xFF
  const r = (colorValue >> 16) & 0xFF
  const g = (colorValue >> 8) & 0xFF
  const b = colorValue & 0xFF
  return [
    0, 0, 0, 0, r / 255,
    0, 0, 0, 0, g / 255,
    0, 0, 0, 0, b / 255,
    0, 0, 0, a / 255, 0
  ]
}

function rgbToColorMatrix(r: number, g: number, b: number, a: number = 255): number[] {
  return [
    0, 0, 0, 0, r / 255,
    0, 0, 0, 0, g / 255,
    0, 0, 0, 0, b / 255,
    0, 0, 0, a / 255, 0
  ]
}

function hslToColorMatrix(h: number, s: number, l: number, a: number = 255): number[] {
  const rgb = hslToRgb(h, s, l)
  return rgbToColorMatrix(rgb.r, rgb.g, rgb.b, a)
}

function cmykToColorMatrix(c: number, m: number, y: number, k: number, a: number = 255): number[] {
  const rgb = cmykToRgb(c, m, y, k)
  return rgbToColorMatrix(rgb.r, rgb.g, rgb.b, a)
}

function parseHexColor(hexColor: string): RgbColor {
  let hex = hexColor.trim()
  if (hex.startsWith('#')) {
    hex = hex.substring(1)
  }
  if (hex.length === 6) {
    hex = 'ff' + hex
  } else if (hex.length === 3) {
    hex = 'ff' + hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]
  } else if (hex.length === 4) {
    hex = hex[3] + hex[3] + hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]
  } else if (hex.length !== 8) {
    console.error(`Invalid hex color format: ${hexColor}`)
    return { r: 1, g: 1, b: 1, a: 1 }
  }
  const a = parseInt(hex.substring(0, 2), 16) / 255
  const r = parseInt(hex.substring(2, 4), 16) / 255
  const g = parseInt(hex.substring(4, 6), 16) / 255
  const b = parseInt(hex.substring(6, 8), 16) / 255
  return { r, g, b, a }
}

function hslToRgb(h: number, s: number, l: number): RgbColor {
  s = s / 100
  l = l / 100

  const hue2rgb = (p: number, q: number, t: number): number => {
    if (t < 0) {
      t += 1
    }
    if (t > 1) {
      t -= 1
    }
    if (t < 1 / 6) {
      return p + (q - p) * 6 * t
    }
    if (t < 1 / 2) {
      return q
    }
    if (t < 2 / 3) {
      return p + (q - p) * (2 / 3 - t) * 6
    }
    return p
  }

  let r: number
  let g: number
  let b: number
  if (s === 0) {
    r = g = b = l
  } else {
    const q = l < 0.5 ? l * (1 + s) : l + s - l * s
    const p = 2 * l - q
    r = hue2rgb(p, q, h / 360 + 1 / 3)
    g = hue2rgb(p, q, h / 360)
    b = hue2rgb(p, q, h / 360 - 1 / 3)
  }

  return { r: r * 255, g: g * 255, b: b * 255, a: 1 }
}

function cmykToRgb(c: number, m: number, y: number, k: number): RgbColor {
  c = c / 100
  m = m / 100
  y = y / 100
  k = k / 100

  const r = (1 - c) * (1 - k) * 255
  const g = (1 - m) * (1 - k) * 255
  const b = (1 - y) * (1 - k) * 255

  return { r, g, b, a: 1 }
}

自测函数 testColorMatrixUtil() 篇幅较长,包含 50+ 用例,如需完整版可参考文末使用方式自行调用。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐