从零手撕一个网页版图形编辑器之线性渐变(4)

本编辑器(土豆猫图形编辑器)社区版代码已开源,开源库地址:
https://gitee.com/longhan13/lgxmap_community.git
https://gitcode.com/longx13/lgxmap_community.git

本编辑器(土豆猫图形编辑器)专业版已发布到个人网站:

土豆猫图形试验台

正文开始之前,先安利CSDN的一个好东西:inscode,可以在线编辑、在线发布、在线运行。为了便于大家相互学习交流,我把本编辑器社区版后台功能做了阉割,做成了一个学习版,已发布在inscode上,大家可以直接线上体验,线上修改代码,在线看到效果,非常方便(本文的截图都是用它画出来的)地址:InsCode - 让你的灵感立刻落地

        矢量图形中,用于填充封闭区域有多种方式:实体填充、图案填充、渐变填充,渐变填充又分线性渐变填充和辐射渐变填充。本文讲解本编辑器中经常用到的一种填充模式:线性渐变填充。效果如下图:

图1 渐变填充效果实例

        我们首先看canvas渐变填充createLinearGradient定义:createLinearGradient(x0, y0, x1, y1)-创建一个从起点(x0,y0)到终点(x1,y1)的线性渐变填充,返回一个线性CanvasGradient对象,创建对象成功后,再调用返回对象的CanvasGradient的addColorStop(offset,color)来创建渐变光圈,所谓光圈就是一个用户指定的颜色,canvas绘制时就在相邻光圈指定的颜色中按canvas颜色渐变算法进行渐变,offset是一个从0到1的浮点数,表示从起点(x0,y0)到该光圈处的距离相对于整根渐变线段(由(x0,y0)到(x1,y1)决定)长度比,等于0表示起点,等于1表示终点,等于0.5则表示中点((x0 + y0)/2,(x1 + y1)/2),以此类推。例如下面这段代码就可以绘制出一根类似圆柱效果的矩形来:

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

let gradient = ctx.createLinearGradient(0, 0, 50, 0);
gradient.addColorStop(0, "blue");
gradient.addColorStop(0.5, "white");
gradient.addColorStop(1, "pink");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 50, 300);

讲到这里,如果只是为了解渐变原理,那就够了,但实际的图形软件应用中,渐变填充是图形对象的一个属性或属性值(本编辑器中线性渐变是填充属性的一个取值),例如图形发生了移动、旋转、缩放等行为,相应地,渐变也要跟着变化,所以我们了解其原理后还要知道怎么把它真正应用到自己的项目中,本文的下面章节将讲解本编辑器的实现方式。

        首先,我们把渐变作为一个类对象定义出来(下面是代码局部,完整代码见:script/canvas/common/struct/fillinfo):

class GradientInfo {
    constructor() {
        let red = new lgxcom.LGXColor();
        red.setVal(255, 0, 0);

        let white = new lgxcom.LGXColor();
        white.setVal(255, 255, 255);
        
        //默认有两个光圈,起点光圈红色,终点光圈白色
        this.stopColorArr = [{ stop: 0, color: red }, { stop: 1, color: white }];
        
        //渐变起点和终点坐标都是局部坐标系,便于实现:所关联图形对象做了各种变换后,其渐变效果仍相对不变
        this.stPt = { x: 0, y: 0 };
        this.endPt = { x: 0, y: 0 };
        
        this.degree = 0;//渐变旋转角度
        this.userSetPosFlag = false;
    }

    toJson();//转为json对象以备后面持久化,具体代码省略
    fromJson(jsonobj);//从持久化的json对象转为一个线性渐变填充对象,具体代码省略

     //设置线性渐变的默认起点、终点坐标
     //参数:refGraph是本渐变类对象所影响的图形对象
    setStopDefaultPos(refGraph) {
        let localZone = refGraph.getLocalZone();//获得图形矩形包围盒
        this.stPt.x = localZone.left;//默认起点为左上角
        this.stPt.y = localZone.top;

        this.endPt.x = localZone.right;//默认终点为右上角
        this.endPt.y = localZone.top;
        this.userSetPosFlag = true;
    }

   //设置渐变光圈参数
    setStop(stopArr) {
        if (!stopArr) {
            return;
        }
        this.stopColorArr = [];
        for (let i = 0; i < stopArr.length; i++) {
            let t = stopArr[i];
            let proprotion = t.stop;
            let colorVal = t.color;
            let colorObj = new lgxcom.LGXColor();
            colorObj.setColorByValue(colorVal);
            this.stopColorArr.push({ stop: parseFloat(proprotion), color: colorObj });
        }
    }
}
 

然后我们再定义一个填充对象类,渐变作为填充的一个属性值存进去(下面是代码局部,完整代码见:script/canvas/common/struct/fillinfo):

class LGXFillDefInfo {
    constructor() {
        this.fillStyle = LGXEnums.LGXFillStyle.EMPTY;
        this.fillColor = new lgxcom.LGXColor();
        this.fillColor.setVal(255, 0, 0);
        this.gradientInfo = null;
    }
}

以上类定义完成后,我们就可以在实际绘图中生成图形后(例如在画布上鼠标拖动生成一个矩形),为该图形的填充对象(_fillDef)中渐变属性(gradientInfo )赋值:

graph._fillDef.gradientInfo = new GradientInfo(),并根据实际需要票配置相应起始点、终止点、以及光圈参数,最后在刷新流水线中,绘制该图形时,使用相应的填充参数调用canvas API显示出来。下面代码是本编辑器的实现代码片段(完整代码请见:script/canvas/graph/paintutil/fillutil.js)

FillUtil.active = function (fillDef, canvasCtx, graph, mapinfo, isPaintSybolUnitFlag, transformInfo) {

    let grd = null;

    let fillGradientInfo = fillDef.gradientInfo;

    let mtx = graph.getLocalMatrix();//获得渐变绑定的图形对象的局部坐标系

    let localP1 = fillGradientInfo.stPt;

    let localP2 = fillGradientInfo.endPt;

    //局部坐标转世界坐标

    let worldPt1 = mtx.MultT({ x: localP1.x, y: localP1.y, z: 0 });

    let worldPt2 = mtx.MultT({ x: localP2.x, y: localP2.y, z: 0 });

    let p1 = worldPt1;

    let p2 = worldPt2;

    if (p1 && p2) {

        //以渐变起点和终点指定的线段的中点为中心旋转渐变起点和终点

        let rotAngle = funcs.degree2Radian(fillGradientInfo.degree);

        let midPt = funcs.getMidPoint(p1, p2);

        let rotp1 = funcs.rotate(p1, midPt, rotAngle);

        let rotp2 = funcs.rotate(p2, midPt, rotAngle);

        let sp1 = null;

        let sp2 = null;

        let errorFlag = false;

        if (isPaintSybolUnitFlag) {

            //如果图形属于图符内,则要先把坐标从图符对应的图形对象的局部坐标系转为世界坐标系

            let wpt1 = CoordTRFUtil.transLocalPt2World(rotp1, 0, transformInfo);

            let wpt2 = CoordTRFUtil.transLocalPt2World(rotp2, 0, transformInfo);

            //渐变起点、终点转为屏幕坐标

            sp1 = CoordTRFUtil.world2Screen(wpt1, mapinfo);

            sp2 = CoordTRFUtil.world2Screen(wpt2, mapinfo);

        }

        else {

            //渐变起点、终点转为屏幕坐标

            sp1 = CoordTRFUtil.world2Screen(rotp1, mapinfo);

            sp2 = CoordTRFUtil.world2Screen(rotp2, mapinfo);

        }

        try {

            //创建一个线性渐变对象

            grd = map.createLinearGradient(sp1.x, sp1.y, sp2.x, sp2.y);

            //为渐变对象增加光圈

            for (let i = 0; i < fillGradientInfo.stopColorArr.length; i++) {

                let t = fillGradientInfo.stopColorArr[i];

                let tmpColor = t.color.getCloneCopy();

                if (graph.getSelect()) {

                    //如果图形被选中,让对应光圈颜色变成半透明,让图形选中效果体验好看一些,一目了然

                    tmpColor.alpha = Math.ceil(tmpColor.alpha / 2);

                }

                grd.addColorStop(t.stop, tmpColor.toHexString());

            }

        }

        catch (e) {

            errorFlag = true;

            console.log(e);

        }

        finally {

            if (errorFlag) {

                grd = null;

            }

        }

    }

    if (grd != null) {

        canvasCtx.fillStyle = grd;//改变画布当前的填充模式为本次创建的渐变填充

        canvasCtx.fill();//正式填充

    }

}

通过以上方法就可以实现各种复杂、绚丽的线性渐变效果,而不管图形位置、形状怎么变换都不会改变渐变与绑定图形的相对位置。下图是使用本编辑器的做出来的一些图形效果

  • 29
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值