UNPKG

inmarkx

Version:
748 lines (639 loc) 26.3 kB
import zrender from 'zrender'; import { merge, calcHypotenuse, cosA } from '../common/utils.js'; import Tools from '../common/Tools'; import ImageConfig from '../config/ImageConfig.js'; import EditRect from '../config/EditRect.js'; import Init from './Init'; let ImageOption, image, group; let count = 0; let degree_out = 0, radian_out = 0, remainder = 0, //余数 remainder_h = 0; //余数弧度 /** * @constructor * @param {Object} opts */ export default class BImage extends Init { constructor(opts) { super(opts, ImageOption); if (opts) { this._option = {}; this._option.id = opts && opts.id; this._option.imgUrl = opts && opts.imgUrl; this._option.offsetX = 0; //auto模式图片等比例缩放后在画布中横轴位移 this._option.offsetY = 0; //auto模式图片等比例缩放后在画布中纵轴位移 this._option.imgZoom = 2; //图片放大限制 this._option.setRate = 0; //auto模式图片的缩放比例 this._option.widthImg = 0; this._option.heightImg = 0; this._option.scale = 1; //original模式图片的缩放比例 this._option.origin = [0, 0]; //旋转和缩放的原点 this._option.offsetM = 0; //original模式画布中横轴位移 this._option.offsetN = 0; //original模式画布中纵轴位移 this._option.draggable = false; this._option.rotateTime = 0; this._option.center = []; this._option.rotate = { radians: 0, degrees: 0 }; this._option.mode = opts && opts.mode || 'auto'; if (opts.style) { this._imgConfig = merge(ImageConfig.style, opts.style); } else { this._imgConfig = ImageConfig.style; } ImageOption = this._option; } else { this._option = ImageOption; } this.type = 'IMAGE'; this.image = null; this._editWidth = EditRect.shape.width; // 回调函数 this._imageDrag = opts && opts.event.onImageDrag; this._imageDragEnd = opts && opts.event.onImageDragEnd; this._onComplete = opts && opts.event.onLoadComplete; this._onRotate = opts && opts.event.onRotate; // console.log(this) this.initialize(); } initialize() { this.renderImg(this.imgUrl); // let toos = new Tools(); } getZrender() { return this.zr; } getImage() { return this.image; } getGroup() { return this.group; } getCanvasObj() { return this.ctx; } setDrag(bol) { this._option.draggable = bol; // console.log(this.group, this.group.children()) //解决window平台下,设置false,框还可以移动bug this.group && this.group.eachChild(item => { if (item.data.type === 'IMAGE') { item.attr({ 'draggable': bol, 'cursor': 'pointer' }); } }); } getDrag() { return this._option.draggable; } /** * @description 在画布中渲染图片 * @params {Array} url 支持http,https图片路径 */ renderImg(url) { if (!url) { return; } //加载图片 group = new zrender.Group(); this.group = group; this._option.group = group; let img = new Image(); img.setAttribute('crossorigin', 'anonymous'); img.src = url; img.onload = () => { if (this._option.mode === 'auto') { //auto模式图片自动适应屏幕大小 const xRate = this.ctx.canvasWidth / img.width; const yRate = this.ctx.canvasHeight / img.height; this._option.setRate = xRate < yRate ? xRate : yRate; if (this._option.setRate > this._option.imgZoom) { this._option.setRate = this._option.imgZoom; } this._option.widthImg = img.width * this._option.setRate; this._option.heightImg = img.height * this._option.setRate; this._option.offsetX = (this.ctx.canvasWidth - this._option.widthImg) / 2; this._option.offsetY = (this.ctx.canvasHeight - this._option.heightImg) / 2; } else if (this._option.mode === 'auto-rotate') { //auto-rotate,旋转模式,增加中心点参考线 //auto模式图片自动适应屏幕大小 const xRate = this.ctx.canvasWidth / img.width; const yRate = this.ctx.canvasHeight / img.height; this._option.setRate = xRate < yRate ? xRate : yRate; if (this._option.setRate > this._option.imgZoom) { this._option.setRate = this._option.imgZoom; } this._option.widthImg = img.width * this._option.setRate; this._option.heightImg = img.height * this._option.setRate; this._option.padding = 20; this._option.offsetX = (this.ctx.canvasWidth - this._option.widthImg) / 2; // 解决图片中心点偏移 let y = ((this.ctx.canvasHeight - this._option.heightImg) / 2); if (y === 0) { this._option.offsetY = this._option.padding; } else { this._option.offsetY = parseFloat(y.toFixed(2)) + this._option.padding; } this._option.heightImg -= this._option.padding * 2; let xLine = new zrender.Line({ shape: { x1: 0, y1: this.ctx.canvasHeight / 2, x2: this.ctx.canvasWidth, y2: this.ctx.canvasHeight / 2 }, data: { type: 'LINE' }, style: this._imgConfig.line, zlevel: 2 }); let yLine = new zrender.Line({ shape: { x1: this.ctx.canvasWidth / 2, y1: 0, x2: this.ctx.canvasWidth / 2, y2: this.ctx.canvasHeight, }, data: { type: 'LINE' }, style: this._imgConfig.line, zlevel: 2 }); let circle = new zrender.Circle({ shape: { cx: this.ctx.canvasWidth / 2, cy: this._option.padding, r: this._imgConfig.circle.r, }, data: { type: 'CIRCLE' }, style: this._imgConfig.circle, cursor: 'crosshair', draggable: false, zlevel: 2 }); let refresh = new zrender.Image({ style: { image: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjAyOTM4OTgzNjkxIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjEwODUiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PGRlZnM+PHN0eWxlIHR5cGU9InRleHQvY3NzIj48L3N0eWxlPjwvZGVmcz48cGF0aCBkPSJNOTM0LjQgMjA2LjkzM2MtMTcuMDY3LTQuMjY2LTM0LjEzMyA2LjQtMzguNCAyMy40NjdsLTIzLjQ2NyA4Ny40NjdDNzk3Ljg2NyAxODMuNDY3IDY1NC45MzMgOTYgNDk3LjA2NyA5NmMtMjMyLjUzNCAwLTQyMi40IDE4NS42LTQyMi40IDQxNnMxODkuODY2IDQxNiA0MjIuNCA0MTZjMTc5LjIgMCAzMzkuMi0xMTAuOTMzIDM5OC45MzMtMjc1LjIgNi40LTE3LjA2Ny0yLjEzMy0zNC4xMzMtMTkuMi00MC41MzMtMTcuMDY3LTYuNC0zNC4xMzMgMi4xMzMtNDAuNTMzIDE5LjJDNzg1LjA2NyA3NzAuMTMzIDY0OC41MzMgODY0IDQ5Ny4wNjcgODY0Yy0xOTguNCAwLTM1OC40LTE1Ny44NjctMzU4LjQtMzUyczE2Mi4xMzMtMzUyIDM1OC40LTM1MmMxNDUuMDY2IDAgMjc3LjMzMyA4Ny40NjcgMzMwLjY2NiAyMTcuNmwtMTI4LTM2LjI2N2MtMTcuMDY2LTQuMjY2LTM0LjEzMyA2LjQtMzguNCAyMy40NjctNC4yNjYgMTcuMDY3IDYuNCAzNC4xMzMgMjMuNDY3IDM4LjRsMTg1LjYgNDkuMDY3YzIuMTMzIDAgNi40IDIuMTMzIDguNTMzIDIuMTMzIDYuNCAwIDEwLjY2Ny0yLjEzMyAxNy4wNjctNC4yNjcgNi40LTQuMjY2IDEyLjgtMTAuNjY2IDE0LjkzMy0xOS4yTDk2MCAyNDUuMzMzYzAtMTcuMDY2LTguNTMzLTM0LjEzMy0yNS42LTM4LjR6IiBmaWxsPSIjZmZmZmZmIiBwLWlkPSIxMDg2Ij48L3BhdGg+PC9zdmc+', x: this.ctx.canvasWidth / 2 - this._imgConfig.circle.r * 1.2 / 2, y: this._option.padding - this._imgConfig.circle.r * 1.2 / 2, width: this._imgConfig.circle.r * 1.2, height: this._imgConfig.circle.r * 1.2, ...this._imgConfig.circle, }, data: { type: 'ROTATEIMAGE' }, cursor: 'crosshair', draggable: false, zlevel: 3 }); // 不放到group,旋转的时候脱离group this.zr.add(xLine); this.zr.add(yLine); let rotateMouse = new zrender.Group(); rotateMouse.add(circle); rotateMouse.add(refresh); this._option.rotateMouse = rotateMouse; this.zr.add(rotateMouse); refresh.on('mousedown', (e) => { this._option.rotateListen = true; }); circle.on('mousedown', (e) => { this._option.rotateListen = true; }); } else { //original模式,1:1展示图片 this._option.setRate = 1; this._option.widthImg = img.width; this._option.heightImg = img.height; this._option.offsetX = 0; this._option.offsetY = 0; } // this._option.offsetY = 0 image = new zrender.Image({ style: { image: url, x: this._option.offsetX, y: this._option.offsetY, width: this._option.widthImg, height: this._option.heightImg }, cursor: 'default', data: { type: 'IMAGE', origin_width: img.width, origin_height: img.height, rate_width: this._option.widthImg, rate_height: this._option.heightImg }, zlevel: 1 }); this._option.center = [this._option.offsetX + (this._option.widthImg / 2), this._option.offsetY + this._option.heightImg / 2]; this.image = image; this._option.image = image; group.add(image); this.zr.add(group); this.image.on('drag', (e) => { this._imageDrag && this._imageDrag(e); }); this.image.on('dragend', (e) => { this._imageDragEnd && this._imageDragEnd(e); }); this.image.attr({ position: [10, 10] }); this._onComplete && this._onComplete(); this._bindEvent(); }; } _zrClick() {} _zrMouseMove(e) { // e.target确保在画布内 if (!e.event.target) { this._option.rotate = { radians: 0, degrees: 0 }; this._onRotate && this._onRotate(this.getRotate()); return; } if (this._option.rotateListen === true) { this.image.attr({ cursor: 'crosshair' }); let center = this.getOrigin(); let centerX = center[0]; let position = []; if (e.event.offsetX >= centerX) { position[0] = e.event.offsetX - centerX; } else { position[0] = -(centerX - e.event.offsetX); } position[1] = e.event.offsetY - this._option.padding; // 旋转监听 let a1 = position[0]; let b1 = position[1]; //求对角线长度 let a = calcHypotenuse(Math.abs(a1), Math.abs(b1)); let b = center[1] - this._option.padding; let c = calcHypotenuse(Math.abs(a1), Math.abs(b - b1)); // 避免鼠标移动到画布外 if (!window.isNaN(a) && !window.isNaN(b) && !window.isNaN(c)) { // cosA = (b²+c²-a²)/2bc let cosA_value = cosA(Math.abs(a), Math.abs(b), Math.abs(c)); let radians = Math.acos(cosA_value); let degree = Math.acos(cosA_value) * 180 / Math.PI; let outValue; if (a1 <= 0) { outValue = 360 - degree; radians = -Math.PI - (Math.PI - radians); } else { outValue = degree; radians = -radians; } this.group.attr({ rotation: radians, position: this._reSetPosition(), origin: this.getOrigin() }); this._option.rotateMouse.attr({ rotation: radians, position: this._reSetPosition(), origin: this.getOrigin() }); this._option.rotate = { radians: radians, degrees: outValue.toFixed(2) }; this._onRotate && this._onRotate(this.getRotate()); } } } _zrMouseDown(e) {} _zrMouseUp(e) { if (this._option.mode === 'auto-rotate') { this.image.attr({ cursor: 'default' }); this._option.rotateListen = false; } } _zrDBClick() {} _reSetCenter() { const box = this.image.getBoundingRect(); this._option.widthImg = box.width * this._option.scale; this._option.heightImg = box.height * this._option.scale; this._option.offsetX = this._option.offsetM; this._option.offsetY = this._option.offsetN; this._option.center = [this._option.offsetX + (this._option.widthImg / 2), this._option.offsetY + (this._option.heightImg / 2)]; return this._option.center; } getRotate() { //返回弧度制,角度制 return this._option.rotate; } resetRotate() { this.rotate(0); } getOrigin() { if (this._option.mode === 'auto' || this._option.mode === 'auto-rotate') { this._option.widthImg = this._option.widthImg * this.group.scale[0]; this._option.heightImg = this._option.heightImg * this.group.scale[0]; this._option.offsetX = (this.ctx.canvasWidth - this._option.widthImg) / 2; this._option.offsetY = (this.ctx.canvasHeight - this._option.heightImg) / 2; this._option.origin = [(this.ctx.canvasWidth / 2), (this.ctx.canvasHeight / 2)]; } else if (this._option.mode === 'original') { const box = this.image.getBoundingRect(); this._option.widthImg = box.width * this.group.scale[0]; this._option.heightImg = box.height * this.group.scale[0]; this._option.origin = [(this._option.widthImg / 2), (this._option.heightImg / 2)]; } return this._option.origin; } getCenter() { return this._reSetCenter(); } _getOffset() { const origin = this.getOrigin(); if (this._option.mode === 'auto' || this._option.mode === 'auto-rotate') { return [0, 0]; } else { let x = -origin[0] * this._option.scale + origin[0]; let y = -origin[1] * this._option.scale + origin[1]; return [-x, -y]; } } _reSetPosition() { const offset = this._getOffset(); if (this._option.mode === 'auto' || this._option.mode === 'auto-rotate') { return offset; } else { return [offset[0] + this._option.offsetM, offset[1] + this._option.offsetN]; } } rotate(degree) { //正值代表逆时针旋转,负值代表顺时针旋转 const oldScale = this.group.scale[0]; //等于0拖拽会发生飘移,所以设定0.003度,无限接近于0 const zero = 0.003 / 180 * Math.PI; if (degree === 0) { // this._option.rotateTime = 0; this.group.attr({ rotation: zero, position: [0, 0], origin: this.getOrigin() }); if (this._option.rotateMouse) { this._option.rotateMouse.attr({ rotation: zero, position: [0, 0], origin: this.getOrigin() }); } this._option.rotate = { radians: 0, degrees: 0 }; return; } let degreePi = degree / 180 * Math.PI; let result; let oldDegree = this._option.rotate.degrees; let oldDegreePi = this._option.rotate.radians; oldDegree -= degree; oldDegreePi -= degreePi; if (oldDegree === 0) { radian_out = 0; result = zero; } else { let dg = oldDegree; let ra = oldDegreePi; if (Math.abs(dg) >= 360 || Math.abs(degree_out - degree) >= 360) { count++; } if (degree > 0) { degree_out = remainder + dg + (count * 360); radian_out = remainder_h + ra + (count * (360 / 180 * Math.PI)); } else { degree_out = remainder + dg - (count * 360); radian_out = remainder_h + ra - (count * (360 / 180 * Math.PI)); } if (degree_out % degree != 0 && count === 1) { remainder = degree_out; remainder_h = radian_out; } result = radian_out; if (radian_out === 0) { result = zero; } count = 0; } this.group.attr({ rotation: result, position: [0, 0], origin: this.getOrigin() }); this._option.rotate = { radians: this.group.rotation === zero ? 0 : this.group.rotation, degrees: (this.group.rotation === zero ? 0 : this.group.rotation) / Math.PI * 180 }; this._onRotate && this._onRotate(this.getRotate()); } _limitAttributes(newAttrs) { const box = this.image.getBoundingRect(); const minX = -box.width + this.ctx.canvasWidth / 2; const maxX = this.ctx.canvasWidth / 2; const x = Math.max(minX, Math.min(newAttrs.x, maxX)); const minY = -box.height + this.ctx.canvasHeight / 2; const maxY = this.ctx.canvasHeight / 2; const y = Math.max(minY, Math.min(newAttrs.y, maxY)); const scale = Math.max(0.05, newAttrs.scale); return { x, y, scale }; } zoomIn(times = 1.109) { // zoomOut取0.8,zoomIn取1.25,执行出错,先放大再缩小,拖动图片会触发无限放大或缩小 this._option.scale += 0.01 this.zoomStage(this._option.scale); } zoomOut(times = 0.9) { this._option.scale -= 0.01 this.zoomStage(this._option.scale); } zoomStage(scaleBy) { // const oldScale = this.group.scale[0]; // const pos = { // x: this.ctx.canvasWidth / 2, // y: this.ctx.canvasHeight / 2 // }; // const mousePointTo = { // x: pos.x / oldScale - this.group.position[0] / oldScale, // y: pos.y / oldScale - this.group.position[1] / oldScale // }; // const newScale = Math.max(0.05, oldScale * scaleBy); // const newPos = { // x: -(mousePointTo.x - pos.x / newScale) * newScale, // y: -(mousePointTo.y - pos.y / newScale) * newScale // }; // const newAttrs = this._limitAttributes({ ...newPos, scale: newScale }); if (scaleBy === 1) { // 先放大再还原到100%比例,拖动图片会触发无限放大或缩小 this.group.attr({ scale: [1.001, 1.001] }); } else { this.group.attr({ // position: [0, 0], scale: [scaleBy, scaleBy], // origin: this.getOrigin(), }); } // let d = this.group.getLocalTransform(); // this._option.offsetM = d[4]; // this._option.offsetN = d[5]; // this._option.scale = newAttrs.scale; return this; } zoomSlider(scale) { // this.setDrawingMode('hander'); if (scale === 1) { // 先放大再还原到100%比例,拖动图片会触发无限放大或缩小 this.group.attr({ scale: [1.001, 1.001], origin: this.getOrigin() }); } else { this.group.attr({ position: [10, 10], scale: [scale, scale], origin: this.getOrigin() }); } let d = this.group.getLocalTransform(); this._option.offsetM = d[4]; this._option.offsetN = d[5]; this._option.scale = scale; return this; } getOffCanvas() { // 获取离屏canvas return this.zr.painter.getRenderedCanvas({ backgroundColor: '#fff' }); } getType() { return this.zr.painter.getType(); } _convertImageToCanvas(img) { let canvas = document.createElement('canvas'); canvas.width = 1000; canvas.height = 1000; let ctx = canvas.getContext('2d'); let rectCenterPoint = { x: this._option.widthImg / 2, y: this._option.heightImg / 2 }; let degree = this._option.rotate.degrees; // canvas修改后的宽高 canvas.width = 6000; canvas.height = 6000; ctx.rect(0, 0, 6000, 6000); ctx.fillStyle = 'rgba(255, 255, 255, 1)'; // ctx.globalAlpha = 0; ctx.fill(); ctx.strokeStyle = 'green'; ctx.strokeRect(0, 0, 6000, 6000); // 以中心点旋转角度 ctx.translate(rectCenterPoint.x, rectCenterPoint.y); ctx.rotate(-this._option.rotate.radians); ctx.translate(-rectCenterPoint.x, -rectCenterPoint.y); if (degree >= 0 && degree < 90) { ctx.drawImage(img, 0, -this._option.heightImg); } else if (degree >= 90 && degree < 180) { ctx.drawImage(img, -this._option.widthImg, -this._option.heightImg); } else if (degree >= 180 && degree < 270) { ctx.drawImage(img, -this._option.widthImg, 0); } else if (degree >= 270 && degree < 360) { ctx.drawImage(img, -this._option.widthImg, 0); } // ctx.drawImage(img, 0, 0, this._option.widthImg, this._option.heightImg); return canvas; } exportOut() { let img = new Image(); img.setAttribute('crossorigin', 'anonymous'); img.src = this._option.imgUrl; img.width = this._option.widthImg; img.height = this._option.heightImg; img.style.background = '#fff'; img.onload = () => { let canvas = this._convertImageToCanvas(img); let imgUrl = canvas.toDataURL('image/jpeg'); this.exportImages(imgUrl); }; } base64ToBlob(code) { let parts = code.split(';base64,'); let contentType = parts[0].split(':')[1]; let raw = window.atob(parts[1]); let rawLength = raw.length; let uInt8Array = new Uint8Array(rawLength); for (let i = 0; i < rawLength; ++i) { uInt8Array[i] = raw.charCodeAt(i); } return new Blob([uInt8Array], { type: contentType }); } exportImages(datas) { let aLink = document.createElement('a'); let blob = this.base64ToBlob(datas); let evt = document.createEvent('HTMLEvents'); evt.initEvent('click', true, true); aLink.download = 'test' + '.jpg'; aLink.href = URL.createObjectURL(blob); aLink.dispatchEvent( new MouseEvent('click', { bubbles: true, cancelable: true, view: window }) ); } exportSimple() { this.zr.painter.getRenderedCanvas({ backgroundColor: '#fff' }).toBlob((blob) => { let url = window.URL.createObjectURL(blob); window.open(url); }, 'image/png'); } export () { //离屏渲染导出 //https://github.com/ecomfe/zrender/issues/363 let { x, y, width, height } = this.group.getBoundingRect(); let zr = zrender.init(document.createElement('div'), { width, height }); let group = new zrender.Group(); group.position = [0 - x, 0 - y]; this.group.eachChild((child) => { // 此处会堆栈溢出, 目前解决办法是用原始数据重新创建新的shape, 再加入到新group中 // var _child = zrender.util.clone(child); group.add(child); }); zr.add(group); zr.refreshImmediately(); return zr.painter.getRenderedCanvas({ backgroundColor: '#fff' }).toBlob((blob) => { let url = window.URL.createObjectURL(blob); window.open(url); }, 'image/png'); } }