最近用aardio做一个小工具,需要创建一段使用渐变色填充的圆弧。
翻了一下GDI+的资料,发现GDI+的线性渐变画刷 LinearGradientBrush 和路径渐变画刷 PathGradientBrush 都不适用。
想起aardio自带的调色工具里有一个渐变的色相环,查看了源码,根据作者的思路实现了渐变圆弧的绘制。
实现原理:
简单来说,就是用逐渐变化的颜色画一连串首尾相连的小短弧,拼成一个大圆弧。
把圆弧平均分成若干段小短弧,每1角度分1段。比如一个90度角的弧分成90段,一个整圆分成360段。
以圆弧的分段数作为2个颜色之间渐变的总步数,计算出渐变过程中每一步的色值。比如90度的圆弧,从首到尾要经过90个颜色的变化,通过算法把这90个颜色的色值计算出来。
用计算出来的色值,画出一段一段的单色小短弧,拼成一个大圆弧,就形成了渐变。比如下图,每一个小方块都是1段单色的小短弧(为了方便演示,短弧画得比较长,短弧之间也留了间隙):
颜色渐变算法
算法就不说了,我并不擅长,大家可以参考这里的资料,提供了好几个方案,并且做了对比:https://www.codenong.com/22607043/
简单来说,如果直接用RGB色彩模式计算渐变色,过渡色会偏暗,显得不自然。
正解是使用HSB色彩模式计算,看起来舒服多了,而且从红过渡到绿的中间是黄,绿过渡到蓝的中间是青,才是正确的。
//计算两个颜色之间的渐变色,并分解成若干个色值,用数组返回
//基于RGB生成的渐变色,色彩偏暗,效果不佳
var gradientToColorsByRGB = function(startColor, endColor, step){
// 得到起始和末尾颜色的rgb色值
var sR,sG,sB = color.getRgba(startColor);
var eR,eG,eB = color.getRgba(endColor);
var rStep = (eR - sR) / step;
var gStep = (eG - sG) / step;
var bStep = (eB - sB) / step;
var colorArr = {};
for (i = 0; step; 1) {
var r = sR + i * rStep;
var g = sG + i * gStep;
var b = sB + i * bStep;
table.push(colorArr, color.argb(r, g, b, 255)); //透明度暂不考虑,统一设置为不透明
}
return colorArr;
}
//计算两个颜色之间的渐变色,并分解成若干个色值,用数组返回
var gradientToColors = function(startColor, endColor, step){
// 得到起始和末尾颜色的HSL色值
var sH,sS,sB = color.getHsb(startColor);
var eH,eS,eB = color.getHsb(endColor);
var hStep = (eH - sH) / (step - 1);
var sStep = (eS - sS) / (step - 1);
var bStep = (eB - sB) / (step - 1);
var colorArr = {};
for (i = 1; step; 1) {
var h = sH + hStep * i;
var s = sS + sStep * i;
var b = sB + bStep * i;
table.push(colorArr, color.argb(color.hsb2rgb(h, s, b)));
}
return colorArr;
}
画渐变圆弧
自定义画渐变圆弧的函数,支持单色和渐变色,支持设置圆弧末端样式:
drawGradientArc = (
graphics,
arcColors/*圆弧默认颜色或颜色组*/,
x, y/*圆弧所在椭圆的左上角坐标*/,
arcWidth, arcHeight/*圆弧的宽和高*/,
arcStartAngle/*起始角度*/,
arcSweepAngle/*扫描角度*/,
lineWidth/*圆弧本身的线宽*/,
arcCapStyle/*弧线端点样式*/
)
以下为完整代码,可以直接在aardio里运行:
import win.ui;
/*DSG{{*/
var winform = win.form(text="渐变圆弧";right=415;bottom=407;bgcolor=16777215)
winform.add(
plus={cls="plus";left=56;top=48;right=356;bottom=348;ah=1;aw=1;z=1}
)
/*}}*/
//计算两个颜色之间的渐变色,并分解成若干个色值,用数组返回
import color; //导入颜色库
var gradientToColors = function(startColor, endColor, step){
// 得到起始和末尾颜色的HSL色值
var sH,sS,sB = color.getHsb(startColor);
var eH,eS,eB = color.getHsb(endColor);
var hStep = (eH - sH) / (step - 1);
var sStep = (eS - sS) / (step - 1);
var bStep = (eB - sB) / (step - 1);
var colorArr = {};
for (i = 1; step; 1) {
var h = sH + hStep * i;
var s = sS + sStep * i;
var b = sB + bStep * i;
if (b < 0) b = 0
elseif (b > 1) b = 1;
table.push(colorArr, color.argb(color.hsb2rgb(h, s, b)));
}
return colorArr;
}
//画渐变圆弧
var drawGradientArc = function(
graphics,
arcColors/*圆弧默认颜色或颜色组*/,
x, y/*圆弧所在椭圆的左上角坐标*/,
arcWidth, arcHeight/*圆弧的宽和高*/,
arcStartAngle/*起始角度*/,
arcSweepAngle/*扫描角度*/,
lineWidth/*圆弧本身的线宽*/,
arcCapStyle/*弧线端点样式*/){
//因为是用pan画笔画圆弧,而画笔的对齐方式是居中对齐
//所以画出的圆弧会大于设定的值,需要进行修正
arcWidth -= lineWidth;
arcHeight -= lineWidth;
x += lineWidth/2;
y += lineWidth/2;
var arcEndAngle = arcStartAngle + arcSweepAngle - 360; //圆弧结束角度
//如果是只有单色,则一笔画成
if(type(arcColors)==type.number){
var pen = gdip.pen(arcColors, lineWidth);
if(arcCapStyle){ //如果设定了端点样式:0平头,2圆头,3尖头
pen.startCap = arcCapStyle;
pen.endCap = arcCapStyle;
}
graphics.drawArc(pen, x, y, arcWidth, arcHeight, arcStartAngle ,arcSweepAngle);
pen.delete();
return;
}
//不是色彩数组,则退出
if(type(arcColors) != type.table) return;
var pen = gdip.pen(0, lineWidth);
var gradientCount = #arcColors - 1; //每两个颜色构成一个渐变区间,所以 渐变区间数量 = 色彩数 - 1
var gradientAngle = arcSweepAngle/gradientCount; //计算每个渐变区间扫略过的角度
for(i=1; gradientCount; 1){ //循环处理每个渐变区间
var colorArr = gradientToColors(arcColors[i], arcColors[i+1], gradientAngle); //分解渐变色到数组
var sweepAngle = gradientAngle / #colorArr; //每段小弧对应的角度
var startAngle = arcStartAngle + (i - 1) * gradientAngle; //每个渐变区间的起始角度
for(n=1; #colorArr; 1){
if(arcCapStyle){ //如果设定了端点样式:0无,1方头,2圆头,3尖角
if(n = 1 && i = 1){ //弧线开端设置样式
pen.startCap = arcCapStyle;
pen.endCap = 0/*_LineCapFlat*/;
}
elseif(n = #colorArr && i = #arcColors - 1){ //弧线末端设置样式
pen.startCap = 0/*_LineCapFlat*/;
pen.endCap = arcCapStyle;
}
else { //中间小弧没有端点
pen.startCap = 0/*_LineCapFlat*/;
pen.endCap = 0/*_LineCapFlat*/;
}
}
//用每个颜色画很短的一小段弧,拼接成大圆弧
pen.color = colorArr[n];
//每段小弧的长度略长一些,可以防止曲率过大时中间出现空隙。这个值可以根据曲率计算出来,但是实际上不需要这么精确,经过测试使用3能基本满足要求。
graphics.drawArc(pen, x, y, arcWidth, arcHeight, startAngle + (n - 1) * sweepAngle, 3);
}
}
pen.delete();
}
var arcColors = {0xFF69EACB, 0xFFEACCF8, 0xFF6654F1}; //圆弧默认颜色或渐变颜色组
var arcStartAngle = 120; //圆弧开口的角度
var arcSweepAngle = 360 - (arcStartAngle - 90)*2; //扫描角度
var lineWidth = 60; //圆弧线的宽度
var arcCapStyle = 2; //圆弧端点样式:0无,1方头,2圆头,3尖角
winform.plus.onDrawContent = function(graphics,rc,txtColor,rcContent,foreColor){
var x,y = 0, 0;
var arcWidth, arcHeight = owner.width, owner.height;
drawGradientArc(graphics, arcColors, x, y, arcWidth, arcHeight, arcStartAngle, arcSweepAngle, lineWidth, arcCapStyle)
winform.plus.redrawTransparent();
}
winform.show()
win.loopMessage();
执行效果,从左到右,分别是:双色渐变+圆角端点,单色填充+方角端点,三色渐变+尖角端点