HarmonyOS6 实战案例之HSV 颜色模型到底在算什么?ColorUtils 代码逐行拆解
文章目录先说 HSV 是什么HEX 转 HSVHSV 转 HEX两个转换函数一个是类方法一个是独立函数在 ColorSelector 里是怎么用的验证一个例子写在最后做颜色选择器绕不开 HSV 颜色模型。这个东西讲起来很容易变成一堆公式但其实真的不难——只要搞清楚ColorUtils.ets这两个函数在干什么HSV 基本就懂了。先说 HSV 是什么RGB 描述颜色靠的是红绿蓝三个通道的混合量直觉上不太好用。你很难说这个颜色要淡一点应该怎么调 RGB。HSV 把颜色描述成三个更直观的维度HHue色相颜色的种类取值 0~360。0/360 是红60 是黄120 是绿180 是青240 是蓝300 是紫SSaturation饱和度颜色有多纯0~1。0 是灰色1 是纯色VValue明度颜色有多亮0~1。0 是黑色1 是最亮颜色选择器用 HSV 的原因用户拖动色相条选红/蓝/绿在面板里选深/浅/纯/灰比直接调 RGB 直观多了。HEX 转 HSV// ColorUtils.etshexToHsv(hex:string):[number,number,number]{constrparseInt(hex.slice(1,3),16)/255;constgparseInt(hex.slice(3,5),16)/255;constbparseInt(hex.slice(5,7),16)/255;先把#rrggbb格式的 HEX 字符串拆开slice(1,3)切出红色分量parseInt(..., 16)转成 0~255 的整数再除以 255 归一化到[0, 1]。constmaxMath.max(r,g,b);constminMath.min(r,g,b);constdeltamax-min;max就是 V明度的原始值delta是最大最小值之差用来判断饱和度和色相。明度 Vconstvmax;就是 RGB 里最大的那个分量值越高颜色越亮。饱和度 Sconstsmax0?0:delta/max;如果max 0说明是纯黑饱和度定义为 0。否则delta/max就是饱和度——三个通道差异越大颜色越纯饱和度越高。色相 Hleth0;if(delta!0){if(maxr){h((g-b)/delta)%6;}elseif(maxg){h(b-r)/delta2;}else{h(r-g)/delta4;}h*60;if(h0){h360;}}色相计算是这里最复杂的部分。HSV 的色相是基于哪个通道最大来定位颜色在色轮上的位置max r红色主导h 在 0~60° 或 300~360° 之间公式(g-b)/delta % 6算出偏移max g绿色主导h 在 60°~180° 之间加 2乘以 60 后是 120°max b蓝色主导h 在 180°~300° 之间加 4乘以 60 后是 240°最后乘以 60把[0,6)的值映射到[0,360)。如果算出负数加 360 补正。return[h,s,v];}返回[H, S, V]的元组。HSV 转 HEX反向转换稍微复杂一点但思路是一样的——先算出 RGB再格式化成 HEX 字符串。hsvToHex(h:number,s:number,v:number):string{letr:number0,g:number0,b:number0;letiMath.floor(h/60);// 色相所在的 60° 区间0~5letfh/60-i;// 区间内的小数偏移letpv*(1-s);// 最小亮度分量letqv*(1-f*s);// 次小亮度分量lettv*(1-(1-f)*s);// 次大亮度分量HSV 转 RGB 把色相环分成 6 个 60° 的扇区每个扇区里 RGB 三个分量按不同的规律变化switch(i%6){case0:rv;gt;bp;break;// 红 → 黄case1:rq;gv;bp;break;// 黄 → 绿case2:rp;gv;bt;break;// 绿 → 青case3:rp;gq;bv;break;// 青 → 蓝case4:rt;gp;bv;break;// 蓝 → 紫case5:rv;gp;bq;break;// 紫 → 红}六个 case 覆盖了完整的色相环。每个区间内哪个通道主导值为 v、哪个通道上升值为 t、哪个通道下降值为 q、哪个通道最低值为 p都是固定的。rMath.round(r*255);gMath.round(g*255);bMath.round(b*255);return#${this.toHex(r)}${this.toHex(g)}${this.toHex(b)};}归一化的 RGB 乘以 255 并取整然后格式化成两位 HEX 字符串。toHex(n:number){lethexn.toString(16);returnhex.length1?0hex:hex;}toHex做了个小处理——如果转出来只有一位比如0a的a前面补0保证每个分量都是两位。两个转换函数一个是类方法一个是独立函数ColorUtils.ets里其实有两套实现。ColorUtils类里是hexToHsv和hsvToHex文件末尾还有一个独立的hsv2rgb函数// 独立函数未被导出内部逻辑相同functionhsv2rgb(h:number,s:number,v:number):ColorRgb{letr:number0,g:number0,b:number0;// ... 同样的 switch 逻辑 ...return{r:r,g:g,b:b};}exportinterfaceColorRgb{r:number;g:number;b:number;}hsv2rgb返回的是ColorRgb接口类型包含r,g,b三个字段而hsvToHex返回 HEX 字符串。前者在项目当前代码里没被调用应该是保留的备用方法。export default new ColorUtils()导出的是ColorUtils的单例所以使用时直接ColorUtils.hexToHsv(...)调用实例方法。在 ColorSelector 里是怎么用的// ColorSelector.etsimportColorUtilsfrom../utils/ColorUtils;// 打开时HEX → HSVaboutToAppear():void{consthsvColorUtils.hexToHsv(this.color)this.huehsv[0]this.sathsv[1]this.valhsv[2]}// 绘制颜色条渐变时HSV → HEXdrawColorBar(){for(letiConstants.HEU_SCALE;i0;i--,count){grad.addColorStop(1-i/Constants.HEU_SCALE,ColorUtils.hsvToHex(i,1,1))}}// 用户选色后输出HSV → HEXgetColor():string{returnColorUtils.hsvToHex(this.hue,this.sat,this.val);}三处用法初始化时把外部颜色转成内部 HSV 表示绘制颜色条时用不同色相生成渐变色用户操作后把 HSV 转回 HEX 输出。验证一个例子试着手算一下蓝色#0000ffr0, g0, b1max1 (b), min0, delta1v 1s 1/1 1max bh (r-g)/delta 4 (0-0)/1 4 4乘以 60 240结果是[240, 1, 1]H240 确实是蓝色。反过来hsvToHex(240, 1, 1)i floor(240/60) 4f 240/60 - 4 0p 1*(1-1) 0, q 1*(1-01) 1, t 1(1-1*1) 0case 4: rt0, gp0, bv1#0000ff✓写在最后HSV 转换这块数学上并不难就是映射关系多了点。ColorUtils里把这两个互转函数封装好ColorSelector用起来就很干净不用在 UI 代码里夹杂颜色计算逻辑。这种工具类单独抽出来的做法在任何稍大一点的项目里都值得坚持。到这里五篇文章就写完了从项目架构到每个模块的细节都过了一遍。如果是第一次接触 HarmonyOS ArkUI 开发这个项目代码量适中涉及的知识点又比较全面很适合拿来边读边练。