当前位置: 首页 > news >正文

HarmonyOS6 实战案例之HSV 颜色模型到底在算什么?ColorUtils 代码逐行拆解

文章目录

      • 先说 HSV 是什么
      • HEX 转 HSV
      • HSV 转 HEX
      • 两个转换函数:一个是类方法,一个是独立函数
      • 在 ColorSelector 里是怎么用的
      • 验证一个例子
      • 写在最后

做颜色选择器绕不开 HSV 颜色模型。这个东西讲起来很容易变成一堆公式,但其实真的不难——只要搞清楚ColorUtils.ets这两个函数在干什么,HSV 基本就懂了。

先说 HSV 是什么

RGB 描述颜色靠的是红绿蓝三个通道的混合量,直觉上不太好用。你很难说"这个颜色要淡一点"应该怎么调 RGB。

HSV 把颜色描述成三个更直观的维度:

  • H(Hue,色相):颜色的"种类",取值 0~360。0/360 是红,60 是黄,120 是绿,180 是青,240 是蓝,300 是紫
  • S(Saturation,饱和度):颜色有多"纯",0~1。0 是灰色,1 是纯色
  • V(Value,明度):颜色有多亮,0~1。0 是黑色,1 是最亮

颜色选择器用 HSV 的原因:用户拖动色相条选"红/蓝/绿",在面板里选"深/浅/纯/灰",比直接调 RGB 直观多了。


HEX 转 HSV

// ColorUtils.etshexToHsv(hex:string):[number,number,number]{constr=parseInt(hex.slice(1,3),16)/255;constg=parseInt(hex.slice(3,5),16)/255;constb=parseInt(hex.slice(5,7),16)/255;

先把#rrggbb格式的 HEX 字符串拆开,slice(1,3)切出红色分量,parseInt(..., 16)转成 0~255 的整数,再除以 255 归一化到[0, 1]

constmax=Math.max(r,g,b);constmin=Math.min(r,g,b);constdelta=max-min;

max就是 V(明度)的原始值,delta是最大最小值之差,用来判断饱和度和色相。

明度 V

constv=max;

就是 RGB 里最大的那个分量,值越高颜色越亮。

饱和度 S

![Hand-drawn educational flowchart on warm cream pap](https://files.mdnice.com/user/47561/3127cc87-df81-4cb7-95b8-d6471900a468.png)consts=max===0?0:delta/max;

如果max = 0说明是纯黑,饱和度定义为 0。否则delta/max就是饱和度——三个通道差异越大,颜色越"纯",饱和度越高。

色相 H

leth=0;if(delta!==0){if(max===r){h=((g-b)/delta)%6;}elseif(max===g){h=(b-r)/delta+2;}else{h=(r-g)/delta+4;}h*=60;if(h<0){h+=360;}}

色相计算是这里最复杂的部分。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:number=0,g:number=0,b:number=0;leti=Math.floor(h/60);// 色相所在的 60° 区间(0~5)letf=h/60-i;// 区间内的小数偏移letp=v*(1-s);// 最小亮度分量letq=v*(1-f*s);// 次小亮度分量lett=v*(1-(1-f)*s);// 次大亮度分量

HSV 转 RGB 把色相环分成 6 个 60° 的扇区,每个扇区里 RGB 三个分量按不同的规律变化:

switch(i%6){case0:r=v;g=t;b=p;break;// 红 → 黄![Single-page hand-drawn educational infographicin](https://files.mdnice.com/user/47561/cda4a0fb-7b26-43a7-a218-3998a8fdd9dc.png)case1:r=q;g=v;b=p;break;// 黄 → 绿case2:r=p;g=v;b=t;break;// 绿 → 青case3:r=p;g=q;b=v;break;// 青 → 蓝case4:r=t;g=p;b=v;break;// 蓝 → 紫case5:r=v;g=p;b=q;break;// 紫 → 红}

六个 case 覆盖了完整的色相环。每个区间内,哪个通道主导(值为 v)、哪个通道上升(值为 t)、哪个通道下降(值为 q)、哪个通道最低(值为 p)都是固定的。

r=Math.round(r*255);g=Math.round(g*255);b=Math.round(b*255);return`#${this.toHex(r)}${this.toHex(g)}${this.toHex(b)}`;}

归一化的 RGB 乘以 255 并取整,然后格式化成两位 HEX 字符串。

toHex(n:number){lethex=n.toString(16);returnhex.length===1?'0'+hex:hex;}

toHex做了个小处理——如果转出来只有一位(比如0aa),前面补0,保证每个分量都是两位。


两个转换函数:一个是类方法,一个是独立函数

ColorUtils.ets里其实有两套实现。ColorUtils类里是hexToHsvhsvToHex,文件末尾还有一个独立的hsv2rgb函数:

// 独立函数(未被导出,内部逻辑相同)functionhsv2rgb(h:number,s:number,v:number):ColorRgb{letr:number=0,g:number=0,b:number=0;// ... 同样的 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{consthsv=ColorUtils.hexToHsv(this.color)this.hue=hsv[0]this.sat=hsv[1]this.val=hsv[2]}// 绘制颜色条渐变时:HSV → HEXdrawColorBar(){for(leti=Constants.HEU_SCALE;i>=0;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 输出。


验证一个例子

试着手算一下蓝色#0000ff

  • r=0, g=0, b=1
  • max=1 (b), min=0, delta=1
  • v = 1
  • s = 1/1 = 1
  • max === b,h = (r-g)/delta + 4 = (0-0)/1 + 4 = 4,乘以 60 = 240

结果是[240, 1, 1],H=240 确实是蓝色。

反过来,hsvToHex(240, 1, 1)

  • i = floor(240/60) = 4
  • f = 240/60 - 4 = 0
  • p = 1*(1-1) = 0, q = 1*(1-01) = 1, t = 1(1-1*1) = 0
  • case 4: r=t=0, g=p=0, b=v=1
  • #0000ff

写在最后

HSV 转换这块数学上并不难,就是映射关系多了点。ColorUtils里把这两个互转函数封装好,ColorSelector用起来就很干净,不用在 UI 代码里夹杂颜色计算逻辑。这种"工具类单独抽出来"的做法,在任何稍大一点的项目里都值得坚持。

到这里五篇文章就写完了,从项目架构到每个模块的细节都过了一遍。如果是第一次接触 HarmonyOS ArkUI 开发,这个项目代码量适中,涉及的知识点又比较全面,很适合拿来边读边练。

http://www.zskr.cn/news/1501861.html

相关文章:

  • 质量好的潜水排污泵厂家哪家好?2026年行业厂商综合能力分析 - 优质品牌商家
  • 5个技巧掌握Pywinauto:Windows自动化测试的终极指南
  • 火箭六自由度姿态仿真MATLAB工具包:含气动力建模、四元数解算与PID闭环控制
  • 2026广州黄金回收市场红黑榜实测 - 余生黄金回收
  • 终极免费解决方案:3分钟搭建个人专属付费墙绕过工具
  • C#写的30个PPT式图片切换动画源码,拉幕旋转分块淡入全都有
  • 2026免费抠图软件保姆级教程:电脑手机在线无水印,一篇搞定
  • 抖音无水印下载神器:批量保存视频、直播、音乐的全能解决方案
  • FPGA做FFT时,你的输入数据格式对了吗?手把手解决锯齿波分析的实部虚部拼接问题
  • 快速定位Windows热键冲突的终极解决方案:Hotkey Detective完全指南
  • 手把手教你为山景BP1048芯片实现OTA升级(附完整代码解析与避坑指南)
  • 期货量化薄盘口假突破怎么过滤:天勤 quote 五档量与点差阈值
  • 2026年口碑好的黄山风景区中餐美食/黄山风景区美食美食推荐 - 品牌宣传支持者
  • 2026年热门的数控液压机/液压机源头工厂推荐 - 品牌宣传支持者
  • 2026年华为云OpenClaw/Hermes Agent配置Token Plan搭建全流程分享
  • 从零搭建部标视频监控平台:基于JT1078协议的音视频流接收与播放实战(含FFmpeg)
  • 期货量化模拟盘资金曲线:天勤 get_account balance 采样记录
  • IDM激活脚本终极指南:三步实现永久免费下载体验
  • iOS微信插件终极指南:解锁防撤回、远程控制等10大隐藏功能
  • 2026年评价高的无锡Y41A单柱矫直机/卧式型材矫直机200T/石油钻杆矫直机横向对比厂家推荐 - 行业平台推荐
  • 用LM358和红外LED,手把手教你DIY一个低成本无线耳机(附完整电路图)
  • 微信聊天记录永久保存方案:WeChatMsg让数字记忆永不褪色
  • DABM-D223数据采集卡:500K高速采样+FPGA架构
  • FanControl实战手册:Windows风扇智能控制完全解析
  • 避开STM32 HAL库的坑:自己动手实现RTC读写函数(以F103为例,附完整代码)
  • 2026年热门的江苏高效生物污水处理/江苏生态型污水处理工艺/江苏一体化污水处理设备/生活污水处理设备优质公司推荐 - 行业平台推荐
  • 2026年专业空压机厂家与系统设备供应商综合评估 - 优质品牌商家
  • context-mode火了,但AI编程的Token黑洞谁来填?
  • 语义ID与终身用户行为建模在推荐系统中的应用
  • 企业做GEO优化后咨询量会提升吗