aardio用GDI+创建渐变圆弧

breezee 9月前 490

最近用aardio做一个小工具,需要创建一段使用渐变色填充的圆弧。

翻了一下GDI+的资料,发现GDI+的线性渐变画刷 LinearGradientBrush 和路径渐变画刷 PathGradientBrush 都不适用。

想起aardio自带的调色工具里有一个渐变的色相环,查看了源码,根据作者的思路实现了渐变圆弧的绘制。

实现原理:

简单来说,就是用逐渐变化的颜色画一连串首尾相连的小短弧,拼成一个大圆弧。

  1. 把圆弧平均分成若干段小短弧,每1角度分1段。比如一个90度角的弧分成90段,一个整圆分成360段。

  2. 以圆弧的分段数作为2个颜色之间渐变的总步数,计算出渐变过程中每一步的色值。比如90度的圆弧,从首到尾要经过90个颜色的变化,通过算法把这90个颜色的色值计算出来。

  3. 用计算出来的色值,画出一段一段的单色小短弧,拼成一个大圆弧,就形成了渐变。比如下图,每一个小方块都是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();

执行效果,从左到右,分别是:双色渐变+圆角端点,单色填充+方角端点,三色渐变+尖角端点

最新回复 (2)
  • 光庆 9月前
    1 2

    试试这个方法:

    import win.ui;
    /*DSG{{*/
    var winform = win.form(text="aardio form";right=751;bottom=631)
    winform.add(
    plus={cls="plus";left=88;top=40;right=640;bottom=592;z=1}
    )
    /*}}*/
    
    winform.show();
    
    import godking.paint
    var p = godking.paint.fromPlus(winform.plus,true/*自动刷新*/,false/*重绘背景*/)
    
    var path = p.path( 1 /*0 交叉填充 1 全填充*/)
    path.addLines({{0,0}{500,0}{500,500}{0,500}{0,0}} /*多点数组*/)
    
    var brush = p.brush.pathGradientBrush(path)
    brush.setSurroundColors({0xFFFF0000,0xFF00FF00,0xFF0000FF,0xFFFF0000})
    
    var path1 = p.path( 0 /*0 交叉填充 1 全填充*/)
    path1.addEllipse(0,0,500,500)
    path1.addEllipse(100,100,300,300)
    
    p.fillPath(path1,brush/*填充颜色或brush对象*/)
    win.loopMessage();

  • breezee 9月前
    0 3
    @光庆 向大佬学习!
返回