From 2a134ba1f84b19a94fd2980ef1d2908650e0c8dd Mon Sep 17 00:00:00 2001 From: wangyaqi Date: Sat, 2 Nov 2019 15:54:55 +0800 Subject: [PATCH] feat(v3): add canvas --- src/core/service/api/context/canvas.js | 876 +++++++++++++++++ src/core/service/platform.js | 6 + .../h5/service/api/context/canvas.js | 877 +----------------- 3 files changed, 884 insertions(+), 875 deletions(-) create mode 100644 src/core/service/api/context/canvas.js diff --git a/src/core/service/api/context/canvas.js b/src/core/service/api/context/canvas.js new file mode 100644 index 000000000..e9813276c --- /dev/null +++ b/src/core/service/api/context/canvas.js @@ -0,0 +1,876 @@ +import createCallbacks from 'uni-helpers/callbacks' + +import { + getCurrentPageId +} from '../../platform' + +const canvasEventCallbacks = createCallbacks('canvasEvent') + +UniServiceJSBridge.subscribe('onDrawCanvas', ({ + callbackId, + data +}) => { + const callback = canvasEventCallbacks.pop(callbackId) + if (callback) { + callback(data) + } +}) + +UniServiceJSBridge.subscribe('onCanvasMethodCallback', ({ + callbackId, + data +}) => { + const callback = canvasEventCallbacks.pop(callbackId) + if (callback) { + callback(data) + } +}) + +function operateCanvas (canvasId, pageId, type, data) { + UniServiceJSBridge.publishHandler(pageId + '-canvas-' + canvasId, { + canvasId, + type, + data + }, pageId) +} + +const predefinedColor = { + aliceblue: '#f0f8ff', + antiquewhite: '#faebd7', + aqua: '#00ffff', + aquamarine: '#7fffd4', + azure: '#f0ffff', + beige: '#f5f5dc', + bisque: '#ffe4c4', + black: '#000000', + blanchedalmond: '#ffebcd', + blue: '#0000ff', + blueviolet: '#8a2be2', + brown: '#a52a2a', + burlywood: '#deb887', + cadetblue: '#5f9ea0', + chartreuse: '#7fff00', + chocolate: '#d2691e', + coral: '#ff7f50', + cornflowerblue: '#6495ed', + cornsilk: '#fff8dc', + crimson: '#dc143c', + cyan: '#00ffff', + darkblue: '#00008b', + darkcyan: '#008b8b', + darkgoldenrod: '#b8860b', + darkgray: '#a9a9a9', + darkgrey: '#a9a9a9', + darkgreen: '#006400', + darkkhaki: '#bdb76b', + darkmagenta: '#8b008b', + darkolivegreen: '#556b2f', + darkorange: '#ff8c00', + darkorchid: '#9932cc', + darkred: '#8b0000', + darksalmon: '#e9967a', + darkseagreen: '#8fbc8f', + darkslateblue: '#483d8b', + darkslategray: '#2f4f4f', + darkslategrey: '#2f4f4f', + darkturquoise: '#00ced1', + darkviolet: '#9400d3', + deeppink: '#ff1493', + deepskyblue: '#00bfff', + dimgray: '#696969', + dimgrey: '#696969', + dodgerblue: '#1e90ff', + firebrick: '#b22222', + floralwhite: '#fffaf0', + forestgreen: '#228b22', + fuchsia: '#ff00ff', + gainsboro: '#dcdcdc', + ghostwhite: '#f8f8ff', + gold: '#ffd700', + goldenrod: '#daa520', + gray: '#808080', + grey: '#808080', + green: '#008000', + greenyellow: '#adff2f', + honeydew: '#f0fff0', + hotpink: '#ff69b4', + indianred: '#cd5c5c', + indigo: '#4b0082', + ivory: '#fffff0', + khaki: '#f0e68c', + lavender: '#e6e6fa', + lavenderblush: '#fff0f5', + lawngreen: '#7cfc00', + lemonchiffon: '#fffacd', + lightblue: '#add8e6', + lightcoral: '#f08080', + lightcyan: '#e0ffff', + lightgoldenrodyellow: '#fafad2', + lightgray: '#d3d3d3', + lightgrey: '#d3d3d3', + lightgreen: '#90ee90', + lightpink: '#ffb6c1', + lightsalmon: '#ffa07a', + lightseagreen: '#20b2aa', + lightskyblue: '#87cefa', + lightslategray: '#778899', + lightslategrey: '#778899', + lightsteelblue: '#b0c4de', + lightyellow: '#ffffe0', + lime: '#00ff00', + limegreen: '#32cd32', + linen: '#faf0e6', + magenta: '#ff00ff', + maroon: '#800000', + mediumaquamarine: '#66cdaa', + mediumblue: '#0000cd', + mediumorchid: '#ba55d3', + mediumpurple: '#9370db', + mediumseagreen: '#3cb371', + mediumslateblue: '#7b68ee', + mediumspringgreen: '#00fa9a', + mediumturquoise: '#48d1cc', + mediumvioletred: '#c71585', + midnightblue: '#191970', + mintcream: '#f5fffa', + mistyrose: '#ffe4e1', + moccasin: '#ffe4b5', + navajowhite: '#ffdead', + navy: '#000080', + oldlace: '#fdf5e6', + olive: '#808000', + olivedrab: '#6b8e23', + orange: '#ffa500', + orangered: '#ff4500', + orchid: '#da70d6', + palegoldenrod: '#eee8aa', + palegreen: '#98fb98', + paleturquoise: '#afeeee', + palevioletred: '#db7093', + papayawhip: '#ffefd5', + peachpuff: '#ffdab9', + peru: '#cd853f', + pink: '#ffc0cb', + plum: '#dda0dd', + powderblue: '#b0e0e6', + purple: '#800080', + rebeccapurple: '#663399', + red: '#ff0000', + rosybrown: '#bc8f8f', + royalblue: '#4169e1', + saddlebrown: '#8b4513', + salmon: '#fa8072', + sandybrown: '#f4a460', + seagreen: '#2e8b57', + seashell: '#fff5ee', + sienna: '#a0522d', + silver: '#c0c0c0', + skyblue: '#87ceeb', + slateblue: '#6a5acd', + slategray: '#708090', + slategrey: '#708090', + snow: '#fffafa', + springgreen: '#00ff7f', + steelblue: '#4682b4', + tan: '#d2b48c', + teal: '#008080', + thistle: '#d8bfd8', + tomato: '#ff6347', + turquoise: '#40e0d0', + violet: '#ee82ee', + wheat: '#f5deb3', + white: '#ffffff', + whitesmoke: '#f5f5f5', + yellow: '#ffff00', + yellowgreen: '#9acd32', + transparent: '#00000000' +} + +function checkColor (e) { + var t = null + if ((t = /^#([0-9|A-F|a-f]{6})$/.exec(e)) != null) { + let n = parseInt(t[1].slice(0, 2), 16) + let o = parseInt(t[1].slice(2, 4), 16) + let r = parseInt(t[1].slice(4), 16) + return [n, o, r, 255] + } + if ((t = /^#([0-9|A-F|a-f]{3})$/.exec(e)) != null) { + let n = t[1].slice(0, 1) + let o = t[1].slice(1, 2) + let r = t[1].slice(2, 3) + n = parseInt(n + n, 16) + o = parseInt(o + o, 16) + r = parseInt(r + r, 16) + return [n, o, r, 255] + } + if ((t = /^rgb\((.+)\)$/.exec(e)) != null) { + return t[1].split(',').map(function (e) { + return Math.min(255, parseInt(e.trim())) + }).concat(255) + } + if ((t = /^rgba\((.+)\)$/.exec(e)) != null) { + return t[1].split(',').map(function (e, t) { + return t === 3 ? Math.floor(255 * parseFloat(e.trim())) : Math.min(255, parseInt(e.trim())) + }) + } + var i = e.toLowerCase() + if (predefinedColor.hasOwnProperty(i)) { + t = /^#([0-9|A-F|a-f]{6,8})$/.exec(predefinedColor[i]) + let n = parseInt(t[1].slice(0, 2), 16) + let o = parseInt(t[1].slice(2, 4), 16) + let r = parseInt(t[1].slice(4, 6), 16) + let a = parseInt(t[1].slice(6, 8), 16) + a = a >= 0 ? a : 255 + return [n, o, r, a] + } + console.group('非法颜色: ' + e) + console.error('不支持颜色:' + e) + console.groupEnd() + return [0, 0, 0, 255] +} + +function TextMetrics (width) { + this.width = width +} + +function Pattern (image, repetition) { + this.image = image + this.repetition = repetition +} + +class CanvasGradient { + constructor (type, data) { + this.type = type + this.data = data + this.colorStop = [] + } + addColorStop (position, color) { + this.colorStop.push([position, checkColor(color)]) + } +} +var methods1 = ['scale', 'rotate', 'translate', 'setTransform', 'transform'] +var methods2 = ['drawImage', 'fillText', 'fill', 'stroke', 'fillRect', 'strokeRect', 'clearRect', + 'strokeText' +] +var methods3 = ['setFillStyle', 'setTextAlign', 'setStrokeStyle', 'setGlobalAlpha', 'setShadow', + 'setFontSize', 'setLineCap', 'setLineJoin', 'setLineWidth', 'setMiterLimit', + 'setTextBaseline', 'setLineDash' +] + +var tempCanvas +function getTempCanvas () { + if (!tempCanvas) { + tempCanvas = document.createElement('canvas') + } + return tempCanvas +} + +class CanvasContext { + constructor (id, pageId) { + this.id = id + this.pageId = pageId + this.actions = [] + this.path = [] + this.subpath = [] + this.currentTransform = [] + this.currentStepAnimates = [] + this.drawingState = [] + this.state = { + lineDash: [0, 0], + shadowOffsetX: 0, + shadowOffsetY: 0, + shadowBlur: 0, + shadowColor: [0, 0, 0, 0], + font: '10px sans-serif', + fontSize: 10, + fontWeight: 'normal', + fontStyle: 'normal', + fontFamily: 'sans-serif' + } + } + draw (reserve = false, callback) { + var actions = [...this.actions] + this.actions = [] + this.path = [] + var callbackId + + if (typeof callback === 'function') { + callbackId = canvasEventCallbacks.push(callback) + } + + operateCanvas(this.id, this.pageId, 'actionsChanged', { + actions, + reserve, + callbackId + }) + } + createLinearGradient (x0, y0, x1, y1) { + return new CanvasGradient('linear', [x0, y0, x1, y1]) + } + createCircularGradient (x, y, r) { + return new CanvasGradient('radial', [x, y, r]) + } + createPattern (image, repetition) { + if (undefined === repetition) { + console.error("Failed to execute 'createPattern' on 'CanvasContext': 2 arguments required, but only 1 present.") + } else if (['repeat', 'repeat-x', 'repeat-y', 'no-repeat'].indexOf(repetition) < 0) { + console.error("Failed to execute 'createPattern' on 'CanvasContext': The provided type ('" + repetition + "') is not one of 'repeat', 'no-repeat', 'repeat-x', or 'repeat-y'.") + } else { + return new Pattern(image, repetition) + } + } + measureText (text) { + var c2d = getTempCanvas().getContext('2d') + c2d.font = this.state.font + return new TextMetrics(c2d.measureText(text).width || 0) + } + save () { + this.actions.push({ + method: 'save', + data: [] + }) + this.drawingState.push(this.state) + } + restore () { + this.actions.push({ + method: 'restore', + data: [] + }) + this.state = this.drawingState.pop() || { + lineDash: [0, 0], + shadowOffsetX: 0, + shadowOffsetY: 0, + shadowBlur: 0, + shadowColor: [0, 0, 0, 0], + font: '10px sans-serif', + fontSize: 10, + fontWeight: 'normal', + fontStyle: 'normal', + fontFamily: 'sans-serif' + } + } + beginPath () { + this.path = [] + this.subpath = [] + } + moveTo (x, y) { + this.path.push({ + method: 'moveTo', + data: [x, y] + }) + this.subpath = [ + [x, y] + ] + } + lineTo (x, y) { + if (this.path.length === 0 && this.subpath.length === 0) { + this.path.push({ + method: 'moveTo', + data: [x, y] + }) + } else { + this.path.push({ + method: 'lineTo', + data: [x, y] + }) + } + this.subpath.push([x, y]) + } + quadraticCurveTo (cpx, cpy, x, y) { + this.path.push({ + method: 'quadraticCurveTo', + data: [cpx, cpy, x, y] + }) + this.subpath.push([x, y]) + } + bezierCurveTo (cp1x, cp1y, cp2x, cp2y, x, y) { + this.path.push({ + method: 'bezierCurveTo', + data: [cp1x, cp1y, cp2x, cp2y, x, y] + }) + this.subpath.push([x, y]) + } + arc (x, y, r, sAngle, eAngle, counterclockwise = false) { + this.path.push({ + method: 'arc', + data: [x, y, r, sAngle, eAngle, counterclockwise] + }) + this.subpath.push([x, y]) + } + rect (x, y, width, height) { + this.path.push({ + method: 'rect', + data: [x, y, width, height] + }) + this.subpath = [ + [x, y] + ] + } + arcTo (x1, y1, x2, y2, radius) { + this.path.push({ + method: 'arcTo', + data: [x1, y1, x2, y2, radius] + }) + this.subpath.push([x2, y2]) + } + clip () { + this.actions.push({ + method: 'clip', + data: [...this.path] + }) + } + closePath () { + this.path.push({ + method: 'closePath', + data: [] + }) + if (this.subpath.length) { + this.subpath = [this.subpath.shift()] + } + } + clearActions () { + this.actions = [] + this.path = [] + this.subpath = [] + } + getActions () { + var actions = [...this.actions] + this.clearActions() + return actions + } + set lineDashOffset (value) { + this.actions.push({ + method: 'setLineDashOffset', + data: [value] + }) + } + set globalCompositeOperation (type) { + this.actions.push({ + method: 'setGlobalCompositeOperation', + data: [type] + }) + } + set shadowBlur (level) { + this.actions.push({ + method: 'setShadowBlur', + data: [level] + }) + } + set shadowColor (color) { + this.actions.push({ + method: 'setShadowColor', + data: [color] + }) + } + set shadowOffsetX (x) { + this.actions.push({ + method: 'setShadowOffsetX', + data: [x] + }) + } + set shadowOffsetY (y) { + this.actions.push({ + method: 'setShadowOffsetY', + data: [y] + }) + } + set font (value) { + var self = this + this.state.font = value + // eslint-disable-next-line + var fontFormat = value.match(/^(([\w\-]+\s)*)(\d+r?px)(\/(\d+\.?\d*(r?px)?))?\s+(.*)/) + if (fontFormat) { + var style = fontFormat[1].trim().split(/\s/) + var fontSize = parseFloat(fontFormat[3]) + var fontFamily = fontFormat[7] + var actions = [] + style.forEach(function (value, index) { + if (['italic', 'oblique', 'normal'].indexOf(value) > -1) { + actions.push({ + method: 'setFontStyle', + data: [value] + }) + self.state.fontStyle = value + } else if (['bold', 'normal'].indexOf(value) > -1) { + actions.push({ + method: 'setFontWeight', + data: [value] + }) + self.state.fontWeight = value + } else if (index === 0) { + actions.push({ + method: 'setFontStyle', + data: ['normal'] + }) + self.state.fontStyle = 'normal' + } else if (index === 1) { + pushAction() + } + }) + if (style.length === 1) { + pushAction() + } + style = actions.map(function (action) { + return action.data[0] + }).join(' ') + this.state.fontSize = fontSize + this.state.fontFamily = fontFamily + this.actions.push({ + method: 'setFont', + data: [`${style} ${fontSize}px ${fontFamily}`] + }) + } else { + console.warn("Failed to set 'font' on 'CanvasContext': invalid format.") + } + function pushAction () { + actions.push({ + method: 'setFontWeight', + data: ['normal'] + }) + self.state.fontWeight = 'normal' + } + } + get font () { + return this.state.font + } + set fillStyle (color) { + this.setFillStyle(color) + } + set strokeStyle (color) { + this.setStrokeStyle(color) + } + set globalAlpha (value) { + value = Math.floor(255 * parseFloat(value)) + this.actions.push({ + method: 'setGlobalAlpha', + data: [value] + }) + } + set textAlign (align) { + this.actions.push({ + method: 'setTextAlign', + data: [align] + }) + } + set lineCap (type) { + this.actions.push({ + method: 'setLineCap', + data: [type] + }) + } + set lineJoin (type) { + this.actions.push({ + method: 'setLineJoin', + data: [type] + }) + } + set lineWidth (value) { + this.actions.push({ + method: 'setLineWidth', + data: [value] + }) + } + set miterLimit (value) { + this.actions.push({ + method: 'setMiterLimit', + data: [value] + }) + } + set textBaseline (type) { + this.actions.push({ + method: 'setTextBaseline', + data: [type] + }) + } +} + +[...methods1, ...methods2].forEach(function (method) { + function get (method) { + switch (method) { + case 'fill': + case 'stroke': + return function () { + this.actions.push({ + method: method + 'Path', + data: [...this.path] + }) + } + case 'fillRect': + return function (x, y, width, height) { + this.actions.push({ + method: 'fillPath', + data: [{ + method: 'rect', + data: [x, y, width, height] + }] + }) + } + case 'strokeRect': + return function (x, y, width, height) { + this.actions.push({ + method: 'strokePath', + data: [{ + method: 'rect', + data: [x, y, width, height] + }] + }) + } + case 'fillText': + case 'strokeText': + return function (text, x, y, maxWidth) { + var data = [text.toString(), x, y] + if (typeof maxWidth === 'number') { + data.push(maxWidth) + } + this.actions.push({ + method, + data + }) + } + case 'drawImage': + return function (imageResource, dx, dy, dWidth, dHeight, sx, sy, sWidth, sHeight) { + if (sHeight === undefined) { + sx = dx + sy = dy + sWidth = dWidth + sHeight = dHeight + dx = undefined + dy = undefined + dWidth = undefined + dHeight = undefined + } + var data + function isNumber (e) { + return typeof e === 'number' + } + data = isNumber(dx) && isNumber(dy) && isNumber(dWidth) && isNumber(dHeight) ? [imageResource, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight] : isNumber(sWidth) && isNumber( + sHeight) ? [imageResource, sx, sy, sWidth, sHeight] : [imageResource, sx, sy] + this.actions.push({ + method, + data + }) + } + default: + return function (...data) { + this.actions.push({ + method, + data + }) + } + } + } + CanvasContext.prototype[method] = get(method) +}) +methods3.forEach(function (method) { + function get (method) { + switch (method) { + case 'setFillStyle': + case 'setStrokeStyle': + return function (color) { + if (typeof color !== 'object') { + this.actions.push({ + method, + data: ['normal', checkColor(color)] + }) + } else { + this.actions.push({ + method, + data: [color.type, color.data, color.colorStop] + }) + } + } + case 'setGlobalAlpha': + return function (alpha) { + alpha = Math.floor(255 * parseFloat(alpha)) + this.actions.push({ + method, + data: [alpha] + }) + } + case 'setShadow': + return function (offsetX, offsetY, blur, color) { + color = checkColor(color) + this.actions.push({ + method, + data: [offsetX, offsetY, blur, color] + }) + this.state.shadowBlur = blur + this.state.shadowColor = color + this.state.shadowOffsetX = offsetX + this.state.shadowOffsetY = offsetY + } + case 'setLineDash': + return function (pattern, offset) { + pattern = pattern || [0, 0] + offset = offset || 0 + this.actions.push({ + method, + data: [pattern, offset] + }) + this.state.lineDash = pattern + } + case 'setFontSize': + return function (fontSize) { + this.state.font = this.state.font.replace(/\d+\.?\d*px/, fontSize + 'px') + this.state.fontSize = fontSize + this.actions.push({ + method, + data: [fontSize] + }) + } + default: + return function (...data) { + this.actions.push({ + method, + data + }) + } + } + } + CanvasContext.prototype[method] = get(method) +}) + +export function createCanvasContext (id, context) { + if (context) { + return new CanvasContext(id, context.$page.id) + } + const pageId = getCurrentPageId() + if (pageId) { + return new CanvasContext(id, pageId) + } else { + UniServiceJSBridge.emit('onError', 'createCanvasContext:fail') + } +} + +const { + invokeCallbackHandler: invoke +} = UniServiceJSBridge + +export function canvasGetImageData ({ + canvasId, + x, + y, + width, + height +}, callbackId) { + var pageId = getCurrentPageId() + if (!pageId) { + invoke(callbackId, { + errMsg: 'canvasGetImageData:fail' + }) + return + } + var cId = canvasEventCallbacks.push(function (data) { + var imgData = data.data + if (imgData && imgData.length) { + data.data = new Uint8ClampedArray(imgData) + } + invoke(callbackId, data) + }) + operateCanvas(canvasId, pageId, 'getImageData', { + x, + y, + width, + height, + callbackId: cId + }) +} + +export function canvasPutImageData ({ + canvasId, + data, + x, + y, + width, + height +}, callbackId) { + var pageId = getCurrentPageId() + if (!pageId) { + invoke(callbackId, { + errMsg: 'canvasPutImageData:fail' + }) + return + } + var cId = canvasEventCallbacks.push(function (data) { + invoke(callbackId, data) + }) + operateCanvas(canvasId, pageId, 'putImageData', { + data: [...data], + x, + y, + width, + height, + callbackId: cId + }) +} + +export function canvasToTempFilePath ({ + x = 0, + y = 0, + width, + height, + destWidth, + destHeight, + canvasId, + fileType, + qualit +}, callbackId) { + var pageId = getCurrentPageId() + if (!pageId) { + invoke(callbackId, { + errMsg: 'canvasToTempFilePath:fail' + }) + return + } + const cId = canvasEventCallbacks.push(function ({ + data, + width, + height + }) { + if (!data || !data.length) { + invoke(callbackId, { + errMsg: 'canvasToTempFilePath:fail' + }) + return + } + let imgData + try { + imgData = new ImageData(new Uint8ClampedArray(data), width, height) + } catch (error) { + invoke(callbackId, { + errMsg: 'canvasToTempFilePath:fail' + }) + return + } + const canvas = getTempCanvas() + canvas.width = width + canvas.height = height + const c2d = canvas.getContext('2d') + c2d.putImageData(imgData, 0, 0) + let base64 = canvas.toDataURL('image/png') + const img = new Image() + img.onload = function () { + if (fileType === 'jpeg') { + c2d.fillStyle = '#fff' + c2d.fillRect(0, 0, width, width) + } + c2d.drawImage(img, 0, 0) + base64 = canvas.toDataURL(`image/${fileType}`, qualit) + invoke(callbackId, { + errMsg: 'canvasToTempFilePath:ok', + tempFilePath: base64 + }) + } + img.src = base64 + }) + operateCanvas(canvasId, pageId, 'getImageData', { + x, + y, + width, + height, + destWidth, + destHeight, + hidpi: false, + callbackId: cId + }) +} diff --git a/src/core/service/platform.js b/src/core/service/platform.js index 556a59266..5e7819dd4 100644 --- a/src/core/service/platform.js +++ b/src/core/service/platform.js @@ -24,3 +24,9 @@ export function getCurrentPageVm (method) { const page = pages[len - 1] return page.$vm } + +export function getCurrentPageId () { + const pages = getCurrentPages() + const page = pages[pages.length - 1] + return page && page.$page.id +} diff --git a/src/platforms/h5/service/api/context/canvas.js b/src/platforms/h5/service/api/context/canvas.js index 03265b51f..290ddcf6f 100644 --- a/src/platforms/h5/service/api/context/canvas.js +++ b/src/platforms/h5/service/api/context/canvas.js @@ -1,881 +1,8 @@ -import createCallbacks from 'uni-helpers/callbacks' - -const canvasEventCallbacks = createCallbacks('canvasEvent') - -UniServiceJSBridge.subscribe('onDrawCanvas', ({ - callbackId, - data -}) => { - const callback = canvasEventCallbacks.pop(callbackId) - if (callback) { - callback(data) - } -}) - -UniServiceJSBridge.subscribe('onCanvasMethodCallback', ({ - callbackId, - data -}) => { - const callback = canvasEventCallbacks.pop(callbackId) - if (callback) { - callback(data) - } -}) - -function operateCanvas (canvasId, pageId, type, data) { +export function operateCanvasContext (canvasId, pageVm, type, data) { + const pageId = pageVm.$page.id UniServiceJSBridge.publishHandler(pageId + '-canvas-' + canvasId, { canvasId, type, data }, pageId) } - -const predefinedColor = { - aliceblue: '#f0f8ff', - antiquewhite: '#faebd7', - aqua: '#00ffff', - aquamarine: '#7fffd4', - azure: '#f0ffff', - beige: '#f5f5dc', - bisque: '#ffe4c4', - black: '#000000', - blanchedalmond: '#ffebcd', - blue: '#0000ff', - blueviolet: '#8a2be2', - brown: '#a52a2a', - burlywood: '#deb887', - cadetblue: '#5f9ea0', - chartreuse: '#7fff00', - chocolate: '#d2691e', - coral: '#ff7f50', - cornflowerblue: '#6495ed', - cornsilk: '#fff8dc', - crimson: '#dc143c', - cyan: '#00ffff', - darkblue: '#00008b', - darkcyan: '#008b8b', - darkgoldenrod: '#b8860b', - darkgray: '#a9a9a9', - darkgrey: '#a9a9a9', - darkgreen: '#006400', - darkkhaki: '#bdb76b', - darkmagenta: '#8b008b', - darkolivegreen: '#556b2f', - darkorange: '#ff8c00', - darkorchid: '#9932cc', - darkred: '#8b0000', - darksalmon: '#e9967a', - darkseagreen: '#8fbc8f', - darkslateblue: '#483d8b', - darkslategray: '#2f4f4f', - darkslategrey: '#2f4f4f', - darkturquoise: '#00ced1', - darkviolet: '#9400d3', - deeppink: '#ff1493', - deepskyblue: '#00bfff', - dimgray: '#696969', - dimgrey: '#696969', - dodgerblue: '#1e90ff', - firebrick: '#b22222', - floralwhite: '#fffaf0', - forestgreen: '#228b22', - fuchsia: '#ff00ff', - gainsboro: '#dcdcdc', - ghostwhite: '#f8f8ff', - gold: '#ffd700', - goldenrod: '#daa520', - gray: '#808080', - grey: '#808080', - green: '#008000', - greenyellow: '#adff2f', - honeydew: '#f0fff0', - hotpink: '#ff69b4', - indianred: '#cd5c5c', - indigo: '#4b0082', - ivory: '#fffff0', - khaki: '#f0e68c', - lavender: '#e6e6fa', - lavenderblush: '#fff0f5', - lawngreen: '#7cfc00', - lemonchiffon: '#fffacd', - lightblue: '#add8e6', - lightcoral: '#f08080', - lightcyan: '#e0ffff', - lightgoldenrodyellow: '#fafad2', - lightgray: '#d3d3d3', - lightgrey: '#d3d3d3', - lightgreen: '#90ee90', - lightpink: '#ffb6c1', - lightsalmon: '#ffa07a', - lightseagreen: '#20b2aa', - lightskyblue: '#87cefa', - lightslategray: '#778899', - lightslategrey: '#778899', - lightsteelblue: '#b0c4de', - lightyellow: '#ffffe0', - lime: '#00ff00', - limegreen: '#32cd32', - linen: '#faf0e6', - magenta: '#ff00ff', - maroon: '#800000', - mediumaquamarine: '#66cdaa', - mediumblue: '#0000cd', - mediumorchid: '#ba55d3', - mediumpurple: '#9370db', - mediumseagreen: '#3cb371', - mediumslateblue: '#7b68ee', - mediumspringgreen: '#00fa9a', - mediumturquoise: '#48d1cc', - mediumvioletred: '#c71585', - midnightblue: '#191970', - mintcream: '#f5fffa', - mistyrose: '#ffe4e1', - moccasin: '#ffe4b5', - navajowhite: '#ffdead', - navy: '#000080', - oldlace: '#fdf5e6', - olive: '#808000', - olivedrab: '#6b8e23', - orange: '#ffa500', - orangered: '#ff4500', - orchid: '#da70d6', - palegoldenrod: '#eee8aa', - palegreen: '#98fb98', - paleturquoise: '#afeeee', - palevioletred: '#db7093', - papayawhip: '#ffefd5', - peachpuff: '#ffdab9', - peru: '#cd853f', - pink: '#ffc0cb', - plum: '#dda0dd', - powderblue: '#b0e0e6', - purple: '#800080', - rebeccapurple: '#663399', - red: '#ff0000', - rosybrown: '#bc8f8f', - royalblue: '#4169e1', - saddlebrown: '#8b4513', - salmon: '#fa8072', - sandybrown: '#f4a460', - seagreen: '#2e8b57', - seashell: '#fff5ee', - sienna: '#a0522d', - silver: '#c0c0c0', - skyblue: '#87ceeb', - slateblue: '#6a5acd', - slategray: '#708090', - slategrey: '#708090', - snow: '#fffafa', - springgreen: '#00ff7f', - steelblue: '#4682b4', - tan: '#d2b48c', - teal: '#008080', - thistle: '#d8bfd8', - tomato: '#ff6347', - turquoise: '#40e0d0', - violet: '#ee82ee', - wheat: '#f5deb3', - white: '#ffffff', - whitesmoke: '#f5f5f5', - yellow: '#ffff00', - yellowgreen: '#9acd32', - transparent: '#00000000' -} - -function checkColor (e) { - var t = null - if ((t = /^#([0-9|A-F|a-f]{6})$/.exec(e)) != null) { - let n = parseInt(t[1].slice(0, 2), 16) - let o = parseInt(t[1].slice(2, 4), 16) - let r = parseInt(t[1].slice(4), 16) - return [n, o, r, 255] - } - if ((t = /^#([0-9|A-F|a-f]{3})$/.exec(e)) != null) { - let n = t[1].slice(0, 1) - let o = t[1].slice(1, 2) - let r = t[1].slice(2, 3) - n = parseInt(n + n, 16) - o = parseInt(o + o, 16) - r = parseInt(r + r, 16) - return [n, o, r, 255] - } - if ((t = /^rgb\((.+)\)$/.exec(e)) != null) { - return t[1].split(',').map(function (e) { - return Math.min(255, parseInt(e.trim())) - }).concat(255) - } - if ((t = /^rgba\((.+)\)$/.exec(e)) != null) { - return t[1].split(',').map(function (e, t) { - return t === 3 ? Math.floor(255 * parseFloat(e.trim())) : Math.min(255, parseInt(e.trim())) - }) - } - var i = e.toLowerCase() - if (predefinedColor.hasOwnProperty(i)) { - t = /^#([0-9|A-F|a-f]{6,8})$/.exec(predefinedColor[i]) - let n = parseInt(t[1].slice(0, 2), 16) - let o = parseInt(t[1].slice(2, 4), 16) - let r = parseInt(t[1].slice(4, 6), 16) - let a = parseInt(t[1].slice(6, 8), 16) - a = a >= 0 ? a : 255 - return [n, o, r, a] - } - console.group('非法颜色: ' + e) - console.error('不支持颜色:' + e) - console.groupEnd() - return [0, 0, 0, 255] -} - -function TextMetrics (width) { - this.width = width -} - -function Pattern (image, repetition) { - this.image = image - this.repetition = repetition -} - -class CanvasGradient { - constructor (type, data) { - this.type = type - this.data = data - this.colorStop = [] - } - addColorStop (position, color) { - this.colorStop.push([position, checkColor(color)]) - } -} -var methods1 = ['scale', 'rotate', 'translate', 'setTransform', 'transform'] -var methods2 = ['drawImage', 'fillText', 'fill', 'stroke', 'fillRect', 'strokeRect', 'clearRect', - 'strokeText' -] -var methods3 = ['setFillStyle', 'setTextAlign', 'setStrokeStyle', 'setGlobalAlpha', 'setShadow', - 'setFontSize', 'setLineCap', 'setLineJoin', 'setLineWidth', 'setMiterLimit', - 'setTextBaseline', 'setLineDash' -] - -var tempCanvas -function getTempCanvas () { - if (!tempCanvas) { - tempCanvas = document.createElement('canvas') - } - return tempCanvas -} - -class CanvasContext { - constructor (id, pageId) { - this.id = id - this.pageId = pageId - this.actions = [] - this.path = [] - this.subpath = [] - this.currentTransform = [] - this.currentStepAnimates = [] - this.drawingState = [] - this.state = { - lineDash: [0, 0], - shadowOffsetX: 0, - shadowOffsetY: 0, - shadowBlur: 0, - shadowColor: [0, 0, 0, 0], - font: '10px sans-serif', - fontSize: 10, - fontWeight: 'normal', - fontStyle: 'normal', - fontFamily: 'sans-serif' - } - } - draw (reserve = false, callback) { - var actions = [...this.actions] - this.actions = [] - this.path = [] - var callbackId - - if (typeof callback === 'function') { - callbackId = canvasEventCallbacks.push(callback) - } - - operateCanvas(this.id, this.pageId, 'actionsChanged', { - actions, - reserve, - callbackId - }) - } - createLinearGradient (x0, y0, x1, y1) { - return new CanvasGradient('linear', [x0, y0, x1, y1]) - } - createCircularGradient (x, y, r) { - return new CanvasGradient('radial', [x, y, r]) - } - createPattern (image, repetition) { - if (undefined === repetition) { - console.error("Failed to execute 'createPattern' on 'CanvasContext': 2 arguments required, but only 1 present.") - } else if (['repeat', 'repeat-x', 'repeat-y', 'no-repeat'].indexOf(repetition) < 0) { - console.error("Failed to execute 'createPattern' on 'CanvasContext': The provided type ('" + repetition + "') is not one of 'repeat', 'no-repeat', 'repeat-x', or 'repeat-y'.") - } else { - return new Pattern(image, repetition) - } - } - measureText (text) { - var c2d = getTempCanvas().getContext('2d') - c2d.font = this.state.font - return new TextMetrics(c2d.measureText(text).width || 0) - } - save () { - this.actions.push({ - method: 'save', - data: [] - }) - this.drawingState.push(this.state) - } - restore () { - this.actions.push({ - method: 'restore', - data: [] - }) - this.state = this.drawingState.pop() || { - lineDash: [0, 0], - shadowOffsetX: 0, - shadowOffsetY: 0, - shadowBlur: 0, - shadowColor: [0, 0, 0, 0], - font: '10px sans-serif', - fontSize: 10, - fontWeight: 'normal', - fontStyle: 'normal', - fontFamily: 'sans-serif' - } - } - beginPath () { - this.path = [] - this.subpath = [] - } - moveTo (x, y) { - this.path.push({ - method: 'moveTo', - data: [x, y] - }) - this.subpath = [ - [x, y] - ] - } - lineTo (x, y) { - if (this.path.length === 0 && this.subpath.length === 0) { - this.path.push({ - method: 'moveTo', - data: [x, y] - }) - } else { - this.path.push({ - method: 'lineTo', - data: [x, y] - }) - } - this.subpath.push([x, y]) - } - quadraticCurveTo (cpx, cpy, x, y) { - this.path.push({ - method: 'quadraticCurveTo', - data: [cpx, cpy, x, y] - }) - this.subpath.push([x, y]) - } - bezierCurveTo (cp1x, cp1y, cp2x, cp2y, x, y) { - this.path.push({ - method: 'bezierCurveTo', - data: [cp1x, cp1y, cp2x, cp2y, x, y] - }) - this.subpath.push([x, y]) - } - arc (x, y, r, sAngle, eAngle, counterclockwise = false) { - this.path.push({ - method: 'arc', - data: [x, y, r, sAngle, eAngle, counterclockwise] - }) - this.subpath.push([x, y]) - } - rect (x, y, width, height) { - this.path.push({ - method: 'rect', - data: [x, y, width, height] - }) - this.subpath = [ - [x, y] - ] - } - arcTo (x1, y1, x2, y2, radius) { - this.path.push({ - method: 'arcTo', - data: [x1, y1, x2, y2, radius] - }) - this.subpath.push([x2, y2]) - } - clip () { - this.actions.push({ - method: 'clip', - data: [...this.path] - }) - } - closePath () { - this.path.push({ - method: 'closePath', - data: [] - }) - if (this.subpath.length) { - this.subpath = [this.subpath.shift()] - } - } - clearActions () { - this.actions = [] - this.path = [] - this.subpath = [] - } - getActions () { - var actions = [...this.actions] - this.clearActions() - return actions - } - set lineDashOffset (value) { - this.actions.push({ - method: 'setLineDashOffset', - data: [value] - }) - } - set globalCompositeOperation (type) { - this.actions.push({ - method: 'setGlobalCompositeOperation', - data: [type] - }) - } - set shadowBlur (level) { - this.actions.push({ - method: 'setShadowBlur', - data: [level] - }) - } - set shadowColor (color) { - this.actions.push({ - method: 'setShadowColor', - data: [color] - }) - } - set shadowOffsetX (x) { - this.actions.push({ - method: 'setShadowOffsetX', - data: [x] - }) - } - set shadowOffsetY (y) { - this.actions.push({ - method: 'setShadowOffsetY', - data: [y] - }) - } - set font (value) { - var self = this - this.state.font = value - // eslint-disable-next-line - var fontFormat = value.match(/^(([\w\-]+\s)*)(\d+r?px)(\/(\d+\.?\d*(r?px)?))?\s+(.*)/) - if (fontFormat) { - var style = fontFormat[1].trim().split(/\s/) - var fontSize = parseFloat(fontFormat[3]) - var fontFamily = fontFormat[7] - var actions = [] - style.forEach(function (value, index) { - if (['italic', 'oblique', 'normal'].indexOf(value) > -1) { - actions.push({ - method: 'setFontStyle', - data: [value] - }) - self.state.fontStyle = value - } else if (['bold', 'normal'].indexOf(value) > -1) { - actions.push({ - method: 'setFontWeight', - data: [value] - }) - self.state.fontWeight = value - } else if (index === 0) { - actions.push({ - method: 'setFontStyle', - data: ['normal'] - }) - self.state.fontStyle = 'normal' - } else if (index === 1) { - pushAction() - } - }) - if (style.length === 1) { - pushAction() - } - style = actions.map(function (action) { - return action.data[0] - }).join(' ') - this.state.fontSize = fontSize - this.state.fontFamily = fontFamily - this.actions.push({ - method: 'setFont', - data: [`${style} ${fontSize}px ${fontFamily}`] - }) - } else { - console.warn("Failed to set 'font' on 'CanvasContext': invalid format.") - } - function pushAction () { - actions.push({ - method: 'setFontWeight', - data: ['normal'] - }) - self.state.fontWeight = 'normal' - } - } - get font () { - return this.state.font - } - set fillStyle (color) { - this.setFillStyle(color) - } - set strokeStyle (color) { - this.setStrokeStyle(color) - } - set globalAlpha (value) { - value = Math.floor(255 * parseFloat(value)) - this.actions.push({ - method: 'setGlobalAlpha', - data: [value] - }) - } - set textAlign (align) { - this.actions.push({ - method: 'setTextAlign', - data: [align] - }) - } - set lineCap (type) { - this.actions.push({ - method: 'setLineCap', - data: [type] - }) - } - set lineJoin (type) { - this.actions.push({ - method: 'setLineJoin', - data: [type] - }) - } - set lineWidth (value) { - this.actions.push({ - method: 'setLineWidth', - data: [value] - }) - } - set miterLimit (value) { - this.actions.push({ - method: 'setMiterLimit', - data: [value] - }) - } - set textBaseline (type) { - this.actions.push({ - method: 'setTextBaseline', - data: [type] - }) - } -} - -[...methods1, ...methods2].forEach(function (method) { - function get (method) { - switch (method) { - case 'fill': - case 'stroke': - return function () { - this.actions.push({ - method: method + 'Path', - data: [...this.path] - }) - } - case 'fillRect': - return function (x, y, width, height) { - this.actions.push({ - method: 'fillPath', - data: [{ - method: 'rect', - data: [x, y, width, height] - }] - }) - } - case 'strokeRect': - return function (x, y, width, height) { - this.actions.push({ - method: 'strokePath', - data: [{ - method: 'rect', - data: [x, y, width, height] - }] - }) - } - case 'fillText': - case 'strokeText': - return function (text, x, y, maxWidth) { - var data = [text.toString(), x, y] - if (typeof maxWidth === 'number') { - data.push(maxWidth) - } - this.actions.push({ - method, - data - }) - } - case 'drawImage': - return function (imageResource, dx, dy, dWidth, dHeight, sx, sy, sWidth, sHeight) { - if (sHeight === undefined) { - sx = dx - sy = dy - sWidth = dWidth - sHeight = dHeight - dx = undefined - dy = undefined - dWidth = undefined - dHeight = undefined - } - var data - function isNumber (e) { - return typeof e === 'number' - } - data = isNumber(dx) && isNumber(dy) && isNumber(dWidth) && isNumber(dHeight) ? [imageResource, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight] : isNumber(sWidth) && isNumber( - sHeight) ? [imageResource, sx, sy, sWidth, sHeight] : [imageResource, sx, sy] - this.actions.push({ - method, - data - }) - } - default: - return function (...data) { - this.actions.push({ - method, - data - }) - } - } - } - CanvasContext.prototype[method] = get(method) -}) -methods3.forEach(function (method) { - function get (method) { - switch (method) { - case 'setFillStyle': - case 'setStrokeStyle': - return function (color) { - if (typeof color !== 'object') { - this.actions.push({ - method, - data: ['normal', checkColor(color)] - }) - } else { - this.actions.push({ - method, - data: [color.type, color.data, color.colorStop] - }) - } - } - case 'setGlobalAlpha': - return function (alpha) { - alpha = Math.floor(255 * parseFloat(alpha)) - this.actions.push({ - method, - data: [alpha] - }) - } - case 'setShadow': - return function (offsetX, offsetY, blur, color) { - color = checkColor(color) - this.actions.push({ - method, - data: [offsetX, offsetY, blur, color] - }) - this.state.shadowBlur = blur - this.state.shadowColor = color - this.state.shadowOffsetX = offsetX - this.state.shadowOffsetY = offsetY - } - case 'setLineDash': - return function (pattern, offset) { - pattern = pattern || [0, 0] - offset = offset || 0 - this.actions.push({ - method, - data: [pattern, offset] - }) - this.state.lineDash = pattern - } - case 'setFontSize': - return function (fontSize) { - this.state.font = this.state.font.replace(/\d+\.?\d*px/, fontSize + 'px') - this.state.fontSize = fontSize - this.actions.push({ - method, - data: [fontSize] - }) - } - default: - return function (...data) { - this.actions.push({ - method, - data - }) - } - } - } - CanvasContext.prototype[method] = get(method) -}) - -export function createCanvasContext (id, context) { - if (context) { - return new CanvasContext(id, context.$page.id) - } - const app = getApp() - if (app.$route && app.$route.params.__id__) { - return new CanvasContext(id, app.$route.params.__id__) - } else { - UniServiceJSBridge.emit('onError', 'createCanvasContext:fail') - } -} - -const { - invokeCallbackHandler: invoke -} = UniServiceJSBridge - -export function canvasGetImageData ({ - canvasId, - x, - y, - width, - height -}, callbackId) { - var pageId - const app = getApp() - if (app.$route && app.$route.params.__id__) { - pageId = app.$route.params.__id__ - } else { - invoke(callbackId, { - errMsg: 'canvasGetImageData:fail' - }) - return - } - var cId = canvasEventCallbacks.push(function (data) { - var imgData = data.data - if (imgData && imgData.length) { - data.data = new Uint8ClampedArray(imgData) - } - invoke(callbackId, data) - }) - operateCanvas(canvasId, pageId, 'getImageData', { - x, - y, - width, - height, - callbackId: cId - }) -} - -export function canvasPutImageData ({ - canvasId, - data, - x, - y, - width, - height -}, callbackId) { - var pageId - const app = getApp() - if (app.$route && app.$route.params.__id__) { - pageId = app.$route.params.__id__ - } else { - invoke(callbackId, { - errMsg: 'canvasPutImageData:fail' - }) - return - } - var cId = canvasEventCallbacks.push(function (data) { - invoke(callbackId, data) - }) - operateCanvas(canvasId, pageId, 'putImageData', { - data: [...data], - x, - y, - width, - height, - callbackId: cId - }) -} - -export function canvasToTempFilePath ({ - x = 0, - y = 0, - width, - height, - destWidth, - destHeight, - canvasId, - fileType, - qualit -}, callbackId) { - let pageId - const app = getApp() - if (app.$route && app.$route.params.__id__) { - pageId = app.$route.params.__id__ - } else { - invoke(callbackId, { - errMsg: 'canvasToTempFilePath:fail' - }) - return - } - const cId = canvasEventCallbacks.push(function ({ - data, - width, - height - }) { - if (!data || !data.length) { - invoke(callbackId, { - errMsg: 'canvasToTempFilePath:fail' - }) - return - } - let imgData - try { - imgData = new ImageData(new Uint8ClampedArray(data), width, height) - } catch (error) { - invoke(callbackId, { - errMsg: 'canvasToTempFilePath:fail' - }) - return - } - const canvas = getTempCanvas() - canvas.width = width - canvas.height = height - const c2d = canvas.getContext('2d') - c2d.putImageData(imgData, 0, 0) - let base64 = canvas.toDataURL('image/png') - const img = new Image() - img.onload = function () { - if (fileType === 'jpeg') { - c2d.fillStyle = '#fff' - c2d.fillRect(0, 0, width, width) - } - c2d.drawImage(img, 0, 0) - base64 = canvas.toDataURL(`image/${fileType}`, qualit) - invoke(callbackId, { - errMsg: 'canvasToTempFilePath:ok', - tempFilePath: base64 - }) - } - img.src = base64 - }) - operateCanvas(canvasId, pageId, 'getImageData', { - x, - y, - width, - height, - destWidth, - destHeight, - hidpi: false, - callbackId: cId - }) -} -- GitLab