软件项目技术点(7)——在canvas上绘制自定义图形
AxeSlide软件项目梳理 canvas绘图系列知识点整理
图形种类
目前我们软件可以绘制出来的形状有如下这几种,作为开发者我们一直想支持用户可以拖拽的类似word里面图形库,但目前还没有找到比较合适的库。
下图中所有的图形都是我们自己写代码在canvas上绘制方法出来的,可以改变实心/空心,改变颜色,大小宽高,线条弯曲度,透明度等。
父类shape类
实现的代码如下:所有的图形均继承自shape类,而shape类继承自CommonElement,shape中的所有图形添加到画布上都是一个元素。
这个类里面有很多功能函数:
prepareStyle() { //绘制准备context绘制属性
setPro() { }//绘制前准备参数
public thiner() {//变细
public thicker() {//加粗
changeColor(color: string, callBack: Function) {//改变绘制颜色
changeAlpha(alpha: number, callBack: Function) {//改变透明度
changeFill(isFill: boolean) {//切换实心空心
drawPath() { }//绘制图形路径,所有图形都会重写该方法
draw() {//绘制调用入口
1 export class Shape extends CommonElement { 2 shapeParameter: ShapeParameter; 3 tempFill: boolean; 4 tempStroke: boolean; 5 constructor(id: string, config: Config, context: CanvasRenderingContext2D, shapeParameter: ShapeParameter, typeName) { 6 super(id, config, context, typeName); 7 this.shapeParameter = shapeParameter; 8 } 9 prepareStyle() { //绘制准备context绘制属性 10 if (this.shapeParameter.computeLineWidth && this.shapeParameter.isStroke) { 11 this.shapeParameter.lineWidth = Math.max(2, Math.min(this.config.width * editor.canvas.canvasImp.scale, this.config.height * editor.canvas.canvasImp.scale) / this.shapeParameter.lineWidthScale); 12 13 } 14 var ctx = this.context; 15 ctx.fillStyle = Common.Util.makeRGBA(this.shapeParameter.fillStyle, this.shapeParameter.alpha); 16 ctx.strokeStyle = Common.Util.makeRGBA(this.shapeParameter.strokeStyle, this.shapeParameter.alpha); 17 ctx.lineWidth = this.shapeParameter.lineWidth / editor.canvas.canvasImp.scale; 18 19 } 20 public setPro() { }//绘制前准备参数 21 public drawPath() { }//绘制图形路径,所有图形都会重写该方法 22 public draw() {//绘制调用入口 23 //保存context状态 24 this.context.save(); 25 this.rotate();//将画布旋转到位 26 this.setPro(); 27 this.prepareStyle(); 28 this.drawPath(); 29 //恢复context状态 30 this.context.restore(); 31 } 32
所有图形类都有的一个属性ShapeParameter 具体内容如下,每个属性值都有各自的作用
1 export class ShapeParameter {//图形属性参数 2 lineWidth: number; 3 isFill: boolean; 4 isStroke: boolean 5 fillStyle: string; 6 strokeStyle: string 7 lineWidthScale: number; 8 isAutiClockwise: boolean; 9 computeLineWidth: boolean; 10 alpha: number; 11 lineDash: any; 12 constructor() { 13 this.lineWidth = 1; 14 this.isStroke = true; 15 this.isFill = false; 16 this.fillStyle = editor.currentShapeColor || editor.canvas.canvasImp.theme.shapeColor; 17 this.strokeStyle = editor.currentShapeColor || editor.canvas.canvasImp.theme.shapeColor; 18 this.lineWidthScale = lineWidthScale; 19 this.isAutiClockwise = false; 20 this.computeLineWidth = true; 21 this.alpha = editor.currentShapAlpha || 1; 22 this.lineDash = [10, 15]; 23 } 24 }
特殊图形绘制计算方法
下面介绍几种绘制各种图形用到的特殊计算方法
如下图中的一个线条,我们可以拖拽中间控制点形成一个弧,这个弧形是某个圆的一部分。
我们定义左侧的第一个点为startPoint,中间的点为controlPoint,右侧的点为endPoint。
我们在绘制出这个图形时,需要知道如何求这三个点确定的圆的圆心坐标怎么计算,还有通过两点计算(x1,y1)为圆心,圆上某点(x2,y2)的角度的计算方法
1 GetCenter(): Point {//获取三个点,确定的一个圆的中心点坐标 2 var p1 = this.startPoint; 3 var p2 = this.controlPoint; 4 var p3 = this.endPoint; 5 var C1 = new Point((p1.x + p2.x) / 2.0, (p1.y + p2.y) / 2.0); 6 var C2 = new Point((p2.x + p3.x) / 2.0, (p2.y + p3.y) / 2.0); 7 var I = new Point(0, 0);; 8 var k1: number, k2: number; 9 if ((p2.y - p1.y) == 0.0) { 10 if ((p3.y - p2.y) == 0.0) { 11 return null; 12 } else { 13 k2 = -1 * (p3.x - p2.x) / (p3.y - p2.y); 14 I.x = C1.x; I.y = k2 * (I.x - C2.x) + C2.y; 15 } 16 } else { 17 k1 = -1 * (p2.x - p1.x) / (p2.y - p1.y); 18 if ((p3.y - p2.y) == 0.0) { 19 I.x = C2.x; 20 I.y = k1 * (I.x - C1.x) + C1.y; 21 } else { 22 k2 = -1 * (p3.x - p2.x) / (p3.y - p2.y); 23 if (k1 == k2) { 24 return null; 25 } else { 26 I.x = (C2.y - C1.y + k1 * C1.x - k2 * C2.x) / (k1 - k2); I.y = k1 * (I.x - C1.x) + C1.y; 27 } 28 } 29 } 30 return I; 31 }
1 //通过两点计算x1,y1为圆心,圆上某点(x2,y2)的角度(按照arc的角度计算) 2 static computeAng(x1, y1, x2, y2) { 3 var ang = (x1 - x2) / (y1 - y2); 4 ang = Math.atan(ang); 5 if (x1 == x2 && y2 > y1) { 6 return 0.5 * Math.PI; 7 } 8 if (x1 == x2 && y2 < y1) { 9 return 1.5 * Math.PI; 10 } 11 if (y1 == y2 && x2 > x1) { 12 return 0; 13 } 14 if (y1 == y2 && x2 < x1) { 15 return Math.PI; 16 } 17 if (x2 > x1 && y2 > y1) { 18 return Math.PI / 2 - ang; 19 } 20 else if (x2 < x1 && y2 > y1) { 21 return Math.PI / 2 - ang; 22 } 23 else if (x2 < x1 && y2 < y1) { 24 return 3 * Math.PI / 2 - ang; 25 } 26 else if (x2 > x1 && y2 < y1) { 27 return 3 * Math.PI / 2 - ang; 28 } 29 }
另外再介绍一下我们的这个箭头是如何实现的
1 drawIsosceles(context: CanvasRenderingContext2D) {//画等腰箭头 2 var triangleSide = this.triangleSide; 3 //First the center of the triangle base (where we start to draw the triangle) 4 var centerBaseArrowX = this.basePoint.x ; 5 var centerBaseArrowY = this.basePoint.y; 6 7 //Let's calculate the first point, easy! 8 var ax = centerBaseArrowX + (triangleSide / 2) * Math.cos(this.centerAngle); 9 var ay = centerBaseArrowY + (triangleSide / 2) * Math.sin(this.centerAngle); 10 11 //Now time to get mad: the farest triangle point from the arrow body 12 var bx = centerBaseArrowX + (1/ 2/Math.sqrt(3) ) * triangleSide * (Math.sin(-this.centerAngle)); 13 var by = centerBaseArrowY + ( 1/ 2/Math.sqrt(3)) * triangleSide * (Math.cos(-this.centerAngle)); 14 15 //Easy , like the a point 16 var cx = centerBaseArrowX - (triangleSide / 2) * Math.cos(this.centerAngle); 17 var cy = centerBaseArrowY - (triangleSide / 2) * Math.sin(this.centerAngle); 18 19 context.beginPath(); 20 //We move to the center of the base 21 context.moveTo(centerBaseArrowX, centerBaseArrowY); 22 context.lineTo(ax, ay); 23 context.lineTo(bx, by); 24 context.lineTo(cx, cy); 25 context.lineTo(centerBaseArrowX, centerBaseArrowY); 26 context.fill(); 27 }
在canvas上绘制多边形的方法
drawPath(context: CanvasRenderingContext2D) { for (var ixVertex = 0; ixVertex <= this.nPoints; ++ixVertex) { var angle = ixVertex * 2 * Math.PI / this.nPoints - this.startAngle; var point = new Point(this.centerPoint.x + this.radius * Math.cos(angle), this.centerPoint.y + this.radius * Math.sin(angle)); context.lineTo(point.x, point.y); } }
在canvas上绘制五角星等多角形的方法
1 drawPath(context:CanvasRenderingContext2D) { 2 for (var ixVertex = 0; ixVertex <= 2 * this.nPoints; ++ixVertex) { 3 var angle = ixVertex * Math.PI / this.nPoints - Math.PI / 2; 4 var radius = ixVertex % 2 == 0 ? this.outerRadius : this.innerRadius; 5 context.lineTo(this.centerPoint.x + radius * Math.cos(angle), this.centerPoint.y + radius * Math.sin(angle)); 6 } 7 }