鸿蒙 ArkTS 颜色工具类封装:从 hex 到 colorFilter 矩阵 支持 hex / RGB / HSL / CMYK / colorValue 五种颜色格式互
鸿蒙 ArkTS 颜色工具类封装:从 hex 到 colorFilter 矩阵,
支持 hex / RGB / HSL / CMYK / colorValue 五种颜色格式互转,以及任意格式一键生成
colorFilter矩阵,含透明度通道。
一、主要用途?
ColorMatrixUtil 颜色工具类,核心能力有两块:
- 颜色矩阵生成:将任意颜色格式(hex、RGB、HSL、CMYK、鸿蒙
colorValue)转换为colorFilter所需的 4×5 矩阵,用于给图片/图标染色。 - 颜色格式全互通:在 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
- 后端返回的是
colorValue(0xAARRGGBB),需要转成 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 转到 Matrixrgb2Hex— RGB 转到 hexhsl2Rgb— HSL 转到 RGBcolorValue2Cmyk— 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+ 用例,如需完整版可参考文末使用方式自行调用。
更多推荐




所有评论(0)