|
@@ -0,0 +1,267 @@
|
|
|
+<template>
|
|
|
+ <canvas ref="canvas" id="panel1"></canvas>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ props: {
|
|
|
+ width: {
|
|
|
+ type: Number,
|
|
|
+ default: 800,
|
|
|
+ },
|
|
|
+ height: {
|
|
|
+ type: Number,
|
|
|
+ default: 300,
|
|
|
+ },
|
|
|
+ lineWidth: {
|
|
|
+ type: Number,
|
|
|
+ default: 4,
|
|
|
+ },
|
|
|
+ lineColor: {
|
|
|
+ type: String,
|
|
|
+ default: "#000000",
|
|
|
+ },
|
|
|
+ bgColor: {
|
|
|
+ type: String,
|
|
|
+ default: "",
|
|
|
+ },
|
|
|
+ isCrop: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ hasDrew: false,
|
|
|
+ resultImg: "",
|
|
|
+ canvasTxt: null,
|
|
|
+ canvas: null,
|
|
|
+ redrawCanvas: null,
|
|
|
+ isMouseDown: false,
|
|
|
+ lastLoc: { x: 0, y: 0 },
|
|
|
+ history: [],
|
|
|
+ sratio: 1,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ ratio() {
|
|
|
+ return this.height / this.width;
|
|
|
+ },
|
|
|
+ stageInfo() {
|
|
|
+ return this.$refs.canvas.getBoundingClientRect();
|
|
|
+ },
|
|
|
+ myBg() {
|
|
|
+ return this.bgColor ? this.bgColor : "rgba(255, 255, 255, 0)";
|
|
|
+ },
|
|
|
+ context() {
|
|
|
+ return this.$refs.canvas.getContext("2d");
|
|
|
+ },
|
|
|
+ redrawCxt() {
|
|
|
+ return this.$refs.canvas.getContext("2d");
|
|
|
+ },
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ myBg: function (newVal) {
|
|
|
+ this.$refs.canvas.style.background = newVal;
|
|
|
+ },
|
|
|
+ },
|
|
|
+ beforeMount() {
|
|
|
+ window.addEventListener("resize", this.$_resizeHandler);
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ window.removeEventListener("resize", this.$_resizeHandler);
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ const canvas = this.$refs.canvas;
|
|
|
+ canvas.height = this.height;
|
|
|
+ canvas.width = this.width;
|
|
|
+ canvas.style.background = this.myBg;
|
|
|
+ this.$_resizeHandler();
|
|
|
+ // 在画板以外松开鼠标后冻结画笔
|
|
|
+ document.onmouseup = () => {
|
|
|
+ this.isMouseDown = false;
|
|
|
+ };
|
|
|
+ this.canvas = canvas;
|
|
|
+ this.redrawCanvas = canvas;
|
|
|
+ this.init();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ reload() {
|
|
|
+ this.reset();
|
|
|
+ this.redraw(this.redrawCxt);
|
|
|
+ },
|
|
|
+ init() {
|
|
|
+ this.canvas.onmousedown = (e) => {
|
|
|
+ this.isMouseDown = true;
|
|
|
+ this.hasDrew = true;
|
|
|
+ this.lastLoc = this.window2Canvas(e.clientX, e.clientY);
|
|
|
+ };
|
|
|
+ this.canvas.onmouseout = (e) => {
|
|
|
+ this.isMouseDown = false;
|
|
|
+ };
|
|
|
+ this.canvas.onmousemove = (e) => {
|
|
|
+ if (this.isMouseDown) {
|
|
|
+ let curLoc = this.window2Canvas(e.clientX, e.clientY); // 获得当前坐标
|
|
|
+ this.draw(this.context, this.lastLoc, curLoc);
|
|
|
+ this.history.push([this.lastLoc, curLoc]);
|
|
|
+ this.lastLoc = Object.assign({}, curLoc); // U know what I mean.
|
|
|
+ }
|
|
|
+ };
|
|
|
+ this.canvas.onmouseup = (e) => {
|
|
|
+ this.isMouseDown = false;
|
|
|
+ if (history.length) {
|
|
|
+ localStorage.setItem("history", JSON.stringify(this.history));
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ window2Canvas(x, y) {
|
|
|
+ let bbox = this.canvas.getBoundingClientRect();
|
|
|
+ return { x: Math.round(x - bbox.left), y: Math.round(y - bbox.top) };
|
|
|
+ },
|
|
|
+ draw(context, lastLoc, curLoc) {
|
|
|
+ if (context) {
|
|
|
+ context.lineWidth = 5;
|
|
|
+ context.beginPath();
|
|
|
+ context.moveTo(lastLoc.x, lastLoc.y);
|
|
|
+ context.lineTo(curLoc.x, curLoc.y);
|
|
|
+ context.strokeStyle = "#000";
|
|
|
+ context.lineCap = "round";
|
|
|
+ context.lineJoin = "round";
|
|
|
+ context.stroke();
|
|
|
+ } else {
|
|
|
+ this.redrawCxt.lineWidth = 5;
|
|
|
+ this.redrawCxt.beginPath();
|
|
|
+ this.redrawCxt.moveTo(lastLoc.x, lastLoc.y);
|
|
|
+ this.redrawCxt.lineTo(curLoc.x, curLoc.y);
|
|
|
+ this.redrawCxt.strokeStyle = "#000";
|
|
|
+ this.redrawCxt.lineCap = "round";
|
|
|
+ this.redrawCxt.lineJoin = "round";
|
|
|
+ this.redrawCxt.stroke();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ redraw(context) {
|
|
|
+ if (localStorage.getItem("history")) {
|
|
|
+ let history = JSON.parse(localStorage.getItem("history"));
|
|
|
+ const len = history.length;
|
|
|
+ let i = 0;
|
|
|
+ const runner = () => {
|
|
|
+ i++;
|
|
|
+ if (i < len) {
|
|
|
+ this.draw(context, history[i][0], history[i][1]);
|
|
|
+ requestAnimationFrame(runner);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ requestAnimationFrame(runner);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ $_resizeHandler() {
|
|
|
+ const canvas = this.$refs.canvas;
|
|
|
+ canvas.style.width = this.width + "px";
|
|
|
+ const realw = parseFloat(window.getComputedStyle(canvas).width);
|
|
|
+ canvas.style.height = this.ratio * realw + "px";
|
|
|
+ this.canvasTxt = canvas.getContext("2d");
|
|
|
+ this.canvasTxt.scale(1 * this.sratio, 1 * this.sratio);
|
|
|
+ this.sratio = realw / this.width;
|
|
|
+ this.canvasTxt.scale(1 / this.sratio, 1 / this.sratio);
|
|
|
+ },
|
|
|
+ // 操作
|
|
|
+ generate() {
|
|
|
+ const pm = new Promise((resolve, reject) => {
|
|
|
+ if (!this.hasDrew) {
|
|
|
+ reject(`Warning: Not Signned!`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ var resImgData = this.canvasTxt.getImageData(
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ this.$refs.canvas.width,
|
|
|
+ this.$refs.canvas.height
|
|
|
+ );
|
|
|
+ this.canvasTxt.globalCompositeOperation = "destination-over";
|
|
|
+ this.canvasTxt.fillStyle = this.myBg;
|
|
|
+ this.canvasTxt.fillRect(
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ this.$refs.canvas.width,
|
|
|
+ this.$refs.canvas.height
|
|
|
+ );
|
|
|
+ this.resultImg = this.$refs.canvas.toDataURL();
|
|
|
+ var resultImg = this.resultImg;
|
|
|
+ this.canvasTxt.clearRect(
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ this.$refs.canvas.width,
|
|
|
+ this.$refs.canvas.height
|
|
|
+ );
|
|
|
+ this.canvasTxt.putImageData(resImgData, 0, 0);
|
|
|
+ this.canvasTxt.globalCompositeOperation = "source-over";
|
|
|
+ if (this.isCrop) {
|
|
|
+ const crop_area = this.getCropArea(resImgData.data);
|
|
|
+ var crop_canvas = document.createElement("canvas");
|
|
|
+ const crop_ctx = crop_canvas.getContext("2d");
|
|
|
+ crop_canvas.width = crop_area[2] - crop_area[0];
|
|
|
+ crop_canvas.height = crop_area[3] - crop_area[1];
|
|
|
+ const crop_imgData = this.canvasTxt.getImageData(...crop_area);
|
|
|
+ crop_ctx.globalCompositeOperation = "destination-over";
|
|
|
+ crop_ctx.putImageData(crop_imgData, 0, 0);
|
|
|
+ crop_ctx.fillStyle = this.myBg;
|
|
|
+ crop_ctx.fillRect(0, 0, crop_canvas.width, crop_canvas.height);
|
|
|
+ resultImg = crop_canvas.toDataURL();
|
|
|
+ crop_canvas = null;
|
|
|
+ }
|
|
|
+ resolve(resultImg);
|
|
|
+ });
|
|
|
+ return pm;
|
|
|
+ },
|
|
|
+ reset() {
|
|
|
+ this.canvasTxt.clearRect(
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ this.$refs.canvas.width,
|
|
|
+ this.$refs.canvas.height
|
|
|
+ );
|
|
|
+ this.$emit("update:bgColor", "");
|
|
|
+ this.$refs.canvas.style.background = "rgba(255, 255, 255, 0)";
|
|
|
+ this.history = [];
|
|
|
+ this.hasDrew = false;
|
|
|
+ this.resultImg = "";
|
|
|
+ },
|
|
|
+ getCropArea(imgData) {
|
|
|
+ var topX = this.$refs.canvas.width;
|
|
|
+ var btmX = 0;
|
|
|
+ var topY = this.$refs.canvas.height;
|
|
|
+ var btnY = 0;
|
|
|
+ for (var i = 0; i < this.$refs.canvas.width; i++) {
|
|
|
+ for (var j = 0; j < this.$refs.canvas.height; j++) {
|
|
|
+ var pos = (i + this.$refs.canvas.width * j) * 4;
|
|
|
+ if (
|
|
|
+ imgData[pos] > 0 ||
|
|
|
+ imgData[pos + 1] > 0 ||
|
|
|
+ imgData[pos + 2] ||
|
|
|
+ imgData[pos + 3] > 0
|
|
|
+ ) {
|
|
|
+ btnY = Math.max(j, btnY);
|
|
|
+ btmX = Math.max(i, btmX);
|
|
|
+ topY = Math.min(j, topY);
|
|
|
+ topX = Math.min(i, topX);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ topX++;
|
|
|
+ btmX++;
|
|
|
+ topY++;
|
|
|
+ btnY++;
|
|
|
+ const data = [topX, topY, btmX, btnY];
|
|
|
+ return data;
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+canvas {
|
|
|
+ max-width: 100%;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+</style>
|