0
点赞
收藏
分享

微信扫一扫

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

正文开始之前,先安利一个好东西:inscode,可以在线编辑、在线发布、在线运行。为了便于大家相互学习交流,我把本编辑器社区版后台功能做了阉割,做成了一个学习版,已发布在inscode上,大家可以直接线上体验,线上修改代码,在线看到效果,非常方便(本文的截图都是用它画出来的)地址:InsCode - 让你的灵感立刻落地         矢量图形中,用于填充封闭区域有多种方式:实体填充、图案填充、渐变填充,渐变填充又分线性渐变填充和辐射渐变填充。本文讲解本编辑器中经常用到的一种填充模式:线性渐变填充。效果如下图: ​image.png 图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); ​image.png 讲到这里,如果只是为了解渐变原理,那就够了,但实际的图形软件应用中,渐变填充是图形对象的一个属性或属性值(本编辑器中线性渐变是填充属性的一个取值),例如图形发生了移动、旋转、缩放等行为,相应地,渐变也要跟着变化,所以我们了解其原理后还要知道怎么把它真正应用到自己的项目中,本文的下面章节将讲解本编辑器的实现方式。         首先,我们把渐变作为一个类对象定义出来(下面是代码局部,完整代码见: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();//正式填充     } } 通过以上方法就可以实现各种复杂、绚丽的线性渐变效果,而不管图形位置、形状怎么变换都不会改变渐变与绑定图形的相对位置。下图是使用本编辑器的做出来的一些图形效果 ​ image.png

本编辑器(土豆猫图形编辑器)社区版代码已开源,开源库地址: https://gitee.com/longhan13/lgxmap_community.git https://gitcode.com/longx13/lgxmap_community.git 本编辑器(土豆猫图形编辑器)专业版已发布到个人网站: 土豆猫图形编辑器-专业版

举报

相关推荐

0 条评论