Compare commits

...

24 Commits

Author SHA1 Message Date
Anton Lavrenov
27fc1b1ba5
Merge pull request #1933 from konvajs/codex/resolve-mouseleave-vs-pointerleave-behavior
Some checks failed
Test Browser / build (20.x) (push) Has been cancelled
Test NodeJS / build (23.x) (push) Has been cancelled
Fix pointerleave bubbling behavior
2025-06-05 16:18:06 -05:00
Anton Lavrenov
cdd61d7179 fix pointerleave bubbling 2025-06-05 16:13:49 -05:00
Anton Lavrenov
f00fd70756
Merge pull request #1929 from MarwanFr/patch-1
Some checks failed
Test Browser / build (20.x) (push) Has been cancelled
Test NodeJS / build (23.x) (push) Has been cancelled
Fix url of docs and api
2025-05-05 12:51:58 -05:00
Marwan
faa2203da7
Fix url of docs and api 2025-05-03 14:11:59 +02:00
Anton Lavrenov
3442071bdd
Merge pull request #1919 from tbo47/typescript-improve
Some checks failed
Test Browser / build (20.x) (push) Has been cancelled
Test NodeJS / build (23.x) (push) Has been cancelled
typescript improvments
2025-04-10 17:59:50 -05:00
tbo47
9c59566a9b typescript improvments 2025-04-10 13:05:36 +00:00
Anton Lavrenov
ca9be838a8
Merge pull request #1915 from tbo47/master
add types
2025-04-08 11:10:29 -05:00
tbo47
09f2838d43 add types 2025-04-08 14:59:13 +00:00
tbo47
a4980fccdc add types 2025-04-08 14:46:18 +00:00
Anton Lavrenov
35873586db
Merge pull request #1913 from tbo47/master
var let const
2025-04-04 20:15:30 -05:00
tbo47
88e3d2a088 var let const 2025-04-04 13:01:18 +00:00
Anton Lavrevov
791a786c81 Merge branch 'master' of github.com:konvajs/konva 2025-04-03 17:21:20 -05:00
Anton Lavrevov
a29157a528 FINALLY FIX THAT DAMN NODE TEST! 2025-04-03 17:21:13 -05:00
Anton Lavrenov
dc8b9df1f5
Merge pull request #1910 from tbo47/master
let var const
2025-04-03 14:36:23 -05:00
Anton Lavrevov
1ff1885de9 changes 2025-04-03 13:04:23 -05:00
Anton Lavrevov
3d5a216f23 Merge branch 'master' of github.com:konvajs/konva 2025-04-03 13:03:32 -05:00
Anton Lavrevov
2e08f7319f fix caching when buffer is used. close #1886
fix svg length calculation. close #1869
2025-04-03 13:03:06 -05:00
tbo47
e12365f14f let var const 2025-03-31 16:30:12 +00:00
Anton Lavrenov
fe80a44407
Merge pull request #1905 from tbo47/master
refactor const let
2025-03-28 14:19:03 -05:00
Anton Lavrevov
8211db3233 update justify option 2025-03-28 14:13:08 -05:00
Anton Lavrevov
f32a416e03 Fixed tween destroy memory leak. close #1898 2025-03-28 13:08:04 -05:00
Anton Lavrevov
fd77f305d1 fix buffer usage in export for stage and layer. fix #1903 2025-03-28 12:52:40 -05:00
tbo47
53a4b494ee better let const var variable 2025-03-27 10:16:05 +00:00
tbo47
9f5a7f0e2d better let const var variable 2025-03-27 08:58:12 +00:00
32 changed files with 496 additions and 330 deletions

1
.gitignore vendored
View File

@ -19,6 +19,7 @@ src_old
types types
out.png out.png
cmj cmj
.test-temp
# Numerous always-ignore extensions # Numerous always-ignore extensions
*.diff *.diff

View File

@ -3,6 +3,13 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
## 9.3.21 (not released)
- Fixed memory leaks on Tween destroy
- Fixed incorrect export of stage/layer when internal nodes used buffer canvas for rendering
- Fixed incorrect render of cached node when buffer canvas is used
- Fixed incorrect path lenth calculations
## 9.3.20 (2025-03-20) ## 9.3.20 (2025-03-20)
- Fix text rendering when ellipses are used - Fix text rendering when ellipses are used

View File

@ -16,7 +16,7 @@ You can draw things onto the stage, add event listeners to them, move them, scal
This repository began as a GitHub fork of [ericdrowell/KineticJS](https://github.com/ericdrowell/KineticJS). This repository began as a GitHub fork of [ericdrowell/KineticJS](https://github.com/ericdrowell/KineticJS).
- **Visit:** The [Home Page](http://konvajs.org/) and follow on [Twitter](https://twitter.com/lavrton) - **Visit:** The [Home Page](http://konvajs.org/) and follow on [Twitter](https://twitter.com/lavrton)
- **Discover:** [Tutorials](http://konvajs.org/docs), [API Documentation](http://konvajs.org/api) - **Discover:** [Tutorials](http://konvajs.org/docs/index.html), [API Documentation](http://konvajs.org/api/Konva.html)
- **Help:** [StackOverflow](http://stackoverflow.com/questions/tagged/konvajs), [Discord Chat](https://discord.gg/8FqZwVT) - **Help:** [StackOverflow](http://stackoverflow.com/questions/tagged/konvajs), [Discord Chat](https://discord.gg/8FqZwVT)
# Quick Look # Quick Look

View File

@ -22,7 +22,8 @@
"test:build": "PARCEL_WORKER_BACKEND=process parcel build ./test/unit-tests.html --dist-dir ./test-build --target none --public-url ./ --no-source-maps", "test:build": "PARCEL_WORKER_BACKEND=process parcel build ./test/unit-tests.html --dist-dir ./test-build --target none --public-url ./ --no-source-maps",
"test:browser": "npm run test:build && mocha-headless-chrome -f ./test-build/unit-tests.html -a disable-web-security -a no-sandbox -a disable-setuid-sandbox", "test:browser": "npm run test:build && mocha-headless-chrome -f ./test-build/unit-tests.html -a disable-web-security -a no-sandbox -a disable-setuid-sandbox",
"test:watch": "rm -rf ./.parcel-cache && PARCEL_WORKERS=0 parcel serve ./test/unit-tests.html ./test/manual-tests.html ./test/sandbox.html ./test/text-paths.html ./test/bunnies.html", "test:watch": "rm -rf ./.parcel-cache && PARCEL_WORKERS=0 parcel serve ./test/unit-tests.html ./test/manual-tests.html ./test/sandbox.html ./test/text-paths.html ./test/bunnies.html",
"test:node": "ts-mocha -r ./test/node-global-setup.mjs -p ./test/tsconfig.json test/unit/**/*.ts --exit && npm run test:import", "test:node:compiled": "rm -rf ./.test-temp && mkdir ./.test-temp && (tsc -p ./test/tsconfig.json --outDir ./.test-temp || true) && mocha './.test-temp/test/unit/**/*.js' -r ./test/node-global-setup.mjs --exit && rm -rf ./.test-temp && npm run test:import",
"test:node": "npm run test:node:compiled",
"tsc": "tsc --removeComments", "tsc": "tsc --removeComments",
"rollup": "rollup -c --bundleConfigAsCjs", "rollup": "rollup -c --bundleConfigAsCjs",
"clean": "rm -rf ./lib && rm -rf ./types && rm -rf ./cmj && rm -rf ./test-build", "clean": "rm -rf ./lib && rm -rf ./types && rm -rf ./cmj && rm -rf ./test-build",

View File

@ -1,8 +1,6 @@
import { Util } from './Util'; import { Util } from './Util';
import { SceneContext, HitContext, Context } from './Context'; import { SceneContext, HitContext, Context } from './Context';
import { Konva } from './Global'; import { Konva } from './Global';
import { Factory } from './Factory';
import { getNumberValidator } from './Validators';
// calculate pixel ratio // calculate pixel ratio
let _pixelRatio; let _pixelRatio;

View File

@ -1,11 +1,10 @@
import { Factory } from './Factory';
import { Node, NodeConfig } from './Node';
import { getNumberValidator } from './Validators';
import { GetSet, IRect } from './types';
import { Shape } from './Shape';
import { HitCanvas, SceneCanvas } from './Canvas'; import { HitCanvas, SceneCanvas } from './Canvas';
import { SceneContext } from './Context'; import { SceneContext } from './Context';
import { Factory } from './Factory';
import { Node, NodeConfig } from './Node';
import { Shape } from './Shape';
import { GetSet, IRect } from './types';
import { getNumberValidator } from './Validators';
export type ClipFuncOutput = export type ClipFuncOutput =
| void | void
@ -51,18 +50,11 @@ export abstract class Container<
* }); * });
*/ */
getChildren(filterFunc?: (item: Node) => boolean) { getChildren(filterFunc?: (item: Node) => boolean) {
if (!filterFunc) {
return this.children || [];
}
const children = this.children || []; const children = this.children || [];
const results: Array<ChildType> = []; if (filterFunc) {
children.forEach(function (child) { return children.filter(filterFunc);
if (filterFunc(child)) { }
results.push(child); return children;
}
});
return results;
} }
/** /**
* determine if node has children * determine if node has children
@ -120,8 +112,6 @@ export abstract class Container<
* layer.add(shape1, shape2, shape3); * layer.add(shape1, shape2, shape3);
* // empty arrays are accepted, though each individual child must be defined * // empty arrays are accepted, though each individual child must be defined
* layer.add(...shapes); * layer.add(...shapes);
* // remember to redraw layer if you changed something
* layer.draw();
*/ */
add(...children: ChildType[]) { add(...children: ChildType[]) {
if (children.length === 0) { if (children.length === 0) {
@ -235,7 +225,7 @@ export abstract class Container<
this._descendants((node) => { this._descendants((node) => {
const valid = node._isMatch(selector); const valid = node._isMatch(selector);
if (valid) { if (valid) {
retArr.push(node as unknown as ChildNode); retArr.push(node as ChildNode);
} }
if (valid && findOne) { if (valid && findOne) {
return true; return true;

View File

@ -70,7 +70,7 @@ export const Factory = {
attr: U, attr: U,
def?: Value<T, U> def?: Value<T, U>
) { ) {
var method = GET + Util._capitalize(attr); const method = GET + Util._capitalize(attr);
constructor.prototype[method] = constructor.prototype[method] =
constructor.prototype[method] || constructor.prototype[method] ||
@ -86,7 +86,7 @@ export const Factory = {
validator?: ValidatorFunc<Value<T, U>>, validator?: ValidatorFunc<Value<T, U>>,
after?: AfterFunc<T> after?: AfterFunc<T>
) { ) {
var method = SET + Util._capitalize(attr); const method = SET + Util._capitalize(attr);
if (!constructor.prototype[method]) { if (!constructor.prototype[method]) {
Factory.overWriteSetter(constructor, attr, validator, after); Factory.overWriteSetter(constructor, attr, validator, after);
@ -99,7 +99,7 @@ export const Factory = {
validator?: ValidatorFunc<Value<T, U>>, validator?: ValidatorFunc<Value<T, U>>,
after?: AfterFunc<T> after?: AfterFunc<T>
) { ) {
var method = SET + Util._capitalize(attr); const method = SET + Util._capitalize(attr);
constructor.prototype[method] = function (val) { constructor.prototype[method] = function (val) {
if (validator && val !== undefined && val !== null) { if (validator && val !== undefined && val !== null) {
val = validator.call(this, val, attr); val = validator.call(this, val, attr);
@ -180,7 +180,7 @@ export const Factory = {
constructor: T, constructor: T,
attr: U attr: U
) { ) {
var capitalizedAttr = Util._capitalize(attr), const capitalizedAttr = Util._capitalize(attr),
setter = SET + capitalizedAttr, setter = SET + capitalizedAttr,
getter = GET + capitalizedAttr; getter = GET + capitalizedAttr;

View File

@ -114,7 +114,7 @@ export class Layer extends Container<Group | Shape> {
return this; return this;
} }
// extend Node.prototype.setZIndex // extend Node.prototype.setZIndex
setZIndex(index) { setZIndex(index: number) {
super.setZIndex(index); super.setZIndex(index);
const stage = this.getStage(); const stage = this.getStage();
if (stage && stage.content) { if (stage && stage.content) {
@ -385,7 +385,7 @@ export class Layer extends Container<Group | Shape> {
// empty pixel // empty pixel
return {}; return {};
} }
drawScene(can?: SceneCanvas, top?: Node) { drawScene(can?: SceneCanvas, top?: Node, bufferCanvas?: SceneCanvas) {
const layer = this.getLayer(), const layer = this.getLayer(),
canvas = can || (layer && layer.getCanvas()); canvas = can || (layer && layer.getCanvas());
@ -397,7 +397,7 @@ export class Layer extends Container<Group | Shape> {
canvas.getContext().clear(); canvas.getContext().clear();
} }
Container.prototype.drawScene.call(this, canvas, top); Container.prototype.drawScene.call(this, canvas, top, bufferCanvas);
this._fire(DRAW, { this._fire(DRAW, {
node: this, node: this,

View File

@ -1,19 +1,19 @@
import { Util, Transform } from './Util'; import { Canvas, HitCanvas, SceneCanvas } from './Canvas';
import { Factory } from './Factory';
import { SceneCanvas, HitCanvas, Canvas } from './Canvas';
import { Konva } from './Global';
import { Container } from './Container'; import { Container } from './Container';
import { GetSet, Vector2d, IRect } from './types'; import { Context } from './Context';
import { DD } from './DragAndDrop'; import { DD } from './DragAndDrop';
import { Factory } from './Factory';
import { Konva } from './Global';
import { Layer } from './Layer';
import { Shape } from './Shape';
import { Stage } from './Stage';
import { GetSet, IRect, Vector2d } from './types';
import { Transform, Util } from './Util';
import { import {
getBooleanValidator,
getNumberValidator, getNumberValidator,
getStringValidator, getStringValidator,
getBooleanValidator,
} from './Validators'; } from './Validators';
import { Stage } from './Stage';
import { Context } from './Context';
import { Shape } from './Shape';
import { Layer } from './Layer';
export type Filter = (this: Node, imageData: ImageData) => void; export type Filter = (this: Node, imageData: ImageData) => void;
@ -88,6 +88,10 @@ const ABSOLUTE_OPACITY = 'absoluteOpacity',
LISTENING = 'listening', LISTENING = 'listening',
MOUSEENTER = 'mouseenter', MOUSEENTER = 'mouseenter',
MOUSELEAVE = 'mouseleave', MOUSELEAVE = 'mouseleave',
POINTERENTER = 'pointerenter',
POINTERLEAVE = 'pointerleave',
TOUCHENTER = 'touchenter',
TOUCHLEAVE = 'touchleave',
NAME = 'name', NAME = 'name',
SET = 'set', SET = 'set',
SHAPE = 'Shape', SHAPE = 'Shape',
@ -248,8 +252,8 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
*/ */
clearCache() { clearCache() {
if (this._cache.has(CANVAS)) { if (this._cache.has(CANVAS)) {
const { scene, filter, hit } = this._cache.get(CANVAS); const { scene, filter, hit, buffer } = this._cache.get(CANVAS);
Util.releaseCanvas(scene, filter, hit); Util.releaseCanvas(scene, filter, hit, buffer);
this._cache.delete(CANVAS); this._cache.delete(CANVAS);
} }
@ -385,6 +389,18 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
sceneContext = cachedSceneCanvas.getContext(), sceneContext = cachedSceneCanvas.getContext(),
hitContext = cachedHitCanvas.getContext(); hitContext = cachedHitCanvas.getContext();
const bufferCanvas = new SceneCanvas({
// width and height already multiplied by pixelRatio
// so we need to revert that
// also increase size by x nd y offset to make sure content fits canvas
width:
cachedSceneCanvas.width / cachedSceneCanvas.pixelRatio + Math.abs(x),
height:
cachedSceneCanvas.height / cachedSceneCanvas.pixelRatio + Math.abs(y),
pixelRatio: cachedSceneCanvas.pixelRatio,
}),
bufferContext = bufferCanvas.getContext();
cachedHitCanvas.isCache = true; cachedHitCanvas.isCache = true;
cachedSceneCanvas.isCache = true; cachedSceneCanvas.isCache = true;
@ -398,16 +414,23 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
sceneContext.save(); sceneContext.save();
hitContext.save(); hitContext.save();
bufferContext.save();
sceneContext.translate(-x, -y); sceneContext.translate(-x, -y);
hitContext.translate(-x, -y); hitContext.translate(-x, -y);
bufferContext.translate(-x, -y);
// hard-code offset to make sure content fits canvas
// @ts-ignore
bufferCanvas.x = x;
// @ts-ignore
bufferCanvas.y = y;
// extra flag to skip on getAbsolute opacity calc // extra flag to skip on getAbsolute opacity calc
this._isUnderCache = true; this._isUnderCache = true;
this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY);
this._clearSelfAndDescendantCache(ABSOLUTE_SCALE); this._clearSelfAndDescendantCache(ABSOLUTE_SCALE);
this.drawScene(cachedSceneCanvas, this); this.drawScene(cachedSceneCanvas, this, bufferCanvas);
this.drawHit(cachedHitCanvas, this); this.drawHit(cachedHitCanvas, this);
this._isUnderCache = false; this._isUnderCache = false;
@ -431,6 +454,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
scene: cachedSceneCanvas, scene: cachedSceneCanvas,
filter: cachedFilterCanvas, filter: cachedFilterCanvas,
hit: cachedHitCanvas, hit: cachedHitCanvas,
buffer: bufferCanvas,
x: x, x: x,
y: y, y: y,
}); });
@ -693,39 +717,32 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
evtStr: K, evtStr: K,
handler: KonvaEventListener<this, NodeEventMap[K]> handler: KonvaEventListener<this, NodeEventMap[K]>
) { ) {
this._cache && this._cache.delete(ALL_LISTENERS); if (this._cache) {
this._cache.delete(ALL_LISTENERS);
}
if (arguments.length === 3) { if (arguments.length === 3) {
return this._delegate.apply(this, arguments as any); return this._delegate.apply(this, arguments as any);
} }
let events = (evtStr as string).split(SPACE), const events = (evtStr as string).split(SPACE);
len = events.length,
n,
event,
parts,
baseEvent,
name;
/* /*
* loop through types and attach event listeners to * loop through types and attach event listeners to
* each one. eg. 'click mouseover.namespace mouseout' * each one. eg. 'click mouseover.namespace mouseout'
* will create three event bindings * will create three event bindings
*/ */
for (n = 0; n < len; n++) { for (let n = 0; n < events.length; n++) {
event = events[n]; const event = events[n];
parts = event.split('.'); const parts = event.split('.');
baseEvent = parts[0]; const baseEvent = parts[0];
name = parts[1] || ''; const name = parts[1] || '';
// create events array if it doesn't exist // create events array if it doesn't exist
if (!this.eventListeners[baseEvent]) { if (!this.eventListeners[baseEvent]) {
this.eventListeners[baseEvent] = []; this.eventListeners[baseEvent] = [];
} }
this.eventListeners[baseEvent].push({ this.eventListeners[baseEvent].push({ name, handler });
name: name,
handler: handler,
});
} }
return this; return this;
@ -881,13 +898,13 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @example * @example
* var x = node.getAttr('x'); * var x = node.getAttr('x');
*/ */
getAttr(attr: string) { getAttr<T>(attr: string) {
const method = 'get' + Util._capitalize(attr); const method = 'get' + Util._capitalize(attr);
if (Util._isFunction((this as any)[method])) { if (Util._isFunction((this as any)[method])) {
return (this as any)[method](); return (this as any)[method]();
} }
// otherwise get directly // otherwise get directly
return this.attrs[attr]; return this.attrs[attr] as T | undefined;
} }
/** /**
* get ancestors * get ancestors
@ -2208,7 +2225,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* node.addName('selected'); * node.addName('selected');
* node.name(); // return 'red selected' * node.name(); // return 'red selected'
*/ */
addName(name) { addName(name: string) {
if (!this.hasName(name)) { if (!this.hasName(name)) {
const oldName = this.name(); const oldName = this.name();
const newName = oldName ? oldName + ' ' + name : name; const newName = oldName ? oldName + ' ' + name : name;
@ -2271,7 +2288,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
* @example * @example
* node.setAttr('x', 5); * node.setAttr('x', 5);
*/ */
setAttr(attr, val) { setAttr(attr: string, val) {
const func = this[SET + Util._capitalize(attr)]; const func = this[SET + Util._capitalize(attr)];
if (Util._isFunction(func)) { if (Util._isFunction(func)) {
@ -2288,7 +2305,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
drawNode?.batchDraw(); drawNode?.batchDraw();
} }
} }
_setAttr(key, val) { _setAttr(key: string, val) {
const oldVal = this.attrs[key]; const oldVal = this.attrs[key];
if (oldVal === val && !Util.isObject(val)) { if (oldVal === val && !Util.isObject(val)) {
return; return;
@ -2322,8 +2339,17 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
evt.target = this; evt.target = this;
} }
const nonBubbling = [
MOUSEENTER,
MOUSELEAVE,
POINTERENTER,
POINTERLEAVE,
TOUCHENTER,
TOUCHLEAVE,
];
const shouldStop = const shouldStop =
(eventType === MOUSEENTER || eventType === MOUSELEAVE) && nonBubbling.indexOf(eventType) !== -1 &&
((compareShape && ((compareShape &&
(this === compareShape || (this === compareShape ||
(this.isAncestorOf && this.isAncestorOf(compareShape)))) || (this.isAncestorOf && this.isAncestorOf(compareShape)))) ||
@ -2334,7 +2360,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
// simulate event bubbling // simulate event bubbling
const stopBubble = const stopBubble =
(eventType === MOUSEENTER || eventType === MOUSELEAVE) && nonBubbling.indexOf(eventType) !== -1 &&
compareShape && compareShape &&
compareShape.isAncestorOf && compareShape.isAncestorOf &&
compareShape.isAncestorOf(this) && compareShape.isAncestorOf(this) &&
@ -2380,7 +2406,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
const topListeners = this._getProtoListeners(eventType); const topListeners = this._getProtoListeners(eventType);
if (topListeners) { if (topListeners) {
for (var i = 0; i < topListeners.length; i++) { for (let i = 0; i < topListeners.length; i++) {
topListeners[i].handler.call(this, evt); topListeners[i].handler.call(this, evt);
} }
} }
@ -2389,7 +2415,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
// because events can be added/removed while firing // because events can be added/removed while firing
const selfListeners = this.eventListeners[eventType]; const selfListeners = this.eventListeners[eventType];
if (selfListeners) { if (selfListeners) {
for (var i = 0; i < selfListeners.length; i++) { for (let i = 0; i < selfListeners.length; i++) {
selfListeners[i].handler.call(this, evt); selfListeners[i].handler.call(this, evt);
} }
} }

View File

@ -213,10 +213,16 @@ export class Shape<
shapes[key] = this; shapes[key] = this;
} }
/**
* @deprecated
*/
getContext() { getContext() {
Util.warn('shape.getContext() method is deprecated. Please do not use it.'); Util.warn('shape.getContext() method is deprecated. Please do not use it.');
return this.getLayer()!.getContext(); return this.getLayer()!.getContext();
} }
/**
* @deprecated
*/
getCanvas() { getCanvas() {
Util.warn('shape.getCanvas() method is deprecated. Please do not use it.'); Util.warn('shape.getCanvas() method is deprecated. Please do not use it.');
return this.getLayer()!.getCanvas(); return this.getLayer()!.getCanvas();
@ -442,7 +448,7 @@ export class Shape<
* @param {Number} point.y * @param {Number} point.y
* @returns {Boolean} * @returns {Boolean}
*/ */
intersects(point) { intersects(point: Vector2d) {
const stage = this.getStage(); const stage = this.getStage();
if (!stage) { if (!stage) {
return false; return false;
@ -594,15 +600,14 @@ export class Shape<
// 3 - when node is cached and we need to draw it into layer // 3 - when node is cached and we need to draw it into layer
const layer = this.getLayer(); const layer = this.getLayer();
let canvas = can || layer!.getCanvas(), const canvas = can || layer!.getCanvas(),
context = canvas.getContext() as SceneContext, context = canvas.getContext() as SceneContext,
cachedCanvas = this._getCanvasCache(), cachedCanvas = this._getCanvasCache(),
drawFunc = this.getSceneFunc(), drawFunc = this.getSceneFunc(),
hasShadow = this.hasShadow(), hasShadow = this.hasShadow();
stage, let stage;
bufferContext;
const skipBuffer = canvas.isCache; const skipBuffer = false;
const cachingSelf = top === this; const cachingSelf = top === this;
if (!this.isVisible() && !cachingSelf) { if (!this.isVisible() && !cachingSelf) {
@ -628,12 +633,12 @@ export class Shape<
if (this._useBufferCanvas() && !skipBuffer) { if (this._useBufferCanvas() && !skipBuffer) {
stage = this.getStage(); stage = this.getStage();
const bc = bufferCanvas || stage.bufferCanvas; const bc = bufferCanvas || stage.bufferCanvas;
bufferContext = bc.getContext(); const bufferContext = bc.getContext();
bufferContext.clear(); bufferContext.clear();
bufferContext.save(); bufferContext.save();
bufferContext._applyLineJoin(this); bufferContext._applyLineJoin(this);
// layer might be undefined if we are using cache before adding to layer // layer might be undefined if we are using cache before adding to layer
var o = this.getAbsoluteTransform(top).getMatrix(); const o = this.getAbsoluteTransform(top).getMatrix();
bufferContext.transform(o[0], o[1], o[2], o[3], o[4], o[5]); bufferContext.transform(o[0], o[1], o[2], o[3], o[4], o[5]);
drawFunc.call(this, bufferContext, this); drawFunc.call(this, bufferContext, this);
@ -646,12 +651,18 @@ export class Shape<
} }
context._applyOpacity(this); context._applyOpacity(this);
context._applyGlobalCompositeOperation(this); context._applyGlobalCompositeOperation(this);
context.drawImage(bc._canvas, 0, 0, bc.width / ratio, bc.height / ratio); context.drawImage(
bc._canvas,
bc.x || 0,
bc.y || 0,
bc.width / ratio,
bc.height / ratio
);
} else { } else {
context._applyLineJoin(this); context._applyLineJoin(this);
if (!cachingSelf) { if (!cachingSelf) {
var o = this.getAbsoluteTransform(top).getMatrix(); const o = this.getAbsoluteTransform(top).getMatrix();
context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); context.transform(o[0], o[1], o[2], o[3], o[4], o[5]);
context._applyOpacity(this); context._applyOpacity(this);
context._applyGlobalCompositeOperation(this); context._applyGlobalCompositeOperation(this);

View File

@ -214,11 +214,11 @@ export class Stage extends Container<Layer> {
*/ */
setContainer(container) { setContainer(container) {
if (typeof container === STRING) { if (typeof container === STRING) {
let id;
if (container.charAt(0) === '.') { if (container.charAt(0) === '.') {
const className = container.slice(1); const className = container.slice(1);
container = document.getElementsByClassName(className)[0]; container = document.getElementsByClassName(className)[0];
} else { } else {
var id;
if (container.charAt(0) !== '#') { if (container.charAt(0) !== '#') {
id = container; id = container;
} else { } else {

View File

@ -200,8 +200,7 @@ export class Tween {
nodeId = node._id, nodeId = node._id,
easing = config.easing || Easings.Linear, easing = config.easing || Easings.Linear,
yoyo = !!config.yoyo; yoyo = !!config.yoyo;
let duration, let duration, key;
key;
if (typeof config.duration === 'undefined') { if (typeof config.duration === 'undefined') {
duration = 0.3; duration = 0.3;
@ -268,11 +267,7 @@ export class Tween {
_addAttr(key, end) { _addAttr(key, end) {
const node = this.node, const node = this.node,
nodeId = node._id; nodeId = node._id;
let diff, let diff, len, trueEnd, trueStart, endRGBA;
len,
trueEnd,
trueStart,
endRGBA;
// remove conflict from tween map if it exists // remove conflict from tween map if it exists
const tweenId = Tween.tweens[nodeId][key]; const tweenId = Tween.tweens[nodeId][key];
@ -352,14 +347,7 @@ export class Tween {
_tweenFunc(i) { _tweenFunc(i) {
const node = this.node, const node = this.node,
attrs = Tween.attrs[node._id][this._id]; attrs = Tween.attrs[node._id][this._id];
let key, let key, attr, start, diff, newVal, n, len, end;
attr,
start,
diff,
newVal,
n,
len,
end;
for (key in attrs) { for (key in attrs) {
attr = attrs[key]; attr = attrs[key];
@ -528,11 +516,26 @@ export class Tween {
this.pause(); this.pause();
// Clean up animation
if (this.anim) {
this.anim.stop();
}
// Clean up tween entries
for (const key in attrs) { for (const key in attrs) {
delete Tween.tweens[nodeId][key]; delete Tween.tweens[nodeId][key];
} }
// Clean up attrs entry
delete Tween.attrs[nodeId][thisId]; delete Tween.attrs[nodeId][thisId];
// Clean up parent objects if empty
if (Object.keys(Tween.tweens[nodeId]).length === 0) {
delete Tween.tweens[nodeId];
}
if (Object.keys(Tween.attrs[nodeId]).length === 0) {
delete Tween.attrs[nodeId];
}
} }
} }

View File

@ -72,8 +72,8 @@ export function getNumberOrArrayOfNumbersValidator<T>(noOfElements: number) {
export function getNumberOrAutoValidator<T>() { export function getNumberOrAutoValidator<T>() {
if (Konva.isUnminified) { if (Konva.isUnminified) {
return function (val: T, attr: string): T { return function (val: T, attr: string): T {
var isNumber = Util._isNumber(val); const isNumber = Util._isNumber(val);
var isAuto = val === 'auto'; const isAuto = val === 'auto';
if (!(isNumber || isAuto)) { if (!(isNumber || isAuto)) {
Util.warn( Util.warn(
@ -175,7 +175,7 @@ export function getNumberArrayValidator<T>() {
export function getBooleanValidator<T>() { export function getBooleanValidator<T>() {
if (Konva.isUnminified) { if (Konva.isUnminified) {
return function (val: T, attr: string): T { return function (val: T, attr: string): T {
var isBool = val === true || val === false; const isBool = val === true || val === false;
if (!isBool) { if (!isBool) {
Util.warn( Util.warn(
_formatValue(val) + _formatValue(val) +

View File

@ -96,11 +96,7 @@ function filterGaussBlurRGBA(imageData, radius) {
width = imageData.width, width = imageData.width,
height = imageData.height; height = imageData.height;
let x, let p,
y,
i,
p,
yp,
yi, yi,
yw, yw,
r_sum, r_sum,
@ -135,7 +131,7 @@ function filterGaussBlurRGBA(imageData, radius) {
stackIn: any = null, stackIn: any = null,
stackOut: any = null; stackOut: any = null;
for (i = 1; i < div; i++) { for (let i = 1; i < div; i++) {
stack = stack.next = new BlurStack(); stack = stack.next = new BlurStack();
if (i === radiusPlus1) { if (i === radiusPlus1) {
stackEnd = stack; stackEnd = stack;
@ -146,7 +142,7 @@ function filterGaussBlurRGBA(imageData, radius) {
yw = yi = 0; yw = yi = 0;
for (y = 0; y < height; y++) { for (let y = 0; y < height; y++) {
r_in_sum = r_in_sum =
g_in_sum = g_in_sum =
b_in_sum = b_in_sum =
@ -169,7 +165,7 @@ function filterGaussBlurRGBA(imageData, radius) {
stack = stackStart; stack = stackStart;
for (i = 0; i < radiusPlus1; i++) { for (let i = 0; i < radiusPlus1; i++) {
stack.r = pr; stack.r = pr;
stack.g = pg; stack.g = pg;
stack.b = pb; stack.b = pb;
@ -177,7 +173,7 @@ function filterGaussBlurRGBA(imageData, radius) {
stack = stack.next; stack = stack.next;
} }
for (i = 1; i < radiusPlus1; i++) { for (let i = 1; i < radiusPlus1; i++) {
p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2); p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);
r_sum += (stack.r = pr = pixels[p]) * (rbs = radiusPlus1 - i); r_sum += (stack.r = pr = pixels[p]) * (rbs = radiusPlus1 - i);
g_sum += (stack.g = pg = pixels[p + 1]) * rbs; g_sum += (stack.g = pg = pixels[p + 1]) * rbs;
@ -194,7 +190,7 @@ function filterGaussBlurRGBA(imageData, radius) {
stackIn = stackStart; stackIn = stackStart;
stackOut = stackEnd; stackOut = stackEnd;
for (x = 0; x < width; x++) { for (let x = 0; x < width; x++) {
pixels[yi + 3] = pa = (a_sum * mul_sum) >> shg_sum; pixels[yi + 3] = pa = (a_sum * mul_sum) >> shg_sum;
if (pa !== 0) { if (pa !== 0) {
pa = 255 / pa; pa = 255 / pa;
@ -246,7 +242,7 @@ function filterGaussBlurRGBA(imageData, radius) {
yw += width; yw += width;
} }
for (x = 0; x < width; x++) { for (let x = 0; x < width; x++) {
g_in_sum = g_in_sum =
b_in_sum = b_in_sum =
a_in_sum = a_in_sum =
@ -270,7 +266,7 @@ function filterGaussBlurRGBA(imageData, radius) {
stack = stackStart; stack = stackStart;
for (i = 0; i < radiusPlus1; i++) { for (let i = 0; i < radiusPlus1; i++) {
stack.r = pr; stack.r = pr;
stack.g = pg; stack.g = pg;
stack.b = pb; stack.b = pb;
@ -278,9 +274,9 @@ function filterGaussBlurRGBA(imageData, radius) {
stack = stack.next; stack = stack.next;
} }
yp = width; let yp = width;
for (i = 1; i <= radius; i++) { for (let i = 1; i <= radius; i++) {
yi = (yp + x) << 2; yi = (yp + x) << 2;
r_sum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i); r_sum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i);
@ -303,7 +299,7 @@ function filterGaussBlurRGBA(imageData, radius) {
yi = x; yi = x;
stackIn = stackStart; stackIn = stackStart;
stackOut = stackEnd; stackOut = stackEnd;
for (y = 0; y < height; y++) { for (let y = 0; y < height; y++) {
p = yi << 2; p = yi << 2;
pixels[p + 3] = pa = (a_sum * mul_sum) >> shg_sum; pixels[p + 3] = pa = (a_sum * mul_sum) >> shg_sum;
if (pa > 0) { if (pa > 0) {

View File

@ -94,15 +94,12 @@ export const Enhance: Filter = function (imageData) {
bMin = 0; bMin = 0;
} }
let rMid, let rGoalMax: number,
rGoalMax, rGoalMin: number,
rGoalMin, gGoalMax: number,
gMid, gGoalMin: number,
gGoalMax, bGoalMax: number,
gGoalMin, bGoalMin: number;
bMid,
bGoalMax,
bGoalMin;
// If the enhancement is positive - stretch the histogram // If the enhancement is positive - stretch the histogram
if (enhanceAmount > 0) { if (enhanceAmount > 0) {
@ -114,13 +111,13 @@ export const Enhance: Filter = function (imageData) {
bGoalMin = bMin - enhanceAmount * (bMin - 0); bGoalMin = bMin - enhanceAmount * (bMin - 0);
// If the enhancement is negative - compress the histogram // If the enhancement is negative - compress the histogram
} else { } else {
rMid = (rMax + rMin) * 0.5; const rMid = (rMax + rMin) * 0.5;
rGoalMax = rMax + enhanceAmount * (rMax - rMid); rGoalMax = rMax + enhanceAmount * (rMax - rMid);
rGoalMin = rMin + enhanceAmount * (rMin - rMid); rGoalMin = rMin + enhanceAmount * (rMin - rMid);
gMid = (gMax + gMin) * 0.5; const gMid = (gMax + gMin) * 0.5;
gGoalMax = gMax + enhanceAmount * (gMax - gMid); gGoalMax = gMax + enhanceAmount * (gMax - gMid);
gGoalMin = gMin + enhanceAmount * (gMin - gMid); gGoalMin = gMin + enhanceAmount * (gMin - gMid);
bMid = (bMax + bMin) * 0.5; const bMid = (bMax + bMin) * 0.5;
bGoalMax = bMax + enhanceAmount * (bMax - bMid); bGoalMax = bMax + enhanceAmount * (bMax - bMid);
bGoalMin = bMin + enhanceAmount * (bMin - bMid); bGoalMin = bMin + enhanceAmount * (bMin - bMid);
} }

View File

@ -1,5 +1,5 @@
import { Factory } from '../Factory'; import { Factory } from '../Factory';
import { Node, Filter } from '../Node'; import { Filter, Node } from '../Node';
import { getNumberValidator } from '../Validators'; import { getNumberValidator } from '../Validators';
/** /**
@ -46,13 +46,11 @@ export const HSV: Filter = function (imageData) {
bg = 0.587 * v - 0.586 * vsu - 1.05 * vsw, bg = 0.587 * v - 0.586 * vsu - 1.05 * vsw,
bb = 0.114 * v + 0.886 * vsu - 0.2 * vsw; bb = 0.114 * v + 0.886 * vsu - 0.2 * vsw;
let r, g, b, a;
for (let i = 0; i < nPixels; i += 4) { for (let i = 0; i < nPixels; i += 4) {
r = data[i + 0]; const r = data[i + 0];
g = data[i + 1]; const g = data[i + 1];
b = data[i + 2]; const b = data[i + 2];
a = data[i + 3]; const a = data[i + 3];
data[i + 0] = rr * r + rg * g + rb * b; data[i + 0] = rr * r + rg * g + rb * b;
data[i + 1] = gr * r + gg * g + gb * b; data[i + 1] = gr * r + gg * g + gb * b;

View File

@ -2,7 +2,7 @@ import { Factory } from '../Factory';
import { Filter, Node } from '../Node'; import { Filter, Node } from '../Node';
import { getNumberValidator } from '../Validators'; import { getNumberValidator } from '../Validators';
function pixelAt(idata, x, y) { function pixelAt(idata, x: number, y: number) {
let idx = (y * idata.width + x) * 4; let idx = (y * idata.width + x) * 4;
const d: Array<number> = []; const d: Array<number> = [];
d.push( d.push(
@ -137,7 +137,7 @@ function dilateMask(mask, sw, sh) {
return maskResult; return maskResult;
} }
function smoothEdgeMask(mask, sw, sh) { function smoothEdgeMask(mask, sw: number, sh: number) {
const weights = [1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9]; const weights = [1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9];
const side = Math.round(Math.sqrt(weights.length)); const side = Math.round(Math.sqrt(weights.length));
const halfSide = Math.floor(side / 2); const halfSide = Math.floor(side / 2);

View File

@ -1,4 +1,3 @@
import { Factory } from '../Factory'; import { Factory } from '../Factory';
import { Util } from '../Util'; import { Util } from '../Util';
import { Node, Filter } from '../Node'; import { Node, Filter } from '../Node';
@ -22,23 +21,9 @@ export const Pixelate: Filter = function (imageData) {
let pixelSize = Math.ceil(this.pixelSize()), let pixelSize = Math.ceil(this.pixelSize()),
width = imageData.width, width = imageData.width,
height = imageData.height, height = imageData.height,
x,
y,
i,
//pixelsPerBin = pixelSize * pixelSize, //pixelsPerBin = pixelSize * pixelSize,
red,
green,
blue,
alpha,
nBinsX = Math.ceil(width / pixelSize), nBinsX = Math.ceil(width / pixelSize),
nBinsY = Math.ceil(height / pixelSize), nBinsY = Math.ceil(height / pixelSize),
xBinStart,
xBinEnd,
yBinStart,
yBinEnd,
xBin,
yBin,
pixelsInBin,
data = imageData.data; data = imageData.data;
if (pixelSize <= 0) { if (pixelSize <= 0) {
@ -46,31 +31,31 @@ export const Pixelate: Filter = function (imageData) {
return; return;
} }
for (xBin = 0; xBin < nBinsX; xBin += 1) { for (let xBin = 0; xBin < nBinsX; xBin += 1) {
for (yBin = 0; yBin < nBinsY; yBin += 1) { for (let yBin = 0; yBin < nBinsY; yBin += 1) {
// Initialize the color accumlators to 0 // Initialize the color accumlators to 0
red = 0; let red = 0;
green = 0; let green = 0;
blue = 0; let blue = 0;
alpha = 0; let alpha = 0;
// Determine which pixels are included in this bin // Determine which pixels are included in this bin
xBinStart = xBin * pixelSize; const xBinStart = xBin * pixelSize;
xBinEnd = xBinStart + pixelSize; const xBinEnd = xBinStart + pixelSize;
yBinStart = yBin * pixelSize; const yBinStart = yBin * pixelSize;
yBinEnd = yBinStart + pixelSize; const yBinEnd = yBinStart + pixelSize;
// Add all of the pixels to this bin! // Add all of the pixels to this bin!
pixelsInBin = 0; let pixelsInBin = 0;
for (x = xBinStart; x < xBinEnd; x += 1) { for (let x = xBinStart; x < xBinEnd; x += 1) {
if (x >= width) { if (x >= width) {
continue; continue;
} }
for (y = yBinStart; y < yBinEnd; y += 1) { for (let y = yBinStart; y < yBinEnd; y += 1) {
if (y >= height) { if (y >= height) {
continue; continue;
} }
i = (width * y + x) * 4; const i = (width * y + x) * 4;
red += data[i + 0]; red += data[i + 0];
green += data[i + 1]; green += data[i + 1];
blue += data[i + 2]; blue += data[i + 2];
@ -86,15 +71,15 @@ export const Pixelate: Filter = function (imageData) {
alpha = alpha / pixelsInBin; alpha = alpha / pixelsInBin;
// Draw this bin // Draw this bin
for (x = xBinStart; x < xBinEnd; x += 1) { for (let x = xBinStart; x < xBinEnd; x += 1) {
if (x >= width) { if (x >= width) {
continue; continue;
} }
for (y = yBinStart; y < yBinEnd; y += 1) { for (let y = yBinStart; y < yBinEnd; y += 1) {
if (y >= height) { if (y >= height) {
continue; continue;
} }
i = (width * y + x) * 4; const i = (width * y + x) * 4;
data[i + 0] = red; data[i + 0] = red;
data[i + 1] = green; data[i + 1] = green;
data[i + 2] = blue; data[i + 2] = blue;

View File

@ -163,7 +163,6 @@ export class Image extends Shape<ImageConfig> {
* Konva.Image.fromURL(imageURL, function(image){ * Konva.Image.fromURL(imageURL, function(image){
* // image is Konva.Image instance * // image is Konva.Image instance
* layer.add(image); * layer.add(image);
* layer.draw();
* }); * });
*/ */
static fromURL( static fromURL(

View File

@ -6,7 +6,15 @@ import { getNumberArrayValidator, getNumberValidator } from '../Validators';
import { Context } from '../Context'; import { Context } from '../Context';
import { GetSet } from '../types'; import { GetSet } from '../types';
function getControlPoints(x0, y0, x1, y1, x2, y2, t) { function getControlPoints(
x0: number,
y0: number,
x1: number,
y1: number,
x2: number,
y2: number,
t: number
) {
const d01 = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2)), const d01 = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2)),
d12 = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)), d12 = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)),
fa = (t * d01) / (d01 + d12), fa = (t * d01) / (d01 + d12),
@ -102,26 +110,24 @@ export class Line<
} }
_sceneFunc(context: Context) { _sceneFunc(context: Context) {
let points = this.points(), const points = this.points(),
length = points.length, length = points.length,
tension = this.tension(), tension = this.tension(),
closed = this.closed(), closed = this.closed(),
bezier = this.bezier(), bezier = this.bezier();
tp,
len,
n;
if (!length) { if (!length) {
return; return;
} }
let n = 0;
context.beginPath(); context.beginPath();
context.moveTo(points[0], points[1]); context.moveTo(points[0], points[1]);
// tension // tension
if (tension !== 0 && length > 4) { if (tension !== 0 && length > 4) {
tp = this.getTensionPoints(); const tp = this.getTensionPoints();
len = tp.length; const len = tp.length;
n = closed ? 0 : 4; n = closed ? 0 : 4;
if (!closed) { if (!closed) {

View File

@ -1,13 +1,13 @@
import { Factory } from '../Factory'; import { Factory } from '../Factory';
import { Shape, ShapeConfig } from '../Shape';
import { _registerNode } from '../Global'; import { _registerNode } from '../Global';
import { Shape, ShapeConfig } from '../Shape';
import { GetSet, PathSegment } from '../types';
import { import {
getCubicArcLength, getCubicArcLength,
getQuadraticArcLength, getQuadraticArcLength,
t2length, t2length,
} from '../BezierFunctions'; } from '../BezierFunctions';
import { GetSet, PathSegment } from '../types';
export interface PathConfig extends ShapeConfig { export interface PathConfig extends ShapeConfig {
data?: string; data?: string;
@ -73,7 +73,7 @@ export class Path extends Shape<PathConfig> {
context.quadraticCurveTo(p[0], p[1], p[2], p[3]); context.quadraticCurveTo(p[0], p[1], p[2], p[3]);
break; break;
case 'A': case 'A':
var cx = p[0], const cx = p[0],
cy = p[1], cy = p[1],
rx = p[2], rx = p[2],
ry = p[3], ry = p[3],
@ -82,9 +82,9 @@ export class Path extends Shape<PathConfig> {
psi = p[6], psi = p[6],
fs = p[7]; fs = p[7];
var r = rx > ry ? rx : ry; const r = rx > ry ? rx : ry;
var scaleX = rx > ry ? 1 : rx / ry; const scaleX = rx > ry ? 1 : rx / ry;
var scaleY = rx > ry ? ry / rx : 1; const scaleY = rx > ry ? ry / rx : 1;
context.translate(cx, cy); context.translate(cx, cy);
context.rotate(psi); context.rotate(psi);
@ -217,7 +217,7 @@ export class Path extends Shape<PathConfig> {
* @example * @example
* var point = path.getPointAtLength(10); * var point = path.getPointAtLength(10);
*/ */
getPointAtLength(length) { getPointAtLength(length: number) {
return Path.getPointAtLengthOfDataArray(length, this.dataArray); return Path.getPointAtLengthOfDataArray(length, this.dataArray);
} }
@ -258,11 +258,19 @@ export class Path extends Shape<PathConfig> {
} }
if (length < 0.01) { if (length < 0.01) {
points = dataArray[i].points.slice(0, 2); const cmd = dataArray[i].command;
return { if (cmd === 'M') {
x: points[0], points = dataArray[i].points.slice(0, 2);
y: points[1], return {
}; x: points[0],
y: points[1],
};
} else {
return {
x: dataArray[i].start.x,
y: dataArray[i].start.y,
};
}
} }
const cp = dataArray[i]; const cp = dataArray[i];
@ -305,13 +313,13 @@ export class Path extends Shape<PathConfig> {
p[3] p[3]
); );
case 'A': case 'A':
var cx = p[0], const cx = p[0],
cy = p[1], cy = p[1],
rx = p[2], rx = p[2],
ry = p[3], ry = p[3],
theta = p[4],
dTheta = p[5], dTheta = p[5],
psi = p[6]; psi = p[6];
let theta = p[4];
theta += (dTheta * length) / cp.pathLength; theta += (dTheta * length) / cp.pathLength;
return Path.getPointOnEllipticalArc(cx, cy, rx, ry, theta, psi); return Path.getPointOnEllipticalArc(cx, cy, rx, ry, theta, psi);
} }
@ -362,26 +370,33 @@ export class Path extends Shape<PathConfig> {
return { x: ix + adjustedRun, y: iy + adjustedRise }; return { x: ix + adjustedRun, y: iy + adjustedRise };
} }
static getPointOnCubicBezier(pct, P1x, P1y, P2x, P2y, P3x, P3y, P4x, P4y) { static getPointOnCubicBezier(
function CB1(t) { pct: number,
P1x: number,
P1y: number,
P2x: number,
P2y: number,
P3x: number,
P3y: number,
P4x: number,
P4y: number
) {
function CB1(t: number) {
return t * t * t; return t * t * t;
} }
function CB2(t) { function CB2(t: number) {
return 3 * t * t * (1 - t); return 3 * t * t * (1 - t);
} }
function CB3(t) { function CB3(t: number) {
return 3 * t * (1 - t) * (1 - t); return 3 * t * (1 - t) * (1 - t);
} }
function CB4(t) { function CB4(t: number) {
return (1 - t) * (1 - t) * (1 - t); return (1 - t) * (1 - t) * (1 - t);
} }
const x = P4x * CB1(pct) + P3x * CB2(pct) + P2x * CB3(pct) + P1x * CB4(pct); const x = P4x * CB1(pct) + P3x * CB2(pct) + P2x * CB3(pct) + P1x * CB4(pct);
const y = P4y * CB1(pct) + P3y * CB2(pct) + P2y * CB3(pct) + P1y * CB4(pct); const y = P4y * CB1(pct) + P3y * CB2(pct) + P2y * CB3(pct) + P1y * CB4(pct);
return { return { x, y };
x: x,
y: y,
};
} }
static getPointOnQuadraticBezier(pct, P1x, P1y, P2x, P2y, P3x, P3y) { static getPointOnQuadraticBezier(pct, P1x, P1y, P2x, P2y, P3x, P3y) {
function QB1(t) { function QB1(t) {
@ -396,10 +411,7 @@ export class Path extends Shape<PathConfig> {
const x = P3x * QB1(pct) + P2x * QB2(pct) + P1x * QB3(pct); const x = P3x * QB1(pct) + P2x * QB2(pct) + P1x * QB3(pct);
const y = P3y * QB1(pct) + P2y * QB2(pct) + P1y * QB3(pct); const y = P3y * QB1(pct) + P2y * QB2(pct) + P1y * QB3(pct);
return { return { x, y };
x: x,
y: y,
};
} }
static getPointOnEllipticalArc( static getPointOnEllipticalArc(
cx: number, cx: number,
@ -483,7 +495,7 @@ export class Path extends Shape<PathConfig> {
// convert white spaces to commas // convert white spaces to commas
cs = cs.replace(new RegExp(' ', 'g'), ','); cs = cs.replace(new RegExp(' ', 'g'), ',');
// create pipes so that we can split the data // create pipes so that we can split the data
for (var n = 0; n < cc.length; n++) { for (let n = 0; n < cc.length; n++) {
cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]); cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]);
} }
// create array // create array
@ -496,7 +508,7 @@ export class Path extends Shape<PathConfig> {
const re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi; const re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi;
let match; let match;
for (n = 1; n < arr.length; n++) { for (let n = 1; n < arr.length; n++) {
let str = arr[n]; let str = arr[n];
let c = str.charAt(0); let c = str.charAt(0);
str = str.slice(1); str = str.slice(1);
@ -536,8 +548,8 @@ export class Path extends Shape<PathConfig> {
const startX = cpx, const startX = cpx,
startY = cpy; startY = cpy;
// Move var from within the switch to up here (jshint) // Move var from within the switch to up here (jshint)
var prevCmd, ctlPtx, ctlPty; // Ss, Tt let prevCmd, ctlPtx, ctlPty; // Ss, Tt
var rx, ry, psi, fa, fs, x1, y1; // Aa let rx, ry, psi, fa, fs, x1, y1; // Aa
// convert l, H, h, V, and v to L // convert l, H, h, V, and v to L
switch (c) { switch (c) {
@ -555,8 +567,8 @@ export class Path extends Shape<PathConfig> {
break; break;
// Note: lineTo handlers need to be above this point // Note: lineTo handlers need to be above this point
case 'm': case 'm':
var dx = p.shift()!; const dx = p.shift()!;
var dy = p.shift()!; const dy = p.shift()!;
cpx += dx; cpx += dx;
cpy += dy; cpy += dy;
cmd = 'M'; cmd = 'M';
@ -782,12 +794,12 @@ export class Path extends Shape<PathConfig> {
case 'A': case 'A':
// Approximates by breaking curve into line segments // Approximates by breaking curve into line segments
len = 0.0; len = 0.0;
var start = points[4]; const start = points[4];
// 4 = theta // 4 = theta
var dTheta = points[5]; const dTheta = points[5];
// 5 = dTheta // 5 = dTheta
var end = points[4] + dTheta; const end = points[4] + dTheta;
var inc = Math.PI / 180.0; let inc = Math.PI / 180.0;
// 1 degree resolution // 1 degree resolution
if (Math.abs(start - end) < inc) { if (Math.abs(start - end) < inc) {
inc = Math.abs(start - end); inc = Math.abs(start - end);
@ -846,15 +858,15 @@ export class Path extends Shape<PathConfig> {
return 0; return 0;
} }
static convertEndpointToCenterParameterization( static convertEndpointToCenterParameterization(
x1, x1: number,
y1, y1: number,
x2, x2: number,
y2, y2: number,
fa, fa: number,
fs, fs: number,
rx, rx: number,
ry, ry: number,
psiDeg psiDeg: number
) { ) {
// Derived from: http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes // Derived from: http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
const psi = psiDeg * (Math.PI / 180.0); const psi = psiDeg * (Math.PI / 180.0);

View File

@ -171,7 +171,7 @@ function checkDefaultFill(config?: TextConfig) {
* @param {String} [config.fontVariant] can be normal or small-caps. Default is normal * @param {String} [config.fontVariant] can be normal or small-caps. Default is normal
* @param {String} [config.textDecoration] can be line-through, underline or empty string. Default is empty string. * @param {String} [config.textDecoration] can be line-through, underline or empty string. Default is empty string.
* @param {String} config.text * @param {String} config.text
* @param {String} [config.align] can be left, center, or right * @param {String} [config.align] can be left, center, right or justify
* @param {String} [config.verticalAlign] can be top, middle or bottom * @param {String} [config.verticalAlign] can be top, middle or bottom
* @param {Number} [config.padding] * @param {Number} [config.padding]
* @param {Number} [config.lineHeight] default is 1 * @param {Number} [config.lineHeight] default is 1
@ -242,9 +242,6 @@ export class Text extends Shape<TextConfig> {
lineHeightPx / 2; lineHeightPx / 2;
} }
var lineTranslateX = 0;
var lineTranslateY = 0;
if (direction === RTL) { if (direction === RTL) {
context.setAttr('direction', direction); context.setAttr('direction', direction);
} }
@ -266,15 +263,12 @@ export class Text extends Shape<TextConfig> {
// draw text lines // draw text lines
for (n = 0; n < textArrLen; n++) { for (n = 0; n < textArrLen; n++) {
var lineTranslateX = 0; let lineTranslateX = 0;
var lineTranslateY = 0; let lineTranslateY = 0;
var obj = textArr[n], const obj = textArr[n],
text = obj.text, text = obj.text,
width = obj.width, width = obj.width,
lastLine = obj.lastInParagraph, lastLine = obj.lastInParagraph;
spacesNumber,
oneWord,
lineWidth;
// horizontal alignment // horizontal alignment
context.save(); context.save();
@ -294,9 +288,7 @@ export class Text extends Shape<TextConfig> {
const x = lineTranslateX; const x = lineTranslateX;
const y = translateY + lineTranslateY + yOffset; const y = translateY + lineTranslateY + yOffset;
context.moveTo(x, y); context.moveTo(x, y);
spacesNumber = text.split(' ').length - 1; const lineWidth =
oneWord = spacesNumber === 0;
lineWidth =
align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width; align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width;
context.lineTo(x + Math.round(lineWidth), y); context.lineTo(x + Math.round(lineWidth), y);
@ -314,9 +306,7 @@ export class Text extends Shape<TextConfig> {
context.beginPath(); context.beginPath();
const yOffset = Konva._fixTextRendering ? -Math.round(fontSize / 4) : 0; const yOffset = Konva._fixTextRendering ? -Math.round(fontSize / 4) : 0;
context.moveTo(lineTranslateX, translateY + lineTranslateY + yOffset); context.moveTo(lineTranslateX, translateY + lineTranslateY + yOffset);
spacesNumber = text.split(' ').length - 1; const lineWidth =
oneWord = spacesNumber === 0;
lineWidth =
align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width; align === JUSTIFY && !lastLine ? totalWidth - padding * 2 : width;
context.lineTo( context.lineTo(
lineTranslateX + Math.round(lineWidth), lineTranslateX + Math.round(lineWidth),
@ -333,7 +323,7 @@ export class Text extends Shape<TextConfig> {
// be supported otherwise. // be supported otherwise.
if (direction !== RTL && (letterSpacing !== 0 || align === JUSTIFY)) { if (direction !== RTL && (letterSpacing !== 0 || align === JUSTIFY)) {
// var words = text.split(' '); // var words = text.split(' ');
spacesNumber = text.split(' ').length - 1; const spacesNumber = text.split(' ').length - 1;
const array = stringToArray(text); const array = stringToArray(text);
for (let li = 0; li < array.length; li++) { for (let li = 0; li < array.length; li++) {
const letter = array[li]; const letter = array[li];
@ -885,6 +875,8 @@ Factory.addGetterSetter(Text, 'padding', 0, getNumberValidator());
* *
* // align text to right * // align text to right
* text.align('right'); * text.align('right');
*
* // justify text
*/ */
Factory.addGetterSetter(Text, 'align', LEFT); Factory.addGetterSetter(Text, 'align', LEFT);

View File

@ -770,11 +770,11 @@ export class Transformer extends Group {
keepProportion = this.keepRatio() || e.shiftKey; keepProportion = this.keepRatio() || e.shiftKey;
} }
var centeredScaling = this.centeredScaling() || e.altKey; let centeredScaling = this.centeredScaling() || e.altKey;
if (this._movingAnchorName === 'top-left') { if (this._movingAnchorName === 'top-left') {
if (keepProportion) { if (keepProportion) {
var comparePoint = centeredScaling const comparePoint = centeredScaling
? { ? {
x: this.width() / 2, x: this.width() / 2,
y: this.height() / 2, y: this.height() / 2,
@ -788,9 +788,9 @@ export class Transformer extends Group {
Math.pow(comparePoint.y - anchorNode.y(), 2) Math.pow(comparePoint.y - anchorNode.y(), 2)
); );
var reverseX = this.findOne('.top-left')!.x() > comparePoint.x ? -1 : 1; const reverseX = this.findOne('.top-left')!.x() > comparePoint.x ? -1 : 1;
var reverseY = this.findOne('.top-left')!.y() > comparePoint.y ? -1 : 1; const reverseY = this.findOne('.top-left')!.y() > comparePoint.y ? -1 : 1;
x = newHypotenuse * this.cos * reverseX; x = newHypotenuse * this.cos * reverseX;
y = newHypotenuse * this.sin * reverseY; y = newHypotenuse * this.sin * reverseY;
@ -802,7 +802,7 @@ export class Transformer extends Group {
this.findOne('.top-left')!.y(anchorNode.y()); this.findOne('.top-left')!.y(anchorNode.y());
} else if (this._movingAnchorName === 'top-right') { } else if (this._movingAnchorName === 'top-right') {
if (keepProportion) { if (keepProportion) {
var comparePoint = centeredScaling const comparePoint = centeredScaling
? { ? {
x: this.width() / 2, x: this.width() / 2,
y: this.height() / 2, y: this.height() / 2,
@ -817,10 +817,10 @@ export class Transformer extends Group {
Math.pow(comparePoint.y - anchorNode.y(), 2) Math.pow(comparePoint.y - anchorNode.y(), 2)
); );
var reverseX = const reverseX =
this.findOne('.top-right')!.x() < comparePoint.x ? -1 : 1; this.findOne('.top-right')!.x() < comparePoint.x ? -1 : 1;
var reverseY = const reverseY =
this.findOne('.top-right')!.y() > comparePoint.y ? -1 : 1; this.findOne('.top-right')!.y() > comparePoint.y ? -1 : 1;
x = newHypotenuse * this.cos * reverseX; x = newHypotenuse * this.cos * reverseX;
@ -838,7 +838,7 @@ export class Transformer extends Group {
this.findOne('.bottom-right')!.x(anchorNode.x()); this.findOne('.bottom-right')!.x(anchorNode.x());
} else if (this._movingAnchorName === 'bottom-left') { } else if (this._movingAnchorName === 'bottom-left') {
if (keepProportion) { if (keepProportion) {
var comparePoint = centeredScaling const comparePoint = centeredScaling
? { ? {
x: this.width() / 2, x: this.width() / 2,
y: this.height() / 2, y: this.height() / 2,
@ -853,9 +853,9 @@ export class Transformer extends Group {
Math.pow(anchorNode.y() - comparePoint.y, 2) Math.pow(anchorNode.y() - comparePoint.y, 2)
); );
var reverseX = comparePoint.x < anchorNode.x() ? -1 : 1; const reverseX = comparePoint.x < anchorNode.x() ? -1 : 1;
var reverseY = anchorNode.y() < comparePoint.y ? -1 : 1; const reverseY = anchorNode.y() < comparePoint.y ? -1 : 1;
x = newHypotenuse * this.cos * reverseX; x = newHypotenuse * this.cos * reverseX;
y = newHypotenuse * this.sin * reverseY; y = newHypotenuse * this.sin * reverseY;
@ -872,7 +872,7 @@ export class Transformer extends Group {
this.findOne('.bottom-right')!.y(anchorNode.y()); this.findOne('.bottom-right')!.y(anchorNode.y());
} else if (this._movingAnchorName === 'bottom-right') { } else if (this._movingAnchorName === 'bottom-right') {
if (keepProportion) { if (keepProportion) {
var comparePoint = centeredScaling const comparePoint = centeredScaling
? { ? {
x: this.width() / 2, x: this.width() / 2,
y: this.height() / 2, y: this.height() / 2,
@ -887,10 +887,10 @@ export class Transformer extends Group {
Math.pow(anchorNode.y() - comparePoint.y, 2) Math.pow(anchorNode.y() - comparePoint.y, 2)
); );
var reverseX = const reverseX =
this.findOne('.bottom-right')!.x() < comparePoint.x ? -1 : 1; this.findOne('.bottom-right')!.x() < comparePoint.x ? -1 : 1;
var reverseY = const reverseY =
this.findOne('.bottom-right')!.y() < comparePoint.y ? -1 : 1; this.findOne('.bottom-right')!.y() < comparePoint.y ? -1 : 1;
x = newHypotenuse * this.cos * reverseX; x = newHypotenuse * this.cos * reverseX;
@ -908,7 +908,7 @@ export class Transformer extends Group {
); );
} }
var centeredScaling = this.centeredScaling() || e.altKey; centeredScaling = this.centeredScaling() || e.altKey;
if (centeredScaling) { if (centeredScaling) {
const topLeft = this.findOne('.top-left')!; const topLeft = this.findOne('.top-left')!;
const bottomRight = this.findOne('.bottom-right')!; const bottomRight = this.findOne('.bottom-right')!;

View File

@ -1,10 +1,25 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2015", "target": "ES2015",
"noEmitOnError": true, "noEmitOnError": false,
"moduleResolution": "node", "moduleResolution": "node",
"lib": ["ES2015", "dom"], "lib": ["ES2015", "dom"],
"module": "CommonJS" "module": "CommonJS",
"skipLibCheck": true,
"noImplicitAny": false,
"allowJs": true,
"noEmit": false,
"checkJs": false,
"allowUnreachableCode": true,
"allowUnusedLabels": true,
"noFallthroughCasesInSwitch": false,
"noImplicitReturns": false,
"noImplicitThis": false,
"noPropertyAccessFromIndexSignature": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"strict": false
}, },
"include": ["../src/**/*.ts"] "include": ["../src/**/*.ts", "./**/*.ts"],
"exclude": ["../node_modules/**/*"]
} }

View File

@ -11,6 +11,7 @@
<script type="module"> <script type="module">
// CORE // CORE
import './unit/Animation-test.ts'; import './unit/Animation-test.ts';
import './unit/Tween-test.ts';
import './unit/Canvas-test.ts'; import './unit/Canvas-test.ts';
import './unit/Container-test.ts'; import './unit/Container-test.ts';
import './unit/Context-test.ts'; import './unit/Context-test.ts';

View File

@ -1083,6 +1083,7 @@ describe('Path', function () {
it('get point at path', function () { it('get point at path', function () {
var stage = addStage(); var stage = addStage();
var layer = new Konva.Layer(); var layer = new Konva.Layer();
stage.add(layer);
const data = const data =
'M 300,10 L 250,100 A 100 40 30 1 0 150 150 C 160,100, 290,100, 300,150'; 'M 300,10 L 250,100 A 100 40 30 1 0 150 150 C 160,100, 290,100, 300,150';
var path = new Konva.Path({ var path = new Konva.Path({
@ -1102,7 +1103,7 @@ describe('Path', function () {
var circle = new Konva.Circle({ var circle = new Konva.Circle({
x: p.x, x: p.x,
y: p.y, y: p.y,
radius: 2, radius: 0.1,
fill: 'black', fill: 'black',
stroke: 'black', stroke: 'black',
}); });
@ -1164,13 +1165,49 @@ describe('Path', function () {
{ x: 299.87435436448743, y: 149.4028482225714 }, { x: 299.87435436448743, y: 149.4028482225714 },
]); ]);
} }
});
it('get point at vertical path', function () {
var stage = addStage();
var layer = new Konva.Layer();
const data = 'M 614.96002,7.5147864 611.20262,429.59529';
var path = new Konva.Path({
stroke: 'red',
strokeWidth: 3,
data,
x: -600,
});
layer.add(path);
if (isBrowser) {
const SVGPath = document.createElementNS(
'http://www.w3.org/2000/svg',
'path'
) as SVGPathElement;
SVGPath.setAttribute('d', data);
for (var i = 0.001; i < path.getLength(); i += 1) {
var p = path.getPointAtLength(i);
console.log(p);
var circle = new Konva.Circle({
x: p.x + path.x(),
y: p.y + path.y(),
radius: 2,
fill: 'black',
stroke: 'black',
});
layer.add(circle);
const position = SVGPath.getPointAtLength(i);
console.log(position);
assert(Math.abs(p.x - position.x) <= 1);
assert(Math.abs(p.y - position.y) <= 1);
}
}
stage.add(layer); stage.add(layer);
}); });
it('get point at path with float attrs', function () { it('get point at path with float attrs', function () {
var stage = addStage(); var stage = addStage();
var layer = new Konva.Layer(); var layer = new Konva.Layer();
stage.add(layer);
const data = const data =
'M419.0000314094981 342.88624187900973 L419.00003140949804 577.0038889378335 L465.014001082264 577.0038889378336 Z'; 'M419.0000314094981 342.88624187900973 L419.00003140949804 577.0038889378335 L465.014001082264 577.0038889378336 Z';

View File

@ -1550,6 +1550,83 @@ describe('Shape', function () {
compareCanvases(canvas2, canvas1, 240, 110); compareCanvases(canvas2, canvas1, 240, 110);
}); });
it('export stage when buffer canvas is for line', async function () {
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
const group = new Konva.Group({
id: 'group01',
draggable: false,
opacity: 0.99,
});
layer.add(group);
const arrow = new Konva.Arrow({
x: 0,
y: 0,
points: [50, 25, 200, 25, 200, 225, 400, 225],
stroke: 'purple',
fill: 'purple',
strokeWidth: 4,
pointerAtEnding: true,
bezier: true,
});
group.add(arrow);
const bounds = layer.getClientRect({ relativeTo: stage });
const pos = stage.getPosition();
const canvas1 = layer.toCanvas({
pixelRatio: 1,
x: bounds.x + pos.x,
y: bounds.y + pos.y,
width: bounds.width,
height: bounds.height,
});
group.opacity(1);
const canvas2 = layer.toCanvas({
pixelRatio: 1,
x: bounds.x + pos.x,
y: bounds.y + pos.y,
width: bounds.width,
height: bounds.height,
});
compareCanvases(canvas1, canvas2, 240, 110);
});
it('check stroke rendering on buffer canvas', async function () {
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
const rect = new Konva.Rect({
x: 150,
y: 50,
width: 50,
height: 50,
fill: '#039BE5',
stroke: 'yellow',
strokeWidth: 5,
shadowColor: 'black',
shadowOffset: { x: 10, y: 10 },
shadowOpacity: 0.5,
});
layer.add(rect);
const canvas1 = layer.toCanvas();
rect.cache();
const canvas2 = layer.toCanvas();
// throw new Error('stop');
compareCanvases(canvas1, canvas2);
});
// ====================================================== // ======================================================
it('optional disable shadow for stroke', function () { it('optional disable shadow for stroke', function () {
var stage = addStage(); var stage = addStage();

View File

@ -8,6 +8,7 @@ import {
simulateTouchStart, simulateTouchStart,
simulateTouchMove, simulateTouchMove,
simulateTouchEnd, simulateTouchEnd,
simulatePointerMove,
compareCanvases, compareCanvases,
createCanvas, createCanvas,
showHit, showHit,
@ -1222,6 +1223,36 @@ describe('Stage', function () {
assert.equal(count, 2); assert.equal(count, 2);
}); });
it('stage pointerleave should not fire when leaving a child', function () {
var stage = addStage();
var layer = new Konva.Layer();
stage.add(layer);
var circle = new Konva.Circle({
fill: 'red',
radius: 30,
x: 50,
y: 50,
});
layer.add(circle);
layer.draw();
var stageLeave = 0;
var circleLeave = 0;
stage.on('pointerleave', function () {
stageLeave += 1;
});
circle.on('pointerleave', function () {
circleLeave += 1;
});
simulatePointerMove(stage, { x: 50, y: 50 });
simulatePointerMove(stage, { x: 90, y: 50 });
assert.equal(circleLeave, 1, 'circle pointerleave should fire');
assert.equal(stageLeave, 0, 'stage pointerleave should not fire');
});
it('toDataURL with hidden layer', function () { it('toDataURL with hidden layer', function () {
var stage = addStage(); var stage = addStage();
var layer = new Konva.Layer(); var layer = new Konva.Layer();

View File

@ -161,7 +161,9 @@ describe('Text', function () {
context.fillStyle = 'darkgrey'; context.fillStyle = 'darkgrey';
context.fillText('😬👧🏿', 10, 10 + 25); context.fillText('😬👧🏿', 10, 10 + 25);
compareLayerAndCanvas(layer, canvas, 254, 100); if (isBrowser) {
compareLayerAndCanvas(layer, canvas, 254, 100);
}
}); });
it('check emoji rendering', function () { it('check emoji rendering', function () {
@ -190,7 +192,9 @@ describe('Text', function () {
context.fillText('😁😁', 10, 10 + 10); context.fillText('😁😁', 10, 10 + 10);
context.fillText('😁', 10, 10 + 30); context.fillText('😁', 10, 10 + 30);
compareLayerAndCanvas(layer, canvas, 254); if (isBrowser) {
compareLayerAndCanvas(layer, canvas, 254, 100);
}
}); });
it('check hindi with letterSpacing', function () { it('check hindi with letterSpacing', function () {

View File

@ -98,8 +98,8 @@ describe('Tween', function () {
tween.destroy(); tween.destroy();
assert.equal(Konva.Tween.tweens[circle._id].x, undefined); assert.equal(Konva.Tween.tweens[circle._id], undefined);
assert.equal(Konva.Tween.attrs[circle._id][tween._id], undefined); assert.equal(Konva.Tween.attrs[circle._id], undefined);
}); });
// ====================================================== // ======================================================
@ -238,7 +238,7 @@ describe('Tween', function () {
duration: 0.1, duration: 0.1,
onFinish: function () { onFinish: function () {
assert.equal(circle.x(), stage.width() / 2); assert.equal(circle.x(), stage.width() / 2);
assert.equal(Object.keys(Konva.Tween.attrs[circle._id]).length, 0); assert.equal(Konva.Tween.attrs[circle._id], undefined);
done(); done();
}, },
}); });
@ -305,16 +305,10 @@ describe('Tween', function () {
points: [100, 100, 200, 100, 200, 200, 100, 200], points: [100, 100, 200, 100, 200, 200, 100, 200],
duration: 0.1, duration: 0.1,
onFinish: function () { onFinish: function () {
assert.deepEqual(line.points(), [ assert.deepEqual(
100, line.points(),
100, [100, 100, 200, 100, 200, 200, 100, 200]
200, );
100,
200,
200,
100,
200,
]);
done(); done();
}, },
}); });
@ -364,16 +358,10 @@ describe('Tween', function () {
tween.reverse(); tween.reverse();
}, },
onReset: function () { onReset: function () {
assert.deepEqual(line.points(), [ assert.deepEqual(
100, line.points(),
100, [100, 100, 200, 100, 200, 200, 100, 200]
200, );
100,
200,
200,
100,
200,
]);
done(); done();
}, },
}); });

View File

@ -57,19 +57,16 @@ function isType(object, type) {
// Type Conversion // Type Conversion
function copyImageData(imageData) { function copyImageData(imageData) {
var height = imageData.height, const height = imageData.height,
width = imageData.width, width = imageData.width,
data = imageData.data, data = imageData.data;
newImageData,
newData,
i;
canvas.width = width; canvas.width = width;
canvas.height = height; canvas.height = height;
newImageData = context.getImageData(0, 0, width, height); const newImageData = context.getImageData(0, 0, width, height);
newData = newImageData.data; const newData = newImageData.data;
for (i = imageData.data.length; i--; ) { for (let i = imageData.data.length; i--; ) {
newData[i] = data[i]; newData[i] = data[i];
} }
@ -90,7 +87,7 @@ function toImageData(object) {
} }
} }
function toImageDataFromImage(image) { function toImageDataFromImage(image) {
var height = image.height, const height = image.height,
width = image.width; width = image.width;
canvas.width = width; canvas.width = width;
canvas.height = height; canvas.height = height;
@ -99,7 +96,7 @@ function toImageDataFromImage(image) {
return context.getImageData(0, 0, width, height); return context.getImageData(0, 0, width, height);
} }
function toImageDataFromCanvas(canvas) { function toImageDataFromCanvas(canvas) {
var height = canvas.height, const height = canvas.height,
width = canvas.width, width = canvas.width,
context = canvas.getContext('2d'); context = canvas.getContext('2d');
if (!width || !height) { if (!width || !height) {
@ -109,13 +106,13 @@ function toImageDataFromCanvas(canvas) {
return context.getImageData(0, 0, width, height); return context.getImageData(0, 0, width, height);
} }
function toImageDataFromContext(context) { function toImageDataFromContext(context) {
var canvas = context.canvas, const canvas = context.canvas,
height = canvas.height, height = canvas.height,
width = canvas.width; width = canvas.width;
return context.getImageData(0, 0, width, height); return context.getImageData(0, 0, width, height);
} }
function toCanvas(object) { function toCanvas(object) {
var data = toImageData(object), const data = toImageData(object),
canvas = getCanvas(data.width, data.height), canvas = getCanvas(data.width, data.height),
context = canvas.getContext('2d'); context = canvas.getContext('2d');
@ -135,16 +132,15 @@ function equalDimensions(a, b) {
} }
export function equal(a, b, tolerance, secondTol) { export function equal(a, b, tolerance, secondTol) {
var aData = a.data, const aData = a.data,
bData = b.data, bData = b.data,
length = aData.length, length = aData.length;
i;
tolerance = tolerance || 0; tolerance = tolerance || 0;
var count = 0; let count = 0;
if (!equalDimensions(a, b)) return false; if (!equalDimensions(a, b)) return false;
for (i = length; i--; ) { for (let i = length; i--; ) {
const d = Math.abs(aData[i] - bData[i]); const d = Math.abs(aData[i] - bData[i]);
if (aData[i] !== bData[i] && d > tolerance) { if (aData[i] !== bData[i] && d > tolerance) {
if (!secondTol) { if (!secondTol) {
@ -168,21 +164,15 @@ function diff(a, b, options) {
return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b, options); return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b, options);
} }
function diffEqual(a, b, options) { function diffEqual(a, b, options) {
var height = a.height, const height = a.height,
width = a.width, width = a.width,
c = getImageData(width, height), // c = a - b c = getImageData(width, height), // c = a - b
aData = a.data, aData = a.data,
bData = b.data, bData = b.data,
cData = c.data, cData = c.data,
length = cData.length, length = cData.length;
row,
column,
i,
j,
k,
v;
for (i = 0; i < length; i += 4) { for (let i = 0; i < length; i += 4) {
cData[i] = Math.abs(aData[i] - bData[i]); cData[i] = Math.abs(aData[i] - bData[i]);
cData[i + 1] = Math.abs(aData[i + 1] - bData[i + 1]); cData[i + 1] = Math.abs(aData[i + 1] - bData[i + 1]);
cData[i + 2] = Math.abs(aData[i + 2] - bData[i + 2]); cData[i + 2] = Math.abs(aData[i + 2] - bData[i + 2]);
@ -192,32 +182,26 @@ function diffEqual(a, b, options) {
return c; return c;
} }
function diffUnequal(a, b, options) { function diffUnequal(a, b, options) {
var height = Math.max(a.height, b.height), const height = Math.max(a.height, b.height),
width = Math.max(a.width, b.width), width = Math.max(a.width, b.width),
c = getImageData(width, height), // c = a - b c = getImageData(width, height), // c = a - b
aData = a.data, aData = a.data,
bData = b.data, bData = b.data,
cData = c.data, cData = c.data,
align = options && options.align, align = options && options.align;
rowOffset, var rowOffset,
columnOffset, columnOffset;
row,
column,
i,
j,
k,
v;
for (i = cData.length - 1; i > 0; i = i - 4) { for (let i = cData.length - 1; i > 0; i = i - 4) {
cData[i] = 255; cData[i] = 255;
} }
// Add First Image // Add First Image
offsets(a); offsets(a);
for (row = a.height; row--; ) { for (let row = a.height; row--; ) {
for (column = a.width; column--; ) { for (let column = a.width; column--; ) {
i = 4 * ((row + rowOffset) * width + (column + columnOffset)); const i = 4 * ((row + rowOffset) * width + (column + columnOffset));
j = 4 * (row * a.width + column); const j = 4 * (row * a.width + column);
cData[i + 0] = aData[j + 0]; // r cData[i + 0] = aData[j + 0]; // r
cData[i + 1] = aData[j + 1]; // g cData[i + 1] = aData[j + 1]; // g
cData[i + 2] = aData[j + 2]; // b cData[i + 2] = aData[j + 2]; // b
@ -227,10 +211,10 @@ function diffUnequal(a, b, options) {
// Subtract Second Image // Subtract Second Image
offsets(b); offsets(b);
for (row = b.height; row--; ) { for (let row = b.height; row--; ) {
for (column = b.width; column--; ) { for (let column = b.width; column--; ) {
i = 4 * ((row + rowOffset) * width + (column + columnOffset)); const i = 4 * ((row + rowOffset) * width + (column + columnOffset));
j = 4 * (row * b.width + column); const j = 4 * (row * b.width + column);
cData[i + 0] = Math.abs(cData[i + 0] - bData[j + 0]); // r cData[i + 0] = Math.abs(cData[i + 0] - bData[j + 0]); // r
cData[i + 1] = Math.abs(cData[i + 1] - bData[j + 1]); // g cData[i + 1] = Math.abs(cData[i + 1] - bData[j + 1]); // g
cData[i + 2] = Math.abs(cData[i + 2] - bData[j + 2]); // b cData[i + 2] = Math.abs(cData[i + 2] - bData[j + 2]); // b
@ -253,8 +237,7 @@ function diffUnequal(a, b, options) {
// Validation // Validation
function checkType(...args) { function checkType(...args) {
var i; for (let i = 0; i < args.length; i++) {
for (i = 0; i < args.length; i++) {
if (!isImageType(args[i])) { if (!isImageType(args[i])) {
throw { throw {
name: 'ImageTypeError', name: 'ImageTypeError',

View File

@ -131,6 +131,14 @@ export function compareCanvases(canvas1, canvas2, tol?, secondTol?) {
b.appendChild(canvas2); b.appendChild(canvas2);
c.appendChild(diffCanvas); c.appendChild(diffCanvas);
div.appendChild(b); div.appendChild(b);
if (!canvas1.parentNode) {
const d = get('div', '<div>Original:</div>');
canvas1.style.position = '';
canvas1.style.display = '';
d.style.float = 'left';
d.appendChild(canvas1);
div.appendChild(d);
}
div.appendChild(c); div.appendChild(c);
getContainer().appendChild(div); getContainer().appendChild(div);
} }