From 99e66c380f3e24c12133c6060bc1abc7c45dc381 Mon Sep 17 00:00:00 2001 From: Anton Lavrenov Date: Wed, 13 Feb 2019 22:04:54 -0500 Subject: [PATCH] cache patterns and gradients --- CHANGELOG.md | 1 + konva.js | 108 ++++++++++---- konva.min.js | 4 +- src/Context.ts | 43 +----- src/Shape.ts | 104 +++++++++++++- test/unit/Shape-test.js | 305 +++++++++++++++++++++++++++++++++++++++- 6 files changed, 500 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca3607a8..f9612872 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). * A bit changed behavior of `removeId` (private method), now it doesn't clear node ref, if object is changed. * simplified `batchDraw` method (it doesn't use `Konva.Animation`) now. * `id` and `name` properties defaults are empty strings, not `undefined` +* Performance improvements for shapes will image patterns, linear and radial fills ### Removed * `Konva.Util.addMethods` diff --git a/konva.js b/konva.js index e13defa0..1ca6a492 100644 --- a/konva.js +++ b/konva.js @@ -8,7 +8,7 @@ * Konva JavaScript Framework v3.0.0-0 * http://konvajs.github.io/ * Licensed under the MIT - * Date: Mon Feb 11 2019 + * Date: Wed Feb 13 2019 * * Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS) * Modified work Copyright (C) 2014 - present by Anton Lavrenov (Konva) @@ -1769,29 +1769,22 @@ if (fillPatternOffsetX || fillPatternOffsetY) { this.translate(-1 * fillPatternOffsetX, -1 * fillPatternOffsetY); } - // TODO: cache pattern - this.setAttr('fillStyle', this.createPattern(shape.getFillPatternImage(), shape.getFillPatternRepeat() || 'repeat')); + this.setAttr('fillStyle', shape._getFillPattern()); shape._fillFunc(this); }; SceneContext.prototype._fillLinearGradient = function (shape) { - var start = shape.getFillLinearGradientStartPoint(), end = shape.getFillLinearGradientEndPoint(), colorStops = shape.getFillLinearGradientColorStops(), grd = this.createLinearGradient(start.x, start.y, end.x, end.y); - if (colorStops) { - // build color stops - for (var n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n], colorStops[n + 1]); - } + var grd = shape._getLinearGradient(); + if (grd) { this.setAttr('fillStyle', grd); shape._fillFunc(this); } }; SceneContext.prototype._fillRadialGradient = function (shape) { - var start = shape.getFillRadialGradientStartPoint(), end = shape.getFillRadialGradientEndPoint(), startRadius = shape.getFillRadialGradientStartRadius(), endRadius = shape.getFillRadialGradientEndRadius(), colorStops = shape.getFillRadialGradientColorStops(), grd = this.createRadialGradient(start.x, start.y, startRadius, end.x, end.y, endRadius); - // build color stops - for (var n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n], colorStops[n + 1]); + var grd = shape._getRadialGradient(); + if (grd) { + this.setAttr('fillStyle', grd); + shape._fillFunc(this); } - this.setAttr('fillStyle', grd); - shape._fillFunc(this); }; SceneContext.prototype._fill = function (shape) { var hasColor = shape.fill(), fillPriority = shape.getFillPriority(); @@ -6808,7 +6801,17 @@ var HAS_SHADOW = 'hasShadow'; var SHADOW_RGBA = 'shadowRGBA'; - // TODO: cache gradient from context + var patternImage = 'patternImage'; + var linearGradient = 'linearGradient'; + var radialGradient = 'radialGradient'; + var dummyContext; + function getDummyContext() { + if (dummyContext) { + return dummyContext; + } + dummyContext = Util.createCanvasElement().getContext('2d'); + return dummyContext; + } // TODO: write a test for adding destroyed shape into the layer // will it draw? // will it pass hit test? @@ -6836,6 +6839,15 @@ function _clearGetShadowRGBACache() { this._clearCache(SHADOW_RGBA); } + function _clearFillPatternCache() { + this._clearCache(patternImage); + } + function _clearLinearGradientCache() { + this._clearCache(linearGradient); + } + function _clearRadialGradientCache() { + this._clearCache(radialGradient); + } /** * Shape constructor. Shapes are primitive objects such as rectangles, * circles, text, lines, etc. @@ -6947,6 +6959,9 @@ shapes[key] = _this; _this.on('shadowColorChange.konva shadowBlurChange.konva shadowOffsetChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', _clearHasShadowCache); _this.on('shadowColorChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', _clearGetShadowRGBACache); + _this.on('fillPriorityChange.konva fillPatternImageChange.konva fillPatternRepeatChange.konva', _clearFillPatternCache); + _this.on('fillPriorityChange.konva fillLinearGradientColorStopsChange.konva fillLinearGradientStartPointXChange.konva fillLinearGradientStartPointYChange.konva fillLinearGradientEndPointXChange.konva fillLinearGradientEndPointYChange.konva', _clearLinearGradientCache); + _this.on('fillPriorityChange.konva fillRadialGradientColorStopsChange.konva fillRadialGradientStartPointXChange.konva fillRadialGradientStartPointYChange.konva fillRadialGradientEndPointXChange.konva fillRadialGradientEndPointYChange.konva fillRadialGradientStartRadiusChange.konva fillRadialGradientEndRadiusChange.konva', _clearRadialGradientCache); return _this; } /** @@ -6987,6 +7002,49 @@ this.shadowOffsetX() || this.shadowOffsetY()))); }; + Shape.prototype._getFillPattern = function () { + return this._getCache(patternImage, this.__getFillPattern); + }; + Shape.prototype.__getFillPattern = function () { + if (this.fillPatternImage()) { + var ctx = getDummyContext(); + return ctx.createPattern(this.fillPatternImage(), this.fillPatternRepeat() || 'repeat'); + } + }; + Shape.prototype._getLinearGradient = function () { + return this._getCache(linearGradient, this.__getLinearGradient); + }; + Shape.prototype.__getLinearGradient = function () { + var colorStops = this.fillLinearGradientColorStops(); + if (colorStops) { + var ctx = getDummyContext(); + var start = this.fillLinearGradientStartPoint(); + var end = this.fillLinearGradientEndPoint(); + var grd = ctx.createLinearGradient(start.x, start.y, end.x, end.y); + // build color stops + for (var n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n], colorStops[n + 1]); + } + return grd; + } + }; + Shape.prototype._getRadialGradient = function () { + return this._getCache(radialGradient, this.__getRadialGradient); + }; + Shape.prototype.__getRadialGradient = function () { + var colorStops = this.fillRadialGradientColorStops(); + if (colorStops) { + var ctx = getDummyContext(); + var start = this.fillRadialGradientStartPoint(); + var end = this.fillRadialGradientEndPoint(); + var grd = ctx.createRadialGradient(start.x, start.y, this.fillRadialGradientStartRadius(), end.x, end.y, this.fillRadialGradientEndRadius()); + // build color stops + for (var n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n], colorStops[n + 1]); + } + return grd; + } + }; Shape.prototype.getShadowRGBA = function () { return this._getCache(SHADOW_RGBA, this._getShadowRGBA); }; @@ -12300,13 +12358,13 @@ ], // cached variables attrChangeListLen$1 = ATTR_CHANGE_LIST$1.length; - var dummyContext; - function getDummyContext() { - if (dummyContext) { - return dummyContext; + var dummyContext$1; + function getDummyContext$1() { + if (dummyContext$1) { + return dummyContext$1; } - dummyContext = Util.createCanvasElement().getContext(CONTEXT_2D); - return dummyContext; + dummyContext$1 = Util.createCanvasElement().getContext(CONTEXT_2D); + return dummyContext$1; } function _fillFunc$1(context) { context.fillText(this.partialText, 0, 0); @@ -12565,7 +12623,7 @@ }; // TODO: make it public, rename to "measure text"? Text.prototype._getTextSize = function (text) { - var _context = getDummyContext(), fontSize = this.fontSize(), metrics; + var _context = getDummyContext$1(), fontSize = this.fontSize(), metrics; _context.save(); _context.font = this._getContextFont(); metrics = _context.measureText(text); @@ -12605,7 +12663,7 @@ Text.prototype._getTextWidth = function (text) { var letterSpacing = this.letterSpacing(); var length = text.length; - return (getDummyContext().measureText(text).width + + return (getDummyContext$1().measureText(text).width + (length ? letterSpacing * (length - 1) : 0)); }; Text.prototype._setTextData = function () { @@ -12613,7 +12671,7 @@ // align = this.align(), shouldWrap = wrap !== NONE$1, wrapAtWord = wrap !== CHAR && shouldWrap, shouldAddEllipsis = this.ellipsis() && !shouldWrap; this.textArr = []; - getDummyContext().font = this._getContextFont(); + getDummyContext$1().font = this._getContextFont(); var additionalWidth = shouldAddEllipsis ? this._getTextWidth(ELLIPSIS) : 0; for (var i = 0, max = lines.length; i < max; ++i) { var line = lines[i]; diff --git a/konva.min.js b/konva.min.js index ea682558..985f79d3 100644 --- a/konva.min.js +++ b/konva.min.js @@ -3,10 +3,10 @@ * Konva JavaScript Framework v3.0.0-0 * http://konvajs.github.io/ * Licensed under the MIT - * Date: Mon Feb 11 2019 + * Date: Wed Feb 13 2019 * * Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS) * Modified work Copyright (C) 2014 - present by Anton Lavrenov (Konva) * * @license - */var e=Math.PI/180,n={},o={},a="undefined"!=typeof window&&("[object Window]"==={}.toString.call(window)||"[object global]"==={}.toString.call(window)),t=/comment/.test(function(){}.toString()),h=function(t,e){e&&(n[e]||(n[e]=[]),n[e].push(t))},l=function(t,e){if(t){var i=n[t];if(i){for(var r=0;r>16&255,g:e>>8&255,b:255&e}},getRandomColor:function(){for(var t=(16777215*Math.random()<<0).toString(16);t.length<6;)t="0"+t;return"#"+t},get:function(t,e){return void 0===t?e:t},getRGB:function(t){var e;return t in y?{r:(e=y[t])[0],g:e[1],b:e[2]}:"#"===t[0]?this._hexToRgb(t.substring(1)):"rgb("===t.substr(0,4)?(e=m.exec(t.replace(/ /g,"")),{r:parseInt(e[1],10),g:parseInt(e[2],10),b:parseInt(e[3],10)}):{r:0,g:0,b:0}},colorToRGBA:function(t){return t=t||"black",O._namedColorToRBA(t)||O._hex3ColorToRGBA(t)||O._hex6ColorToRGBA(t)||O._rgbColorToRGBA(t)||O._rgbaColorToRGBA(t)},_namedColorToRBA:function(t){var e=y[t.toLowerCase()];return e?{r:e[0],g:e[1],b:e[2],a:1}:null},_rgbColorToRGBA:function(t){if(0===t.indexOf("rgb(")){var e=(t=t.match(/rgb\(([^)]+)\)/)[1]).split(/ *, */).map(Number);return{r:e[0],g:e[1],b:e[2],a:1}}},_rgbaColorToRGBA:function(t){if(0===t.indexOf("rgba(")){var e=(t=t.match(/rgba\(([^)]+)\)/)[1]).split(/ *, */).map(Number);return{r:e[0],g:e[1],b:e[2],a:e[3]}}},_hex6ColorToRGBA:function(t){if("#"===t[0]&&7===t.length)return{r:parseInt(t.slice(1,3),16),g:parseInt(t.slice(3,5),16),b:parseInt(t.slice(5,7),16),a:1}},_hex3ColorToRGBA:function(t){if("#"===t[0]&&4===t.length)return{r:parseInt(t[1]+t[1],16),g:parseInt(t[2]+t[2],16),b:parseInt(t[3]+t[3],16),a:1}},_merge:function(t,e){var i=this._clone(e);for(var r in t)this._isPlainObject(t[r])?i[r]=this._merge(t[r],i[r]):i[r]=t[r];return i},haveIntersection:function(t,e){return!(e.x>t.x+t.width||e.x+e.widtht.y+t.height||e.y+e.heighte.length){var o=e;e=t,t=o}for(r=0;r=L().traceArrMax&&e.shift()},t.prototype.reset=function(){var t=this.getCanvas().getPixelRatio();this.setTransform(1*t,0,0,1*t,0,0)},t.prototype.getCanvas=function(){return this.canvas},t.prototype.clear=function(t){var e=this.getCanvas();t?this.clearRect(t.x||0,t.y||0,t.width||0,t.height||0):this.clearRect(0,0,e.getWidth()/e.pixelRatio,e.getHeight()/e.pixelRatio)},t.prototype._applyLineCap=function(t){var e=t.getLineCap();e&&this.setAttr("lineCap",e)},t.prototype._applyOpacity=function(t){var e=t.getAbsoluteOpacity();1!==e&&this.setAttr("globalAlpha",e)},t.prototype._applyLineJoin=function(t){var e=t.getLineJoin();e&&this.setAttr("lineJoin",e)},t.prototype.setAttr=function(t,e){this._context[t]=e},t.prototype.arc=function(t,e,i,r,n,a){this._context.arc(t,e,i,r,n,a)},t.prototype.arcTo=function(t,e,i,r,n,a){this._context.arc(t,e,i,r,n,a)},t.prototype.beginPath=function(){this._context.beginPath()},t.prototype.bezierCurveTo=function(t,e,i,r,n,a){this._context.bezierCurveTo(t,e,i,r,n,a)},t.prototype.clearRect=function(t,e,i,r){this._context.clearRect(t,e,i,r)},t.prototype.clip=function(){this._context.clip()},t.prototype.closePath=function(){this._context.closePath()},t.prototype.createImageData=function(t,e){var i=arguments;return 2===i.length?this._context.createImageData(t,e):1===i.length?this._context.createImageData(t):void 0},t.prototype.createLinearGradient=function(t,e,i,r){return this._context.createLinearGradient(t,e,i,r)},t.prototype.createPattern=function(t,e){return this._context.createPattern(t,e)},t.prototype.createRadialGradient=function(t,e,i,r,n,a){return this._context.createRadialGradient(t,e,i,r,n,a)},t.prototype.drawImage=function(t,e,i,r,n,a,o,s,h){var l=arguments,d=this._context;3===l.length?d.drawImage(t,e,i):5===l.length?d.drawImage(t,e,i,r,n):9===l.length&&d.drawImage(t,e,i,r,n,a,o,s,h)},t.prototype.isPointInPath=function(t,e){return this._context.isPointInPath(t,e)},t.prototype.fill=function(){this._context.fill()},t.prototype.fillRect=function(t,e,i,r){this._context.fillRect(t,e,i,r)},t.prototype.strokeRect=function(t,e,i,r){this._context.strokeRect(t,e,i,r)},t.prototype.fillText=function(t,e,i){this._context.fillText(t,e,i)},t.prototype.measureText=function(t){return this._context.measureText(t)},t.prototype.getImageData=function(t,e,i,r){return this._context.getImageData(t,e,i,r)},t.prototype.lineTo=function(t,e){this._context.lineTo(t,e)},t.prototype.moveTo=function(t,e){this._context.moveTo(t,e)},t.prototype.rect=function(t,e,i,r){this._context.rect(t,e,i,r)},t.prototype.putImageData=function(t,e,i){this._context.putImageData(t,e,i)},t.prototype.quadraticCurveTo=function(t,e,i,r){this._context.quadraticCurveTo(t,e,i,r)},t.prototype.restore=function(){this._context.restore()},t.prototype.rotate=function(t){this._context.rotate(t)},t.prototype.save=function(){this._context.save()},t.prototype.scale=function(t,e){this._context.scale(t,e)},t.prototype.setLineDash=function(t){this._context.setLineDash?this._context.setLineDash(t):"mozDash"in this._context?this._context.mozDash=t:"webkitLineDash"in this._context&&(this._context.webkitLineDash=t)},t.prototype.getLineDash=function(){return this._context.getLineDash()},t.prototype.setTransform=function(t,e,i,r,n,a){this._context.setTransform(t,e,i,r,n,a)},t.prototype.stroke=function(){this._context.stroke()},t.prototype.strokeText=function(t,e,i,r){this._context.strokeText(t,e,i,r)},t.prototype.transform=function(t,e,i,r,n,a){this._context.transform(t,e,i,r,n,a)},t.prototype.translate=function(t,e){this._context.translate(t,e)},t.prototype._enableTrace=function(){var t,r,n=this,e=k.length,a=O._simplifyArray,i=this.setAttr,o=function(t){var e,i=n[t];n[t]=function(){return r=a(Array.prototype.slice.call(arguments,0)),e=i.apply(n,arguments),n._trace({method:t,args:r}),e}};for(t=0;tthis.duration?this.yoyo?(this._time=this.duration,this.reverse()):this.finish():t<0?this.yoyo?(this._time=0,this.play()):this.reset():(this._time=t,this.update())},t.prototype.getTime=function(){return this._time},t.prototype.setPosition=function(t){this.prevPos=this._pos,this.propFunc(t),this._pos=t},t.prototype.getPosition=function(t){return void 0===t&&(t=this._time),this.func(t,this.begin,this._change,this.duration)},t.prototype.play=function(){this.state=2,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onPlay")},t.prototype.reverse=function(){this.state=3,this._time=this.duration-this._time,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onReverse")},t.prototype.seek=function(t){this.pause(),this._time=t,this.update(),this.fire("onSeek")},t.prototype.reset=function(){this.pause(),this._time=0,this.update(),this.fire("onReset")},t.prototype.finish=function(){this.pause(),this._time=this.duration,this.update(),this.fire("onFinish")},t.prototype.update=function(){this.setPosition(this.getPosition(this._time))},t.prototype.onEnterFrame=function(){var t=this.getTimer()-this._startTime;2===this.state?this.setTime(t):3===this.state&&this.setTime(this.duration-t)},t.prototype.pause=function(){this.state=1,this.fire("onPause")},t.prototype.getTimer=function(){return(new Date).getTime()},t}(),Nt=function(){function p(t){var e,i,r=this,n=t.node,a=n._id,o=t.easing||Ft.Linear,s=!!t.yoyo;e=void 0===t.duration?.3:0===t.duration?.001:t.duration,this.node=n,this._id=Dt++;var h=n.getLayer()||(n instanceof L().Stage?n.getLayers():null);for(i in h||O.error("Tween constructor have `node` that is not in a layer. Please add node into layer first."),this.anim=new I(function(){r.tween.onEnterFrame()},h),this.tween=new Ot(i,function(t){r._tweenFunc(t)},o,0,1,1e3*e,s),this._addListeners(),p.attrs[a]||(p.attrs[a]={}),p.attrs[a][this._id]||(p.attrs[a][this._id]={}),p.tweens[a]||(p.tweens[a]={}),t)void 0===Rt[i]&&this._addAttr(i,t[i]);this.reset(),this.onFinish=t.onFinish,this.onReset=t.onReset}return p.prototype._addAttr=function(t,e){var i,r,n,a,o,s,h,l,d=this.node,c=d._id;if((n=p.tweens[c][t])&&delete p.attrs[c][n][t],i=d.getAttr(t),O._isArray(e))if(r=[],o=Math.max(e.length,i.length),"points"===t&&e.length!==i.length&&(e.length>i.length?(h=i,i=O._prepareArrayForTween(i,e,d.closed())):(s=e,e=O._prepareArrayForTween(e,i,d.closed()))),0===t.indexOf("fill"))for(a=0;athis.dataArray[i].pathLength;)t-=this.dataArray[i].pathLength,++i;if(i===r)return{x:(e=this.dataArray[i-1].points.slice(-2))[0],y:e[1]};if(t<.01)return{x:(e=this.dataArray[i].points.slice(0,2))[0],y:e[1]};var n=this.dataArray[i],a=n.points;switch(n.command){case"L":return p.getPointOnLine(t,n.start.x,n.start.y,a[0],a[1]);case"C":return p.getPointOnCubicBezier(t/n.pathLength,n.start.x,n.start.y,a[0],a[1],a[2],a[3],a[4],a[5]);case"Q":return p.getPointOnQuadraticBezier(t/n.pathLength,n.start.x,n.start.y,a[0],a[1],a[2],a[3]);case"A":var o=a[0],s=a[1],h=a[2],l=a[3],d=a[4],c=a[5],u=a[6];return d+=c*t/n.pathLength,p.getPointOnEllipticalArc(o,s,h,l,d,u)}return null},p.getLineLength=function(t,e,i,r){return Math.sqrt((i-t)*(i-t)+(r-e)*(r-e))},p.getPointOnLine=function(t,e,i,r,n,a,o){void 0===a&&(a=e),void 0===o&&(o=i);var s=(n-i)/(r-e+1e-8),h=Math.sqrt(t*t/(1+s*s));r>>1,T=_.slice(0,k+1),P=this._getTextWidth(T)+v;P<=l?(S=k+1,w=T+(g?"…":""),C=P):x=k}if(!w)break;if(f){var M,A=_[w.length];0<(M=(" "===A||"-"===A)&&C<=l?w.length:Math.max(w.lastIndexOf(" "),w.lastIndexOf("-"))+1)&&(S=M,w=w.slice(0,S),C=this._getTextWidth(w))}if(w=w.trimRight(),this._addTextLine(w),i=Math.max(i,C),c+=r,!p||s&&de?g=Qt.getPointOnLine(e,f.x,f.y,v.points[0],v.points[1],f.x,f.y):v=void 0;break;case"A":var o=v.points[4],s=v.points[5],h=v.points[4]+s;0===m?m=o+1e-8:iv.pathLength?1e-8:e/v.pathLength:i>V,0!==C?(C=255/C,T[s]=(l*B>>V)*C,T[s+1]=(d*B>>V)*C,T[s+2]=(c*B>>V)*C):T[s]=T[s+1]=T[s+2]=0,l-=p,d-=f,c-=g,u-=v,p-=I.r,f-=I.g,g-=I.b,v-=I.a,a=h+((a=i+e+1)>V,0>V)*C,T[a+1]=(d*B>>V)*C,T[a+2]=(c*B>>V)*C):T[a]=T[a+1]=T[a+2]=0,l-=p,d-=f,c-=g,u-=v,p-=I.r,f-=I.g,g-=I.b,v-=I.a,a=i+((a=r+D)>16&255,g:e>>8&255,b:255&e}},getRandomColor:function(){for(var t=(16777215*Math.random()<<0).toString(16);t.length<6;)t="0"+t;return"#"+t},get:function(t,e){return void 0===t?e:t},getRGB:function(t){var e;return t in y?{r:(e=y[t])[0],g:e[1],b:e[2]}:"#"===t[0]?this._hexToRgb(t.substring(1)):"rgb("===t.substr(0,4)?(e=m.exec(t.replace(/ /g,"")),{r:parseInt(e[1],10),g:parseInt(e[2],10),b:parseInt(e[3],10)}):{r:0,g:0,b:0}},colorToRGBA:function(t){return t=t||"black",O._namedColorToRBA(t)||O._hex3ColorToRGBA(t)||O._hex6ColorToRGBA(t)||O._rgbColorToRGBA(t)||O._rgbaColorToRGBA(t)},_namedColorToRBA:function(t){var e=y[t.toLowerCase()];return e?{r:e[0],g:e[1],b:e[2],a:1}:null},_rgbColorToRGBA:function(t){if(0===t.indexOf("rgb(")){var e=(t=t.match(/rgb\(([^)]+)\)/)[1]).split(/ *, */).map(Number);return{r:e[0],g:e[1],b:e[2],a:1}}},_rgbaColorToRGBA:function(t){if(0===t.indexOf("rgba(")){var e=(t=t.match(/rgba\(([^)]+)\)/)[1]).split(/ *, */).map(Number);return{r:e[0],g:e[1],b:e[2],a:e[3]}}},_hex6ColorToRGBA:function(t){if("#"===t[0]&&7===t.length)return{r:parseInt(t.slice(1,3),16),g:parseInt(t.slice(3,5),16),b:parseInt(t.slice(5,7),16),a:1}},_hex3ColorToRGBA:function(t){if("#"===t[0]&&4===t.length)return{r:parseInt(t[1]+t[1],16),g:parseInt(t[2]+t[2],16),b:parseInt(t[3]+t[3],16),a:1}},_merge:function(t,e){var i=this._clone(e);for(var r in t)this._isPlainObject(t[r])?i[r]=this._merge(t[r],i[r]):i[r]=t[r];return i},haveIntersection:function(t,e){return!(e.x>t.x+t.width||e.x+e.widtht.y+t.height||e.y+e.heighte.length){var o=e;e=t,t=o}for(r=0;r=D().traceArrMax&&e.shift()},t.prototype.reset=function(){var t=this.getCanvas().getPixelRatio();this.setTransform(1*t,0,0,1*t,0,0)},t.prototype.getCanvas=function(){return this.canvas},t.prototype.clear=function(t){var e=this.getCanvas();t?this.clearRect(t.x||0,t.y||0,t.width||0,t.height||0):this.clearRect(0,0,e.getWidth()/e.pixelRatio,e.getHeight()/e.pixelRatio)},t.prototype._applyLineCap=function(t){var e=t.getLineCap();e&&this.setAttr("lineCap",e)},t.prototype._applyOpacity=function(t){var e=t.getAbsoluteOpacity();1!==e&&this.setAttr("globalAlpha",e)},t.prototype._applyLineJoin=function(t){var e=t.getLineJoin();e&&this.setAttr("lineJoin",e)},t.prototype.setAttr=function(t,e){this._context[t]=e},t.prototype.arc=function(t,e,i,r,n,a){this._context.arc(t,e,i,r,n,a)},t.prototype.arcTo=function(t,e,i,r,n,a){this._context.arc(t,e,i,r,n,a)},t.prototype.beginPath=function(){this._context.beginPath()},t.prototype.bezierCurveTo=function(t,e,i,r,n,a){this._context.bezierCurveTo(t,e,i,r,n,a)},t.prototype.clearRect=function(t,e,i,r){this._context.clearRect(t,e,i,r)},t.prototype.clip=function(){this._context.clip()},t.prototype.closePath=function(){this._context.closePath()},t.prototype.createImageData=function(t,e){var i=arguments;return 2===i.length?this._context.createImageData(t,e):1===i.length?this._context.createImageData(t):void 0},t.prototype.createLinearGradient=function(t,e,i,r){return this._context.createLinearGradient(t,e,i,r)},t.prototype.createPattern=function(t,e){return this._context.createPattern(t,e)},t.prototype.createRadialGradient=function(t,e,i,r,n,a){return this._context.createRadialGradient(t,e,i,r,n,a)},t.prototype.drawImage=function(t,e,i,r,n,a,o,s,h){var l=arguments,d=this._context;3===l.length?d.drawImage(t,e,i):5===l.length?d.drawImage(t,e,i,r,n):9===l.length&&d.drawImage(t,e,i,r,n,a,o,s,h)},t.prototype.isPointInPath=function(t,e){return this._context.isPointInPath(t,e)},t.prototype.fill=function(){this._context.fill()},t.prototype.fillRect=function(t,e,i,r){this._context.fillRect(t,e,i,r)},t.prototype.strokeRect=function(t,e,i,r){this._context.strokeRect(t,e,i,r)},t.prototype.fillText=function(t,e,i){this._context.fillText(t,e,i)},t.prototype.measureText=function(t){return this._context.measureText(t)},t.prototype.getImageData=function(t,e,i,r){return this._context.getImageData(t,e,i,r)},t.prototype.lineTo=function(t,e){this._context.lineTo(t,e)},t.prototype.moveTo=function(t,e){this._context.moveTo(t,e)},t.prototype.rect=function(t,e,i,r){this._context.rect(t,e,i,r)},t.prototype.putImageData=function(t,e,i){this._context.putImageData(t,e,i)},t.prototype.quadraticCurveTo=function(t,e,i,r){this._context.quadraticCurveTo(t,e,i,r)},t.prototype.restore=function(){this._context.restore()},t.prototype.rotate=function(t){this._context.rotate(t)},t.prototype.save=function(){this._context.save()},t.prototype.scale=function(t,e){this._context.scale(t,e)},t.prototype.setLineDash=function(t){this._context.setLineDash?this._context.setLineDash(t):"mozDash"in this._context?this._context.mozDash=t:"webkitLineDash"in this._context&&(this._context.webkitLineDash=t)},t.prototype.getLineDash=function(){return this._context.getLineDash()},t.prototype.setTransform=function(t,e,i,r,n,a){this._context.setTransform(t,e,i,r,n,a)},t.prototype.stroke=function(){this._context.stroke()},t.prototype.strokeText=function(t,e,i,r){this._context.strokeText(t,e,i,r)},t.prototype.transform=function(t,e,i,r,n,a){this._context.transform(t,e,i,r,n,a)},t.prototype.translate=function(t,e){this._context.translate(t,e)},t.prototype._enableTrace=function(){var t,r,n=this,e=k.length,a=O._simplifyArray,i=this.setAttr,o=function(t){var e,i=n[t];n[t]=function(){return r=a(Array.prototype.slice.call(arguments,0)),e=i.apply(n,arguments),n._trace({method:t,args:r}),e}};for(t=0;tthis.duration?this.yoyo?(this._time=this.duration,this.reverse()):this.finish():t<0?this.yoyo?(this._time=0,this.play()):this.reset():(this._time=t,this.update())},t.prototype.getTime=function(){return this._time},t.prototype.setPosition=function(t){this.prevPos=this._pos,this.propFunc(t),this._pos=t},t.prototype.getPosition=function(t){return void 0===t&&(t=this._time),this.func(t,this.begin,this._change,this.duration)},t.prototype.play=function(){this.state=2,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onPlay")},t.prototype.reverse=function(){this.state=3,this._time=this.duration-this._time,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onReverse")},t.prototype.seek=function(t){this.pause(),this._time=t,this.update(),this.fire("onSeek")},t.prototype.reset=function(){this.pause(),this._time=0,this.update(),this.fire("onReset")},t.prototype.finish=function(){this.pause(),this._time=this.duration,this.update(),this.fire("onFinish")},t.prototype.update=function(){this.setPosition(this.getPosition(this._time))},t.prototype.onEnterFrame=function(){var t=this.getTimer()-this._startTime;2===this.state?this.setTime(t):3===this.state&&this.setTime(this.duration-t)},t.prototype.pause=function(){this.state=1,this.fire("onPause")},t.prototype.getTimer=function(){return(new Date).getTime()},t}(),Wt=function(){function p(t){var e,i,r=this,n=t.node,a=n._id,o=t.easing||Yt.Linear,s=!!t.yoyo;e=void 0===t.duration?.3:0===t.duration?.001:t.duration,this.node=n,this._id=Vt++;var h=n.getLayer()||(n instanceof D().Stage?n.getLayers():null);for(i in h||O.error("Tween constructor have `node` that is not in a layer. Please add node into layer first."),this.anim=new F(function(){r.tween.onEnterFrame()},h),this.tween=new Ht(i,function(t){r._tweenFunc(t)},o,0,1,1e3*e,s),this._addListeners(),p.attrs[a]||(p.attrs[a]={}),p.attrs[a][this._id]||(p.attrs[a][this._id]={}),p.tweens[a]||(p.tweens[a]={}),t)void 0===Bt[i]&&this._addAttr(i,t[i]);this.reset(),this.onFinish=t.onFinish,this.onReset=t.onReset}return p.prototype._addAttr=function(t,e){var i,r,n,a,o,s,h,l,d=this.node,c=d._id;if((n=p.tweens[c][t])&&delete p.attrs[c][n][t],i=d.getAttr(t),O._isArray(e))if(r=[],o=Math.max(e.length,i.length),"points"===t&&e.length!==i.length&&(e.length>i.length?(h=i,i=O._prepareArrayForTween(i,e,d.closed())):(s=e,e=O._prepareArrayForTween(e,i,d.closed()))),0===t.indexOf("fill"))for(a=0;athis.dataArray[i].pathLength;)t-=this.dataArray[i].pathLength,++i;if(i===r)return{x:(e=this.dataArray[i-1].points.slice(-2))[0],y:e[1]};if(t<.01)return{x:(e=this.dataArray[i].points.slice(0,2))[0],y:e[1]};var n=this.dataArray[i],a=n.points;switch(n.command){case"L":return p.getPointOnLine(t,n.start.x,n.start.y,a[0],a[1]);case"C":return p.getPointOnCubicBezier(t/n.pathLength,n.start.x,n.start.y,a[0],a[1],a[2],a[3],a[4],a[5]);case"Q":return p.getPointOnQuadraticBezier(t/n.pathLength,n.start.x,n.start.y,a[0],a[1],a[2],a[3]);case"A":var o=a[0],s=a[1],h=a[2],l=a[3],d=a[4],c=a[5],u=a[6];return d+=c*t/n.pathLength,p.getPointOnEllipticalArc(o,s,h,l,d,u)}return null},p.getLineLength=function(t,e,i,r){return Math.sqrt((i-t)*(i-t)+(r-e)*(r-e))},p.getPointOnLine=function(t,e,i,r,n,a,o){void 0===a&&(a=e),void 0===o&&(o=i);var s=(n-i)/(r-e+1e-8),h=Math.sqrt(t*t/(1+s*s));r>>1,P=_.slice(0,k+1),T=this._getTextWidth(P)+v;T<=l?(S=k+1,w=P+(g?"…":""),C=T):x=k}if(!w)break;if(f){var M,G=_[w.length];0<(M=(" "===G||"-"===G)&&C<=l?w.length:Math.max(w.lastIndexOf(" "),w.lastIndexOf("-"))+1)&&(S=M,w=w.slice(0,S),C=this._getTextWidth(w))}if(w=w.trimRight(),this._addTextLine(w),i=Math.max(i,C),c+=r,!p||s&&de?g=ne.getPointOnLine(e,f.x,f.y,v.points[0],v.points[1],f.x,f.y):v=void 0;break;case"A":var o=v.points[4],s=v.points[5],h=v.points[4]+s;0===m?m=o+1e-8:iv.pathLength?1e-8:e/v.pathLength:i>V,0!==C?(C=255/C,P[s]=(l*B>>V)*C,P[s+1]=(d*B>>V)*C,P[s+2]=(c*B>>V)*C):P[s]=P[s+1]=P[s+2]=0,l-=p,d-=f,c-=g,u-=v,p-=F.r,f-=F.g,g-=F.b,v-=F.a,a=h+((a=i+e+1)>V,0>V)*C,P[a+1]=(d*B>>V)*C,P[a+2]=(c*B>>V)*C):P[a]=P[a+1]=P[a+2]=0,l-=p,d-=f,c-=g,u-=v,p-=F.r,f-=F.g,g-=F.b,v-=F.a,a=i+((a=r+L); fillRadialGradientEndRadius: GetSet; fillRadialGradientColorStops: GetSet, this>; + fillRadialGradientStartPoint: GetSet; + fillRadialGradientStartPointX: GetSet; + fillRadialGradientStartPointY: GetSet; + fillRadialGradientEndPoint: GetSet; + fillRadialGradientEndPointX: GetSet; + fillRadialGradientEndPointY: GetSet; fillPatternOffset: GetSet; fillPatternOffsetX: GetSet; fillPatternOffsetY: GetSet; diff --git a/test/unit/Shape-test.js b/test/unit/Shape-test.js index 6ee48311..799587f5 100644 --- a/test/unit/Shape-test.js +++ b/test/unit/Shape-test.js @@ -377,7 +377,7 @@ suite('Shape', function() { assert.equal( trace, - 'clearRect(0,0,578,200);save();transform(3,0,0,1.5,10,15);beginPath();rect(0,0,100,100);closePath();createLinearGradient(0,0,100,100);fillStyle=[object CanvasGradient];fill();lineWidth=2;createLinearGradient(0,0,100,100);strokeStyle=[object CanvasGradient];stroke();restore();' + 'clearRect(0,0,578,200);save();transform(3,0,0,1.5,10,15);beginPath();rect(0,0,100,100);closePath();fillStyle=[object CanvasGradient];fill();lineWidth=2;createLinearGradient(0,0,100,100);strokeStyle=[object CanvasGradient];stroke();restore();' ); }); @@ -1471,4 +1471,307 @@ suite('Shape', function() { layer.add(rect); stage.add(layer); }); + + // ====================================================== + test('cache fill pattern', function(done) { + var imageObj = new Image(); + imageObj.onload = function() { + var stage = addStage(); + var layer = new Konva.Layer(); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + + fillPatternImage: imageObj, + fillPatternX: -20, + fillPatternY: -30, + fillPatternScale: { x: 0.5, y: 0.5 }, + fillPatternOffset: { x: 219, y: 150 }, + fillPatternRotation: 90, + fillPatternRepeat: 'no-repeat', + + stroke: 'blue', + strokeWidth: 5, + draggable: true + }); + + layer.add(star); + stage.add(layer); + + var ctx = layer.getContext(); + var oldCreate = ctx.createPattern; + + var callCount = 0; + ctx.createPattern = function() { + callCount += 1; + return oldCreate.apply(this, arguments); + }; + + layer.draw(); + layer.draw(); + assert.equal(callCount, 0); + done(); + }; + imageObj.src = 'assets/darth-vader.jpg'; + }); + + test('recache fill pattern on changes', function(done) { + var imageObj = new Image(); + imageObj.onload = function() { + var stage = addStage(); + var layer = new Konva.Layer(); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + + fillPatternImage: imageObj, + fillPatternX: -20, + fillPatternY: -30, + fillPatternScale: { x: 0.5, y: 0.5 }, + fillPatternOffset: { x: 219, y: 150 }, + fillPatternRotation: 90, + fillPatternRepeat: 'no-repeat', + + stroke: 'blue', + strokeWidth: 5, + draggable: true + }); + + layer.add(star); + stage.add(layer); + + var pattern1 = star._getFillPattern(); + + star.fillPatternImage(document.createElement('canvas')); + + var pattern2 = star._getFillPattern(); + + assert.notEqual(pattern1, pattern2); + + star.fillPatternRepeat('repeat'); + + var pattern3 = star._getFillPattern(); + + assert.notEqual(pattern2, pattern3); + + done(); + }; + imageObj.src = 'assets/darth-vader.jpg'; + }); + + // ====================================================== + test('cache linear gradient', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + + fillLinearGradientStartPoint: { x: -50, y: -50 }, + fillLinearGradientEndPoint: { x: 50, y: 50 }, + fillLinearGradientColorStops: [0, 'red', 1, 'yellow'], + + stroke: 'blue', + strokeWidth: 5, + draggable: true + }); + + layer.add(star); + stage.add(layer); + + var ctx = layer.getContext(); + var oldCreate = ctx.createLinearGradient; + + var callCount = 0; + ctx.createLinearGradient = function() { + callCount += 1; + return oldCreate.apply(this, arguments); + }; + + layer.draw(); + layer.draw(); + assert.equal(callCount, 0); + }); + + test('recache linear gradient on changes', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + + fillLinearGradientStartPoint: { x: -50, y: -50 }, + fillLinearGradientEndPoint: { x: 50, y: 50 }, + fillLinearGradientColorStops: [0, 'red', 1, 'yellow'], + + stroke: 'blue', + strokeWidth: 5, + draggable: true + }); + + layer.add(star); + stage.add(layer); + + var gradient1 = star._getLinearGradient(); + + star.fillLinearGradientStartPointX(-10); + + var gradient2 = star._getLinearGradient(); + + assert.notEqual(gradient1, gradient2); + + star.fillLinearGradientStartPointY(-10); + + var gradient3 = star._getLinearGradient(); + + assert.notEqual(gradient2, gradient3); + + star.fillLinearGradientEndPointX(100); + + var gradient4 = star._getLinearGradient(); + + assert.notEqual(gradient3, gradient4); + + star.fillLinearGradientEndPointY(100); + + var gradient5 = star._getLinearGradient(); + + assert.notEqual(gradient4, gradient5); + + star.fillLinearGradientColorStops([0, 'red', 1, 'green']); + + var gradient6 = star._getLinearGradient(); + + assert.notEqual(gradient5, gradient6); + + layer.draw(); + }); + + // ====================================================== + test('cache radial gradient', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + + fillRadialGradientStartPoint: { x: 0, y: 0 }, + fillRadialGradientStartRadius: 0, + fillRadialGradientEndPoint: { x: 0, y: 0 }, + fillRadialGradientEndRadius: 70, + fillRadialGradientColorStops: [0, 'red', 0.5, 'yellow', 1, 'blue'], + + stroke: 'blue', + strokeWidth: 5, + draggable: true + }); + + layer.add(star); + stage.add(layer); + + var ctx = layer.getContext(); + var oldCreate = ctx.createRadialGradient; + + var callCount = 0; + ctx.createRadialGradient = function() { + callCount += 1; + return oldCreate.apply(this, arguments); + }; + + layer.draw(); + layer.draw(); + assert.equal(callCount, 0); + }); + + test('recache linear gradient on changes', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, + + fillRadialGradientStartPoint: { x: 0, y: 0 }, + fillRadialGradientStartRadius: 0, + fillRadialGradientEndPoint: { x: 0, y: 0 }, + fillRadialGradientEndRadius: 70, + fillRadialGradientColorStops: [0, 'red', 0.5, 'yellow', 1, 'blue'], + + stroke: 'blue', + strokeWidth: 5, + draggable: true + }); + + layer.add(star); + stage.add(layer); + + var gradient1 = star._getRadialGradient(); + + star.fillRadialGradientStartPointX(-10); + + var gradient2 = star._getRadialGradient(); + + assert.notEqual(gradient1, gradient2); + + star.fillRadialGradientStartPointY(-10); + + var gradient3 = star._getRadialGradient(); + + assert.notEqual(gradient2, gradient3); + + star.fillRadialGradientEndPointX(100); + + var gradient4 = star._getRadialGradient(); + + assert.notEqual(gradient3, gradient4); + + star.fillRadialGradientEndPointY(100); + + var gradient5 = star._getRadialGradient(); + + assert.notEqual(gradient4, gradient5); + + star.fillRadialGradientColorStops([0, 'red', 1, 'green']); + + var gradient6 = star._getRadialGradient(); + + assert.notEqual(gradient5, gradient6); + + star.fillRadialGradientStartRadius(10); + + var gradient7 = star._getRadialGradient(); + + assert.notEqual(gradient6, gradient7); + + star.fillRadialGradientEndRadius(200); + + var gradient8 = star._getRadialGradient(); + + assert.notEqual(gradient7, gradient8); + + layer.draw(); + }); });