SCanvasPaintEngine.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. /*
  2. * *********************************************************************************************************************
  3. *
  4. * !!
  5. * .F88X
  6. * X8888Y
  7. * .}888888N;
  8. * i888888N; .:! .I$WI:
  9. * R888888I .'N88~ i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
  10. * .R888888I .;N8888~ .X8' "8I.!,/8" !%NY8`"8I8~~8>,88I
  11. * +888888N; .8888888Y "&&8Y.}8,
  12. * ./888888N; .R888888Y .'}~ .>}'.`+> i}! "i' +/' .'i~ !11,.:">, .~]! .i}i
  13. * ~888888%: .I888888l .]88~`1/iY88Ii+1'.R$8$8]"888888888> Y8$ W8E X8E W8888'188Il}Y88$*
  14. * 18888888 E8888881 .]W%8$`R8X'&8%++N8i,8N%N8+l8%` .}8N:.R$RE%N88N%N$K$R 188,FE$8%~Y88I
  15. * .E888888I .i8888888' .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
  16. * 8888888I .,N888888~ ~88i"8W,!N8*.I88.}888%F,i$88"F88" 888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
  17. * i888888N' I888Y ]88;/EX*IFKFK88X K8R .l8W 88Y ~88}'88E&%8W.X8N``]88!.$8K .:W8I
  18. * .i888888N; I8Y .&8$ .X88! i881.:%888>I88 ;88] +88+.';;;;:.Y88X 18N.,88l .+88/
  19. * .:R888888I
  20. * .&888888I Copyright (c) 2009-2020. 博锐尚格科技股份有限公司
  21. * ~8888'
  22. * .!88~ All rights reserved.
  23. *
  24. * *********************************************************************************************************************
  25. */
  26. import { SPaintEngine } from "./SPaintEngine";
  27. import {
  28. SBrushType,
  29. SFont,
  30. SGradientStop,
  31. SLine,
  32. SLinearGradient,
  33. SLineCapStyle,
  34. SPaintEngineType,
  35. SPoint,
  36. SRadialGradient,
  37. SRect,
  38. STextAlign,
  39. STextBaseLine,
  40. STextDirection
  41. } from "..";
  42. import { SPath } from "../SPath";
  43. /**
  44. * Canvas 绘制引擎基类
  45. *
  46. * @author 庞利祥 <sybotan@126.com>
  47. */
  48. export class SCanvasPaintEngine extends SPaintEngine {
  49. /** 画布对象 */
  50. private readonly _canvas: CanvasRenderingContext2D;
  51. /** 融合类型 */
  52. static gcoList = [
  53. "copy",
  54. "destination-atop",
  55. "destination-in",
  56. "destination-out",
  57. "destination-over",
  58. "lighter",
  59. "source-atop",
  60. "source-in",
  61. "source-out",
  62. "source-over",
  63. "xor"
  64. ];
  65. /**
  66. * 绘制引擎类型
  67. *
  68. * @return 返回 Canvas 绘制引擎类型
  69. */
  70. get type(): SPaintEngineType {
  71. return SPaintEngineType.Canvas;
  72. }
  73. /**
  74. * 构造函数
  75. *
  76. * @param canvas canvas对象
  77. */
  78. constructor(canvas: CanvasRenderingContext2D) {
  79. super();
  80. this._canvas = canvas;
  81. this._canvas.imageSmoothingEnabled = true;
  82. }
  83. /**
  84. * 清空矩形区域
  85. *
  86. * @param rect 矩形
  87. */
  88. clearRect(rect: SRect): void {
  89. this.setMatrix();
  90. this._canvas.clearRect(rect.x, rect.y, rect.width, rect.height);
  91. }
  92. /**
  93. * 绘制矩形
  94. *
  95. * @param rect 矩形
  96. */
  97. drawRect(rect: SRect): void {
  98. this.init((): void => {
  99. this._canvas.beginPath();
  100. this._canvas.rect(rect.x, rect.y, rect.width, rect.height);
  101. this._canvas.stroke();
  102. this._canvas.fill();
  103. });
  104. }
  105. /**
  106. * 绘制圆形
  107. *
  108. * @param cx 圆心 x 坐标
  109. * @param cy 圆心 y 坐标
  110. * @param r 圆半径
  111. */
  112. drawCircle(cx: number, cy: number, r: number): void {
  113. this.init((): void => {
  114. this._canvas.beginPath();
  115. this._canvas.arc(cx, cy, r, 0, 2 * Math.PI, true);
  116. this._canvas.fill();
  117. this._canvas.stroke();
  118. });
  119. }
  120. /**
  121. * 绘制椭圆
  122. *
  123. * @param cx 圆点 X 坐标
  124. * @param cy 圆点 Y 坐标
  125. * @param rx 水平半径
  126. * @param ry 垂直半径
  127. */
  128. drawEllipse(cx: number, cy: number, rx: number, ry: number): void {
  129. this.init((): void => {
  130. this._canvas.beginPath();
  131. this._canvas.ellipse(cx, cy, rx, ry, 0, 0, 2 * Math.PI, true);
  132. this._canvas.fill();
  133. this._canvas.stroke();
  134. });
  135. }
  136. /**
  137. * 绘制线段
  138. *
  139. * @param line 线段
  140. */
  141. drawLine(line: SLine): void {
  142. this.init((): void => {
  143. this._canvas.beginPath();
  144. this._canvas.moveTo(line.x1, line.y1);
  145. this._canvas.lineTo(line.x2, line.y2);
  146. this._canvas.stroke();
  147. });
  148. }
  149. /**
  150. * 绘制折线
  151. *
  152. * @param points 折线折点
  153. */
  154. drawPolyline(points: SPoint[]): void {
  155. // 折线至少要有2个节点
  156. if (points.length < 2) {
  157. return;
  158. }
  159. this.init((): void => {
  160. this._canvas.beginPath();
  161. this._canvas.moveTo(points[0].x, points[0].y);
  162. // 遍历折线折点数组,绘制折线
  163. for (let p of points) {
  164. this._canvas.lineTo(p.x, p.y);
  165. }
  166. this._canvas.stroke();
  167. });
  168. }
  169. /**
  170. * 绘制多边形
  171. *
  172. * @param points 多边形顶点
  173. */
  174. drawPolygon(points: SPoint[]): void {
  175. // 多边形至少要有3个节点
  176. if (points.length < 3) {
  177. return;
  178. }
  179. this.init((): void => {
  180. this._canvas.beginPath();
  181. this._canvas.moveTo(points[0].x, points[0].y);
  182. // 遍历顶点数组,绘制折线
  183. for (let p of points) {
  184. this._canvas.lineTo(p.x, p.y);
  185. }
  186. this._canvas.closePath();
  187. this._canvas.fill();
  188. this._canvas.stroke();
  189. });
  190. }
  191. /**
  192. * 绘制路径
  193. *
  194. * @param path 路径
  195. */
  196. drawPath(path: SPath): void {
  197. this.init((): void => {
  198. this.drawWay(path);
  199. });
  200. }
  201. /**
  202. * 绘制文本
  203. *
  204. * @param text 文本内容
  205. * @param x X 坐标
  206. * @param y Y 坐标
  207. * @param maxWidth 最大宽度
  208. */
  209. drawText(text: string, x: number, y: number, maxWidth?: number): void {
  210. this.init((): void => {
  211. this.setFont();
  212. // 不传最大宽度时,绘制时不限制最大宽度
  213. if (maxWidth == undefined) {
  214. this._canvas.strokeText(text, x, y);
  215. this._canvas.fillText(text, x, y);
  216. } else { // 绘制时限制最大宽度
  217. this._canvas.strokeText(text, x, y, maxWidth);
  218. this._canvas.fillText(text, x, y, maxWidth);
  219. }
  220. });
  221. }
  222. /**
  223. * 绘制图片
  224. *
  225. * @param img 图片
  226. * @param x X 坐标
  227. * @param y Y 坐标
  228. * @param width 宽度
  229. * @param height 高度
  230. */
  231. drawImage(
  232. img: CanvasImageSource,
  233. x: number,
  234. y: number,
  235. width?: number,
  236. height?: number
  237. ): void {
  238. this.init((): void => {
  239. try {
  240. // 不传宽度时,绘制图片不限制宽高
  241. if (width == undefined) {
  242. this._canvas.drawImage(img, x, y);
  243. } else { // 绘制时限制图片宽高
  244. this._canvas.drawImage(img, x, y, width, height as number);
  245. }
  246. } catch (e) { // 捕获 获取图片异常
  247. console.log(e);
  248. }
  249. });
  250. }
  251. /**
  252. * 预测量文本宽度
  253. *
  254. * @param text 预测的文本
  255. * @return 文本长度像素
  256. */
  257. textWidth(text: string): number {
  258. return this._canvas.measureText(text).width;
  259. }
  260. /**
  261. * 绘制二次贝塞尔曲线
  262. *
  263. * @param cp1x 控制点 X 坐标
  264. * @param cp1y 控制点 Y 坐标
  265. * @param x 结束点 X 坐标
  266. * @param y 结束点 Y 坐标
  267. */
  268. quadraticCurveTo(cp1x: number, cp1y: number, x: number, y: number): void {
  269. this._canvas.quadraticCurveTo(cp1x, cp1y, x, y);
  270. }
  271. /**
  272. * 绘制三次贝塞尔曲线
  273. *
  274. * @param cp1x 起始点控制点 X 坐标
  275. * @param cp1y 起始点控制点 Y 坐标
  276. * @param cp2x 结束点控制点 X 坐标
  277. * @param cp2y 结束点控制点 Y 坐标
  278. * @param x 结束点X坐标
  279. * @param y 结束点Y坐标
  280. */
  281. bezierCurveTo(
  282. cp1x: number,
  283. cp1y: number,
  284. cp2x: number,
  285. cp2y: number,
  286. x: number,
  287. y: number
  288. ): void {
  289. this._canvas.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
  290. }
  291. /**
  292. * 绘制带导角空心矩形
  293. *
  294. * @param rect 矩形
  295. * @param r 导角半径
  296. */
  297. drawRoundRect(rect: SRect, r: number): void {
  298. this.init((): void => {
  299. this._canvas.beginPath();
  300. this._canvas.moveTo(rect.x, rect.y + r);
  301. this._canvas.lineTo(rect.x, rect.bottom - r);
  302. this._canvas.quadraticCurveTo(
  303. rect.x,
  304. rect.bottom,
  305. rect.x + r,
  306. rect.bottom
  307. );
  308. this._canvas.lineTo(rect.right - r, rect.bottom);
  309. this._canvas.quadraticCurveTo(
  310. rect.right,
  311. rect.bottom,
  312. rect.right,
  313. rect.bottom - r
  314. );
  315. this._canvas.lineTo(rect.right, rect.y + r);
  316. this._canvas.quadraticCurveTo(
  317. rect.right,
  318. rect.y,
  319. rect.right - r,
  320. rect.y
  321. );
  322. this._canvas.lineTo(rect.x + r, rect.y);
  323. this._canvas.quadraticCurveTo(rect.x, rect.y, rect.x, rect.y + r);
  324. this._canvas.fill();
  325. this._canvas.stroke();
  326. });
  327. }
  328. /**
  329. * 设置字体
  330. *
  331. * @param font 字体
  332. */
  333. changeFont(font: SFont): void {
  334. this._canvas.font = `${font.size}px ${font.name}`;
  335. this.setTextAlign(font.textAlign);
  336. this.setBaseLine(font.textBaseLine);
  337. this.setTextDirection(font.textDirection);
  338. }
  339. /**
  340. * 设置整体绘制状态
  341. *
  342. * @param fn 绘制图形的具体函数
  343. */
  344. private init(fn: Function): void {
  345. this.setMatrix();
  346. this.setPen();
  347. this.setBrush();
  348. this.setComposite();
  349. this.setShadow();
  350. this.setClip();
  351. fn();
  352. }
  353. /**
  354. * 设置画笔
  355. */
  356. private setPen(): void {
  357. this._canvas.strokeStyle = `rgba(${this.state.pen.color.red}, ${
  358. this.state.pen.color.green
  359. }, ${this.state.pen.color.blue}, ${this.state.pen.color.alpha /
  360. 255.0})`;
  361. this._canvas.lineWidth = this.state.pen.lineWidth;
  362. this._canvas.miterLimit = this.state.pen.miterLimit;
  363. // 设定了画笔的虚线样式
  364. if (this.state.pen.lineDash != null) {
  365. this._canvas.setLineDash(this.state.pen.lineDash);
  366. this._canvas.lineDashOffset = this.state.pen.dashOffset;
  367. } else { // 没有设定画笔的虚线样式
  368. this._canvas.setLineDash([]);
  369. this._canvas.lineDashOffset = 0;
  370. }
  371. this.setLineCapStyle(this.state.pen.lineCapStyle);
  372. }
  373. /**
  374. * 线段端点风格
  375. *
  376. * @param style 风格
  377. */
  378. private setLineCapStyle(style: SLineCapStyle): void {
  379. // 没有设定风格
  380. if (style == undefined) {
  381. return;
  382. }
  383. // 圆头
  384. if (style == SLineCapStyle.Round) {
  385. this._canvas.lineCap = "round";
  386. } else if (style == SLineCapStyle.Square) { // 方头
  387. this._canvas.lineCap = "square";
  388. } else { // 平头
  389. this._canvas.lineCap = "butt";
  390. }
  391. }
  392. /**
  393. * 设置画刷
  394. */
  395. private setBrush(): void {
  396. // 实心画刷
  397. if (this.state.brush.type == SBrushType.Color) {
  398. this._canvas.fillStyle = `rgba(${this.state.brush.color.red}, ${
  399. this.state.brush.color.green
  400. }, ${this.state.brush.color.blue}, ${this.state.brush.color.alpha /
  401. 255.0})`;
  402. } else if (this.state.brush.type == SBrushType.Gradient) { // 渐变画刷
  403. let gradient = this.state.brush.gradient,
  404. drawGradient: CanvasGradient;
  405. // 线性渐变画刷
  406. if (gradient instanceof SLinearGradient) {
  407. drawGradient = this._canvas.createLinearGradient(
  408. gradient.x1,
  409. gradient.y1,
  410. gradient.x2,
  411. gradient.y2
  412. );
  413. } else if (gradient instanceof SRadialGradient) { // 径向渐变画刷
  414. drawGradient = this._canvas.createRadialGradient(
  415. gradient.x1,
  416. gradient.y1,
  417. gradient.r1,
  418. gradient.x2,
  419. gradient.y2,
  420. gradient.r2
  421. );
  422. }
  423. // TODO:建龙补充
  424. // @ts-ignore
  425. if (gradient && drawGradient) {
  426. gradient.stopList.forEach((t: SGradientStop): void => {
  427. drawGradient.addColorStop(
  428. t.pos,
  429. `rgba(${t.color.red},${t.color.green},${
  430. t.color.blue
  431. },${t.color.alpha / 255.0})`
  432. );
  433. });
  434. this._canvas.fillStyle = drawGradient;
  435. }
  436. }
  437. }
  438. /**
  439. * 设置融合
  440. */
  441. private setComposite(): void {
  442. this._canvas.globalCompositeOperation =
  443. SCanvasPaintEngine.gcoList[this.state._composite];
  444. }
  445. /**
  446. * 设置阴影
  447. */
  448. private setShadow(): void {
  449. // 存在阴影扩散距离和阴影颜色
  450. if (this.state.shadow.shadowBlur > 0 && this.state.shadow.shadowColor) {
  451. this._canvas.shadowBlur = this.state.shadow.shadowBlur;
  452. this._canvas.shadowColor = `rgba(${
  453. this.state.shadow.shadowColor.red
  454. },${this.state.shadow.shadowColor.green}, ${
  455. this.state.shadow.shadowColor.blue
  456. }, ${this.state.shadow.shadowColor.alpha / 255.0})`;
  457. this._canvas.shadowOffsetX = this.state.shadow.shadowOffsetX;
  458. this._canvas.shadowOffsetY = this.state.shadow.shadowOffsetY;
  459. } else { // 否则
  460. this._canvas.shadowBlur = 0;
  461. this._canvas.shadowColor = "";
  462. this._canvas.shadowOffsetX = 0;
  463. this._canvas.shadowOffsetY = 0;
  464. }
  465. }
  466. /**
  467. * 设置字体
  468. */
  469. private setFont(): void {
  470. this._canvas.font = `${this.state.font.size}px ${this.state.font.name}`;
  471. this.setTextAlign(this.state.font.textAlign);
  472. this.setBaseLine(this.state.font.textBaseLine);
  473. this.setTextDirection(this.state.font.textDirection);
  474. }
  475. /**
  476. * 文本对齐选项
  477. *
  478. * @param value 对齐方式
  479. */
  480. private setTextAlign(value: STextAlign): void {
  481. // 不设置对齐方式时
  482. if (value == undefined) {
  483. return;
  484. }
  485. // 文本在指定的位置开始
  486. if (value === STextAlign.Start) {
  487. this._canvas.textAlign = "start";
  488. } else if (value === STextAlign.End) { // 文本在指定的位置结束
  489. this._canvas.textAlign = "end";
  490. } else if (value === STextAlign.Left) { // 左对齐
  491. this._canvas.textAlign = "left";
  492. } else if (value === STextAlign.Center) { // 文本的中心被放置在指定的位置
  493. this._canvas.textAlign = "center";
  494. } else { // 右对齐
  495. this._canvas.textAlign = "right";
  496. }
  497. }
  498. /**
  499. * 设置文本基线对齐选项
  500. *
  501. * @param value 对齐方式
  502. */
  503. private setBaseLine(value: STextBaseLine): void {
  504. if (value == undefined) {
  505. return;
  506. }
  507. if (value == STextBaseLine.Alphabetic) {
  508. this._canvas.textBaseline = "alphabetic";
  509. } else if (value == STextBaseLine.Top) {
  510. this._canvas.textBaseline = "top";
  511. } else if (value == STextBaseLine.Hanging) {
  512. this._canvas.textBaseline = "hanging";
  513. } else if (value == STextBaseLine.Middle) {
  514. this._canvas.textBaseline = "middle";
  515. } else if (value == STextBaseLine.Ideographic) {
  516. this._canvas.textBaseline = "ideographic";
  517. } else {
  518. this._canvas.textBaseline = "bottom";
  519. }
  520. }
  521. /**
  522. * 设置文本方向选项
  523. *
  524. * @param value 文本方向
  525. */
  526. private setTextDirection(value: STextDirection): void {
  527. if (value == undefined) {
  528. return;
  529. }
  530. if (value == STextDirection.Inherit) {
  531. this._canvas.direction = "inherit";
  532. } else if (value == STextDirection.LTR) {
  533. this._canvas.direction = "ltr";
  534. } else {
  535. this._canvas.direction = "rtl";
  536. }
  537. }
  538. /**
  539. * 设置变型矩阵
  540. */
  541. private setMatrix(): void {
  542. this._canvas.setTransform(
  543. this.state.matrix.a,
  544. this.state.matrix.b,
  545. this.state.matrix.c,
  546. this.state.matrix.d,
  547. this.state.matrix.e,
  548. this.state.matrix.f
  549. );
  550. }
  551. /**
  552. * 设置裁剪路径
  553. *
  554. */
  555. setClip(): void {
  556. if (this.state.clip) {
  557. this.drawWay(this.state.clip);
  558. this._canvas.clip();
  559. }
  560. }
  561. /**
  562. * 绘制路径
  563. *
  564. */
  565. private drawWay(path: SPath): void {
  566. this._canvas.beginPath();
  567. path.cmdList.forEach((cmd): void => {
  568. if (cmd.command == "L") {
  569. this._canvas.lineTo(cmd.args[0], cmd.args[1]);
  570. } else if (cmd.command == "M") {
  571. this._canvas.moveTo(cmd.args[0], cmd.args[1]);
  572. } else if (cmd.command == "Z") {
  573. this._canvas.closePath();
  574. } else if (cmd.command == "Q") {
  575. this._canvas.quadraticCurveTo(
  576. cmd.args[0],
  577. cmd.args[1],
  578. cmd.args[2],
  579. cmd.args[3]
  580. );
  581. } else if (cmd.command == "T") {
  582. // @ts-ignore
  583. this._canvas.bezierCurveTo(
  584. cmd.args[0],
  585. cmd.args[1],
  586. cmd.args[2],
  587. cmd.args[3],
  588. cmd.args[4]
  589. );
  590. } else if (cmd.command == "Ellipse") {
  591. this._canvas.ellipse(
  592. cmd.args[0],
  593. cmd.args[1],
  594. cmd.args[2],
  595. cmd.args[3],
  596. 0,
  597. 0,
  598. Math.PI * 2
  599. );
  600. } else if (cmd.command == "Arc") {
  601. this._canvas.arc(
  602. cmd.args[0],
  603. cmd.args[1],
  604. cmd.args[2],
  605. cmd.args[3],
  606. cmd.args[4],
  607. cmd.args[5] == 1
  608. );
  609. } else if (cmd.command == "ArcTo") {
  610. this._canvas.arcTo(
  611. cmd.args[0],
  612. cmd.args[1],
  613. cmd.args[2],
  614. cmd.args[3],
  615. cmd.args[4]
  616. );
  617. }
  618. });
  619. this._canvas.fill();
  620. this._canvas.stroke();
  621. }
  622. }