ROIDrawer.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. import Drawer from './drawer.js';
  2. class ROIDrawer extends Drawer {
  3. constructor(props) {
  4. super(props);
  5. this.currentState = 'end';
  6. this.points = [];
  7. this.polygons = [];
  8. this.MAX_POLYGON = 1;
  9. this.onDrawROIFinishedCallback = null;
  10. }
  11. _init() {
  12. this.context.lineWidth = 2;
  13. this.context.font = 'bold 20px Arial';
  14. this.canvas.addEventListener('click', this._onClick.bind(this));
  15. this.canvas.addEventListener('contextmenu', this._oncontextmenu.bind(this));
  16. this.canvas.addEventListener('mousemove', this._onmousemove.bind(this));
  17. }
  18. _onClick(e) {
  19. switch (this.currentState) {
  20. case 'begin':
  21. this.points.push(getMousePos(this.canvas, e));
  22. this.currentState = 'firstPoint';
  23. break;
  24. case 'firstPoint': // 防止初始点点两次
  25. break;
  26. case 'move':
  27. this.currentState = 'points';
  28. break;
  29. case 'points':
  30. let currentPoint = getMousePos(this.canvas, e);
  31. if ((currentPoint.x === this.points[this.points.length - 1].x) && (currentPoint.y === this.points[this.points.length - 1].y)) {
  32. return;
  33. }
  34. this.points.push(currentPoint);
  35. if (checkPolygon(this.points)) {
  36. this.points.pop();
  37. }
  38. //console.log(this.points);
  39. break;
  40. case 'end':
  41. //console.log(this.points)
  42. break;
  43. default:
  44. console.log('unknown state: ', this.currentState);
  45. break;
  46. }
  47. // this._drawPolygons(this.points, true);
  48. }
  49. _oncontextmenu(e) {
  50. e.preventDefault();
  51. if (this.currentState === 'end') { //防止点击右键启动绘制
  52. return;
  53. }
  54. if (this.points.length <= 2) {
  55. this._resetPoints();
  56. } else {
  57. if ((this.points[0].x !== this.points[this.points.length - 1].x) || (this.points[0].y !== this.points[this.points.length - 1].y)) {
  58. this.points.push(this.points[0]);
  59. }
  60. if (checkPolygon(this.points)) {
  61. this.points.pop();
  62. } else {
  63. this.currentState = 'end';
  64. this.polygons.push([...this.points]);
  65. if (this.polygons.length < this.MAX_POLYGON) {
  66. this._resetPoints();
  67. } else {
  68. this.onDrawROIFinishedCallback && this.onDrawROIFinishedCallback();
  69. this._drawPolygons([]);
  70. }
  71. }
  72. }
  73. }
  74. _onmousemove(e) {
  75. let pos = getMousePos(this.canvas, e);
  76. //console.log('mousemove');
  77. if (e.button === 0) {
  78. if (this.currentState === 'firstPoint') {
  79. this.currentState = 'move';
  80. this.points.push(pos);
  81. } else if (this.currentState === 'move') {
  82. this.points.pop();
  83. this.points.push(pos);
  84. }
  85. if (this.currentState !== 'end') {
  86. this.points.push(pos);
  87. this._drawPolygons(this.points);
  88. this.points.pop();
  89. } else {
  90. //this._drawPolygons(this.points);
  91. }
  92. if (this.currentState === 'begin') {
  93. this._drawText('区域' + (this.polygons.length + 1), pos.x, pos.y)
  94. }
  95. }
  96. }
  97. getROIData() {
  98. if (this.currentState !== 'end') { //非完成状态
  99. return null;
  100. }
  101. let polygons = [];
  102. this.polygons.map((points, k) => {
  103. let data = points.slice(0, points.length - 1);
  104. data.map((point, k) => {
  105. data[k] = this._to8191Coordinate(point, this.canvas);
  106. });
  107. polygons.push([...data]);
  108. });
  109. return polygons;
  110. }
  111. setROI(polygons) {
  112. if (!polygons) {
  113. return;
  114. }
  115. let data = [];
  116. this.polygons = [];
  117. polygons.map((points, k) => {
  118. points.map((point, k) => {
  119. let result = this._toRealCoordinate(point.x, point.y);
  120. data[k] = { x: result[0], y: result[1] };
  121. });
  122. if (data.length) {
  123. data[data.length] = data[0];
  124. }
  125. this.polygons.push([...data]);
  126. data = [];
  127. });
  128. this.currentState = 'end';
  129. this.points = [];
  130. this._drawPolygons(this.points);
  131. }
  132. redrawROI() {
  133. this._drawPolygons(this.points);
  134. }
  135. reset() {
  136. this.currentState = 'begin';
  137. this.polygons = [];
  138. this.points.length = 0;
  139. this._drawPolygons(this.points);
  140. }
  141. setROIFinishedCallback(callback) {
  142. this.onDrawROIFinishedCallback = callback;
  143. }
  144. setPolygonNum(num) {
  145. this.MAX_POLYGON = num;
  146. }
  147. terminate() {
  148. this.clearCanvas();
  149. this.canvas.width = 0;
  150. this.canvas.height = 0;
  151. this.currentState = 'end';
  152. this.points.length = 0;
  153. this.canvas.removeEventListener('click', this._onClick);
  154. this.canvas.removeEventListener('contextmenu', this._oncontextmenu);
  155. this.canvas.removeEventListener('mousemove', this._onmousemove);
  156. }
  157. _drawPolygons(points) {
  158. this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
  159. this.polygons.map((points, k) => {
  160. this._drawPolygon(points);
  161. });
  162. this._drawPolygon(points);
  163. }
  164. _drawPolygon(points) {
  165. if (!points.length) {
  166. return;
  167. }
  168. const end = this.currentState === 'end';
  169. //console.log(points)
  170. this.context.strokeStyle = end ? '#0000ff' : "#ffff00";
  171. this.context.beginPath();
  172. this.context.moveTo(points[0].x, points[0].y);
  173. for (let i = 1; i < points.length; i++) {
  174. this.context.lineTo(points[i].x, points[i].y);
  175. }
  176. if (end) {
  177. // draw is done, fill polygon with a color
  178. this.context.fillStyle = "rgba(255, 0, 0, 0.2)";
  179. this.context.fill();
  180. } else {
  181. this.context.stroke();
  182. }
  183. this.context.closePath();
  184. this.polygons.map((points, k) => {
  185. this._drawText('区域' + (k + 1), points[0].x - 20, points[0].y - 10);
  186. });
  187. }
  188. _resetPoints() {
  189. this.currentState = 'begin';
  190. this.points.length = 0;
  191. this._drawPolygons(this.points);
  192. }
  193. _drawText(text, x, y) {
  194. this.context.beginPath();
  195. this.context.fillStyle = "rgba(255, 255, 0, 1)";
  196. this.context.fillText(text, x, y);
  197. this.context.closePath();
  198. }
  199. }
  200. class StandingDrawer extends Drawer {
  201. constructor(props) {
  202. super(props);
  203. this.currentState = 'end';
  204. this.points = [];
  205. this.onDrawROIFinishedCallback = null;
  206. }
  207. _init() {
  208. this.context.lineWidth = 2;
  209. this.context.font = 'bold 20px Arial';
  210. this.canvas.addEventListener('click', this._onClick.bind(this));
  211. this.canvas.addEventListener('contextmenu', this._oncontextmenu.bind(this));
  212. this.canvas.addEventListener('mousemove', this._onmousemove.bind(this));
  213. }
  214. _onClick(e) {
  215. let pos = getMousePos(this.canvas, e);
  216. switch (this.currentState) {
  217. case 'begin':
  218. this.points.push(pos);
  219. this.currentState = 'firstPoint';
  220. break;
  221. case 'firstPoint': // 防止初始点点两次
  222. break;
  223. case 'move':
  224. this.currentState = 'points';
  225. break;
  226. case 'points':
  227. if (((pos.x === this.points[this.points.length - 1].x) && (pos.y === this.points[this.points.length - 1].y))
  228. || this.points.length > 6) {
  229. return;
  230. }
  231. this.points.push(pos);
  232. let points = this.points.length > 4 ? this.points.slice(0, 4) : this.points;
  233. if (checkPolygon(points)) {
  234. this.points.pop();
  235. }
  236. if (this.points.length === 6) {
  237. this.currentState = 'end';
  238. this.onDrawROIFinishedCallback && this.onDrawROIFinishedCallback();
  239. }
  240. this._drawArea(this.points);
  241. break;
  242. case 'end':
  243. break;
  244. default:
  245. console.log('unknown state: ', this.currentState);
  246. break;
  247. }
  248. }
  249. _oncontextmenu(e) {
  250. e.preventDefault();
  251. }
  252. _onmousemove(e) {
  253. let pos = getMousePos(this.canvas, e);
  254. if (e.button === 0) {
  255. if (this.currentState === 'firstPoint') {
  256. this.currentState = 'move';
  257. this.points.push(pos);
  258. } else if (this.currentState === 'move') {
  259. this.points.pop();
  260. this.points.push(pos);
  261. }
  262. if (this.currentState !== 'end') {
  263. this.points.push(pos);
  264. this._drawArea(this.points);
  265. this.points.pop();
  266. } else {
  267. }
  268. // if(this.currentState === 'begin') {
  269. // this._drawText('区域' + (this.polygons.length + 1), pos.x, pos.y)
  270. // }
  271. }
  272. }
  273. _drawArea(points) {
  274. this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
  275. let polygon = points.length > 4 ? points.slice(0, 4) : points;
  276. this._drawPolygon(polygon);
  277. let line = points.length > 4 ? points.slice(4) : [];
  278. this._drawLine(line);
  279. }
  280. _drawPolygon(points) {
  281. if (!points.length) {
  282. return;
  283. }
  284. this.context.strokeStyle = '#00ff00';
  285. this.context.beginPath();
  286. this.context.moveTo(points[0].x, points[0].y);
  287. for (let i = 1; i < points.length; i++) {
  288. this.context.lineTo(points[i].x, points[i].y);
  289. }
  290. this.points.length >= 4 && this.context.lineTo(points[0].x, points[0].y);
  291. this.context.stroke();
  292. this.context.closePath();
  293. }
  294. _drawLine(points) {
  295. if (points.length !== 2) {
  296. return;
  297. }
  298. this.context.strokeStyle = '#0000ff';
  299. this.context.beginPath();
  300. this.context.moveTo(points[0].x, points[0].y);
  301. this.context.lineTo(points[1].x, points[1].y);
  302. this.context.stroke();
  303. this.context.closePath();
  304. }
  305. getROIData() {
  306. if (this.currentState !== 'end') { //非完成状态
  307. return null;
  308. }
  309. let points = [];
  310. this.points.map((point, k) => {
  311. points.push(this._to8191Coordinate(point, this.canvas));
  312. });
  313. return points;
  314. }
  315. setROI(points) {
  316. if (!points) {
  317. return;
  318. }
  319. let data = [];
  320. points.map((point, k) => {
  321. let result = this._toRealCoordinate(point.x, point.y);
  322. data[k] = { x: result[0], y: result[1] };
  323. });
  324. this.points = [...data];
  325. this._drawArea(data);
  326. this.currentState = 'end';
  327. }
  328. redrawROI() {
  329. this._drawArea(this.points);
  330. }
  331. reset() {
  332. this.currentState = 'begin';
  333. this.points.length = 0;
  334. this._drawArea(this.points);
  335. }
  336. setROIFinishedCallback(callback) {
  337. this.onDrawROIFinishedCallback = callback;
  338. }
  339. terminate() {
  340. this.clearCanvas();
  341. this.canvas.width = 0;
  342. this.canvas.height = 0;
  343. this.currentState = 'end';
  344. this.points.length = 0;
  345. this.canvas.removeEventListener('click', this._onClick);
  346. this.canvas.removeEventListener('contextmenu', this._oncontextmenu);
  347. this.canvas.removeEventListener('mousemove', this._onmousemove);
  348. }
  349. }
  350. class SpeedLine extends Drawer {
  351. constructor(canvas) {
  352. super(canvas);
  353. this.currentState = 'end';
  354. this.MAXLINE = 2;
  355. this.points = [];
  356. this.onDrawROIFinishedCallback = null;
  357. }
  358. _init() {
  359. this.canvas.addEventListener('click', this._onClick.bind(this));
  360. this.canvas.addEventListener('contextmenu', this._oncontextmenu.bind(this));
  361. this.canvas.addEventListener('mousemove', this._onmousemove.bind(this));
  362. }
  363. _onClick(e) {
  364. if (this.points.length === this.MAXLINE * 2) {
  365. return;
  366. }
  367. switch (this.currentState) {
  368. case 'begin':
  369. this.points.push(getMousePos(this.canvas, e));
  370. this.currentState = 'firstPoint';
  371. break;
  372. case 'firstPoint': // 防止初始点点两次
  373. break;
  374. case 'move':
  375. this.currentState = 'points';
  376. break;
  377. case 'points':
  378. let currentPoint = getMousePos(this.canvas, e);
  379. if ((currentPoint.x === this.points[this.points.length - 1].x) && (currentPoint.y === this.points[this.points.length - 1].y)) {
  380. return;
  381. }
  382. this.points.push(currentPoint);
  383. if (this.points.length === this.MAXLINE * 2) {
  384. if (this._checkLine(this.points)) {
  385. this.points.pop();
  386. } else {
  387. this.currentState = 'end';
  388. this._drawLines(this.points);
  389. this.onDrawROIFinishedCallback && this.onDrawROIFinishedCallback();
  390. }
  391. }
  392. break;
  393. case 'end':
  394. break;
  395. default:
  396. console.log('unknown state: ', this.currentState);
  397. break;
  398. }
  399. }
  400. _onmousemove(e) {
  401. let pos = getMousePos(this.canvas, e);
  402. if (e.button === 0) {
  403. if (this.currentState === 'firstPoint') {
  404. this.currentState = 'move';
  405. this.points.push(pos);
  406. } else if (this.currentState === 'move') {
  407. this.points.pop();
  408. this.points.push(pos);
  409. }
  410. if (this.currentState !== 'end' && this.currentState !== 'begin') {
  411. this.points.push(pos);
  412. this._drawLines(this.points);
  413. this.points.pop();
  414. }
  415. }
  416. }
  417. _oncontextmenu(e) {
  418. e.preventDefault();
  419. }
  420. /**
  421. *
  422. * @param points [] x1,y1,x2,y2...
  423. * @private
  424. */
  425. _checkLine(points) {
  426. for (let i = 0; i < points.length - 3; i = i + 2) {
  427. for (let j = i + 2; j < points.length - 1; j = j + 2) {
  428. let result = segmentsIntr(points[i], points[i + 1], points[j], points[j + 1]);
  429. if (result) {
  430. console.log('intersect:');
  431. console.log(result)
  432. return result;
  433. }
  434. }
  435. }
  436. return false;
  437. }
  438. _drawLines(points) {
  439. this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
  440. if (!points.length) {
  441. return;
  442. }
  443. const end = this.currentState === 'end';
  444. this.context.strokeStyle = end ? '#00ff00' : "#ffff00";
  445. this.context.beginPath();
  446. for (let i = 0; i < points.length - 1; i = i + 2) {
  447. this.context.moveTo(points[i].x, points[i].y);
  448. this.context.lineTo(points[i + 1].x, points[i + 1].y);
  449. }
  450. this.context.stroke();
  451. this.context.closePath();
  452. }
  453. getROIData() {
  454. if (this.currentState !== 'end') { //非完成状态
  455. return null;
  456. }
  457. let data = this.points;
  458. data.map((point, k) => {
  459. data[k] = this._to8191Coordinate(point, this.canvas);
  460. });
  461. return data;
  462. }
  463. setROI(data) {
  464. data.map((point, k) => {
  465. let result = this._toRealCoordinate(point.x, point.y);
  466. data[k] = { x: result[0], y: result[1] };
  467. });
  468. this.currentState = 'end';
  469. this.points = data;
  470. this._drawLines(this.points);
  471. }
  472. redrawROI() {
  473. this._drawLines(this.points);
  474. }
  475. reset() {
  476. this.currentState = 'begin';
  477. this.points.length = 0;
  478. console.log(this.points)
  479. this._drawLines(this.points);
  480. }
  481. setROIFinishedCallback(callback) {
  482. this.onDrawROIFinishedCallback = callback;
  483. }
  484. setLineNum(num) {
  485. this.MAXLINE = num;
  486. }
  487. terminate() {
  488. this.clearCanvas();
  489. this.canvas.width = 0;
  490. this.canvas.height = 0;
  491. this.currentState = 'end';
  492. this.points.length = 0;
  493. this.canvas.removeEventListener('click', this._onClick);
  494. this.canvas.removeEventListener('contextmenu', this._oncontextmenu);
  495. this.canvas.removeEventListener('mousemove', this._onmousemove);
  496. }
  497. }
  498. class BlankROIDrawer extends Drawer {
  499. constructor(canvas) {
  500. super(canvas);
  501. }
  502. getROIData() {
  503. }
  504. setROI() {
  505. }
  506. redrawROI() {
  507. }
  508. reset() {
  509. }
  510. terminate() {
  511. }
  512. }
  513. function getMousePos(canvas, event) {
  514. var rect = canvas.getBoundingClientRect();
  515. var x = event.clientX - rect.left * (canvas.width / rect.width);
  516. var y = event.clientY - rect.top * (canvas.height / rect.height);
  517. //console.log("x:"+x+",y:"+y);
  518. return { x: x, y: y };
  519. }
  520. function checkPolygon(points) {
  521. for (let i = 0, length = points.length - 1; i < length; i++) {
  522. for (let j = i + 1, len = points.length - 1; j < len; j++) {
  523. let result = segmentsIntr(points[i], points[i + 1], points[j], points[j + 1]);
  524. if (result) {
  525. console.log('intersect:');
  526. console.log(result)
  527. return result;
  528. }
  529. }
  530. }
  531. return false;
  532. }
  533. function segmentsIntr(a, b, c, d) {
  534. // 三角形abc 面积的2倍
  535. var area_abc = (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x);
  536. // 三角形abd 面积的2倍
  537. var area_abd = (a.x - d.x) * (b.y - d.y) - (a.y - d.y) * (b.x - d.x);
  538. // 面积符号相同则两点在线段同侧,不相交 (对点在线段上的情况,本例当作不相交处理);
  539. if (area_abc * area_abd >= 0) {
  540. return false;
  541. }
  542. // 三角形cda 面积的2倍
  543. var area_cda = (c.x - a.x) * (d.y - a.y) - (c.y - a.y) * (d.x - a.x);
  544. // 三角形cdb 面积的2倍
  545. // 注意: 这里有一个小优化.不需要再用公式计算面积,而是通过已知的三个面积加减得出.
  546. var area_cdb = area_cda + area_abc - area_abd;
  547. if (area_cda * area_cdb >= 0) {
  548. return false;
  549. }
  550. //计算交点坐标
  551. var t = area_cda / (area_abd - area_abc);
  552. var dx = t * (b.x - a.x),
  553. dy = t * (b.y - a.y);
  554. return { x: a.x + dx, y: a.y + dy };
  555. }
  556. export { ROIDrawer, StandingDrawer, SpeedLine, BlankROIDrawer };