| (function (global) { |
| function makeSprite(size, canvas, draw) { |
| // http://simonsarris.com/blog/346-how-you-clear-your-canvas-matters |
| // http://jsperf.com/canvasclear |
| // Set width and height is fast |
| // And use the exist canvas if possible |
| // http://jsperf.com/create-canvas-vs-set-width-height/2 |
| var canvas = canvas || document.createElement('canvas'); |
| canvas.width = size; |
| canvas.height = size; |
| var ctx = canvas.getContext('2d'); |
| |
| draw && draw(ctx); |
| |
| return canvas; |
| } |
| |
| function makePath(symbol, symbolSize, style, marginBias) { |
| if (!echarts.util.isArray(symbolSize)) { |
| symbolSize = [symbolSize, symbolSize]; |
| } |
| var margin = spriteUtil.getMarginByStyle(style, marginBias); |
| var width = symbolSize[0] + margin.left + margin.right; |
| var height = symbolSize[1] + margin.top + margin.bottom; |
| var path = echarts.helper.createSymbol(symbol, 0, 0, symbolSize[0], symbolSize[1]); |
| |
| var size = Math.max(width, height); |
| |
| path.position = [margin.left, margin.top]; |
| if (width > height) { |
| path.position[1] += (size - height) / 2; |
| } |
| else { |
| path.position[0] += (size - width) / 2; |
| } |
| |
| var rect = path.getBoundingRect(); |
| path.position[0] -= rect.x; |
| path.position[1] -= rect.y; |
| |
| path.setStyle(style); |
| |
| path.update(); |
| |
| path.__size = size; |
| |
| return path; |
| } |
| |
| // http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf |
| function generateSDF(ctx, sourceImageData, range) { |
| |
| var sourceWidth = sourceImageData.width; |
| var sourceHeight = sourceImageData.height; |
| |
| var width = ctx.canvas.width; |
| var height = ctx.canvas.height; |
| |
| var scaleX = sourceWidth / width; |
| var scaleY = sourceHeight / height; |
| |
| function sign(r) { |
| return r < 128 ? 1 : -1; |
| } |
| function searchMinDistance(x, y) { |
| var minDistSqr = Infinity; |
| x = Math.floor(x * scaleX); |
| y = Math.floor(y * scaleY); |
| var i = y * sourceWidth + x; |
| var r = sourceImageData.data[i * 4]; |
| var a = sign(r); |
| // Search for min distance |
| for (var y2 = Math.max(y - range, 0); y2 < Math.min(y + range, sourceHeight); y2++) { |
| for (var x2 = Math.max(x - range, 0); x2 < Math.min(x + range, sourceWidth); x2++) { |
| var i = y2 * sourceWidth + x2; |
| var r2 = sourceImageData.data[i * 4]; |
| var b = sign(r2); |
| var dx = x2 - x; |
| var dy = y2 - y; |
| if (a !== b) { |
| var distSqr = dx * dx + dy * dy; |
| if (distSqr < minDistSqr) { |
| minDistSqr = distSqr; |
| } |
| } |
| } |
| } |
| return a * Math.sqrt(minDistSqr); |
| } |
| |
| var sdfImageData = ctx.createImageData(width, height); |
| for (var y = 0; y < height; y++) { |
| for (var x = 0; x < width; x++) { |
| var dist = searchMinDistance(x, y); |
| |
| var normalized = dist / range * 0.5 + 0.5; |
| var i = (y * width + x) * 4; |
| sdfImageData.data[i++] = (1.0 - normalized) * 255; |
| sdfImageData.data[i++] = (1.0 - normalized) * 255; |
| sdfImageData.data[i++] = (1.0 - normalized) * 255; |
| sdfImageData.data[i++] = 255; |
| } |
| } |
| |
| return sdfImageData; |
| } |
| |
| global.spriteUtil = { |
| |
| getMarginByStyle: function (style) { |
| var marginBias = style.marginBias || 0; |
| |
| var lineWidth = 0; |
| if (style.stroke && style.stroke !== 'none') { |
| lineWidth = style.lineWidth == null ? 1 : style.lineWidth; |
| } |
| var shadowBlurSize = style.shadowBlur || 0; |
| var shadowOffsetX = style.shadowOffsetX || 0; |
| var shadowOffsetY = style.shadowOffsetY || 0; |
| |
| var margin = {}; |
| margin.left = Math.max(lineWidth / 2, -shadowOffsetX + shadowBlurSize) + marginBias; |
| margin.right = Math.max(lineWidth / 2, shadowOffsetX + shadowBlurSize) + marginBias; |
| margin.top = Math.max(lineWidth / 2, -shadowOffsetY + shadowBlurSize) + marginBias; |
| margin.bottom = Math.max(lineWidth / 2, shadowOffsetY + shadowBlurSize) + marginBias; |
| |
| return margin; |
| }, |
| |
| // TODO Not consider shadowOffsetX, shadowOffsetY. |
| /** |
| * @param {string} symbol |
| * @param {number | Array.<number>} symbolSize |
| * @param {Object} style |
| * @param {number} marginBias |
| */ |
| createSymbolSprite: function (symbol, symbolSize, style, canvas) { |
| |
| // TODO marginBias can be set. |
| var path = makePath(symbol, symbolSize, style); |
| |
| var margin = spriteUtil.getMarginByStyle(style); |
| |
| return { |
| image: makeSprite(path.__size, canvas, function (ctx) { |
| path.brush(ctx); |
| }), |
| margin: margin |
| }; |
| }, |
| |
| createSDFFromCanvas: function (canvas, size, range, outCanvas) { |
| // TODO Create a low resolution SDF from high resolution image. |
| return makeSprite(size, outCanvas, function (outCtx) { |
| var ctx = canvas.getContext('2d'); |
| var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
| |
| outCtx.putImageData(generateSDF(outCtx, imgData, range), 0, 0); |
| }); |
| }, |
| |
| createSimpleSprite: function (size, canvas) { |
| return makeSprite(size, canvas, function (ctx) { |
| var halfSize = size / 2; |
| ctx.beginPath(); |
| ctx.arc(halfSize, halfSize, 60, 0, Math.PI * 2, false) ; |
| ctx.closePath(); |
| |
| var gradient = ctx.createRadialGradient( |
| halfSize, halfSize, 0, halfSize, halfSize, halfSize |
| ); |
| gradient.addColorStop(0, 'rgba(255, 255, 255, 1)'); |
| gradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.5)'); |
| gradient.addColorStop(1, 'rgba(255, 255, 255, 0)'); |
| ctx.fillStyle = gradient; |
| ctx.fill(); |
| }); |
| } |
| }; |
| })(window); |