The ancient Greek philosopher Zeno said, "The more you know, the more you find you don't know."Copy the code

Ever team didn’t have enough technology to share system, also not form a certain rules and habits, for team members sharing technology accumulation and achievements cannot achieve good results, for everybody has faster technology improvement and ability to improve, to the team this year do the corresponding planning technology sharing, each team member will be involved, Summarize the knowledge points summed up in the development process, and conduct technical exchanges and sharing in the form of special topics once every two weeks. In the early stage, accumulate various technical points, and gradually form a system, so that everyone can constantly enrich their own technology stack and technical depth.

At the beginning of the New Year of 2022, a new starting point. Related articles will be updated and shared continuously in the future. Welcome to exchange and guidance in the comments section about the good implementation experience of team technology improvement and team construction.

This paper is the second part of the team’s technology sharing, which sorted out the two technical points about Canvas in the previous business development, as follows:

A canvas canvas to achieve mouse drag rectangular box

1. Implementation principle

Canvas is a non-reserved drawing interface, that is, it does not record the drawing operations performed in the past, but keeps the final result. If you want to make the Canvas interactive, for example, the user can select and drag graphics on the Canvas. Then we must record every object we draw so that we can modify and redraw them flexibly in the future to interact with them. Using the coordinates of the mouse as the base point, change the size of the rectangle. The difference is that the canvas is emptied and redrawn every frame, so that the final effect is that the rectangle changes with the drag of the mouse.

2. Related parameters

The drawer: {drawType: 1.// 1. Round 2.
  layers: [].// Store frame information
  c: null.// Get the canvas
  ctx: null.// Get the drawing 2D environment
  currentR: null.// The currently selected object
  leftDistance: 0.// The difference between the abscissa and the left side of the currently selected rectangle
  topDistance: 0.// The difference between the vertical coordinate and the left side of the currently selected rectangle
  flag: 0.// Whether to click the mouse icon, 0 do not click, 1 click the mouse
  op: 0.Op Operation Type 0 None operation 1 Draw a rectangle 2 Drag a rectangle
  x: 0.// Mouse position
  y: 0.// Mouse position
  imgUrl: "".// Image address
  elementWidth: 0.// Width of canvas
  elementHeight: 0.// Height of canvas
}
Copy the code

2.1 isPointInPath: Judge whether the coordinate point of the mouse is inside a rectangle of the canvas every time you redraw the cloth. Only when the judgment method is realized can drag and change the size of the rectangular box be realized. Returns true if the specified point is in the current path; Otherwise return false.

context.isPointInPath(x,y)
Copy the code

2.2 Reshow: The redrawing function puts the related properties of the drawn rectangle in the layers array. The redrawing operation of each frame is to reproduce the rectangular frame according to the description of the object, and then determine whether X and y are on the path of the rectangular frame, thereby determining whether the mouse is in the rectangular frame.

reshow: (x, y) = > {
    let allNotIn = 1;
    drawer.layers.forEach((item) = > {
      drawer.ctx.beginPath();
      drawer.ctx.strokeStyle = item.strokeStyle;
      drawer.ctx.lineWidth = item.lineWidth;
			/ / rectangle
      if (item.drawType == 1) {
        drawer.ctx.rect(item.x1, item.y1, item.width, item.height);
        // Update the size area
        if (
          x >= item.x1 - 10 / drawer.scale &&
          x <= item.x1 + 10 / drawer.scale &&
          y <= item.y2 - 10 / drawer.scale &&
          y >= item.y1 + 10 / drawer.scale
        ) {
          drawer.resizeLeft(item);
        } else if (
          x >= item.x2 - 10 / drawer.scale &&
          x <= item.x2 + 10 / drawer.scale &&
          y <= item.y2 - 10 / drawer.scale &&
          y >= item.y1 + 10 / drawer.scale
        ) {
          drawer.resizeWidth(item);
        } else if (
          y >= item.y1 - 10 / drawer.scale &&
          y <= item.y1 + 10 / drawer.scale &&
          x <= item.x2 - 10 / drawer.scale &&
          x >= item.x1 + 10 / drawer.scale
        ) {
          drawer.resizeTop(item);
        } else if (
          y >= item.y2 - 10 / drawer.scale &&
          y <= item.y2 + 10 / drawer.scale &&
          x <= item.x2 - 10 / drawer.scale &&
          x >= item.x1 + 10 / drawer.scale
        ) {
          drawer.resizeHeight(item);
        } else if (
          x >= item.x1 - 10 / drawer.scale &&
          x <= item.x1 + 10 / drawer.scale &&
          y <= item.y1 + 10 / drawer.scale &&
          y >= item.y1 - 10 / drawer.scale
        ) {
          drawer.resizeLT(item);
        } else if (
          x >= item.x2 - 10 / drawer.scale &&
          x <= item.x2 + 10 / drawer.scale &&
          y <= item.y2 + 10 / drawer.scale &&
          y >= item.y2 - 10 / drawer.scale
        ) {
          drawer.resizeWH(item);
        } else if (
          x >= item.x1 - 10 / drawer.scale &&
          x <= item.x1 + 10 / drawer.scale &&
          y <= item.y2 + 10 / drawer.scale &&
          y >= item.y2 - 10 / drawer.scale
        ) {
          drawer.resizeLH(item);
        } else if (
          x >= item.x2 - 10 / drawer.scale &&
          x <= item.x2 + 10 / drawer.scale &&
          y <= item.y1 + 10 / drawer.scale &&
          y >= item.y1 - 10/ drawer.scale ) { drawer.resizeWT(item); }}if (item.drawType == 2) {
        drawer.ctx.arc(item.x1, item.y1, item.r, 0.2 * Math.PI);

        let newR = Math.sqrt(
          Math.pow(x - item.x1, 2) + Math.pow(y - item.y1, 2));if (
          newR >= item.r - 10 / drawer.scale &&
          newR <= item.r + 10/ drawer.scale ) { drawer.resizeCircle(item); }}if (drawer.ctx.isPointInPath(x * drawer.scale, y * drawer.scale)) {
        drawer.render(item);
        allNotIn = 0;
      }
      drawer.ctx.stroke();
    });
    if (drawer.flag && allNotIn && drawer.op < 3) {
      drawer.op = 1; }},Copy the code

2.3 Render function: Used to modify the coordinates of the current moving rectangular box

render: (rect) = > {
    drawer.c.style.cursor = "move";
    if (drawer.flag && drawer.op == 0) {
      drawer.op = 2;
    }

    if (drawer.flag && drawer.op == 2) {
      if (!drawer.currentR) {
        drawer.currentR = rect;
      }

      drawer.currentR.x2 += drawer.x - drawer.leftDistance - drawer.currentR.x1;
      drawer.currentR.x1 += drawer.x - drawer.leftDistance - drawer.currentR.x1;
      drawer.currentR.y2 += drawer.y - drawer.topDistance - drawer.currentR.y1;
      drawer.currentR.y1 += drawer.y - drawer.topDistance - drawer.currentR.y1;
    }
  },
Copy the code

2.4 isPointInRetc(x, Y) : Checks whether the mouse is in a rectangle or a circle

function isPointInRetc(x, y) {
  	let len = drawer.layers.length;
  	for (let i = 0; i < len; i++) {
        // Rectangle area judgment
        if (drawer.layers[i].drawType === 1) {
           if (
                drawer.layers[i].x1 < x &&
                x < drawer.layers[i].x2 &&  drawer.layers[i].y1 < y && y < drawer.layers[i].y2
                       ) {
                       returndrawer.layers[i]; }}// Circle area judgment
             if (drawer.layers[i].drawType === 2) {
                       let oriR =
                       Math.pow(drawer.layers[i].x1 - drawer.layers[i].x2, 2) +
                       Math.pow(drawer.layers[i].y1 - drawer.layers[i].y2, 2);
                       let newR = Math.pow(drawer.layers[i].x1 - x, 2) + Math.pow(drawer.layers[i].y1 - y, 2);
       					if (newR <= oriR) {
										returndrawer.layers[i]; }}}}Copy the code

2.5 MouseDown: Press down the mouse

let mousedown = function (e) {
			// Get the mouse point
      startx =
        (e.pageX - drawer.c.offsetLeft + drawer.c.parentElement.scrollLeft)
      starty =
        (e.pageY - drawer.c.offsetTop + drawer.c.parentElement.scrollTop) 
      drawer.currentR = isPointInRetc(startx, starty);
      if (drawer.currentR) {
        drawer.leftDistance = startx - drawer.currentR.x1;
        drawer.topDistance = starty - drawer.currentR.y1;
      }
      drawer.ctx.strokeRect(drawer.x, drawer.y, 0.0);
      drawer.ctx.strokeStyle = "#00ff00";
      drawer.ctx.lineWidth = 2;
      drawer.flag = 1;
    };
Copy the code

2.6 Mousemove: Moves the mouse

let mousemove = function (e) {
      drawer.x =
        (e.pageX - drawer.c.offsetLeft + drawer.c.parentElement.scrollLeft) 
      drawer.y =
        (e.pageY - drawer.c.offsetTop + drawer.c.parentElement.scrollTop) 
      drawer.ctx.save();
      drawer.ctx.setLineDash([5]);
      drawer.c.style.cursor = "default";
      drawer.ctx.lineWidth = 2;
      drawer.ctx.clearRect(0.0, drawer.elementWidth, drawer.elementHeight);
      if (drawer.flag && drawer.op == 1) {
        if (drawer.drawType == 1) {
          drawer.ctx.strokeRect(
            startx,
            starty,
            drawer.x - startx,
            drawer.y - starty
          );
        }

        if (drawer.drawType == 2) {
          let r = Math.sqrt(
            Math.pow(drawer.x - startx, 2) + Math.pow(drawer.y - starty, 2)); drawer.ctx.beginPath(); drawer.ctx.arc(startx, starty, r,0.2 * Math.PI);
          drawer.ctx.stroke();
        }
      }
      drawer.ctx.restore();
      drawer.reshow(drawer.x, drawer.y);
    };
Copy the code

2.7 Mouseup: The mouse is up

let mouseup = function (e) {
      if (drawer.x - startx > 20 || drawer.y - starty > 20) {
          drawer.layers.push(
            fixPosition({
              x1: startx,
              y1: starty,
              x2: drawer.x,
              y2: drawer.y,
              strokeStyle: "#00ff00".lineWidth: 2.drawType: drawer.drawType,
            })
          );
        }
      drawer.currentR = null;
      drawer.flag = 0;
      drawer.reshow(drawer.x, drawer.y);
      drawer.op = 0;
    };
Copy the code

3. Effect Display:

2. Using SVG to implement mouse drag rectangle box

SVG is a scalable vector graph. Images are defined in XML format and corresponding DOM nodes can be generated to facilitate interactive manipulation of a single graph. A little more flexible than CANVAS. For the basics of SVG, see SVG Learning Addresses

1. Graphics and basic properties of SVG

1.1 Basic Graphics

<rect> width width height height rx ry Rounded corner size (rx or RY is set to the same value, <circle> Cx cy abscissa and ordinate (center point of the circle) r radius <ellipse> ellipse Cx cy abscissa and ordinate rx ry transverse radius and longitudinal radius <line> segment x1 y1 Y2 <polyline> points="x1 y1 x2 y2 x3 y3.."Set x and y values for as many nodes as possible <polygon> polygon points="x1 y1 x2 y2 x3 y3.."Set x and y values for as many nodes as possible, and the first and last nodes will be automatically connectedCopy the code

1.2 Basic Attributes

Fill = "#FFB3AE" fill color stroke = #971817Stroke color stroke-width =10Stroke thickness transform ="rotate(30)"Rotational deformationCopy the code

Case 2,

 <svg
   id="svgelem"
   style=" position: absolute;"
   width="300"
   height="200"
   xmlns="http://www.w3.org/2000/svg"
   @mousemove="mover"
   @mousedown="mdown"
   @mouseup="mup"
>
   <rect
         v-for="(tag, index3) in newTags"
         :key="'rect' + index3 + '2'"
         :id="'rect' + index3"
         :x="tag.x"
         :y="tag.y"
         :width="tag.width"
         :height="tag.height"
         style="fill-opacity: 0; stroke-width: 2"
         :style="{ stroke: tag.color, strokeDasharray: tag.dash }"
         @dblclick="handleChooseTag(index3)"
   ></rect>
   <text v-for="(tag, index4) in newTags" :key="'textName' + index4 + '3'" :x="tag.x"                :y="tag.y - 5" :fill="tag.color">
     {{ tag.tagName }}
   </text>
   <text
         v-for="(tag, index5) in newTags"
         :key="'textIndex' + index5 + '4'"
         :x="tag.x + tag.width / 2"
         :y="tag.y + tag.height / 2"
         :fill="tag.color"
   >
     		{{ index5 + 1 }}
   </text>                                       
</svg>
Copy the code

2.1 Opening images

This class provides an observer that will be called on each resize event by instantiating a listener and calling the Observe method to pass in a DOM or SVG element to be listened on. The callback passed in ResizeObserver instantiation is executed each time the REsize event is triggered. By default, this callback returns an array containing each listener element and its readable information, and the order in which the elements are visited (yes, instantiating a listener, Observe multiple times to listen on different elements.

let _that = this;
var ro = new ResizeObserver(entries= > {
  _that.oriWidthFull = _that.oriWidthFull || oriImg.offsetWidth;
  _that.oriHeightFull = _that.oriHeightFull || oriImg.offsetHeight;
  _that.resizeDrawSvg(_that.oriWidthFull, _that.oriHeightFull);
  _that.oriWidthFull = entries[0].contentRect.width;
  _that.oriHeightFull = entries[0].contentRect.height;
});
this.$nextTick(async () => {
  ro.observe(document.getElementById('oriImg'));
  this.drawSvg();
});
Copy the code

2.2 drawSvg: Initial frame

drawSvg() {
  this.$nextTick(() = > {
    let orisize = this.chooseSample.size.split(The '*');
    let oriWidth = +orisize[0];
    let oriHeight = +orisize[1];
    let imgWidth = oriImg.offsetWidth;
    let imgHeight = oriImg.offsetHeight;
    svgelem.setAttribute('width', imgWidth);
    svgelem.setAttribute('height', imgHeight);
    let scale = imgWidth / oriWidth;
    // Change the actual coordinates
    this.newTags.forEach(tag= > {
      tag.oriX = tag.x || 0;
      tag.oriY = tag.y || 0;
      tag.oriWidth = tag.width || 0;
      tag.oriHeight = tag.height || 0;
      tag.x = Math.round(tag.oriX * scale);
      tag.y = Math.round(tag.oriY * scale);
      tag.width = Math.round(tag.oriWidth * scale);
      tag.height = Math.round(tag.oriHeight * scale);
    });
    this.currentTags.forEach(tag= > {
      tag.oriX = tag.x || 0;
      tag.oriY = tag.y || 0;
      tag.oriWidth = tag.width || 0;
      tag.oriHeight = tag.height || 0;
      tag.x = Math.round(tag.oriX * scale);
      tag.y = Math.round(tag.oriY * scale);
      tag.width = Math.round(tag.oriWidth * scale);
      tag.height = Math.round(tag.oriHeight * scale);
    });
  });
},
Copy the code

2.3 resizeDrawSvg: Redraw the page change box

resizeDrawSvg(oriWidth, oriHeight) {
  this.$nextTick(() = > {
    let imgWidth = oriImg.offsetWidth;
    let imgHeight = oriImg.offsetHeight;
    svgelem.setAttribute('width', imgWidth);
    svgelem.setAttribute('height', imgHeight);
    let scale = imgWidth / oriWidth;
    // Change the actual coordinates
    this.newTags.forEach(tag= > {
      tag.oriX = tag.x || 0;
      tag.oriY = tag.y || 0;
      tag.oriWidth = tag.width || 0;
      tag.oriHeight = tag.height || 0;
      tag.x = Math.round(tag.oriX * scale);
      tag.y = Math.round(tag.oriY * scale);
      tag.width = Math.round(tag.oriWidth * scale);
      tag.height = Math.round(tag.oriHeight * scale);
    });
    this.currentTags.forEach(tag= > {
      tag.oriX = tag.x || 0;
      tag.oriY = tag.y || 0;
      tag.oriWidth = tag.width || 0;
      tag.oriHeight = tag.height || 0;
      tag.x = Math.round(tag.oriX * scale);
      tag.y = Math.round(tag.oriY * scale);
      tag.width = Math.round(tag.oriWidth * scale);
      tag.height = Math.round(tag.oriHeight * scale);
    });
  });
},
Copy the code

2.4 handleChooseTag: Select a tag

// Select an annotation
handleChooseTag(index) {
  this.editIndex = index;
  let temp = JSON.parse(JSON.stringify(this.newTags));
  temp.forEach((tag, ind) = > {
    tag.isActive = ind == index;
    tag.color = ind == index ? 'red' : '#00ff00';
  });
  this.newTags = JSON.parse(JSON.stringify(temp));
},
Copy the code

3. Effect display

3. Build image annotation components based on Vue + fabric.js

Fabric.js is a powerful and simple Javascript HTML5 canvas library Fabric provides an interactive object model on top of canvas elements Fabric also has svG-to-Canvas (and Canvas-to-SVG) parsers. With fabric.js, you can create and fill objects on the canvas; Such as simple geometric shapes — rectangles, circles, ovals, polygons, custom images or more complex shapes made up of hundreds or thousands of simple paths. In addition, you can scale, move, and rotate these objects with the mouse; Modify their attributes – color, transparency, Z-index, etc. You can also combine objects on the canvas.

1, install,

Yarn add fabric-s or NPM I fabric-s

2, rely on

Download [customiseControls. Min. Js] (HTTP: / / https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2Fpurestart%2Fvue-fabric%2Fblob%2Fma ster%2Fstatic%2Fjs%2FcustomiseControls.min.js). and [fabric.min.js](https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2Fpurestart%2Fvue-fabric%2Fblob%2Fmaster%2Fstatic %2Fjs% 2ffabric.min.js) is imported into the local static/js file <br /> local project index.htmlCopy the code
<script type="text/javascript" src="./static/js/fabric.min.js"></script> 
<script type="text/javascript" src="./static/js/customiseControls.min.js"></script
Copy the code

3, use,

// Reference in main.js
import 'vue-fabric/dist/vue-fabric.min.css'; 
import { Fabric } from 'vue-fabric';
Vue.use(Fabric);
Copy the code
<vue-fabric ref="canvas" id="canvas" :width="width" :height="height" @selection:created="selected" @selection:updated="selected"></vue-fabric>
Copy the code

3.1 Draw a simple graph

3.1.1 Fabric provides seven base shapes:

  • Fabric. Circle (round)
  • Fabric.Ellipse (Ellipse)
  • Fabric. The Line (Line)
  • Fabric.polyline (multiple lines drawn to form a graph)
  • Fabric. Triangle (triangle)
  • Fabric.rect (rectangle)
  • Fabric.polygon

3.1.2 Effect display

Images, rectangles, triangles, and dashed lines can be added to the canvas and rotated.

3.1.2 Code display

<template> <div class="container"> <div class="header"> <div class="logo"> LOGO </div> </div> <div class="content-wrapper"> <div class="list-wraper"> <div :key="item.id" v-for="item in list" class="image-wrapper"> <img :src="item.url" /> <i @click="handleAdd(item.url)" class="pt-iconfont icon-plus-circle"></i> </div> </div> <vue-fabric ref="canvas" id="canvas" :width="width" :height="height" @selection:created="selected" @Selection :updated="selected"></vue-fabric> <div class="tool-wrapper"> < i@click ="createRect" > Create rectangle </ I >< I @click="createCircle" > createCircle </ I > < i@click ="createTriangle" > createTriangle </ I > < i@click ="createEqualTriangle" > create isosceles triangle </ I > < I @click="drawDottedline" > </ I > < @click="handleDelete" class="pt-iconfont icon-delete"></i> <i @click="rotate" class="pt-iconfont icon-shuaxin"></i> <i @click="changeDrawMore" class="pt-iconfont  icon-crop"></i> <i @click="selected" class="pt-iconfont icon-crop"></i> </div> </div> <vue-image-model :close="()=>{imgUrl=''}" v-show="imgUrl.length>0" :url="imgUrl"></vue-image-model> </div> </template> <script type='text/ecmascript-6'> import VueImageModel from '.. /components/image-model.vue'; export default { components: { VueImageModel }, data () { return { // http://data618.oss-cn-qingdao.aliyuncs.com/ys/3524/img/b.jpg imgUrl: '', width: 300, height: 500, list: [ { id: 1, url: '/static/images/sticker1.png' }, { id: 2, url: '/static/images/sticker2.png' }, { id: 3, url: '/static/images/sticker3.png' }, { id: 4, url: '/static/images/sticker4.png' }, { id: 5, url: '/static/images/sticker5.png' } ], isDrawingMode: true }; }, created () { this.width = document.body.offsetWidth - 200; this.height = document.body.offsetHeight - 60; console.log(document.body.offsetWidth); }, mounted () { // this.$refs.canvas.createTriangle({ id: 'Triangle', x: 100, y: 100, x1: 150, y1: 200, x2: 180, y2: 190, fill: 'yellow', left: 80 }); // this.$refs.canvas.createImage('/static/images/sticker1.png', { id: 'myImage', width: 100, height: 100, left: 10, top: 10 ,evented:false, selectable: false}); // // this.$refs.canvas.createImage('/static/images/sticker2.png'); // // this.$refs.canvas.createImage('/static/images/sticker3.png'); // let options = { // x: 100, y: 100, x1: 600, y1: 600, color: '#B2B2B2', drawWidth: 2, id: 'Triangle' // }; // // this.$refs.canvas.drawDottedline(options); // // this.$refs.canvas.createEllipse({ rx: 200, ry: 400, left: 300 }); $refs.canvas. CreateItext (' refs.canvas. CreateItext ', {top: 100, left: 300, width: $refs.canvas. 50 ,editable:false}); // this.$refs.canvas.setCornerIcons({ size: 20, tl: '/static/images/cow.png' }); // this.$refs.canvas.drawByPath([[50, 50], [120, 120], [80, 160]], {}); var img = new Image(); img.setAttribute('crossOrigin', 'anonymous'); let that = this; img.onload = function () { that.$refs.canvas.createImageByImg(img, { id: 'myImage', width: 100, height: 100, left: 10, top: 10, evented: true, selectable: true, crossOrigin: 'anonymous'}); }; img.src = '/static/images/sticker1.png'; this.$refs.canvas.setSelection(false); let options = { imgUrl: 'https://weiliicimg9.pstatp.com/weili/l/701712572929933335.webp', width: this.width, height: Height, opacity: 1, scaleX: 1.5}; // this.$refs.canvas.setBackgroundImage(options); this.$refs.canvas.setBackgroundColor('#ffffff00'); }, methods: { changeDrawMore () { this.isDrawingMode = ! this.isDrawingMode; this.$refs.canvas.freeDrawConfig({isDrawingMode: this.isDrawingMode}); }, setErase () { this.$refs.canvas.eraseDrawConfig({drawWidth: 20}); }, toggleMirror () { this.$refs.canvas.toggleMirror({flip: 'Y'}); }, discardActive () { this.$refs.canvas.discardActive(); }, handleAdd (url) { console.log('handleAdd'); this.$refs.canvas.createImage(url); }, createRect () { this.$refs.canvas.createRect({width: 100, height: 20}); }, createCircle () { this.$refs.canvas.createCircle(); }, createTriangle () { this.$refs.canvas.createTriangle({x1: 10, y1: 30, x2: 10, y2: 70}); }, createEqualTriangle () { this.$refs.canvas.createEqualTriangle(); }, createLine () { this.$refs.canvas.createLine(); }, drawDottedline () { this.$refs.canvas.drawDottedline(); }, handleDelete () { this.$refs.canvas.removeCurrentObj(); }, rotate () { this.$refs.canvas.setRotate(); }, createImg () { let dataUrl = this.$refs.canvas.toDataUrl(); // console.log(dataUrl); this.imgUrl = dataUrl; }, selected (obj, option) { this.$refs.canvas.setSelection(true); // console.log(obj); // console.log(option); }}}; </script>Copy the code
<template>
  <div>
    <canvas :id="id" :width="width" :height="height"></canvas>
  </div>
</template>

<script type="text/ecmascript-6">
import Utils from '../../utils';
const dotCircleImg = require('../../assets/dot-circle.png');
const rotateMdrImg = require('../../assets/rotate-mdr.png');
export default {
  name: 'VueFabric',
  props: {
    id: {
      type: String,
      required: false,
      default: 'fabricCanvas'
    },
    width: {
      type: Number,
      required: true
    },
    height: {
      type: Number,
      required: true
    }
  },
  data () {
    return {
      canvas: null,
      currentObj: null
    };
  },
  created () {

  },
  mounted () {
    this.canvas = new fabric.Canvas(this.id, { preserveObjectStacking: true });
    let canvas = this.canvas;
    // 客製化控制项
    fabric.Canvas.prototype.customiseControls({
      tl: {
        action: 'scale'
        // cursor: '../../assets/rotate-mdr.png'
      },
      tr: {
        action: 'scale'
      },
      bl: {
        action: 'scale',
        cursor: 'pointer'
      },
      br: {
        action: 'scale',
        cursor: 'pointer'
      },
      mb: {
        action: 'scale',
        cursor: 'pointer'
      },
      // mr: {
      //     // action: function(e, target) {
      //     //     target.set({
      //     //         left: 200,
      //     //     });
      //     //     canvas.renderAll();
      //     // },
      //     action: 'scale',
      //     cursor: 'pointer',
      // },
      mt: {
        // action: {
        //   rotateByDegrees: 30
        // },
        action: 'scale',
        cursor: 'pointer'
      },
      // only is hasRotatingPoint is not set to false
      mtr: {
        action: 'rotate'
        // cursor: '../../assets/cow.png',
      }
    });
    this.setCornerIcons({});
    // canvas.add(new fabric.Circle({ radius: 30, fill: '#f55', top: 100, left: 100 }));
    canvas.backgroundColor = '#ffffff';
    // canvas.renderAll();
    // this.canvas.push(canvas);
    let that = this;
    this.canvas.controlsAboveOverlay = false;
    this.canvas.skipOffscreen = true;
    // this.drawControls();
    // 初次选中图层
    this.canvas.on('selection:created', function (options) {
      that.$emit('selection:created', options);
    });
    this.canvas.on('mouse:down', function (options) {
      that.$emit('mouse:down', options);
    });
    this.canvas.on('mouse:up', function (options) {
      that.$emit('mouse:up', options);
    });
    this.canvas.on('mouse:move', function (options) {
      that.$emit('mouse:move', options);
    });
    this.canvas.on('mouse:dblclick', function (options) {
      that.$emit('mouse:dblclick', options);
    });
    this.canvas.on('mouse:over', function (options) {
      that.$emit('mouse:over', options);
    });
    this.canvas.on('mouse:out', function (options) {
      that.$emit('mouse:out', options);
    });
    // 添加图层
    this.canvas.on('object:added', function (options) {
      that.$emit('object:added', options);
    });
    // 移除图层
    this.canvas.on('object:removed', function (options) {
      that.$emit('object:removed', options);
    });
    // 编辑图层
    this.canvas.on('object:modified', function (options) {
      that.$emit('object:modified', options);
    });
    this.canvas.on('object:rotating', function (options) {
      that.$emit('object:rotating', options);
    });
    this.canvas.on('object:scaling', function (options) {
      that.$emit('object:scaling', options);
    });
    this.canvas.on('object:moving', function (options) {
      that.$emit('object:moving', options);
    });
    // 图层选择变化
    this.canvas.on('selection:updated', function (options) {
      that.$emit('selection:updated', options);
    });
    // 清空图层选中
    this.canvas.on('selection:cleared', function (options) {
      that.$emit('selection:cleared', options);
    });
    this.canvas.on('before:selection:cleared', function (options) {
      that.$emit('before:selection:cleared', options);
    });
  },
  methods: {
    setCornerIcons ({ size = 20, borderColor = '#e4e4e4', cornerBackgroundColor = '#ffffff', cornerShape = 'rect', tl = dotCircleImg, tr = dotCircleImg, bl = dotCircleImg, br = dotCircleImg, ml = dotCircleImg, mr = dotCircleImg, mtr = rotateMdrImg }) {
      // basic settings
      let that = this;
      fabric.Object.prototype.customiseCornerIcons(
        {
          settings: {
            borderColor: borderColor,
            cornerSize: size,
            cornerShape: cornerShape,  // 'rect', 'circle'
            cornerBackgroundColor: cornerBackgroundColor
          },
          tl: {
            icon: tl
          },
          tr: {
            icon: tr
          },
          bl: {
            icon: bl
          },
          br: {
            icon: br
          },
          ml: {
            icon: ml
          },
          mr: {
            icon: mr
          },
          // only is hasRotatingPoint is not set to false
          mtr: {
            icon: mtr
          }
        },
        function () {
          that.canvas.renderAll();
        }
      );
    },
    drawDottedline (options) {
      options = Object.assign({ x: 0, y: 0, x1: 10, y1: 10, color: '#B2B2B2', drawWidth: 2, offset: 6, empty: 3 }, options);
      let canvasObject = new fabric.Line([options.x, options.y, options.x1, options.y1], {
        strokeDashArray: [options.offset, options.empty],
        stroke: options.color,
        strokeWidth: options.drawWidth,
        ...options
      });
      this.canvas.add(canvasObject);
      this.canvas.renderAll();
    },
    drawArrowLine (options) {
      options = Object.assign({ x: 0, y: 0, x1: 0, y1: 0, color: '#B2B2B2', drawWidth: 2, fillColor: 'rgba(255,255,255,0)', theta: 35, headlen: 35 }, options);
      let canvasObject = new fabric.Path(this.drawArrowBase(options.x, options.y, options.x1, options.y1, options.theta, options.headlen), {
        stroke: options.color,
        fill: options.fillColor,
        strokeWidth: options.drawWidth,
        ...options
      });
      this.canvas.add(canvasObject);
      this.canvas.renderAll();
    },
    drawArrowBase (fromX, fromY, toX, toY, theta, headlen) {
      theta = typeof theta !== 'undefined' ? theta : 30;
      headlen = typeof theta !== 'undefined' ? headlen : 10;
      // 计算各角度和对应的P2,P3坐标
      var angle = (Math.atan2(fromY - toY, fromX - toX) * 180) / Math.PI,
        angle1 = ((angle + theta) * Math.PI) / 180,
        angle2 = ((angle - theta) * Math.PI) / 180,
        topX = headlen * Math.cos(angle1),
        topY = headlen * Math.sin(angle1),
        botX = headlen * Math.cos(angle2),
        botY = headlen * Math.sin(angle2);
      var arrowX = fromX - topX,
        arrowY = fromY - topY;
      var path = ' M ' + fromX + ' ' + fromY;
      path += ' L ' + toX + ' ' + toY;
      arrowX = toX + topX;
      arrowY = toY + topY;
      path += ' M ' + arrowX + ' ' + arrowY;
      path += ' L ' + toX + ' ' + toY;
      arrowX = toX + botX;
      arrowY = toY + botY;
      path += ' L ' + arrowX + ' ' + arrowY;
      return path;
    },
    freeDrawConfig (options) {
      options = Object.assign({color: '#b2b2b2', drawWidth: 2}, options);

      this.canvas.isDrawingMode = options.isDrawingMode;
      this.canvas.freeDrawingBrush.color = options.color; // 设置自由绘颜色
      this.canvas.freeDrawingBrush.width = options.drawWidth;
      this.canvas.renderAll();
    },
    eraseDrawConfig (options) {
      options = Object.assign({color: 'white', drawWidth: 2}, options);

      this.canvas.freeDrawingBrush.color = options.color; // 设置自由绘颜色
      this.canvas.freeDrawingBrush.width = options.drawWidth;
      this.canvas.renderAll();
    },
    removeCurrentObj () {
      let obj = this.canvas.getActiveObject();
      // console.log(obj);
      this.canvas.remove(obj);
      this.canvas.renderAll();
    },
    getEditObj () {
      let obj = this.canvas.getActiveObject();
      this.removeCurrentObj();
      return obj;
    },
    setEditObj (obj) {
      this.canvas.add(obj);
      this.canvas.renderAll();
    },
    setRotate (deg = 36) {
      let obj = this.canvas.getActiveObject();
      let angle = obj.angle;
      obj.rotate(angle + deg);
      this.canvas.renderAll();
    },
    discardActive () {
      this.canvas.discardActiveObject();
      // this.canvas.discardActiveGroup();
      this.canvas.renderAll();
    },
    deactivateAll () {
      // this.canvas.deactivateAll().renderAll();
    },
    deactivateOne (obj) {
      var activeGroup = this.canvas.getActiveGroup();
      activeGroup.removeWithUpdate(obj);
      this.canvas.renderAll();
    },
    setSelection (flag) {
      this.canvas.selection = flag;
      this.canvas.renderAll();
    },
    moveTo () {
      let obj = this.canvas.getActiveObject();
      console.log(this.canvas.sendBackwards);
      this.canvas.sendBackwards(obj, true);
      this.canvas.discardActiveObject();
      // this.canvas.discardActiveGroup();
    },
    createRect (options) {
      debugger;
      options = Object.assign({ width: 0, height: 0, fillColor: 'red', left: 50, top: 50 }, options);
      let rect = new fabric.Rect({
        fill: options.fillColor, // 填充的颜色
        ...options
      });
      this.canvas.add(rect);
      this.canvas.renderAll();
    },
    createCircle (options) {
      options = Object.assign({ left: 0, top: 0, radius: 30, fillColor: 'rgba(255, 255, 255, 0)', color: '#B2B2B2', drawWidth: 2 }, options);
      let defaultOption = {
        fill: options.fillColor,
        strokeWidth: options.drawWidth,
        stroke: options.color,
        ...options
      };
      let Circle = new fabric.Circle(defaultOption);
      this.canvas.add(Circle);
      this.canvas.renderAll();
    },
    createTriangle (options) {
      options = Object.assign({ x: 0, y: 0, x1: 0, y1: 0, x2: 0, y2: 0, left: 100, top: 100, color: '#B2B2B2', drawWidth: 2, fillColor: 'rgba(255, 255, 255, 0)', id: 'triangle' }, options);
      var path = 'M ' + options.x + ' ' + options.y + ' L ' + options.x1 + ' ' + options.y1 + ' L ' + options.x2 + ' ' + options.y2 + ' z';
      let canvasObject = new fabric.Path(path, {
        stroke: options.color,
        strokeWidth: options.drawWidth,
        fill: options.fillColor,
        ...options
      });
      this.canvas.add(canvasObject);
      this.canvas.renderAll();
    },
    createEqualTriangle (options) {
      options = Object.assign({ left: 100, top: 100, width: 50, height: 80, fillColor: 'rgba(255, 255, 255, 0)', color: '#B2B2B2', drawWidth: 2 }, options);
      // console.log(defaultOption);
      let triangle = new fabric.Triangle({
        fill: options.fillColor,
        strokeWidth: options.drawWidth,
        stroke: options.color,
        ...options
      });
      this.setContronVisibility(triangle);
      this.canvas.add(triangle);
      this.canvas.renderAll();
    },
    createLine (options) {
      options = Object.assign({ x: 0, y: 0, x1: 10, y1: 10, fillColor: 'rgba(255, 255, 255, 0)', strokeColor: '#B0B0B0' }, options);
      let line = new fabric.Line([options.x, options.y, options.x1, options.y1], {
        fill: options.fillColor,
        stroke: options.strokeColor,
        ...options
      });
      this.canvas.add(line);
      this.canvas.renderAll();
    },
    createEllipse (options) {
      options = Object.assign({ rx: 100, ry: 200, fillColor: 'rgba(255, 255, 255, 0)', angle: 90, strokeColor: '#B0B0B0', strokeWidth: 3, left: 50, top: 50 }, options);
      var ellipse = new fabric.Ellipse({
        fill: options.fillColor,
        stroke: options.strokeColor,
        ...options
      });
      this.canvas.add(ellipse);
      this.canvas.renderAll();
    },
    createText (text, options) {
      options = Object.assign({ left: 100, top: 100 }, options);
      var canvasObj = new fabric.Text(text, { ...options });
      this.canvas.add(canvasObj);
      this.canvas.renderAll();
    },
    createItext (text, options) {
      options = Object.assign({ left: 0, top: 0, fill: '#000'}, options);
      let IText = new fabric.IText(text, options);
      this.canvas.add(IText);
      this.canvas.renderAll();
    },
    createTextbox (text, options) {
      // _fontSizeMult: 5,
      options.fillColor = options.fillColor ? options.fillColor : options.fill;
      options = Object.assign({ fontSize: 14, fillColor: '#000000', registeObjectEvent: false, width: 50, left: 100, top: 100 }, options);
      var canvasObj = new fabric.Textbox(text, {
        fill: options.fillColor,
        ...options
      });
      // let arr = canvasObj._splitTextIntoLines(text);
      // console.log(arr);
      this.canvas.add(canvasObj);
      if (options.registeObjectEvent) {
        Utils.registeObjectEvent(this, canvasObj);
      }
      this.canvas.renderAll();
    },
    createImageByImg (img, options) {
      options = options || {};
      let canvas = this.canvas;
      let that = this;
        // let maxWidth = that.width;
      let width = 0;
      let height = 0;
      width = img.width;
      height = img.height;
        // if (img.width > img.height) {
        //   if (img.width > maxWidth) {
        //     width = maxWidth;
        //     height = (img.height / img.width) * width;
        //   } else {
        //     width = img.width;
        //     height = img.height;
        //   }
        // } else {
        //   if (img.height > maxWidth) {
        //     height = maxWidth;
        //     width = (img.width / img.height) * height;
        //   } else {
        //     width = img.width;
        //     height = img.height;
        //   }
        // }
      if (options && options.width) {
        width = options.width;
      }
      if (options && options.height) {
        height = options.height;
      }
      let leftP = that.width / 2;
      let topP = that.height / 2;
      if ((options && options.left) || (options && options.left == 0)) {
        leftP = options.left + width / 2;
      }
      if ((options && options.top) || (options && options.top == 0)) {
        topP = options.top + height / 2;
      }
      let imgOptions = Object.assign(options, {
        id: (options && options.id) ? options.id : 'image',
        left: leftP,
        top: topP,
        scaleX: width / img.width,
        scaleY: height / img.height,
        originX: 'center',
        originY: 'center',
        cornerStrokeColor: 'blue'
      });
      delete imgOptions.width;
      delete imgOptions.height;
      var canvasImage = new fabric.Image(img, imgOptions);

      canvasImage.hasControls = true;
      canvasImage.hasBorders = true;

      canvas.add(canvasImage); // 把图片添加到画布上
      if (options && options.registeObjectEvent) {
        Utils.registeObjectEvent(that, canvasImage);
      }
      canvas.renderAll.bind(canvas);
    },
    // 读取图片地址,创建图片
    createImage (url, options) {
      options = options || {};
      let canvas = this.canvas;
      let that = this;
      fabric.Image.fromURL(url, function (img) {
        // 添加过滤器
        // img.filters.push(new fabric.Image.filters.Grayscale());
        // 应用过滤器并重新渲染画布执行
        // img.applyFilters(canvas.renderAll.bind(canvas));
        // console.log(img);
        let maxWidth = that.width / 2;
        let width = 0;
        let height = 0;
        width = img.width;
        height = img.height;
        // if (img.width > img.height) {
        //   if (img.width > maxWidth) {
        //     width = maxWidth;
        //     height = (img.height / img.width) * width;
        //   } else {
        //     width = img.width;
        //     height = img.height;
        //   }
        // } else {
        //   if (img.height > maxWidth) {
        //     height = maxWidth;
        //     width = (img.width / img.height) * height;
        //   } else {
        //     width = img.width;
        //     height = img.height;
        //   }
        // }
        if (options && options.width) {
          width = options.width;
        }
        if (options && options.height) {
          height = options.height;
        }
        let leftP = that.width / 2;
        let topP = that.height / 2;
        if ((options && options.left) || (options && options.left == 0)) {
          leftP = options.left + width / 2;
        }
        if ((options && options.top) || (options && options.top == 0)) {
          topP = options.top + height / 2;
        }
        // console.log(options);
        let imgOptions = Object.assign(options, {
          // ...options,
          id: (options && options.id) ? options.id : 'image',
          left: leftP,
          top: topP,
          scaleX: width / img.width,
          scaleY: height / img.height,
          originX: 'center',
          originY: 'center',
          cornerStrokeColor: 'blue'
        });
        delete imgOptions.width;
        delete imgOptions.height;
        console.log('imgOptions', imgOptions);
        img.set(imgOptions);

        var oldOriginX = img.get('originX');
        var oldOriginY = img.get('originY');
        var center = img.getCenterPoint();
        img.hasControls = true;
        img.hasBorders = true;
        
        canvas.add(img); // 把图片添加到画布上
        if (options && options.registeObjectEvent) {
          Utils.registeObjectEvent(that, img);
        }
        canvas.renderAll.bind(canvas);
      });
    },
    toJson () {
      let json = this.canvas.toJSON();
      return json;
    },
    toDataUrl () {
      let canvas = this.canvas;
      let dataURL = canvas.toDataURL({
        format: 'jpeg',
        quality: 1
      });
      return dataURL;
    },
    loadFromJSON (json, cb) {
      let canvas = this.canvas;
      canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function (
        o,
        object
      ) {
        // `o` = json object
        // `object` = fabric.Object instance
        // ... do some stuff ...
        cb(o);
        object.setControlsVisibility({
          bl: true,
          br: true,
          mb: false,
          ml: true,
          mr: true,
          mt: false,
          mtr: true,
          tl: true,
          tr: true
        });
      });
    },
    clear () {
      this.canvas.clear();
    },
    getObjects () {
      return this.canvas.getObjects();
    },
    renderAll () {
      this.canvas.renderAll(this.canvas);
    },
    renderTop () {
      this.canvas.renderTop();
    },
    // 设置背景颜色
    setBackgroundColor (color) {
      let canvas = this.canvas;
      this.canvas.setBackgroundColor(color, canvas.renderAll.bind(canvas));
    },
    // 设置画布背景
    setBackgroundImage (options) {
      let canvas = this.canvas;
      let opt = {
        opacity: 1,
        left: 0,
        top: 0,
        angle: 0,
        crossOrigin: null,
        originX: 'left',
        originY: 'top',
        scaleX: 1,
        scaleY: 1
      };
      // console.log(options);
      if (Object.prototype.toString.call(options) == '[object String]') {
        console.log('字符串兼容');
        opt.imgUrl = options;
      } else {
        opt = Object.assign(opt, options);
      }

      fabric.Image.fromURL(opt.imgUrl, function (img) {
        img.set({width: opt.width ? opt.width : canvas.width, height: opt.height ? opt.height : canvas.height, originX: 'left', originY: 'top', scaleX: opt.scaleX, scaleY: opt.scaleY });
        canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas), {...opt});
      });
    },
    toSvg () {
      return this.canvas.toSVG();
    },
    drawControls () {
      let canvas = document.createElement('canvas');
      var ctx = canvas.getContext('2d');
      ctx.setLineDash([]);
      ctx.beginPath();
      ctx.ellipse(100, 100, 50, 75, (45 * Math.PI) / 180, 0, 2 * Math.PI); // 倾斜45°角
      ctx.stroke();
      ctx.setLineDash([5]);
      ctx.moveTo(0, 200);
      ctx.lineTo(200, 0);
      ctx.stroke();
      this.canvas.drawControls(ctx);
      // this.canvas.controlsAboveOverlay=true;
    },
    setContronVisibility (obj) {
      obj.setControlsVisibility({
        bl: true,
        br: true,
        mb: false,
        ml: true,
        mr: true,
        mt: false,
        mtr: true,
        tl: true,
        tr: true
      });
    },
    // 设置mirror
    toggleMirror (options) {
      options = options || {};
      options = Object.assign({ flip: 'X' }, options);
      let img = this.canvas.getActiveObject();
      // if (img && img.type == 'image') {
      if (options.flip === 'X') {
        img.toggle('flipX');
      } else {
        img.toggle('flipY');
      }
      this.renderAll();
      // }
    },
    // 设置层级
    toNextLayer () {
      let obj = this.canvas.getActiveObject();
      if (!obj) {
        return;
      }
      obj.sendBackwards(true);
      this.renderTop();
      // this.canvas.setActiveObject(obj);
    },
    toBottomLayer () {
      let obj = this.canvas.getActiveObject();
      if (!obj) {
        return;
      }
      obj.sendToBack();
      this.renderTop();
      // this.canvas.setActiveObject(obj);
    },
    toLastLayer () {
      let obj = this.canvas.getActiveObject();
      if (!obj) {
        return;
      }
      obj.bringForward(true);
      this.renderTop();
    },
    toTopLayer () {
      let obj = this.canvas.getActiveObject();
      if (!obj) {
        return;
      }
      obj.bringToFront();
      this.renderTop();
    },
    drawByPath (pathArray, options) {
      options = Object.assign({ fillColor: 'rgba(255, 255, 255, 0)', left: 150, top: 150, strokeColor: '#B0B0B0', strokeWidth: 3 }, options);
      let pathStr = 'M ';
      for (let item of pathArray) {
        pathStr = pathStr + item[0] + ' ' + item[1] + ' ';
      }
      pathStr = pathStr + 'z';
      console.log(pathStr);
      var path = new fabric.Path(pathStr);
      path.set({
        stroke: options.strokeColor,
        fill: options.fillColor,
        ...options
      });
      this.canvas.add(path);
    }
  }
};
</script>
Copy the code

4. Canvas based color change picture

1. Color composition of the picture

RGB color is a color standard in the industry, which is used to obtain various colors through the changes of red (R), green (G) and blue (B) color channels and their superposition among each other. RGB is the color representing the three channels of red, green and blue. This standard includes almost all colors perceived by human vision. It is one of the most widely used color systems. Three primary colors Red, Green, Blue, each color value range is 0~255, so each color with 1 byte =8 bits can be completely represented in the computer. And the different combinations of R, G, and B produce almost all colors. Of course, the colors in nature are much more abundant than these. Using R, G, and B, if expressed in 24 shades, the number of colors that can be represented in a computer is 2^8, 2^8, 2^8 = 16777216.

2. Picture storage in computer

Non-digital image information, such as photos and images, is arranged in the form of “pixels” in order (matrix form). A pixel is the basic unit of an image. Each pixel in the image will correspond to a matrix of color values, if a 480A 320 pixel black and white image will have a 480320 color value (0,255) matrix. Here is a picture that is often used to get started with convolutional neural networks.

The pictures are actually stored in the computer.

3. Canvas based demo

<div> <img id="imgs" style="display:none"></img> </div> <div> <canvas id='drawing' style="border:1px solid black;" Width ="640px" height="480px"> </canvas> </div> //canvas loadImg() {let img = document.getelementById ('imgs'); this.mycanvas = document.getElementById('drawing'); // get dom let CTX = this.myCanvas. GetContext ('2d'); img.crossOrigin = ''; Img. onload = function() {ctx.drawImage(img, 0, 0); console.log(ctx.getImageData(0, 0, img.width, img.height)); }; . Img SRC = "http://192.168.79.140:80/testN/aaaa/demo.jpg"; GetImgPixData () {let img = document.getelementById ('imgs'); var context = this.mycanvas.getContext('2d'); let imageData = context.getImageData(0, 0, img.width, img.height); Let data = imagedata.data; this.changeImage(context, imageData); ChangeImage (context, imageData) {this.times = this.times + 1; let jtem = 0; for (let j = 0; j < 10000000; j++) { jtem = j + jtem; } for (let i = 0; i < imageData.data.length; i += 4) { imageData.data[i + 0] = imageData.data[i + 0] - 2 * this.times; imageData.data[i + 1] = 255 - imageData.data[i + 1] - 5 * this.times; imageData.data[i + 2] = imageData.data[i + 2] - 5 * this.times; // imageData.data[i + 3] = 255; } context.putImageData(imageData, 0, 0); // Draw the image data into the canvas}Copy the code

The image data returned by Canvas contains four aspects of information, namely RGBA value:

  • R – Red (0-255)
  • G – Green (0-255)
  • B – Blue (0-255)
  • A-alpha channel (0-255; 0 is transparent, 255 is fully visible)



Results show



This article is from Dong Xiaopang and Chinese Miaozi of JSC Intelligent Data Front End Team