| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- import FillArea from "../common/fillarea";
- import { ColorMap, Mark } from "../common/interfaces";
- import Utils from "../common/utils";
- import { Point } from "../core/interface";
- import Layer from "../core/layer";
- import Rect from "../core/rect";
- import MarkTool from "./mark-tool";
- const TAG = 'MarkEditLayer';
- export default class MarkEditLayer extends Layer {
- output: HTMLCanvasElement;
- outputCtx: CanvasRenderingContext2D;
- raw: HTMLImageElement;
- page: HTMLImageElement;
- map: HTMLImageElement;
- mapPixels: Uint32Array;
- colorMap: ColorMap;
- areaMap: any = [];
- areaList: any[];
- undoList: any[] = [];
- redoList: any[] = [];
- cacheList: any[] = [];
- marks: Mark[]; // 标记点
- innerMarks: InnerMark[] = []; // 仅供内部使用
- override get defaultToolKey(): string { return 'mark-edit-mark'; }
- constructor(raw: HTMLImageElement, page: HTMLImageElement, map: HTMLImageElement, colorMap: ColorMap, marks: Mark[]) {
- super(page.width, page.height);
- this.raw = raw;
- this.page = page;
- this.map = map;
- this.colorMap = colorMap;
- this.marks = marks;
- console.log("marks:", marks);
- if (this.marks && this.marks.length > 0) {
- this.innerMarks = this.marks.map(m => { return {x: m.x, y: m.y, radius: m.radius, note: m.note, status: 0} });
- }
- this.init();
- this.addTool(new MarkTool(this));
- }
- init() {
- this.output = Utils.createCanvas(this.width, this.height);
- this.outputCtx = this.output.getContext('2d');
- this.outputCtx.fillStyle = 'white';
- this.outputCtx.fillRect(0, 0, this.width, this.height);
- this.mapPixels = new Uint32Array(Utils.getImageData(this.map).data.buffer);
- this.createAreaMap();
- this.drawToOutput();
- // undo redo support.
- this.undoList = [];
- this.redoList = [];
- this.cacheList = [];
- }
- drawToOutput() {
- console.time('drawToOutput');
- this.outputCtx.fillStyle = 'white';
- this.outputCtx.fillRect(0, 0, this.width, this.height);
- //fast create output by map
- let outData = this.outputCtx.getImageData(0, 0, this.output.width, this.output.height)
- let outPixels = new Uint32Array(outData.data.buffer);
- let areaIndex;
- for (var i = 0; i < outPixels.length; i++) {
- areaIndex = this.mapPixels[i];
- if (this.colorMap[areaIndex]) {
- outPixels[i] = this.colorMap[areaIndex].color;
- }
- }
- this.outputCtx.putImageData(outData, 0, 0);
- console.timeEnd('drawToOutput');
- }
- /**
- * Create area map from map pixels.
- */
- createAreaMap() {
- let width = this.width;
- let height = this.height;
- let floodArr = this.mapPixels;
- let areaMap = {};
- let i: number, x: number, y: number, color: number;
- for (i = 0; i < floodArr.length; i++) {
- color = floodArr[i];
- if (!color) continue;
- x = i % width;
- //y = parseInt(i / width);
- y = Math.floor(i / width);
- if (areaMap[color]) {
- areaMap[color].addPoint(x, y);
- } else {
- areaMap[color] = new FillArea(color, x, y);
- }
- }
- this.areaMap = areaMap;
- this.areaList = Object.keys(areaMap).map(key => {
- return areaMap[key];
- });
- }
- update(page?: HTMLImageElement, raw?: HTMLImageElement, map?: HTMLImageElement, colorMap?: ColorMap) {
- this.raw = raw || this.raw;
- this.page = page || this.page;
- this.map = map || this.map;
-
- this.invalidate();
- }
- updateing = false;
- editUpdate() {
- this.marks = this.innerMarks.filter(m => m.radius > 0)
- .map(m => {
- return {x: m.x, y: m.y, radius: m.radius, note: m.note};
- });
- // 控制下触发频率
- if (this.updateing) return;
- this.updateing = true;
- setTimeout(() => {
- this.trigger('edit-update');
- this.updateing = false;
- }, 1000);
- }
- get curMarkIdx(): number {
- return this.getSelectedMarkIdx();
- }
- set curMarkIdx(idx: number) {
- if (idx >= 0 && idx < this.innerMarks.length) {
- this.selectMarkIdx(idx);
- }
- }
- // 获取标注文字
- getMarkNote(idx: number) {
- if (idx >= 0 && idx < this.innerMarks.length) {
- let mark = this.innerMarks[idx];
- return mark.note;
- }
- return null;
- }
- // 设置标注文字
- setMarkNote(idx: number, note: string) {
- if (idx >= 0 && idx < this.innerMarks.length) {
- this.innerMarks[idx].note = note;
- this.marks[idx].note = note;
- this.trigger('edit-update');
- }
- }
- /**
- * Draw this layer.
- * @Override Layer#draw(ctx)
- */
- override draw(ctx) {
- ctx.imageSmoothingEnabled = this.smoothing;
- ctx.fillStyle = "white";
- ctx.fillRect(0, 0, this.width, this.height);
- ctx.drawImage(this.output, 0, 0, this.output.width, this.output.height, 0, 0, this.width, this.height);
- if (this.raw) {
- ctx.drawImage(this.raw, 0, 0, this.page.width, this.page.height, 0, 0, this.width, this.height);
- } else {
- ctx.drawImage(this.page, 0, 0, this.page.width, this.page.height, 0, 0, this.width, this.height);
- }
- this.drawMarks(ctx);
- }
- // 生成标注效果图
- getMarkBlob(): Promise<Blob> {
- let scale = 2;
- let canvas = Utils.createCanvas(this.width / scale, this.height / scale); // 压缩下,变为原来的一半大小即可,节省空间
- let ctx = canvas.getContext('2d');
- ctx.drawImage(this.output, 0, 0, this.output.width, this.output.height, 0, 0, canvas.width, canvas.height);
- if (this.raw) {
- ctx.drawImage(this.raw, 0, 0, this.page.width, this.page.height, 0, 0, canvas.width, canvas.height);
- } else {
- ctx.drawImage(this.page, 0, 0, this.page.width, this.page.height, 0, 0, canvas.width, canvas.height);
- }
- this.drawMarksOut(ctx, scale);
- return new Promise((done, reject) => { canvas.toBlob(b => { done(b) }) });
- }
-
-
- // 创建圈圈标记
- createMark(x: number, y: number, radius: number): InnerMark {
- let mark = {x, y, radius, note: '', status: 0};
- this.innerMarks.push(mark);
- this.selectMark(mark);
- this.invalidate();
- this.editUpdate();
- return mark;
- }
- // 移动圈圈
- moveMark(mark: InnerMark, x: number, y: number) {
- mark.x = x;
- mark.y = y;
- this.editUpdate();
- this.invalidate();
- }
- // 调整圈圈大小
- resizeMark(mark: InnerMark, radius: number) {
- mark.radius = radius;
- this.editUpdate();
- this.invalidate();
- }
- // 选中某个圈圈
- selectMark(mark: InnerMark) {
- for (let m of this.innerMarks) {
- if (m == mark) {
- m.status = 1;
- } else {
- m.status = 0;
- }
- }
- this.invalidate();
- }
- // 选中指定下标的圈圈
- selectMarkIdx(idx: number) {
- if (idx >= 0 && idx < this.innerMarks.length) {
- let mark = this.innerMarks[idx];
- this.selectMark(mark);
- }
- }
- // 全部置为非选中
- unselectMarks() {
- this.innerMarks.forEach(m => {
- m.status = 0;
- })
- this.invalidate();
- }
- // 获取当前选中
- getSelectedMark(): InnerMark {
- return this.innerMarks.find(m => m.status == 1)
- }
- // 获取当前选中圈圈下标
- getSelectedMarkIdx(): number {
- return this.innerMarks.findIndex(m => m.status == 1)
- }
- // 删除指定
- deleteSelectedMark() {
- let idx = this.innerMarks.findIndex(m => m.status == 1);
- if (idx >= 0) {
- this.innerMarks.splice(idx, 1);
- this.editUpdate();
- this.invalidate();
- }
- }
- // 判断某点是否落在某个圈圈标记内
- checkInMarks(x: number, y: number): InnerMark {
- let dx, dy;
- for (let mark of this.innerMarks) {
- dx = mark.x - x;
- dy = mark.y - y;
- if (mark.radius * mark.radius >= dx * dx + dy * dy) { // in circle
- return mark;
- }
- }
- return null;
- }
- // 判断某点落在哪个象限(正方形选框之内和mark圈圈之外的4个角,右上/左上/左下/右下分别对应1/2/3/4象限,对应方位东北/西北/西南/东南)
- checkQuadrant(mark: InnerMark, x: number, y: number): number {
- let dx = mark.x - x
- let dy = mark.y - y;
- if (mark.radius * mark.radius >= dx * dx + dy * dy) { // in circle
- return 0;
- }
- let rect = new Rect(mark.x - mark.radius, mark.y - mark.radius, mark.x + mark.radius, mark.y + mark.radius);
- if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) { // out of square
- return 0;
- }
- if (x > mark.x && y < mark.y) return 1;
- if (x < mark.x && y < mark.y) return 2;
- if (x < mark.x && y > mark.y) return 3;
- if (x > mark.x && y > mark.y) return 4;
- return 0;
- }
- /**
- * 绘制标记圈圈
- * @param ctx
- */
- drawMarks(ctx: CanvasRenderingContext2D) {
- ctx.save();
- ctx.strokeStyle = '#00cc00';
- ctx.lineWidth = 8;
- this.innerMarks.forEach(m => {
- ctx.beginPath();
- ctx.arc(m.x, m.y, m.radius, 0, 2 * Math.PI);
- ctx.stroke();
- if (m.status == 1) { // 选中状态,画个外框
- let rect = new Rect(m.x - m.radius, m.y - m.radius, m.x + m.radius, m.y + m.radius);
- ctx.save();
- ctx.strokeStyle = 'yellow';
- ctx.lineWidth = 2;
- ctx.setLineDash([4, 2]);
- ctx.strokeRect(rect.left, rect.top, rect.width(), rect.height());
- ctx.restore();
- }
- });
- ctx.restore();
- }
- /**
- * 绘制标记圈圈, 用于导出
- * @param ctx
- * @param scale 压缩比例
- */
- drawMarksOut(ctx: CanvasRenderingContext2D, scale = 1) {
- ctx.save();
- ctx.font = `25px sans-serif`;
- ctx.fillStyle = 'red';
- ctx.strokeStyle = '#00cc00';
- ctx.lineWidth = 8 / scale;
- this.innerMarks.forEach(m => {
- ctx.beginPath();
- ctx.arc(m.x / scale, m.y / scale, m.radius / scale, 0, 2 * Math.PI);
- let pos = this.getBestTextPos(ctx, m, scale);
- ctx.fillText(m.note, pos.x, pos.y);
- ctx.stroke();
- });
- ctx.restore();
- }
- // 获取最佳文字排版位置
- getBestTextPos(ctx: CanvasRenderingContext2D, mark: InnerMark, scale = 1): Point{
- let text = ctx.measureText(mark.note); // TextMetrics object
- let width = text.width;
- let x = Math.max(mark.x / scale - width / 2, 0);
- if (x + width > ctx.canvas.width) { // 文字超出了
- x = Math.max(ctx.canvas.width - width, 0);
- }
- let y = Math.min(mark.y / scale + 10, this.width / scale);
- return {x, y};
- }
- }
- export interface InnerMark {
- x: number;
- y: number;
- radius: number;
- note: string; // 文字说明
- status: number; //0-未选中,1-选中
- }
|