first commit

This commit is contained in:
2026-10-17 12:09:36 -04:00
commit 56046a63fc
1149 changed files with 658055 additions and 0 deletions
+84
View File
@@ -0,0 +1,84 @@
import type { Layer } from './Layer.ts';
import type { IFrame, AnimationFn } from './types.ts';
/**
* Animation constructor.
* @constructor
* @memberof Konva
* @param {AnimationFn} func function executed on each animation frame. The function is passed a frame object, which contains
* timeDiff, lastTime, time, and frameRate properties. The timeDiff property is the number of milliseconds that have passed
* since the last animation frame. The time property accumulates elapsed animation time in milliseconds across stop/start calls, excluding paused time.
* The lastTime property is the current frame's clock timestamp in milliseconds. The frameRate property is the current frame rate in frames / second.
* Return false from function, if you don't need to redraw layer/layers on some frames.
* @param {Konva.Layer|Array} [layers] layer(s) to be redrawn on each animation frame. Can be a layer, an array of layers, or null.
* Not specifying a node will result in no redraw.
* @example
* // move a node to the right at 50 pixels / second
* var velocity = 50;
*
* var anim = new Konva.Animation(function(frame) {
* var dist = velocity * (frame.timeDiff / 1000);
* node.move({x: dist, y: 0});
* }, layer);
*
* anim.start();
*/
export declare class Animation {
func: AnimationFn;
id: number;
layers: Layer[];
frame: IFrame;
constructor(func: AnimationFn, layers?: any);
/**
* set layers to be redrawn on each animation frame
* @method
* @name Konva.Animation#setLayers
* @param {Konva.Layer|Array} [layers] layer(s) to be redrawn. Can be a layer, an array of layers, or null. Not specifying a node will result in no redraw.
* @return {Konva.Animation} this
*/
setLayers(layers: null | Layer | Layer[]): this;
/**
* get layers
* @method
* @name Konva.Animation#getLayers
* @return {Array} Array of Konva.Layer
*/
getLayers(): Layer[];
/**
* add layer. Returns true if the layer was added, and false if it was not
* @method
* @name Konva.Animation#addLayer
* @param {Konva.Layer} layer to add
* @return {Bool} true if layer is added to animation, otherwise false
*/
addLayer(layer: Layer): boolean;
/**
* determine if animation is running or not. returns true or false
* @method
* @name Konva.Animation#isRunning
* @return {Bool} is animation running?
*/
isRunning(): boolean;
/**
* start animation
* @method
* @name Konva.Animation#start
* @return {Konva.Animation} this
*/
start(): this;
/**
* stop animation
* @method
* @name Konva.Animation#stop
* @return {Konva.Animation} this
*/
stop(): this;
_updateFrameObject(time: number): void;
static animations: Set<Animation>;
static animIdCounter: number;
static animRunning: boolean;
static _addAnimation(anim: Animation): void;
static _removeAnimation(anim: Animation): void;
static _runFrames(): void;
static _animationLoop(): void;
static _handleAnimation(): void;
}
+166
View File
@@ -0,0 +1,166 @@
var _a;
import { glob } from "./Global.js";
import { Node } from "./Node.js";
import { Util } from "./Util.js";
const now = ((_a = glob.performance) === null || _a === void 0 ? void 0 : _a.now)
? () => glob.performance.now()
: Date.now;
/**
* Animation constructor.
* @constructor
* @memberof Konva
* @param {AnimationFn} func function executed on each animation frame. The function is passed a frame object, which contains
* timeDiff, lastTime, time, and frameRate properties. The timeDiff property is the number of milliseconds that have passed
* since the last animation frame. The time property accumulates elapsed animation time in milliseconds across stop/start calls, excluding paused time.
* The lastTime property is the current frame's clock timestamp in milliseconds. The frameRate property is the current frame rate in frames / second.
* Return false from function, if you don't need to redraw layer/layers on some frames.
* @param {Konva.Layer|Array} [layers] layer(s) to be redrawn on each animation frame. Can be a layer, an array of layers, or null.
* Not specifying a node will result in no redraw.
* @example
* // move a node to the right at 50 pixels / second
* var velocity = 50;
*
* var anim = new Konva.Animation(function(frame) {
* var dist = velocity * (frame.timeDiff / 1000);
* node.move({x: dist, y: 0});
* }, layer);
*
* anim.start();
*/
export class Animation {
constructor(func, layers) {
this.id = Animation.animIdCounter++;
this.frame = {
time: 0,
timeDiff: 0,
lastTime: now(),
frameRate: 0,
};
this.func = func;
this.setLayers(layers);
}
/**
* set layers to be redrawn on each animation frame
* @method
* @name Konva.Animation#setLayers
* @param {Konva.Layer|Array} [layers] layer(s) to be redrawn. Can be a layer, an array of layers, or null. Not specifying a node will result in no redraw.
* @return {Konva.Animation} this
*/
setLayers(layers) {
// a copy, so that addLayer() never pushes into the caller's array
this.layers = layers ? [].concat(layers) : [];
return this;
}
/**
* get layers
* @method
* @name Konva.Animation#getLayers
* @return {Array} Array of Konva.Layer
*/
getLayers() {
return this.layers;
}
/**
* add layer. Returns true if the layer was added, and false if it was not
* @method
* @name Konva.Animation#addLayer
* @param {Konva.Layer} layer to add
* @return {Bool} true if layer is added to animation, otherwise false
*/
addLayer(layer) {
const layers = this.layers;
const len = layers.length;
// don't add the layer if it already exists
for (let n = 0; n < len; n++) {
if (layers[n]._id === layer._id) {
return false;
}
}
this.layers.push(layer);
return true;
}
/**
* determine if animation is running or not. returns true or false
* @method
* @name Konva.Animation#isRunning
* @return {Bool} is animation running?
*/
isRunning() {
return Animation.animations.has(this);
}
/**
* start animation
* @method
* @name Konva.Animation#start
* @return {Konva.Animation} this
*/
start() {
this.stop();
this.frame.timeDiff = 0;
this.frame.lastTime = now();
Animation._addAnimation(this);
return this;
}
/**
* stop animation
* @method
* @name Konva.Animation#stop
* @return {Konva.Animation} this
*/
stop() {
Animation._removeAnimation(this);
return this;
}
_updateFrameObject(time) {
this.frame.timeDiff = time - this.frame.lastTime;
this.frame.lastTime = time;
this.frame.time += this.frame.timeDiff;
this.frame.frameRate = 1000 / this.frame.timeDiff;
}
static _addAnimation(anim) {
this.animations.add(anim);
this._handleAnimation();
}
static _removeAnimation(anim) {
this.animations.delete(anim);
}
static _runFrames() {
// every layer is drawn once, however many animations touch it
const layersToDraw = new Set();
// an animation may stop or restart itself, or another one, inside its
// function: each one runs at most once per frame, a stopped one not at all
Node._updateBatch(() => {
Array.from(this.animations).forEach((anim) => {
if (!this.animations.has(anim)) {
return;
}
anim._updateFrameObject(now());
// the function returns false to skip the redraw
if (anim.func && anim.func.call(anim, anim.frame) === false) {
return;
}
anim.layers.forEach((layer) => layer && layersToDraw.add(layer));
});
});
layersToDraw.forEach((layer) => layer.batchDraw());
}
static _animationLoop() {
const Anim = Animation;
if (Anim.animations.size) {
Anim._runFrames();
Util.requestAnimFrame(Anim._animationLoop);
}
else {
Anim.animRunning = false;
}
}
static _handleAnimation() {
if (!this.animRunning) {
this.animRunning = true;
Util.requestAnimFrame(this._animationLoop);
}
}
}
Animation.animations = new Set();
Animation.animIdCounter = 0;
Animation.animRunning = false;
+18
View File
@@ -0,0 +1,18 @@
export declare const tValues: number[][];
export declare const cValues: number[][];
export declare const binomialCoefficients: number[][];
export declare const getCubicArcLength: (xs: number[], ys: number[], t: number) => number;
export declare const getQuadraticArcLength: (xs: number[], ys: number[], t?: number) => number;
export declare const t2length: (length: number, totalLength: number, func: (t: number) => number) => number;
/**
* Points where a quadratic bezier segment reaches its highest or lowest value
* on one of the two axes, as a flat [x, y, x, y, ...] list. The two end points
* are not included, because the caller knows them already.
*/
export declare const getQuadraticExtremaPoints: (x0: number, y0: number, x1: number, y1: number, x2: number, y2: number) => number[];
/**
* Points where a cubic bezier segment reaches its highest or lowest value on
* one of the two axes, as a flat [x, y, x, y, ...] list. The two end points
* are not included, because the caller knows them already.
*/
export declare const getCubicExtremaPoints: (x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number) => number[];
+822
View File
@@ -0,0 +1,822 @@
// Credits: rveciana/svg-path-properties
// Keep order 20 separate so the main bundle can omit the other quadrature rows.
// Legendre-Gauss abscissae (xi values, defined at i=n as the roots of the nth order Legendre polynomial Pn(x))
const tValues20 = [
-0.0765265211334973337546404093988382110047,
0.0765265211334973337546404093988382110047,
-0.227785851141645078080496195368574624743,
0.227785851141645078080496195368574624743,
-0.3737060887154195606725481770249272373957,
0.3737060887154195606725481770249272373957,
-0.5108670019508270980043640509552509984254,
0.5108670019508270980043640509552509984254,
-0.6360536807265150254528366962262859367433,
0.6360536807265150254528366962262859367433,
-0.7463319064601507926143050703556415903107,
0.7463319064601507926143050703556415903107,
-0.8391169718222188233945290617015206853296,
0.8391169718222188233945290617015206853296,
-0.9122344282513259058677524412032981130491,
0.9122344282513259058677524412032981130491,
-0.963971927277913791267666131197277221912,
0.963971927277913791267666131197277221912,
-0.9931285991850949247861223884713202782226,
0.9931285991850949247861223884713202782226,
];
export const tValues = [
[],
[],
[
-0.5773502691896257645091487805019574556476,
0.5773502691896257645091487805019574556476,
],
[
0, -0.7745966692414833770358530799564799221665,
0.7745966692414833770358530799564799221665,
],
[
-0.3399810435848562648026657591032446872005,
0.3399810435848562648026657591032446872005,
-0.8611363115940525752239464888928095050957,
0.8611363115940525752239464888928095050957,
],
[
0, -0.5384693101056830910363144207002088049672,
0.5384693101056830910363144207002088049672,
-0.9061798459386639927976268782993929651256,
0.9061798459386639927976268782993929651256,
],
[
0.6612093864662645136613995950199053470064,
-0.6612093864662645136613995950199053470064,
-0.2386191860831969086305017216807119354186,
0.2386191860831969086305017216807119354186,
-0.9324695142031520278123015544939946091347,
0.9324695142031520278123015544939946091347,
],
[
0, 0.4058451513773971669066064120769614633473,
-0.4058451513773971669066064120769614633473,
-0.7415311855993944398638647732807884070741,
0.7415311855993944398638647732807884070741,
-0.9491079123427585245261896840478512624007,
0.9491079123427585245261896840478512624007,
],
[
-0.1834346424956498049394761423601839806667,
0.1834346424956498049394761423601839806667,
-0.5255324099163289858177390491892463490419,
0.5255324099163289858177390491892463490419,
-0.7966664774136267395915539364758304368371,
0.7966664774136267395915539364758304368371,
-0.9602898564975362316835608685694729904282,
0.9602898564975362316835608685694729904282,
],
[
0, -0.8360311073266357942994297880697348765441,
0.8360311073266357942994297880697348765441,
-0.9681602395076260898355762029036728700494,
0.9681602395076260898355762029036728700494,
-0.3242534234038089290385380146433366085719,
0.3242534234038089290385380146433366085719,
-0.6133714327005903973087020393414741847857,
0.6133714327005903973087020393414741847857,
],
[
-0.1488743389816312108848260011297199846175,
0.1488743389816312108848260011297199846175,
-0.4333953941292471907992659431657841622,
0.4333953941292471907992659431657841622,
-0.6794095682990244062343273651148735757692,
0.6794095682990244062343273651148735757692,
-0.8650633666889845107320966884234930485275,
0.8650633666889845107320966884234930485275,
-0.9739065285171717200779640120844520534282,
0.9739065285171717200779640120844520534282,
],
[
0, -0.2695431559523449723315319854008615246796,
0.2695431559523449723315319854008615246796,
-0.5190961292068118159257256694586095544802,
0.5190961292068118159257256694586095544802,
-0.7301520055740493240934162520311534580496,
0.7301520055740493240934162520311534580496,
-0.8870625997680952990751577693039272666316,
0.8870625997680952990751577693039272666316,
-0.9782286581460569928039380011228573907714,
0.9782286581460569928039380011228573907714,
],
[
-0.1252334085114689154724413694638531299833,
0.1252334085114689154724413694638531299833,
-0.3678314989981801937526915366437175612563,
0.3678314989981801937526915366437175612563,
-0.587317954286617447296702418940534280369,
0.587317954286617447296702418940534280369,
-0.7699026741943046870368938332128180759849,
0.7699026741943046870368938332128180759849,
-0.9041172563704748566784658661190961925375,
0.9041172563704748566784658661190961925375,
-0.9815606342467192506905490901492808229601,
0.9815606342467192506905490901492808229601,
],
[
0, -0.2304583159551347940655281210979888352115,
0.2304583159551347940655281210979888352115,
-0.4484927510364468528779128521276398678019,
0.4484927510364468528779128521276398678019,
-0.6423493394403402206439846069955156500716,
0.6423493394403402206439846069955156500716,
-0.8015780907333099127942064895828598903056,
0.8015780907333099127942064895828598903056,
-0.9175983992229779652065478365007195123904,
0.9175983992229779652065478365007195123904,
-0.9841830547185881494728294488071096110649,
0.9841830547185881494728294488071096110649,
],
[
-0.1080549487073436620662446502198347476119,
0.1080549487073436620662446502198347476119,
-0.3191123689278897604356718241684754668342,
0.3191123689278897604356718241684754668342,
-0.5152486363581540919652907185511886623088,
0.5152486363581540919652907185511886623088,
-0.6872929048116854701480198030193341375384,
0.6872929048116854701480198030193341375384,
-0.8272013150697649931897947426503949610397,
0.8272013150697649931897947426503949610397,
-0.928434883663573517336391139377874264477,
0.928434883663573517336391139377874264477,
-0.986283808696812338841597266704052801676,
0.986283808696812338841597266704052801676,
],
[
0, -0.2011940939974345223006283033945962078128,
0.2011940939974345223006283033945962078128,
-0.3941513470775633698972073709810454683627,
0.3941513470775633698972073709810454683627,
-0.5709721726085388475372267372539106412383,
0.5709721726085388475372267372539106412383,
-0.7244177313601700474161860546139380096308,
0.7244177313601700474161860546139380096308,
-0.8482065834104272162006483207742168513662,
0.8482065834104272162006483207742168513662,
-0.9372733924007059043077589477102094712439,
0.9372733924007059043077589477102094712439,
-0.9879925180204854284895657185866125811469,
0.9879925180204854284895657185866125811469,
],
[
-0.0950125098376374401853193354249580631303,
0.0950125098376374401853193354249580631303,
-0.281603550779258913230460501460496106486,
0.281603550779258913230460501460496106486,
-0.45801677765722738634241944298357757354,
0.45801677765722738634241944298357757354,
-0.6178762444026437484466717640487910189918,
0.6178762444026437484466717640487910189918,
-0.7554044083550030338951011948474422683538,
0.7554044083550030338951011948474422683538,
-0.8656312023878317438804678977123931323873,
0.8656312023878317438804678977123931323873,
-0.9445750230732325760779884155346083450911,
0.9445750230732325760779884155346083450911,
-0.9894009349916499325961541734503326274262,
0.9894009349916499325961541734503326274262,
],
[
0, -0.1784841814958478558506774936540655574754,
0.1784841814958478558506774936540655574754,
-0.3512317634538763152971855170953460050405,
0.3512317634538763152971855170953460050405,
-0.5126905370864769678862465686295518745829,
0.5126905370864769678862465686295518745829,
-0.6576711592166907658503022166430023351478,
0.6576711592166907658503022166430023351478,
-0.7815140038968014069252300555204760502239,
0.7815140038968014069252300555204760502239,
-0.8802391537269859021229556944881556926234,
0.8802391537269859021229556944881556926234,
-0.9506755217687677612227169578958030214433,
0.9506755217687677612227169578958030214433,
-0.9905754753144173356754340199406652765077,
0.9905754753144173356754340199406652765077,
],
[
-0.0847750130417353012422618529357838117333,
0.0847750130417353012422618529357838117333,
-0.2518862256915055095889728548779112301628,
0.2518862256915055095889728548779112301628,
-0.4117511614628426460359317938330516370789,
0.4117511614628426460359317938330516370789,
-0.5597708310739475346078715485253291369276,
0.5597708310739475346078715485253291369276,
-0.6916870430603532078748910812888483894522,
0.6916870430603532078748910812888483894522,
-0.8037049589725231156824174550145907971032,
0.8037049589725231156824174550145907971032,
-0.8926024664975557392060605911271455154078,
0.8926024664975557392060605911271455154078,
-0.9558239495713977551811958929297763099728,
0.9558239495713977551811958929297763099728,
-0.9915651684209309467300160047061507702525,
0.9915651684209309467300160047061507702525,
],
[
0, -0.1603586456402253758680961157407435495048,
0.1603586456402253758680961157407435495048,
-0.3165640999636298319901173288498449178922,
0.3165640999636298319901173288498449178922,
-0.4645707413759609457172671481041023679762,
0.4645707413759609457172671481041023679762,
-0.6005453046616810234696381649462392798683,
0.6005453046616810234696381649462392798683,
-0.7209661773352293786170958608237816296571,
0.7209661773352293786170958608237816296571,
-0.8227146565371428249789224867127139017745,
0.8227146565371428249789224867127139017745,
-0.9031559036148179016426609285323124878093,
0.9031559036148179016426609285323124878093,
-0.960208152134830030852778840687651526615,
0.960208152134830030852778840687651526615,
-0.9924068438435844031890176702532604935893,
0.9924068438435844031890176702532604935893,
],
tValues20,
[
0, -0.1455618541608950909370309823386863301163,
0.1455618541608950909370309823386863301163,
-0.288021316802401096600792516064600319909,
0.288021316802401096600792516064600319909,
-0.4243421202074387835736688885437880520964,
0.4243421202074387835736688885437880520964,
-0.551618835887219807059018796724313286622,
0.551618835887219807059018796724313286622,
-0.667138804197412319305966669990339162597,
0.667138804197412319305966669990339162597,
-0.7684399634756779086158778513062280348209,
0.7684399634756779086158778513062280348209,
-0.8533633645833172836472506385875676702761,
0.8533633645833172836472506385875676702761,
-0.9200993341504008287901871337149688941591,
0.9200993341504008287901871337149688941591,
-0.9672268385663062943166222149076951614246,
0.9672268385663062943166222149076951614246,
-0.9937521706203895002602420359379409291933,
0.9937521706203895002602420359379409291933,
],
[
-0.0697392733197222212138417961186280818222,
0.0697392733197222212138417961186280818222,
-0.2078604266882212854788465339195457342156,
0.2078604266882212854788465339195457342156,
-0.3419358208920842251581474204273796195591,
0.3419358208920842251581474204273796195591,
-0.4693558379867570264063307109664063460953,
0.4693558379867570264063307109664063460953,
-0.5876404035069115929588769276386473488776,
0.5876404035069115929588769276386473488776,
-0.6944872631866827800506898357622567712673,
0.6944872631866827800506898357622567712673,
-0.7878168059792081620042779554083515213881,
0.7878168059792081620042779554083515213881,
-0.8658125777203001365364256370193787290847,
0.8658125777203001365364256370193787290847,
-0.9269567721871740005206929392590531966353,
0.9269567721871740005206929392590531966353,
-0.9700604978354287271239509867652687108059,
0.9700604978354287271239509867652687108059,
-0.994294585482399292073031421161298980393,
0.994294585482399292073031421161298980393,
],
[
0, -0.1332568242984661109317426822417661370104,
0.1332568242984661109317426822417661370104,
-0.264135680970344930533869538283309602979,
0.264135680970344930533869538283309602979,
-0.390301038030290831421488872880605458578,
0.390301038030290831421488872880605458578,
-0.5095014778460075496897930478668464305448,
0.5095014778460075496897930478668464305448,
-0.6196098757636461563850973116495956533871,
0.6196098757636461563850973116495956533871,
-0.7186613631319501944616244837486188483299,
0.7186613631319501944616244837486188483299,
-0.8048884016188398921511184069967785579414,
0.8048884016188398921511184069967785579414,
-0.8767523582704416673781568859341456716389,
0.8767523582704416673781568859341456716389,
-0.9329710868260161023491969890384229782357,
0.9329710868260161023491969890384229782357,
-0.9725424712181152319560240768207773751816,
0.9725424712181152319560240768207773751816,
-0.9947693349975521235239257154455743605736,
0.9947693349975521235239257154455743605736,
],
[
-0.0640568928626056260850430826247450385909,
0.0640568928626056260850430826247450385909,
-0.1911188674736163091586398207570696318404,
0.1911188674736163091586398207570696318404,
-0.3150426796961633743867932913198102407864,
0.3150426796961633743867932913198102407864,
-0.4337935076260451384870842319133497124524,
0.4337935076260451384870842319133497124524,
-0.5454214713888395356583756172183723700107,
0.5454214713888395356583756172183723700107,
-0.6480936519369755692524957869107476266696,
0.6480936519369755692524957869107476266696,
-0.7401241915785543642438281030999784255232,
0.7401241915785543642438281030999784255232,
-0.8200019859739029219539498726697452080761,
0.8200019859739029219539498726697452080761,
-0.8864155270044010342131543419821967550873,
0.8864155270044010342131543419821967550873,
-0.9382745520027327585236490017087214496548,
0.9382745520027327585236490017087214496548,
-0.9747285559713094981983919930081690617411,
0.9747285559713094981983919930081690617411,
-0.9951872199970213601799974097007368118745,
0.9951872199970213601799974097007368118745,
],
];
// Legendre-Gauss weights (wi values, defined by a function linked to in the Bezier primer article)
const cValues20 = [
0.1527533871307258506980843319550975934919,
0.1527533871307258506980843319550975934919,
0.1491729864726037467878287370019694366926,
0.1491729864726037467878287370019694366926,
0.1420961093183820513292983250671649330345,
0.1420961093183820513292983250671649330345,
0.1316886384491766268984944997481631349161,
0.1316886384491766268984944997481631349161,
0.118194531961518417312377377711382287005,
0.118194531961518417312377377711382287005,
0.1019301198172404350367501354803498761666,
0.1019301198172404350367501354803498761666,
0.0832767415767047487247581432220462061001,
0.0832767415767047487247581432220462061001,
0.0626720483341090635695065351870416063516,
0.0626720483341090635695065351870416063516,
0.040601429800386941331039952274932109879,
0.040601429800386941331039952274932109879,
0.0176140071391521183118619623518528163621,
0.0176140071391521183118619623518528163621,
];
export const cValues = [
[],
[],
[1.0, 1.0],
[
0.8888888888888888888888888888888888888888,
0.5555555555555555555555555555555555555555,
0.5555555555555555555555555555555555555555,
],
[
0.6521451548625461426269360507780005927646,
0.6521451548625461426269360507780005927646,
0.3478548451374538573730639492219994072353,
0.3478548451374538573730639492219994072353,
],
[
0.5688888888888888888888888888888888888888,
0.4786286704993664680412915148356381929122,
0.4786286704993664680412915148356381929122,
0.2369268850561890875142640407199173626432,
0.2369268850561890875142640407199173626432,
],
[
0.3607615730481386075698335138377161116615,
0.3607615730481386075698335138377161116615,
0.4679139345726910473898703439895509948116,
0.4679139345726910473898703439895509948116,
0.1713244923791703450402961421727328935268,
0.1713244923791703450402961421727328935268,
],
[
0.4179591836734693877551020408163265306122,
0.3818300505051189449503697754889751338783,
0.3818300505051189449503697754889751338783,
0.2797053914892766679014677714237795824869,
0.2797053914892766679014677714237795824869,
0.1294849661688696932706114326790820183285,
0.1294849661688696932706114326790820183285,
],
[
0.3626837833783619829651504492771956121941,
0.3626837833783619829651504492771956121941,
0.3137066458778872873379622019866013132603,
0.3137066458778872873379622019866013132603,
0.2223810344533744705443559944262408844301,
0.2223810344533744705443559944262408844301,
0.1012285362903762591525313543099621901153,
0.1012285362903762591525313543099621901153,
],
[
0.3302393550012597631645250692869740488788,
0.1806481606948574040584720312429128095143,
0.1806481606948574040584720312429128095143,
0.0812743883615744119718921581105236506756,
0.0812743883615744119718921581105236506756,
0.3123470770400028400686304065844436655987,
0.3123470770400028400686304065844436655987,
0.2606106964029354623187428694186328497718,
0.2606106964029354623187428694186328497718,
],
[
0.295524224714752870173892994651338329421,
0.295524224714752870173892994651338329421,
0.2692667193099963550912269215694693528597,
0.2692667193099963550912269215694693528597,
0.2190863625159820439955349342281631924587,
0.2190863625159820439955349342281631924587,
0.1494513491505805931457763396576973324025,
0.1494513491505805931457763396576973324025,
0.0666713443086881375935688098933317928578,
0.0666713443086881375935688098933317928578,
],
[
0.272925086777900630714483528336342189156,
0.2628045445102466621806888698905091953727,
0.2628045445102466621806888698905091953727,
0.2331937645919904799185237048431751394317,
0.2331937645919904799185237048431751394317,
0.1862902109277342514260976414316558916912,
0.1862902109277342514260976414316558916912,
0.1255803694649046246346942992239401001976,
0.1255803694649046246346942992239401001976,
0.0556685671161736664827537204425485787285,
0.0556685671161736664827537204425485787285,
],
[
0.2491470458134027850005624360429512108304,
0.2491470458134027850005624360429512108304,
0.2334925365383548087608498989248780562594,
0.2334925365383548087608498989248780562594,
0.2031674267230659217490644558097983765065,
0.2031674267230659217490644558097983765065,
0.160078328543346226334652529543359071872,
0.160078328543346226334652529543359071872,
0.1069393259953184309602547181939962242145,
0.1069393259953184309602547181939962242145,
0.047175336386511827194615961485017060317,
0.047175336386511827194615961485017060317,
],
[
0.2325515532308739101945895152688359481566,
0.2262831802628972384120901860397766184347,
0.2262831802628972384120901860397766184347,
0.2078160475368885023125232193060527633865,
0.2078160475368885023125232193060527633865,
0.1781459807619457382800466919960979955128,
0.1781459807619457382800466919960979955128,
0.1388735102197872384636017768688714676218,
0.1388735102197872384636017768688714676218,
0.0921214998377284479144217759537971209236,
0.0921214998377284479144217759537971209236,
0.0404840047653158795200215922009860600419,
0.0404840047653158795200215922009860600419,
],
[
0.2152638534631577901958764433162600352749,
0.2152638534631577901958764433162600352749,
0.2051984637212956039659240656612180557103,
0.2051984637212956039659240656612180557103,
0.1855383974779378137417165901251570362489,
0.1855383974779378137417165901251570362489,
0.1572031671581935345696019386238421566056,
0.1572031671581935345696019386238421566056,
0.1215185706879031846894148090724766259566,
0.1215185706879031846894148090724766259566,
0.0801580871597602098056332770628543095836,
0.0801580871597602098056332770628543095836,
0.0351194603317518630318328761381917806197,
0.0351194603317518630318328761381917806197,
],
[
0.2025782419255612728806201999675193148386,
0.1984314853271115764561183264438393248186,
0.1984314853271115764561183264438393248186,
0.1861610000155622110268005618664228245062,
0.1861610000155622110268005618664228245062,
0.1662692058169939335532008604812088111309,
0.1662692058169939335532008604812088111309,
0.1395706779261543144478047945110283225208,
0.1395706779261543144478047945110283225208,
0.1071592204671719350118695466858693034155,
0.1071592204671719350118695466858693034155,
0.0703660474881081247092674164506673384667,
0.0703660474881081247092674164506673384667,
0.0307532419961172683546283935772044177217,
0.0307532419961172683546283935772044177217,
],
[
0.1894506104550684962853967232082831051469,
0.1894506104550684962853967232082831051469,
0.1826034150449235888667636679692199393835,
0.1826034150449235888667636679692199393835,
0.1691565193950025381893120790303599622116,
0.1691565193950025381893120790303599622116,
0.1495959888165767320815017305474785489704,
0.1495959888165767320815017305474785489704,
0.1246289712555338720524762821920164201448,
0.1246289712555338720524762821920164201448,
0.0951585116824927848099251076022462263552,
0.0951585116824927848099251076022462263552,
0.0622535239386478928628438369943776942749,
0.0622535239386478928628438369943776942749,
0.0271524594117540948517805724560181035122,
0.0271524594117540948517805724560181035122,
],
[
0.1794464703562065254582656442618856214487,
0.1765627053669926463252709901131972391509,
0.1765627053669926463252709901131972391509,
0.1680041021564500445099706637883231550211,
0.1680041021564500445099706637883231550211,
0.1540457610768102880814315948019586119404,
0.1540457610768102880814315948019586119404,
0.1351363684685254732863199817023501973721,
0.1351363684685254732863199817023501973721,
0.1118838471934039710947883856263559267358,
0.1118838471934039710947883856263559267358,
0.0850361483171791808835353701910620738504,
0.0850361483171791808835353701910620738504,
0.0554595293739872011294401653582446605128,
0.0554595293739872011294401653582446605128,
0.0241483028685479319601100262875653246916,
0.0241483028685479319601100262875653246916,
],
[
0.1691423829631435918406564701349866103341,
0.1691423829631435918406564701349866103341,
0.1642764837458327229860537764659275904123,
0.1642764837458327229860537764659275904123,
0.1546846751262652449254180038363747721932,
0.1546846751262652449254180038363747721932,
0.1406429146706506512047313037519472280955,
0.1406429146706506512047313037519472280955,
0.1225552067114784601845191268002015552281,
0.1225552067114784601845191268002015552281,
0.1009420441062871655628139849248346070628,
0.1009420441062871655628139849248346070628,
0.0764257302548890565291296776166365256053,
0.0764257302548890565291296776166365256053,
0.0497145488949697964533349462026386416808,
0.0497145488949697964533349462026386416808,
0.0216160135264833103133427102664524693876,
0.0216160135264833103133427102664524693876,
],
[
0.1610544498487836959791636253209167350399,
0.1589688433939543476499564394650472016787,
0.1589688433939543476499564394650472016787,
0.152766042065859666778855400897662998461,
0.152766042065859666778855400897662998461,
0.1426067021736066117757461094419029724756,
0.1426067021736066117757461094419029724756,
0.1287539625393362276755157848568771170558,
0.1287539625393362276755157848568771170558,
0.1115666455473339947160239016817659974813,
0.1115666455473339947160239016817659974813,
0.0914900216224499994644620941238396526609,
0.0914900216224499994644620941238396526609,
0.0690445427376412265807082580060130449618,
0.0690445427376412265807082580060130449618,
0.0448142267656996003328381574019942119517,
0.0448142267656996003328381574019942119517,
0.0194617882297264770363120414644384357529,
0.0194617882297264770363120414644384357529,
],
cValues20,
[
0.1460811336496904271919851476833711882448,
0.1445244039899700590638271665537525436099,
0.1445244039899700590638271665537525436099,
0.1398873947910731547221334238675831108927,
0.1398873947910731547221334238675831108927,
0.132268938633337461781052574496775604329,
0.132268938633337461781052574496775604329,
0.1218314160537285341953671771257335983563,
0.1218314160537285341953671771257335983563,
0.1087972991671483776634745780701056420336,
0.1087972991671483776634745780701056420336,
0.0934444234560338615532897411139320884835,
0.0934444234560338615532897411139320884835,
0.0761001136283793020170516533001831792261,
0.0761001136283793020170516533001831792261,
0.0571344254268572082836358264724479574912,
0.0571344254268572082836358264724479574912,
0.0369537897708524937999506682993296661889,
0.0369537897708524937999506682993296661889,
0.0160172282577743333242246168584710152658,
0.0160172282577743333242246168584710152658,
],
[
0.1392518728556319933754102483418099578739,
0.1392518728556319933754102483418099578739,
0.1365414983460151713525738312315173965863,
0.1365414983460151713525738312315173965863,
0.1311735047870623707329649925303074458757,
0.1311735047870623707329649925303074458757,
0.1232523768105124242855609861548144719594,
0.1232523768105124242855609861548144719594,
0.1129322960805392183934006074217843191142,
0.1129322960805392183934006074217843191142,
0.1004141444428809649320788378305362823508,
0.1004141444428809649320788378305362823508,
0.0859416062170677274144436813727028661891,
0.0859416062170677274144436813727028661891,
0.0697964684245204880949614189302176573987,
0.0697964684245204880949614189302176573987,
0.0522933351526832859403120512732112561121,
0.0522933351526832859403120512732112561121,
0.0337749015848141547933022468659129013491,
0.0337749015848141547933022468659129013491,
0.0146279952982722006849910980471854451902,
0.0146279952982722006849910980471854451902,
],
[
0.1336545721861061753514571105458443385831,
0.132462039404696617371642464703316925805,
0.132462039404696617371642464703316925805,
0.1289057221880821499785953393997936532597,
0.1289057221880821499785953393997936532597,
0.1230490843067295304675784006720096548158,
0.1230490843067295304675784006720096548158,
0.1149966402224113649416435129339613014914,
0.1149966402224113649416435129339613014914,
0.1048920914645414100740861850147438548584,
0.1048920914645414100740861850147438548584,
0.0929157660600351474770186173697646486034,
0.0929157660600351474770186173697646486034,
0.0792814117767189549228925247420432269137,
0.0792814117767189549228925247420432269137,
0.0642324214085258521271696151589109980391,
0.0642324214085258521271696151589109980391,
0.0480376717310846685716410716320339965612,
0.0480376717310846685716410716320339965612,
0.0309880058569794443106942196418845053837,
0.0309880058569794443106942196418845053837,
0.0134118594871417720813094934586150649766,
0.0134118594871417720813094934586150649766,
],
[
0.1279381953467521569740561652246953718517,
0.1279381953467521569740561652246953718517,
0.1258374563468282961213753825111836887264,
0.1258374563468282961213753825111836887264,
0.121670472927803391204463153476262425607,
0.121670472927803391204463153476262425607,
0.1155056680537256013533444839067835598622,
0.1155056680537256013533444839067835598622,
0.1074442701159656347825773424466062227946,
0.1074442701159656347825773424466062227946,
0.0976186521041138882698806644642471544279,
0.0976186521041138882698806644642471544279,
0.086190161531953275917185202983742667185,
0.086190161531953275917185202983742667185,
0.0733464814110803057340336152531165181193,
0.0733464814110803057340336152531165181193,
0.0592985849154367807463677585001085845412,
0.0592985849154367807463677585001085845412,
0.0442774388174198061686027482113382288593,
0.0442774388174198061686027482113382288593,
0.0285313886289336631813078159518782864491,
0.0285313886289336631813078159518782864491,
0.0123412297999871995468056670700372915759,
0.0123412297999871995468056670700372915759,
],
];
// LUT for binomial coefficient arrays per curve order 'n'
export const binomialCoefficients = [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]];
export const getCubicArcLength = (xs, ys, t) => {
const z = t / 2;
let sum = 0;
for (let i = 0; i < tValues20.length; i++) {
const correctedT = z * tValues20[i] + z;
sum += cValues20[i] * BFunc(xs, ys, correctedT);
}
return z * sum;
};
// a quadratic elevated to a cubic is the same curve, and the cubic quadrature
// is stable where the closed form for a quadratic cancels itself out
export const getQuadraticArcLength = (xs, ys, t = 1) => getCubicArcLength([
xs[0],
xs[0] + (2 / 3) * (xs[1] - xs[0]),
xs[2] + (2 / 3) * (xs[1] - xs[2]),
xs[2],
], [
ys[0],
ys[0] + (2 / 3) * (ys[1] - ys[0]),
ys[2] + (2 / 3) * (ys[1] - ys[2]),
ys[2],
], t);
function BFunc(xs, ys, t) {
// Interpolate the three derivative control points of a cubic curve.
const mt = 1 - t;
const a = mt * mt;
const b = 2 * mt * t;
const c = t * t;
const dx = a * (3 * (xs[1] - xs[0])) +
b * (3 * (xs[2] - xs[1])) +
c * (3 * (xs[3] - xs[2]));
const dy = a * (3 * (ys[1] - ys[0])) +
b * (3 * (ys[2] - ys[1])) +
c * (3 * (ys[3] - ys[2]));
return Math.sqrt(dx * dx + dy * dy);
}
export const t2length = (length, totalLength, func) => {
let error = 1;
let t = length / totalLength;
let step = (length - func(t)) / totalLength;
let numIterations = 0;
while (error > 0.001) {
const increasedTLength = func(t + step);
const increasedTError = Math.abs(length - increasedTLength) / totalLength;
if (increasedTError < error) {
error = increasedTError;
t += step;
}
else {
const decreasedTLength = func(t - step);
const decreasedTError = Math.abs(length - decreasedTLength) / totalLength;
if (decreasedTError < error) {
error = decreasedTError;
t -= step;
}
else {
step /= 2;
}
}
numIterations++;
if (numIterations > 500) {
break;
}
}
return t;
};
const quadraticAt = (p0, p1, p2, t) => {
const mt = 1 - t;
return mt * mt * p0 + 2 * mt * t * p1 + t * t * p2;
};
/**
* Points where a quadratic bezier segment reaches its highest or lowest value
* on one of the two axes, as a flat [x, y, x, y, ...] list. The two end points
* are not included, because the caller knows them already.
*/
export const getQuadraticExtremaPoints = (x0, y0, x1, y1, x2, y2) => {
const extrema = [];
for (const axis of [
[x0, x1, x2],
[y0, y1, y2],
]) {
// the derivative of the quadratic is linear, so it has one root. A straight
// segment makes the denominator 0, and the division then gives a value that
// is not in the range below.
const t = (axis[0] - axis[1]) / (axis[0] - 2 * axis[1] + axis[2]);
if (t > 0 && t < 1) {
extrema.push(quadraticAt(x0, x1, x2, t), quadraticAt(y0, y1, y2, t));
}
}
return extrema;
};
const cubicAt = (p0, p1, p2, p3, t) => {
const mt = 1 - t;
return (mt * mt * mt * p0 +
3 * mt * mt * t * p1 +
3 * mt * t * t * p2 +
t * t * t * p3);
};
/**
* Points where a cubic bezier segment reaches its highest or lowest value on
* one of the two axes, as a flat [x, y, x, y, ...] list. The two end points
* are not included, because the caller knows them already.
*/
export const getCubicExtremaPoints = (x0, y0, x1, y1, x2, y2, x3, y3) => {
const extrema = [];
for (const axis of [
[x0, x1, x2, x3],
[y0, y1, y2, y3],
]) {
// the derivative of the cubic is a * t * t + b * t + c
const a = -3 * axis[0] + 9 * axis[1] - 9 * axis[2] + 3 * axis[3];
const b = 6 * axis[0] - 12 * axis[1] + 6 * axis[2];
const c = -3 * axis[0] + 3 * axis[1];
const discriminant = b * b - 4 * a * c;
if (discriminant < 0) {
continue;
}
// stable form of the quadratic formula. The plain (-b +- d) / (2 * a)
// loses the root that stays finite when a is zero or near zero, which is
// what a symmetric pair of control points gives. Roots that are not
// finite fall out of the range test below.
const q = -(b + (b < 0 ? -1 : 1) * Math.sqrt(discriminant)) / 2;
for (const t of [q / a, c / q]) {
if (t > 0 && t < 1) {
extrema.push(cubicAt(x0, x1, x2, x3, t), cubicAt(y0, y1, y2, y3, t));
}
}
}
return extrema;
};
+94
View File
@@ -0,0 +1,94 @@
import type { IRect } from './types.ts';
import type { Context } from './Context.ts';
interface ICanvasConfig {
width?: number;
height?: number;
pixelRatio?: number;
willReadFrequently?: boolean;
}
/**
* Canvas Renderer constructor. It is a wrapper around native canvas element.
* Usually you don't need to use it manually.
* @constructor
* @abstract
* @memberof Konva
* @param {Object} config
* @param {Number} config.width
* @param {Number} config.height
* @param {Number} config.pixelRatio
*/
export declare class Canvas {
pixelRatio: number;
_canvas: HTMLCanvasElement;
context: Context;
width: number;
height: number;
_logicalWidth: number;
_logicalHeight: number;
isCache: boolean;
constructor(config: ICanvasConfig);
/**
* get canvas context
* @method
* @name Konva.Canvas#getContext
* @returns {CanvasContext} context
*/
getContext(): Context;
/**
* get pixel ratio
* @method
* @name Konva.Canvas#getPixelRatio
* @returns {Number} pixel ratio
* @example
* var pixelRatio = layer.getCanvas().getPixelRatio();
*/
getPixelRatio(): number;
/**
* set pixel ratio
* KonvaJS automatically handles pixel ratio adustments in order to render crisp drawings
* on all devices. Most desktops, low end tablets, and low end phones, have device pixel ratios
* of 1. Some high end tablets and phones, like iPhones and iPads have a device pixel ratio
* of 2. Some Macbook Pros, and iMacs also have a device pixel ratio of 2. Some high end Android devices have pixel
* ratios of 2 or 3. Some browsers like Firefox allow you to configure the pixel ratio of the viewport. Unless otherwise
* specificed, the pixel ratio will be defaulted to the actual device pixel ratio. You can override the device pixel
* ratio for special situations, or, if you don't want the pixel ratio to be taken into account, you can set it to 1.
* @method
* @name Konva.Canvas#setPixelRatio
* @param {Number} pixelRatio
* @example
* layer.getCanvas().setPixelRatio(3);
*/
setPixelRatio(pixelRatio: any): void;
setWidth(width: any): void;
setHeight(height: any): void;
getWidth(): number;
getHeight(): number;
_bitmapSize(size: number): number;
setSize(width: any, height: any): void;
setSizeIfChanged(width: number, height: number): void;
/**
* to data url
* @method
* @name Konva.Canvas#toDataURL
* @param {String} mimeType
* @param {Number} quality between 0 and 1 for jpg mime types
* @returns {String} data url string
*/
toDataURL(mimeType: any, quality: any): string;
}
export declare class SceneCanvas extends Canvas {
private _isolationCanvas?;
_isolationRect?: IRect;
private _isolationPeak;
private _isolationLowFrames;
_prepareIsolationCanvas(rect?: IRect): SceneCanvas;
_trimIsolationCanvas(): void;
_releaseIsolationCanvas(): void;
setSize(width: any, height: any): void;
constructor(config?: ICanvasConfig);
}
export declare class HitCanvas extends Canvas {
hitCanvas: boolean;
constructor(config?: ICanvasConfig);
}
export {};
+253
View File
@@ -0,0 +1,253 @@
import { Transform, Util } from "./Util.js";
import { SceneContext, HitContext } from "./Context.js";
import { Konva } from "./Global.js";
/**
* Canvas Renderer constructor. It is a wrapper around native canvas element.
* Usually you don't need to use it manually.
* @constructor
* @abstract
* @memberof Konva
* @param {Object} config
* @param {Number} config.width
* @param {Number} config.height
* @param {Number} config.pixelRatio
*/
export class Canvas {
constructor(config) {
this.pixelRatio = 1;
// the bitmap size, in device pixels
this.width = 0;
this.height = 0;
// the size the canvas was given, in CSS pixels; the bitmap truncates it
this._logicalWidth = 0;
this._logicalHeight = 0;
this.isCache = false;
const conf = config || {};
// a Konva.pixelRatio reset to undefined or 0 means the device ratio
const pixelRatio = conf.pixelRatio ||
Konva.pixelRatio ||
Konva._global.devicePixelRatio ||
1;
this.pixelRatio = pixelRatio;
this._canvas = Util.createCanvasElement();
// set inline styles
this._canvas.style.padding = '0';
this._canvas.style.margin = '0';
this._canvas.style.border = '0';
this._canvas.style.background = 'transparent';
this._canvas.style.position = 'absolute';
this._canvas.style.top = '0';
this._canvas.style.left = '0';
}
/**
* get canvas context
* @method
* @name Konva.Canvas#getContext
* @returns {CanvasContext} context
*/
getContext() {
return this.context;
}
/**
* get pixel ratio
* @method
* @name Konva.Canvas#getPixelRatio
* @returns {Number} pixel ratio
* @example
* var pixelRatio = layer.getCanvas().getPixelRatio();
*/
getPixelRatio() {
return this.pixelRatio;
}
/**
* set pixel ratio
* KonvaJS automatically handles pixel ratio adustments in order to render crisp drawings
* on all devices. Most desktops, low end tablets, and low end phones, have device pixel ratios
* of 1. Some high end tablets and phones, like iPhones and iPads have a device pixel ratio
* of 2. Some Macbook Pros, and iMacs also have a device pixel ratio of 2. Some high end Android devices have pixel
* ratios of 2 or 3. Some browsers like Firefox allow you to configure the pixel ratio of the viewport. Unless otherwise
* specificed, the pixel ratio will be defaulted to the actual device pixel ratio. You can override the device pixel
* ratio for special situations, or, if you don't want the pixel ratio to be taken into account, you can set it to 1.
* @method
* @name Konva.Canvas#setPixelRatio
* @param {Number} pixelRatio
* @example
* layer.getCanvas().setPixelRatio(3);
*/
setPixelRatio(pixelRatio) {
this.pixelRatio = pixelRatio;
this.setSize(this._logicalWidth, this._logicalHeight);
}
setWidth(width) {
this.setSize(width, this._logicalHeight);
}
setHeight(height) {
this.setSize(this._logicalWidth, height);
}
getWidth() {
return this.width;
}
getHeight() {
return this.height;
}
// the bitmap holds whole pixels: a fractional pixel ratio truncates, and
// a size that does not match the bitmap would resample every draw of it
_bitmapSize(size) {
return Math.floor((size || 0) * this.pixelRatio);
}
setSize(width, height) {
// a NaN or Infinity size would silently give an empty bitmap
if (!isFinite(width !== null && width !== void 0 ? width : 0) || !isFinite(height !== null && height !== void 0 ? height : 0)) {
Util.error(`Canvas size must be finite numbers, got ${width}x${height}. The canvas is left empty.`);
width = height = 0;
}
width = width || 0;
height = height || 0;
const pixelRatio = this.pixelRatio;
const context = this.getContext()._context;
const imageSmoothingEnabled = context.imageSmoothingEnabled;
this._logicalWidth = width;
this._logicalHeight = height;
// take into account pixel ratio. Assigning a dimension reallocates the
// bitmap and resets the whole context state, so scale once after both
this.width = this._canvas.width = this._bitmapSize(width);
this.height = this._canvas.height = this._bitmapSize(height);
this._canvas.style.width = width + 'px';
this._canvas.style.height = height + 'px';
context.scale(pixelRatio, pixelRatio);
context.imageSmoothingEnabled = imageSmoothingEnabled;
}
// setSize() re-allocates and clears the canvas even for the same size,
// so lazily sized canvases use this to stay untouched when nothing changed
setSizeIfChanged(width, height) {
if (this.width !== this._bitmapSize(width) ||
this.height !== this._bitmapSize(height)) {
this.setSize(width, height);
}
}
/**
* to data url
* @method
* @name Konva.Canvas#toDataURL
* @param {String} mimeType
* @param {Number} quality between 0 and 1 for jpg mime types
* @returns {String} data url string
*/
toDataURL(mimeType, quality) {
try {
// If the requested encoding fails, retry with the default PNG encoding.
return this._canvas.toDataURL(mimeType, quality);
}
catch (e) {
try {
return this._canvas.toDataURL();
}
catch (err) {
Util.error('Unable to get data URL. ' +
err.message +
' For more info read https://konvajs.org/docs/posts/Tainted_Canvas.html.');
throw err;
}
}
}
}
export class SceneCanvas extends Canvas {
// A cleared surface over `rect` (in the current drawing space), or over the
// whole canvas, in this canvas's device pixels. Siblings reuse it; nested
// groups borrow from the surface itself, so they cannot clear a parent's
// unfinished image.
_prepareIsolationCanvas(rect) {
const { a, b, c, d, e, f } = this.getContext()._context.getTransform();
const view = this._isolationRect || this;
let x = 0, y = 0, width = view.width, height = view.height;
if (rect) {
const box = new Transform([a, b, c, d, e, f])._getTransformedRect(rect);
// Round outwards, with a pixel for antialiasing.
x = Math.max(0, Math.floor(box.x) - 1);
y = Math.max(0, Math.floor(box.y) - 1);
width = Math.min(width, Math.ceil(box.x + box.width) + 1) - x;
height = Math.min(height, Math.ceil(box.y + box.height) + 1) - y;
}
if (!(width > 0 && height > 0)) {
// An empty source must still composite for copy/destination-in/etc.
x = y = 0;
width = height = 1;
}
const surface = (this._isolationCanvas || (this._isolationCanvas = new SceneCanvas({
width: 0,
height: 0,
pixelRatio: this.pixelRatio,
})));
if (surface.width < width || surface.height < height) {
// Headroom on a short side, so a growing group does not reallocate
// every frame.
const ratio = this.pixelRatio;
const grow = (need, have, max) => Math.min(max, Math.ceil((have < need ? Math.max(need, have * 1.5) : have) / ratio));
surface.setSize(grow(width, surface.width, this._logicalWidth), grow(height, surface.height, this._logicalHeight));
}
surface._isolationRect = { x, y, width, height };
const peak = surface._isolationPeak;
peak.width = Math.max(peak.width, width);
peak.height = Math.max(peak.height, height);
const context = surface.getContext();
context.setTransform(1, 0, 0, 1, 0, 0);
context.clearRect(0, 0, width, height);
context.setTransform(a, b, c, d, e - x, f - y);
const parent = this.getContext();
context.imageSmoothingEnabled = parent.imageSmoothingEnabled;
context.imageSmoothingQuality = parent.imageSmoothingQuality;
context.direction = parent.direction;
return surface;
}
// After a frame, free surfaces that ten frames in a row did not use or used
// a small part of. Content that changes size keeps its surface.
_trimIsolationCanvas() {
const surface = this._isolationCanvas;
if (!surface)
return;
const peak = surface._isolationPeak;
const low = surface.width * surface.height > 4 * peak.width * peak.height;
surface._isolationLowFrames = low ? surface._isolationLowFrames + 1 : 0;
if (surface._isolationLowFrames >= 10) {
this._releaseIsolationCanvas();
}
else {
peak.width = peak.height = 0;
surface._trimIsolationCanvas();
}
}
_releaseIsolationCanvas() {
const surface = this._isolationCanvas;
if (surface) {
surface._releaseIsolationCanvas();
Util.releaseCanvas(surface._canvas);
this._isolationCanvas = undefined;
}
}
setSize(width, height) {
// A bitmap reset also resets its origin-clean state. A surface from the
// previous lifetime may be tainted even after clearing its pixels.
this._releaseIsolationCanvas();
super.setSize(width, height);
}
constructor(config = { width: 0, height: 0, willReadFrequently: false }) {
super(config);
// The largest width and height requested since the last trim.
this._isolationPeak = { width: 0, height: 0 };
// Consecutive frames that used less than a quarter of the surface.
this._isolationLowFrames = 0;
this.context = new SceneContext(this, {
willReadFrequently: config.willReadFrequently,
});
this.setSize(config.width, config.height);
}
}
export class HitCanvas extends Canvas {
constructor(config = { width: 0, height: 0 }) {
super(config);
this.hitCanvas = true;
this.context = new HitContext(this);
this.context.imageSmoothingEnabled = false;
this.setSize(config.width, config.height);
}
}
+203
View File
@@ -0,0 +1,203 @@
import type { HitCanvas, SceneCanvas } from './Canvas.ts';
import type { SceneContext } from './Context.ts';
import type { GetClientRectConfig, NodeConfig } from './Node.ts';
import { Node } from './Node.ts';
import type { Shape } from './Shape.ts';
import type { GetSet, IRect } from './types.ts';
export type ClipFuncOutput = void | [Path2D | CanvasFillRule] | [Path2D, CanvasFillRule];
export interface ContainerConfig extends NodeConfig {
clearBeforeDraw?: boolean;
clipFunc?: (ctx: SceneContext, container: Container) => ClipFuncOutput;
clipX?: number;
clipY?: number;
clipWidth?: number;
clipHeight?: number;
}
/**
* Container constructor.&nbsp; Containers are used to contain nodes or other containers
* @constructor
* @memberof Konva
* @augments Konva.Node
* @abstract
* @param {Object} config
* @@nodeParams
* @@containerParams
*/
export declare abstract class Container<ChildType extends Node = Node, Config extends ContainerConfig = ContainerConfig> extends Node<Config> {
children: Array<ChildType>;
/**
* returns an array of direct descendant nodes
* @method
* @name Konva.Container#getChildren
* @param {Function} [filterFunc] filter function
* @returns {Array}
* @example
* // get all children
* var children = layer.getChildren();
*
* // get only circles
* var circles = layer.getChildren(function(node){
* return node.getClassName() === 'Circle';
* });
*/
getChildren(filterFunc?: (item: Node) => boolean): ChildType[];
/**
* determine if node has children
* @method
* @name Konva.Container#hasChildren
* @returns {Boolean}
*/
hasChildren(): boolean;
/**
* remove all children. Children will be still in memory.
* If you want to completely destroy all children please use "destroyChildren" method instead
* @method
* @name Konva.Container#removeChildren
*/
removeChildren(): this;
/**
* destroy all children nodes.
* @method
* @name Konva.Container#destroyChildren
*/
destroyChildren(): this;
_clearChildren(dispose: 'remove' | 'destroy'): this;
abstract _validateAdd(node: Node): void;
/**
* add a child and children into container
* @name Konva.Container#add
* @method
* @param {...Konva.Node} children
* @returns {Container}
* @example
* layer.add(rect);
* layer.add(shape1, shape2, shape3);
* // empty arrays are accepted, though each individual child must be defined
* layer.add(...shapes);
*/
add(...children: ChildType[]): this;
destroy(): this;
/**
* return an array of nodes that match the selector.
* You can provide a string with '#' for id selections and '.' for name selections.
* Or a function that will return true/false when a node is passed through. See example below.
* With strings you can also select by type or class name. Pass multiple selectors
* separated by a comma.
* @method
* @name Konva.Container#find
* @param {String | Function} selector
* @returns {Array}
* @example
*
* Passing a string as a selector
* // select node with id foo
* var node = stage.find('#foo');
*
* // select nodes with name bar inside layer
* var nodes = layer.find('.bar');
*
* // select all groups inside layer
* var nodes = layer.find('Group');
*
* // select all rectangles inside layer
* var nodes = layer.find('Rect');
*
* // select node with an id of foo or a name of bar inside layer
* var nodes = layer.find('#foo, .bar');
*
* Passing a function as a selector
*
* // get all groups with a function
* var groups = stage.find(node => {
* return node.getType() === 'Group';
* });
*
* // get only Nodes with partial opacity
* var alphaNodes = layer.find(node => {
* return node.getType() === 'Node' && node.getAbsoluteOpacity() < 1;
* });
*/
find<ChildNode extends Node>(selector: any): Array<ChildNode>;
/**
* return a first node from `find` method
* @method
* @name Konva.Container#findOne
* @param {String | Function} selector
* @returns {Konva.Node | Undefined}
* @example
* // select node with id foo
* var node = stage.findOne('#foo');
*
* // select node with name bar inside layer
* var nodes = layer.findOne('.bar');
*
* // select the first node to return true in a function
* var node = stage.findOne(node => {
* return node.getType() === 'Shape'
* })
*/
findOne<ChildNode extends Node = Node>(selector: string | Function): ChildNode | undefined;
_generalFind<ChildNode extends Node>(selector: string | Function, findOne: boolean): ChildNode[];
private _descendants;
toObject(): {
attrs: any;
className: string;
children?: Array<any>;
};
/**
* determine if node is an ancestor
* of descendant
* @method
* @name Konva.Container#isAncestorOf
* @param {Konva.Node} node
*/
isAncestorOf(node: Node): boolean;
clone(obj?: any): this;
/**
* get all shapes that intersect a point. Note: because this method must clear a temporary
* canvas and redraw every shape inside the container, it should only be used for special situations
* because it performs very poorly. Please use the {@link Konva.Stage#getIntersection} method if at all possible
* because it performs much better.
* Nodes with listening set to false or invisible nodes are not detected
* @method
* @name Konva.Container#getAllIntersections
* @param {Object} pos
* @param {Number} pos.x
* @param {Number} pos.y
* @returns {Array} array of shapes
*/
getAllIntersections(pos: any): Shape<import("./Shape.ts").ShapeConfig>[];
_clearSelfAndDescendantCache(attr?: string): void;
_setChildrenIndices(): void;
drawScene(can?: SceneCanvas, top?: Node): this;
drawHit(can?: HitCanvas, top?: Node): this;
_drawChildren(drawMethod: any, canvas: any, top: any): void;
_drawChildNodes(drawMethod: any, canvas: any, top?: Node): void;
getClientRect(config?: GetClientRectConfig): IRect;
clip: GetSet<IRect, this>;
clipX: GetSet<number, this>;
clipY: GetSet<number, this>;
clipWidth: GetSet<number, this>;
clipHeight: GetSet<number, this>;
clipFunc: GetSet<(ctx: SceneContext, container: Container) => ClipFuncOutput, this>;
}
/**
* get/set clip function
* @name Konva.Container#clipFunc
* @method
* @param {Function} function
* @returns {Function}
* @example
* // get clip function
* var clipFunction = container.clipFunc();
*
* // set clip function
* container.clipFunc(function(ctx) {
* ctx.rect(0, 0, 100, 100);
* });
*
* container.clipFunc(function(ctx) {
* // optionally return a clip Path2D and clip-rule or just the clip-rule
* return [new Path2D('M0 0v50h50Z'), 'evenodd']
* });
*/
+573
View File
@@ -0,0 +1,573 @@
import { Factory } from "./Factory.js";
import { Node } from "./Node.js";
import { getNumberValidator } from "./Validators.js";
/**
* Container constructor.&nbsp; Containers are used to contain nodes or other containers
* @constructor
* @memberof Konva
* @augments Konva.Node
* @abstract
* @param {Object} config
* @@nodeParams
* @@containerParams
*/
export class Container extends Node {
constructor() {
super(...arguments);
this.children = [];
}
/**
* returns an array of direct descendant nodes
* @method
* @name Konva.Container#getChildren
* @param {Function} [filterFunc] filter function
* @returns {Array}
* @example
* // get all children
* var children = layer.getChildren();
*
* // get only circles
* var circles = layer.getChildren(function(node){
* return node.getClassName() === 'Circle';
* });
*/
getChildren(filterFunc) {
// a copy, so that removing children while iterating over it is safe
const children = this.children || [];
return filterFunc ? children.filter(filterFunc) : children.slice();
}
/**
* determine if node has children
* @method
* @name Konva.Container#hasChildren
* @returns {Boolean}
*/
hasChildren() {
return this.children.length > 0;
}
/**
* remove all children. Children will be still in memory.
* If you want to completely destroy all children please use "destroyChildren" method instead
* @method
* @name Konva.Container#removeChildren
*/
removeChildren() {
return this._clearChildren('remove');
}
/**
* destroy all children nodes.
* @method
* @name Konva.Container#destroyChildren
*/
destroyChildren() {
return this._clearChildren('destroy');
}
_clearChildren(dispose) {
return Node._updateBatch(() => {
this.children.forEach((child) => {
// reset parent to prevent many _setChildrenIndices calls
child.parent = null;
child.index = 0;
child[dispose]();
});
this.children = [];
// because all children were detached from parent, request draw via container
this._notifySubtreeChange();
this._requestDraw();
return this;
});
}
/**
* add a child and children into container
* @name Konva.Container#add
* @method
* @param {...Konva.Node} children
* @returns {Container}
* @example
* layer.add(rect);
* layer.add(shape1, shape2, shape3);
* // empty arrays are accepted, though each individual child must be defined
* layer.add(...shapes);
*/
add(...children) {
if (children.length === 0) {
return this;
}
if (children.length > 1) {
for (let i = 0; i < children.length; i++) {
this.add(children[i]);
}
return this;
}
const child = children[0];
if (child.getParent()) {
child.moveTo(this);
return this;
}
this._validateAdd(child);
child.index = this.children.length;
child.parent = this;
child._clearCaches();
this.children.push(child);
this._fire('add', {
child: child,
});
this._notifySubtreeChange();
this._requestDraw();
// chainable
return this;
}
destroy() {
if (this.hasChildren()) {
this.destroyChildren();
}
super.destroy();
return this;
}
/**
* return an array of nodes that match the selector.
* You can provide a string with '#' for id selections and '.' for name selections.
* Or a function that will return true/false when a node is passed through. See example below.
* With strings you can also select by type or class name. Pass multiple selectors
* separated by a comma.
* @method
* @name Konva.Container#find
* @param {String | Function} selector
* @returns {Array}
* @example
*
* Passing a string as a selector
* // select node with id foo
* var node = stage.find('#foo');
*
* // select nodes with name bar inside layer
* var nodes = layer.find('.bar');
*
* // select all groups inside layer
* var nodes = layer.find('Group');
*
* // select all rectangles inside layer
* var nodes = layer.find('Rect');
*
* // select node with an id of foo or a name of bar inside layer
* var nodes = layer.find('#foo, .bar');
*
* Passing a function as a selector
*
* // get all groups with a function
* var groups = stage.find(node => {
* return node.getType() === 'Group';
* });
*
* // get only Nodes with partial opacity
* var alphaNodes = layer.find(node => {
* return node.getType() === 'Node' && node.getAbsoluteOpacity() < 1;
* });
*/
find(selector) {
// protecting _generalFind to prevent user from accidentally adding
// second argument and getting unexpected `findOne` result
return this._generalFind(selector, false);
}
/**
* return a first node from `find` method
* @method
* @name Konva.Container#findOne
* @param {String | Function} selector
* @returns {Konva.Node | Undefined}
* @example
* // select node with id foo
* var node = stage.findOne('#foo');
*
* // select node with name bar inside layer
* var nodes = layer.findOne('.bar');
*
* // select the first node to return true in a function
* var node = stage.findOne(node => {
* return node.getType() === 'Shape'
* })
*/
findOne(selector) {
const result = this._generalFind(selector, true);
return result.length > 0 ? result[0] : undefined;
}
_generalFind(selector, findOne) {
const retArr = [];
this._descendants((node) => {
const valid = node._isMatch(selector);
if (valid) {
retArr.push(node);
}
if (valid && findOne) {
return true;
}
return false;
});
return retArr;
}
_descendants(fn) {
let shouldStop = false;
for (const child of this.children) {
shouldStop = fn(child);
if (shouldStop) {
return true;
}
if (!child.hasChildren()) {
continue;
}
shouldStop = child._descendants(fn);
if (shouldStop) {
return true;
}
}
return false;
}
// extenders
toObject() {
const obj = Node.prototype.toObject.call(this);
obj.children = [];
this.children.forEach((child) => {
obj.children.push(child.toObject());
});
return obj;
}
/**
* determine if node is an ancestor
* of descendant
* @method
* @name Konva.Container#isAncestorOf
* @param {Konva.Node} node
*/
isAncestorOf(node) {
let parent = node.getParent();
while (parent) {
if (parent._id === this._id) {
return true;
}
parent = parent.getParent();
}
return false;
}
clone(obj) {
// call super method
const node = Node.prototype.clone.call(this, obj);
this.children.forEach(function (no) {
node.add(no.clone());
});
return node;
}
/**
* get all shapes that intersect a point. Note: because this method must clear a temporary
* canvas and redraw every shape inside the container, it should only be used for special situations
* because it performs very poorly. Please use the {@link Konva.Stage#getIntersection} method if at all possible
* because it performs much better.
* Nodes with listening set to false or invisible nodes are not detected
* @method
* @name Konva.Container#getAllIntersections
* @param {Object} pos
* @param {Number} pos.x
* @param {Number} pos.y
* @returns {Array} array of shapes
*/
getAllIntersections(pos) {
const arr = [];
this.find('Shape').forEach((shape) => {
if (shape.isVisible() && shape.intersects(pos)) {
arr.push(shape);
}
});
return arr;
}
_clearSelfAndDescendantCache(attr) {
var _a;
// Perf: an absoluteTransform cascade is an update batch, so a listener on
// one of N descendants defers its work until the outermost walk returns.
// try/finally so depth/flush survive listener exceptions.
const isAbsTransform = attr === undefined || attr === 'absoluteTransform';
if (isAbsTransform)
Node._updateBatchDepth++;
try {
super._clearSelfAndDescendantCache(attr);
// a cached container is drawn from its bitmap, so its descendants keep
// their transform caches while it moves (perf); Node.getAbsoluteTransform
// skips those caches under a cached ancestor
if (attr === 'absoluteTransform' && this.isCached())
return;
(_a = this.children) === null || _a === void 0 ? void 0 : _a.forEach(function (node) {
node._clearSelfAndDescendantCache(attr);
});
}
finally {
if (isAbsTransform && --Node._updateBatchDepth === 0) {
Node._flushUpdateBatch();
}
}
}
_setChildrenIndices() {
var _a;
(_a = this.children) === null || _a === void 0 ? void 0 : _a.forEach(function (child, n) {
child.index = n;
});
this._notifySubtreeChange();
this._requestDraw();
}
drawScene(can, top) {
const layer = this.getLayer(), canvas = can || (layer && layer.getCanvas()), context = canvas && canvas.getContext(), cachedCanvas = this._getCanvasCache(), cachedSceneCanvas = cachedCanvas && cachedCanvas.scene;
const caching = top === this;
if (!(top ? this._isVisible(top) : this.isVisible()) && !caching) {
return this;
}
if (cachedSceneCanvas) {
context.save();
const m = this.getAbsoluteTransform(top).getMatrix();
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
this._drawCachedSceneCanvas(context);
context.restore();
}
else {
this._drawChildren('drawScene', canvas, top);
}
return this;
}
drawHit(can, top) {
if (!this.shouldDrawHit(top)) {
return this;
}
const layer = this.getLayer(), canvas = can || (layer && layer.hitCanvas), context = canvas && canvas.getContext(), cachedHitCanvas = this._getCachedHitCanvas(top);
if (cachedHitCanvas) {
context.save();
const m = this.getAbsoluteTransform(top).getMatrix();
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
this._drawCachedHitCanvas(context, cachedHitCanvas);
context.restore();
}
else {
this._drawChildren('drawHit', canvas, top);
}
return this;
}
_drawChildren(drawMethod, canvas, top) {
const context = canvas && canvas.getContext(), clipWidth = this.clipWidth(), clipHeight = this.clipHeight(), clipFunc = this.clipFunc(), hasClip = (typeof clipWidth === 'number' && typeof clipHeight === 'number') ||
clipFunc;
const selfCache = top === this;
if (hasClip) {
context.save();
const transform = this.getAbsoluteTransform(top);
let m = transform.getMatrix();
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
context.beginPath();
let clipArgs;
if (clipFunc) {
clipArgs = clipFunc.call(this, context, this);
}
else {
const clipX = this.clipX();
const clipY = this.clipY();
context.rect(clipX || 0, clipY || 0, clipWidth, clipHeight);
}
context.clip.apply(context, clipArgs);
m = transform.copy().invert().getMatrix();
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
}
const hasComposition = !selfCache &&
this.globalCompositeOperation() !== 'source-over' &&
drawMethod === 'drawScene';
if (hasComposition) {
context.save();
context._applyGlobalCompositeOperation(this);
}
try {
this._drawChildNodes(drawMethod, canvas, top);
}
finally {
if (hasComposition) {
context.restore();
}
if (hasClip) {
context.restore();
}
}
}
_drawChildNodes(drawMethod, canvas, top) {
var _a;
(_a = this.children) === null || _a === void 0 ? void 0 : _a.forEach(function (child) {
child[drawMethod](canvas, top);
});
}
getClientRect(config = {}) {
var _a;
const cachedRect = this._getCachedSceneRect(config);
if (cachedRect)
return cachedRect;
const skipTransform = config.skipTransform;
const relativeTo = config.relativeTo;
const [a, b, c, d] = this.getAbsoluteTransform().getMatrix();
// A zero scale cannot project local padding for a non-scaling stroke.
const measureTransformed = !skipTransform && (config._forDrawing || a * d - b * c === 0);
let minX, minY, maxX, maxY;
let selfRect = {
x: Infinity,
y: Infinity,
width: 0,
height: 0,
};
const that = this;
(_a = this.children) === null || _a === void 0 ? void 0 : _a.forEach(function (child) {
// skip invisible children
if (!child.visible()) {
return;
}
const rect = child.getClientRect({
relativeTo: measureTransformed ? relativeTo : that,
_forDrawing: config._forDrawing,
skipShadow: config.skipShadow,
skipStroke: config.skipStroke,
});
// skip invisible children (like empty groups) and a child whose rect
// would poison the whole box
if ((rect.width === 0 && rect.height === 0) ||
!isFinite(rect.x + rect.y + rect.width + rect.height)) {
return;
}
if (minX === undefined) {
// initial value for first child
minX = rect.x;
minY = rect.y;
maxX = rect.x + rect.width;
maxY = rect.y + rect.height;
}
else {
minX = Math.min(minX, rect.x);
minY = Math.min(minY, rect.y);
maxX = Math.max(maxX, rect.x + rect.width);
maxY = Math.max(maxY, rect.y + rect.height);
}
});
if (minX !== undefined) {
selfRect = {
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY,
};
}
else {
selfRect = {
x: 0,
y: 0,
width: 0,
height: 0,
};
}
if (!skipTransform && !measureTransformed) {
return this._transformedRect(selfRect, relativeTo);
}
return selfRect;
}
}
// add getters setters
Factory.addComponentsGetterSetter(Container, 'clip', [
'x',
'y',
'width',
'height',
]);
/**
* get/set clip
* @method
* @name Konva.Container#clip
* @param {Object} clip
* @param {Number} clip.x
* @param {Number} clip.y
* @param {Number} clip.width
* @param {Number} clip.height
* @returns {Object}
* @example
* // get clip
* var clip = container.clip();
*
* // set clip
* container.clip({
* x: 20,
* y: 20,
* width: 20,
* height: 20
* });
*/
Factory.addGetterSetter(Container, 'clipX', undefined, getNumberValidator());
/**
* get/set clip x
* @name Konva.Container#clipX
* @method
* @param {Number} x
* @returns {Number}
* @example
* // get clip x
* var clipX = container.clipX();
*
* // set clip x
* container.clipX(10);
*/
Factory.addGetterSetter(Container, 'clipY', undefined, getNumberValidator());
/**
* get/set clip y
* @name Konva.Container#clipY
* @method
* @param {Number} y
* @returns {Number}
* @example
* // get clip y
* var clipY = container.clipY();
*
* // set clip y
* container.clipY(10);
*/
Factory.addGetterSetter(Container, 'clipWidth', undefined, getNumberValidator());
/**
* get/set clip width
* @name Konva.Container#clipWidth
* @method
* @param {Number} width
* @returns {Number}
* @example
* // get clip width
* var clipWidth = container.clipWidth();
*
* // set clip width
* container.clipWidth(100);
*/
Factory.addGetterSetter(Container, 'clipHeight', undefined, getNumberValidator());
/**
* get/set clip height
* @name Konva.Container#clipHeight
* @method
* @param {Number} height
* @returns {Number}
* @example
* // get clip height
* var clipHeight = container.clipHeight();
*
* // set clip height
* container.clipHeight(100);
*/
Factory.addGetterSetter(Container, 'clipFunc');
/**
* get/set clip function
* @name Konva.Container#clipFunc
* @method
* @param {Function} function
* @returns {Function}
* @example
* // get clip function
* var clipFunction = container.clipFunc();
*
* // set clip function
* container.clipFunc(function(ctx) {
* ctx.rect(0, 0, 100, 100);
* });
*
* container.clipFunc(function(ctx) {
* // optionally return a clip Path2D and clip-rule or just the clip-rule
* return [new Path2D('M0 0v50h50Z'), 'evenodd']
* });
*/
+344
View File
@@ -0,0 +1,344 @@
import type { Canvas, SceneCanvas } from './Canvas.ts';
import type { Shape } from './Shape.ts';
import type { IRect } from './types.ts';
import type { Node } from './Node.ts';
declare const CONTEXT_PROPERTIES: readonly ["fillStyle", "strokeStyle", "shadowColor", "shadowBlur", "shadowOffsetX", "shadowOffsetY", "letterSpacing", "lineCap", "lineDashOffset", "lineJoin", "lineWidth", "miterLimit", "direction", "font", "textAlign", "textBaseline", "globalAlpha", "globalCompositeOperation", "imageSmoothingEnabled", "imageSmoothingQuality", "filter"];
export declare function isCSSFiltersSupported(): boolean;
interface ExtendedCanvasRenderingContext2D extends CanvasRenderingContext2D {
letterSpacing: string;
}
/**
* Konva wrapper around native 2d canvas context. It has almost the same API of 2d context with some additional functions.
* With core Konva shapes you don't need to use this object. But you will use it if you want to create
* a [custom shape](/docs/react/Custom_Shape.html) or a [custom hit regions](/docs/events/Custom_Hit_Region.html).
* For full information about each 2d context API use [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D)
* @constructor
* @memberof Konva
* @example
* const rect = new Konva.Shape({
* fill: 'red',
* width: 100,
* height: 100,
* sceneFunc: (ctx, shape) => {
* // ctx - is context wrapper
* // shape - is instance of Konva.Shape, so it equals to "rect" variable
* ctx.rect(0, 0, shape.getAttr('width'), shape.getAttr('height'));
*
* // automatically fill shape from props and draw hit region
* ctx.fillStrokeShape(shape);
* }
* })
*/
export declare class Context {
canvas: Canvas;
_context: CanvasRenderingContext2D;
traceArr: Array<string>;
constructor(canvas: Canvas);
/**
* fill shape
* @method
* @name Konva.Context#fillShape
* @param {Konva.Shape} shape
*/
fillShape(shape: Shape): void;
_fill(shape: Shape): void;
/**
* stroke shape
* @method
* @name Konva.Context#strokeShape
* @param {Konva.Shape} shape
*/
strokeShape(shape: Shape): void;
_stroke(shape: Shape): void;
/**
* fill then stroke
* @method
* @name Konva.Context#fillStrokeShape
* @param {Konva.Shape} shape
*/
fillStrokeShape(shape: Shape): void;
getTrace(relaxed?: boolean, rounded?: boolean): string;
clearTrace(): void;
_trace(str: any): void;
/**
* reset canvas context transform
* @method
* @name Konva.Context#reset
*/
reset(): void;
/**
* get canvas wrapper
* @method
* @name Konva.Context#getCanvas
* @returns {Konva.Canvas}
*/
getCanvas(): Canvas;
/**
* clear canvas
* @method
* @name Konva.Context#clear
* @param {Object} [bounds]
* @param {Number} [bounds.x]
* @param {Number} [bounds.y]
* @param {Number} [bounds.width]
* @param {Number} [bounds.height]
*/
clear(bounds?: IRect): void;
_drawDeviceBuffer(canvas: SceneCanvas): void;
_applyLineCap(shape: Shape): void;
_opacityRoot: Node | undefined;
_getOpacity(shape: Node): any;
_applyOpacity(shape: Node): void;
_applyLineJoin(shape: Shape): void;
_applyMiterLimit(shape: Shape): void;
setAttr(attr: string, val: any): void;
/**
* arc function.
* @method
* @name Konva.Context#arc
*/
arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterClockwise?: boolean): void;
/**
* arcTo function.
* @method
* @name Konva.Context#arcTo
*
*/
arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void;
/**
* beginPath function.
* @method
* @name Konva.Context#beginPath
*/
beginPath(): void;
/**
* bezierCurveTo function.
* @method
* @name Konva.Context#bezierCurveTo
*/
bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void;
/**
* clearRect function.
* @method
* @name Konva.Context#clearRect
*/
clearRect(x: number, y: number, width: number, height: number): void;
/**
* clip function.
* @method
* @name Konva.Context#clip
*/
clip(fillRule?: CanvasFillRule): void;
clip(path: Path2D, fillRule?: CanvasFillRule): void;
/**
* closePath function.
* @method
* @name Konva.Context#closePath
*/
closePath(): void;
/**
* createImageData function.
* @method
* @name Konva.Context#createImageData
*/
createImageData(width: any, height: any): ImageData | undefined;
/**
* createLinearGradient function.
* @method
* @name Konva.Context#createLinearGradient
*/
createLinearGradient(x0: number, y0: number, x1: number, y1: number): CanvasGradient;
/**
* createPattern function.
* @method
* @name Konva.Context#createPattern
*/
createPattern(image: CanvasImageSource, repetition: string | null): CanvasPattern | null;
/**
* createRadialGradient function.
* @method
* @name Konva.Context#createRadialGradient
*/
createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): CanvasGradient;
/**
* drawImage function.
* @method
* @name Konva.Context#drawImage
*/
drawImage(image: CanvasImageSource, sx: number, sy: number, sWidth?: number, sHeight?: number, dx?: number, dy?: number, dWidth?: number, dHeight?: number): void;
/**
* ellipse function.
* @method
* @name Konva.Context#ellipse
*/
ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void;
/**
* isPointInPath function.
* @method
* @name Konva.Context#isPointInPath
*/
isPointInPath(x: number, y: number, path?: Path2D, fillRule?: CanvasFillRule): boolean;
/**
* fill function.
* @method
* @name Konva.Context#fill
*/
fill(fillRule?: CanvasFillRule): void;
fill(path: Path2D, fillRule?: CanvasFillRule): void;
/**
* fillRect function.
* @method
* @name Konva.Context#fillRect
*/
fillRect(x: number, y: number, width: number, height: number): void;
/**
* strokeRect function.
* @method
* @name Konva.Context#strokeRect
*/
strokeRect(x: number, y: number, width: number, height: number): void;
/**
* fillText function.
* @method
* @name Konva.Context#fillText
*/
fillText(text: string, x: number, y: number, maxWidth?: number): void;
/**
* measureText function.
* @method
* @name Konva.Context#measureText
*/
measureText(text: string): TextMetrics;
/**
* getImageData function.
* @method
* @name Konva.Context#getImageData
*/
getImageData(sx: number, sy: number, sw: number, sh: number): ImageData;
/**
* lineTo function.
* @method
* @name Konva.Context#lineTo
*/
lineTo(x: number, y: number): void;
/**
* moveTo function.
* @method
* @name Konva.Context#moveTo
*/
moveTo(x: number, y: number): void;
/**
* rect function.
* @method
* @name Konva.Context#rect
*/
rect(x: number, y: number, width: number, height: number): void;
/**
* roundRect function.
* @method
* @name Konva.Context#roundRect
*/
roundRect(x: number, y: number, width: number, height: number, radii: number | DOMPointInit | (number | DOMPointInit)[]): void;
/**
* putImageData function.
* @method
* @name Konva.Context#putImageData
*/
putImageData(imageData: ImageData, dx: number, dy: number): void;
/**
* quadraticCurveTo function.
* @method
* @name Konva.Context#quadraticCurveTo
*/
quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void;
/**
* restore function.
* @method
* @name Konva.Context#restore
*/
restore(): void;
/**
* rotate function.
* @method
* @name Konva.Context#rotate
*/
rotate(angle: number): void;
/**
* save function.
* @method
* @name Konva.Context#save
*/
save(): void;
/**
* scale function.
* @method
* @name Konva.Context#scale
*/
scale(x: number, y: number): void;
/**
* setLineDash function.
* @method
* @name Konva.Context#setLineDash
*/
setLineDash(segments: number[]): void;
/**
* getLineDash function.
* @method
* @name Konva.Context#getLineDash
*/
getLineDash(): number[];
/**
* setTransform function.
* @method
* @name Konva.Context#setTransform
*/
setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void;
/**
* stroke function.
* @method
* @name Konva.Context#stroke
*/
stroke(path2d?: Path2D): void;
/**
* strokeText function.
* @method
* @name Konva.Context#strokeText
*/
strokeText(text: string, x: number, y: number, maxWidth?: number): void;
/**
* transform function.
* @method
* @name Konva.Context#transform
*/
transform(a: number, b: number, c: number, d: number, e: number, f: number): void;
/**
* translate function.
* @method
* @name Konva.Context#translate
*/
translate(x: number, y: number): void;
_enableTrace(): void;
_applyGlobalCompositeOperation(node: any): void;
}
type CanvasContextProps = Pick<ExtendedCanvasRenderingContext2D, (typeof CONTEXT_PROPERTIES)[number]>;
export interface Context extends CanvasContextProps {
}
export declare class SceneContext extends Context {
constructor(canvas: Canvas, { willReadFrequently }?: {
willReadFrequently?: boolean | undefined;
});
_getFillPattern(shape: Shape): any;
_getFillStyle(shape: Shape): any;
_fill(shape: Shape): void;
_strokeLinearGradient(shape: any): void;
_applyStrokeStyle(shape: Shape): void;
_stroke(shape: any): void;
_applyShadow(shape: any): void;
}
export declare class HitContext extends Context {
constructor(canvas: Canvas);
_fill(shape: Shape): void;
strokeShape(shape: Shape): void;
_stroke(shape: any): void;
}
export {};
+831
View File
@@ -0,0 +1,831 @@
import { Transform, Util } from "./Util.js";
import { Konva } from "./Global.js";
function simplifyArray(arr) {
const retArr = [], len = arr.length, util = Util;
for (let n = 0; n < len; n++) {
let val = arr[n];
if (util._isNumber(val)) {
val = Math.round(val * 1000) / 1000;
}
else if (!util._isString(val)) {
val = val + '';
}
retArr.push(val);
}
return retArr;
}
const COMMA = ',', OPEN_PAREN = '(', CLOSE_PAREN = ')', SEMICOLON = ';', DOUBLE_PAREN = '()',
// EMPTY_STRING = '',
EQUALS = '=',
// SET = 'set',
CONTEXT_METHODS = [
'arc',
'arcTo',
'beginPath',
'bezierCurveTo',
'clearRect',
'clip',
'closePath',
'createLinearGradient',
'createPattern',
'createRadialGradient',
'drawImage',
'ellipse',
'fill',
'fillText',
'getImageData',
'createImageData',
'lineTo',
'moveTo',
'putImageData',
'quadraticCurveTo',
'rect',
'roundRect',
'restore',
'rotate',
'save',
'scale',
'setLineDash',
'setTransform',
'stroke',
'strokeText',
'transform',
'translate',
];
const CONTEXT_PROPERTIES = [
'fillStyle',
'strokeStyle',
'shadowColor',
'shadowBlur',
'shadowOffsetX',
'shadowOffsetY',
'letterSpacing',
'lineCap',
'lineDashOffset',
'lineJoin',
'lineWidth',
'miterLimit',
'direction',
'font',
'textAlign',
'textBaseline',
'globalAlpha',
'globalCompositeOperation',
'imageSmoothingEnabled',
'imageSmoothingQuality',
'filter',
];
const traceArrMax = 100;
// Check if CSS filters are supported in the current browser
let _cssFiltersSupported = null;
export function isCSSFiltersSupported() {
if (_cssFiltersSupported !== null) {
return _cssFiltersSupported;
}
try {
const canvas = Util.createCanvasElement();
const ctx = canvas.getContext('2d');
_cssFiltersSupported = !!ctx && 'filter' in ctx;
Util.releaseCanvas(canvas);
}
catch (e) {
_cssFiltersSupported = false;
}
return _cssFiltersSupported;
}
/**
* Konva wrapper around native 2d canvas context. It has almost the same API of 2d context with some additional functions.
* With core Konva shapes you don't need to use this object. But you will use it if you want to create
* a [custom shape](/docs/react/Custom_Shape.html) or a [custom hit regions](/docs/events/Custom_Hit_Region.html).
* For full information about each 2d context API use [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D)
* @constructor
* @memberof Konva
* @example
* const rect = new Konva.Shape({
* fill: 'red',
* width: 100,
* height: 100,
* sceneFunc: (ctx, shape) => {
* // ctx - is context wrapper
* // shape - is instance of Konva.Shape, so it equals to "rect" variable
* ctx.rect(0, 0, shape.getAttr('width'), shape.getAttr('height'));
*
* // automatically fill shape from props and draw hit region
* ctx.fillStrokeShape(shape);
* }
* })
*/
export class Context {
constructor(canvas) {
this.canvas = canvas;
if (Konva.enableTrace) {
this.traceArr = [];
this._enableTrace();
}
}
/**
* fill shape
* @method
* @name Konva.Context#fillShape
* @param {Konva.Shape} shape
*/
fillShape(shape) {
if (shape.fillEnabled()) {
this._fill(shape);
}
}
_fill(shape) {
// abstract
}
/**
* stroke shape
* @method
* @name Konva.Context#strokeShape
* @param {Konva.Shape} shape
*/
strokeShape(shape) {
if (shape.hasStroke()) {
this._stroke(shape);
}
}
_stroke(shape) {
// abstract
}
/**
* fill then stroke
* @method
* @name Konva.Context#fillStrokeShape
* @param {Konva.Shape} shape
*/
fillStrokeShape(shape) {
if (shape.attrs.fillAfterStrokeEnabled) {
this.strokeShape(shape);
this.fillShape(shape);
}
else {
this.fillShape(shape);
this.strokeShape(shape);
}
}
getTrace(relaxed, rounded) {
let traceArr = this.traceArr, len = traceArr.length, str = '', n, trace, method, args;
for (n = 0; n < len; n++) {
trace = traceArr[n];
method = trace.method;
// methods
if (method) {
args = trace.args;
str += method;
if (relaxed) {
str += DOUBLE_PAREN;
}
else {
if (rounded) {
args = args.map((a) => (typeof a === 'number' ? Math.floor(a) : a));
}
str += OPEN_PAREN + args.join(COMMA) + CLOSE_PAREN;
}
}
else {
// properties
str += trace.property;
if (!relaxed) {
str += EQUALS + trace.val;
}
}
str += SEMICOLON;
}
return str;
}
clearTrace() {
this.traceArr = [];
}
_trace(str) {
let traceArr = this.traceArr, len;
traceArr.push(str);
len = traceArr.length;
if (len >= traceArrMax) {
traceArr.shift();
}
}
/**
* reset canvas context transform
* @method
* @name Konva.Context#reset
*/
reset() {
const pixelRatio = this.getCanvas().getPixelRatio();
this.setTransform(1 * pixelRatio, 0, 0, 1 * pixelRatio, 0, 0);
}
/**
* get canvas wrapper
* @method
* @name Konva.Context#getCanvas
* @returns {Konva.Canvas}
*/
getCanvas() {
return this.canvas;
}
/**
* clear canvas
* @method
* @name Konva.Context#clear
* @param {Object} [bounds]
* @param {Number} [bounds.x]
* @param {Number} [bounds.y]
* @param {Number} [bounds.width]
* @param {Number} [bounds.height]
*/
clear(bounds) {
const canvas = this.getCanvas();
if (bounds) {
this.clearRect(bounds.x || 0, bounds.y || 0, bounds.width || 0, bounds.height || 0);
}
else {
this.clearRect(0, 0, canvas.getWidth() / canvas.pixelRatio, canvas.getHeight() / canvas.pixelRatio);
}
}
// Copy a buffer already rasterized in destination pixels. The caller owns
// save/restore, opacity, compositing and the destination clip.
_drawDeviceBuffer(canvas) {
const { x, y, width, height } = canvas._isolationRect;
this.setTransform(1, 0, 0, 1, 0, 0);
this.imageSmoothingEnabled = false;
this.drawImage(canvas._canvas, 0, 0, width, height, x, y, width, height);
}
_applyLineCap(shape) {
const lineCap = shape.attrs.lineCap;
if (lineCap) {
this.setAttr('lineCap', lineCap);
}
}
_getOpacity(shape) {
if (!this._opacityRoot) {
return shape.getAbsoluteOpacity();
}
let opacity = shape.opacity();
let parent = shape.parent;
while (parent && parent !== this._opacityRoot) {
opacity *= parent.opacity();
parent = parent.parent;
}
return opacity;
}
_applyOpacity(shape) {
const absOpacity = this._getOpacity(shape);
if (absOpacity !== 1) {
this.setAttr('globalAlpha', absOpacity);
}
}
_applyLineJoin(shape) {
const lineJoin = shape.attrs.lineJoin;
if (lineJoin) {
this.setAttr('lineJoin', lineJoin);
}
}
_applyMiterLimit(shape) {
const miterLimit = shape.attrs.miterLimit;
if (miterLimit != null) {
this.setAttr('miterLimit', miterLimit);
}
}
setAttr(attr, val) {
this._context[attr] = val;
}
/**
* arc function.
* @method
* @name Konva.Context#arc
*/
arc(x, y, radius, startAngle, endAngle, counterClockwise) {
this._context.arc(x, y, radius, startAngle, endAngle, counterClockwise);
}
/**
* arcTo function.
* @method
* @name Konva.Context#arcTo
*
*/
arcTo(x1, y1, x2, y2, radius) {
this._context.arcTo(x1, y1, x2, y2, radius);
}
/**
* beginPath function.
* @method
* @name Konva.Context#beginPath
*/
beginPath() {
this._context.beginPath();
}
/**
* bezierCurveTo function.
* @method
* @name Konva.Context#bezierCurveTo
*/
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
this._context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
}
/**
* clearRect function.
* @method
* @name Konva.Context#clearRect
*/
clearRect(x, y, width, height) {
this._context.clearRect(x, y, width, height);
}
clip(...args) {
this._context.clip.apply(this._context, args);
}
/**
* closePath function.
* @method
* @name Konva.Context#closePath
*/
closePath() {
this._context.closePath();
}
/**
* createImageData function.
* @method
* @name Konva.Context#createImageData
*/
createImageData(width, height) {
const a = arguments;
if (a.length === 2) {
return this._context.createImageData(width, height);
}
else if (a.length === 1) {
return this._context.createImageData(width);
}
}
/**
* createLinearGradient function.
* @method
* @name Konva.Context#createLinearGradient
*/
createLinearGradient(x0, y0, x1, y1) {
return this._context.createLinearGradient(x0, y0, x1, y1);
}
/**
* createPattern function.
* @method
* @name Konva.Context#createPattern
*/
createPattern(image, repetition) {
return this._context.createPattern(image, repetition);
}
/**
* createRadialGradient function.
* @method
* @name Konva.Context#createRadialGradient
*/
createRadialGradient(x0, y0, r0, x1, y1, r1) {
return this._context.createRadialGradient(x0, y0, r0, x1, y1, r1);
}
/**
* drawImage function.
* @method
* @name Konva.Context#drawImage
*/
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) {
// this._context.drawImage(...arguments);
const a = arguments, _context = this._context;
if (a.length === 3) {
_context.drawImage(image, sx, sy);
}
else if (a.length === 5) {
_context.drawImage(image, sx, sy, sWidth, sHeight);
}
else if (a.length === 9) {
_context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
}
}
/**
* ellipse function.
* @method
* @name Konva.Context#ellipse
*/
ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise) {
this._context.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise);
}
/**
* isPointInPath function.
* @method
* @name Konva.Context#isPointInPath
*/
isPointInPath(x, y, path, fillRule) {
if (path) {
return this._context.isPointInPath(path, x, y, fillRule);
}
return this._context.isPointInPath(x, y, fillRule);
}
fill(...args) {
// this._context.fill();
this._context.fill.apply(this._context, args);
}
/**
* fillRect function.
* @method
* @name Konva.Context#fillRect
*/
fillRect(x, y, width, height) {
this._context.fillRect(x, y, width, height);
}
/**
* strokeRect function.
* @method
* @name Konva.Context#strokeRect
*/
strokeRect(x, y, width, height) {
this._context.strokeRect(x, y, width, height);
}
/**
* fillText function.
* @method
* @name Konva.Context#fillText
*/
fillText(text, x, y, maxWidth) {
if (maxWidth) {
this._context.fillText(text, x, y, maxWidth);
}
else {
this._context.fillText(text, x, y);
}
}
/**
* measureText function.
* @method
* @name Konva.Context#measureText
*/
measureText(text) {
return this._context.measureText(text);
}
/**
* getImageData function.
* @method
* @name Konva.Context#getImageData
*/
getImageData(sx, sy, sw, sh) {
return this._context.getImageData(sx, sy, sw, sh);
}
/**
* lineTo function.
* @method
* @name Konva.Context#lineTo
*/
lineTo(x, y) {
this._context.lineTo(x, y);
}
/**
* moveTo function.
* @method
* @name Konva.Context#moveTo
*/
moveTo(x, y) {
this._context.moveTo(x, y);
}
/**
* rect function.
* @method
* @name Konva.Context#rect
*/
rect(x, y, width, height) {
this._context.rect(x, y, width, height);
}
/**
* roundRect function.
* @method
* @name Konva.Context#roundRect
*/
roundRect(x, y, width, height, radii) {
this._context.roundRect(x, y, width, height, radii);
}
/**
* putImageData function.
* @method
* @name Konva.Context#putImageData
*/
putImageData(imageData, dx, dy) {
this._context.putImageData(imageData, dx, dy);
}
/**
* quadraticCurveTo function.
* @method
* @name Konva.Context#quadraticCurveTo
*/
quadraticCurveTo(cpx, cpy, x, y) {
this._context.quadraticCurveTo(cpx, cpy, x, y);
}
/**
* restore function.
* @method
* @name Konva.Context#restore
*/
restore() {
this._context.restore();
}
/**
* rotate function.
* @method
* @name Konva.Context#rotate
*/
rotate(angle) {
this._context.rotate(angle);
}
/**
* save function.
* @method
* @name Konva.Context#save
*/
save() {
this._context.save();
}
/**
* scale function.
* @method
* @name Konva.Context#scale
*/
scale(x, y) {
this._context.scale(x, y);
}
/**
* setLineDash function.
* @method
* @name Konva.Context#setLineDash
*/
setLineDash(segments) {
this._context.setLineDash(segments);
}
/**
* getLineDash function.
* @method
* @name Konva.Context#getLineDash
*/
getLineDash() {
return this._context.getLineDash();
}
/**
* setTransform function.
* @method
* @name Konva.Context#setTransform
*/
setTransform(a, b, c, d, e, f) {
this._context.setTransform(a, b, c, d, e, f);
}
/**
* stroke function.
* @method
* @name Konva.Context#stroke
*/
stroke(path2d) {
if (path2d) {
this._context.stroke(path2d);
}
else {
this._context.stroke();
}
}
/**
* strokeText function.
* @method
* @name Konva.Context#strokeText
*/
strokeText(text, x, y, maxWidth) {
this._context.strokeText(text, x, y, maxWidth);
}
/**
* transform function.
* @method
* @name Konva.Context#transform
*/
transform(a, b, c, d, e, f) {
this._context.transform(a, b, c, d, e, f);
}
/**
* translate function.
* @method
* @name Konva.Context#translate
*/
translate(x, y) {
this._context.translate(x, y);
}
_enableTrace() {
let that = this, len = CONTEXT_METHODS.length, origSetter = this.setAttr, n, args;
// to prevent creating scope function at each loop
const func = function (methodName) {
let origMethod = that[methodName], ret;
that[methodName] = function () {
args = simplifyArray(Array.prototype.slice.call(arguments, 0));
ret = origMethod.apply(that, arguments);
that._trace({
method: methodName,
args: args,
});
return ret;
};
};
// methods
for (n = 0; n < len; n++) {
func(CONTEXT_METHODS[n]);
}
// attrs
that.setAttr = function () {
origSetter.apply(that, arguments);
const prop = arguments[0];
let val = arguments[1];
if (prop === 'shadowOffsetX' ||
prop === 'shadowOffsetY' ||
prop === 'shadowBlur') {
val = val / this.canvas.getPixelRatio();
}
that._trace({
property: prop,
val: val,
});
};
}
_applyGlobalCompositeOperation(node) {
const op = node.attrs.globalCompositeOperation;
const def = !op || op === 'source-over';
if (!def) {
this.setAttr('globalCompositeOperation', op);
}
}
}
CONTEXT_PROPERTIES.forEach(function (prop) {
Object.defineProperty(Context.prototype, prop, {
get() {
return this._context[prop];
},
set(val) {
this._context[prop] = val;
},
});
});
export class SceneContext extends Context {
constructor(canvas, { willReadFrequently = false } = {}) {
super(canvas);
this._context = canvas._canvas.getContext('2d', {
willReadFrequently,
});
}
_getFillPattern(shape) {
// node-canvas exposes pattern smoothing through patternQuality.
const context = this._context;
if ('patternQuality' in context) {
context.patternQuality = context.imageSmoothingEnabled
? 'good'
: 'nearest';
}
return shape._getFillPattern();
}
_getFillStyle(shape) {
const color = shape.fill();
const priority = shape.fillPriority();
if (color && priority === 'color')
return color;
const pattern = shape.fillPatternImage();
if (pattern && priority === 'pattern')
return this._getFillPattern(shape);
const linear = shape.fillLinearGradientColorStops();
if (linear && priority === 'linear-gradient')
return shape._getLinearGradient();
const radial = shape.fillRadialGradientColorStops();
if (radial && priority === 'radial-gradient')
return shape._getRadialGradient();
if (color)
return color;
if (pattern)
return this._getFillPattern(shape);
if (linear)
return shape._getLinearGradient();
if (radial)
return shape._getRadialGradient();
}
_fill(shape) {
const style = this._getFillStyle(shape);
if (style !== undefined) {
this.setAttr('fillStyle', style);
shape._fillFunc(this);
}
}
_strokeLinearGradient(shape) {
let start = shape.getStrokeLinearGradientStartPoint(), end = shape.getStrokeLinearGradientEndPoint();
if (!shape.getStrokeScaleEnabled()) {
const { a, b, c, d, e, f } = this._context.getTransform();
const transform = new Transform([a, b, c, d, e, f]);
const ratio = this.canvas.getPixelRatio();
const dx = end.x - start.x;
const dy = end.y - start.y;
// Gradient colors follow the inverse-transposed direction. Transforming
// the endpoints alone distorts them under skew or nonuniform scaling.
const nx = d * dx - b * dy;
const ny = a * dy - c * dx;
const lengthSquared = nx * nx + ny * ny;
const scale = lengthSquared
? ((dx * dx + dy * dy) * (a * d - b * c)) / lengthSquared
: 0;
start = transform.point(start);
end = { x: start.x + nx * scale, y: start.y + ny * scale };
start = { x: start.x / ratio, y: start.y / ratio };
end = { x: end.x / ratio, y: end.y / ratio };
}
const colorStops = shape.getStrokeLinearGradientColorStops(), grd = this.createLinearGradient(start.x, start.y, end.x, end.y);
if (colorStops) {
// build color stops
for (let n = 0; n < colorStops.length; n += 2) {
grd.addColorStop(colorStops[n], colorStops[n + 1]);
}
this.setAttr('strokeStyle', grd);
}
}
_applyStrokeStyle(shape) {
if (shape.strokeLinearGradientColorStops()) {
this._strokeLinearGradient(shape);
}
else {
this.setAttr('strokeStyle', shape.stroke());
}
}
_stroke(shape) {
const dash = shape.dash(),
// ignore strokeScaleEnabled for Text
strokeScaleEnabled = shape.getStrokeScaleEnabled();
if (!strokeScaleEnabled) {
this.save();
}
this._applyLineCap(shape);
if (dash && shape.dashEnabled()) {
this.setLineDash(dash);
this.setAttr('lineDashOffset', shape.dashOffset());
}
this.setAttr('lineWidth', shape.strokeWidth());
const shadowColor = this.shadowColor;
const shadowForStrokeEnabled = shape.getShadowForStrokeEnabled();
if (!shadowForStrokeEnabled) {
this.setAttr('shadowColor', 'rgba(0,0,0,0)');
}
this._applyStrokeStyle(shape);
// Resolve the gradient in local coordinates before drawing an unscaled stroke.
if (!strokeScaleEnabled) {
const pixelRatio = this.getCanvas().getPixelRatio();
this.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
}
try {
shape._strokeFunc(this);
}
finally {
if (!shadowForStrokeEnabled) {
this.setAttr('shadowColor', shadowColor);
}
if (!strokeScaleEnabled) {
this.restore();
}
}
}
_applyShadow(shape) {
var _a;
const color = (_a = shape.getShadowRGBA()) !== null && _a !== void 0 ? _a : 'black', blur = shape.getShadowBlur(), offset = shape.getShadowOffset(), scale = shape.getAbsoluteScale(), ratio = this.canvas.getPixelRatio(), scaleX = scale.x * ratio, scaleY = scale.y * ratio;
this.setAttr('shadowColor', color);
this.setAttr('shadowBlur', blur * Math.min(Math.abs(scaleX), Math.abs(scaleY)));
this.setAttr('shadowOffsetX', offset.x * scaleX);
this.setAttr('shadowOffsetY', offset.y * scaleY);
}
}
export class HitContext extends Context {
constructor(canvas) {
super(canvas);
this._context = canvas._canvas.getContext('2d', {
willReadFrequently: true,
});
}
_fill(shape) {
this.save();
this.setAttr('fillStyle', shape.colorKey);
shape._fillFuncHit(this);
this.restore();
}
strokeShape(shape) {
if (shape.hasHitStroke()) {
this._stroke(shape);
}
}
_stroke(shape) {
// ignore strokeScaleEnabled for Text
const strokeScaleEnabled = shape.getStrokeScaleEnabled();
if (!strokeScaleEnabled) {
this.save();
const pixelRatio = this.getCanvas().getPixelRatio();
this.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
}
this._applyLineCap(shape);
const hitStrokeWidth = shape.hitStrokeWidth();
const strokeWidth = hitStrokeWidth === 'auto' ? shape.strokeWidth() : hitStrokeWidth;
this.setAttr('lineWidth', strokeWidth);
this.setAttr('strokeStyle', shape.colorKey);
shape._strokeFuncHit(this);
if (!strokeScaleEnabled) {
this.restore();
}
}
}
+3
View File
@@ -0,0 +1,3 @@
export { Konva } from './_CoreInternals.ts';
import { Konva } from './_CoreInternals.ts';
export default Konva;
+4
View File
@@ -0,0 +1,4 @@
// enter file of limited Konva version with only core functions
export { Konva } from "./_CoreInternals.js";
import { Konva } from "./_CoreInternals.js";
export default Konva;
+24
View File
@@ -0,0 +1,24 @@
import type { Node } from './Node.ts';
import type { Stage } from './Stage.ts';
import type { Vector2d } from './types.ts';
type DragElement = {
node: Node;
startPointerPos: Vector2d;
offset: Vector2d;
pointerId?: number;
pointerEventType?: Stage['_pointerEventType'];
lastPos?: Vector2d;
startEvent?: any;
dragStatus: 'ready' | 'dragging' | 'stopped';
};
export declare const DD: {
readonly isDragging: boolean;
readonly node: Node<import("./Node.ts").NodeConfig> | undefined;
_dragElements: Map<number, DragElement>;
_listenToWindow(win: Window): void;
_batchEvents(callback: () => void, win?: Window): void;
_drag(evt: any, win?: Window): void;
_endDragBefore(evt?: any, win?: Window, only?: DragElement): void;
_endDragAfter(evt: any, only?: DragElement): void;
};
export {};
+203
View File
@@ -0,0 +1,203 @@
import { Util } from "./Util.js";
export const DD = {
get isDragging() {
let flag = false;
DD._dragElements.forEach((elem) => {
if (elem.dragStatus === 'dragging') {
flag = true;
}
});
return flag;
},
get node() {
let node;
for (const elem of DD._dragElements.values()) {
if (elem.dragStatus === 'dragging')
return elem.node;
node = elem.node;
}
// isDragReady() also uses this getter before movement starts.
return node;
},
_dragElements: new Map(),
// Konva is imported into one window, but a stage may be rendered in another
// one: an iframe, or a window opened with `window.open`. Such a window sends
// its pointer events to itself, so `Konva.Stage` asks for every window it is
// rendered in. The handlers get the window they listen to, as only that
// window has pointer positions the stages of that window can use
_listenToWindow(win) {
const endDragBefore = (evt) => DD._endDragBefore(evt, win);
const drag = (evt) => DD._batchEvents(() => DD._drag(evt, win), win);
const endDragAfter = (evt) => DD._batchEvents(() => DD._endDragAfter(evt));
win.addEventListener('mouseup', endDragBefore, true);
win.addEventListener('touchend', endDragBefore, true);
// add touchcancel to fix this: https://github.com/konvajs/konva/issues/1843
win.addEventListener('touchcancel', endDragBefore, true);
win.addEventListener('mousemove', drag);
win.addEventListener('touchmove', drag);
win.addEventListener('mouseup', endDragAfter, false);
win.addEventListener('touchend', endDragAfter, false);
win.addEventListener('touchcancel', endDragAfter, false);
},
_batchEvents(callback, win) {
if (!DD._dragElements.size)
return;
// One scope per integration, even when several stages use the same callback.
// Keep all positions and handlers in the original order across those stages.
const batches = new Set();
for (const { node } of DD._dragElements.values()) {
const stage = node.getStage();
if (!stage || (win && stage._getOwnerWindow() !== win))
continue;
const batch = stage.eventBatchFunc();
if (batch)
batches.add(batch);
}
let run = callback;
for (const batch of batches) {
const next = run;
run = () => Util._batchEvents(batch, next);
}
run();
},
// methods
_drag(evt, win) {
const nodesToFireEvents = [];
// reading the positions forces a layout, so do it once per stage
const positioned = new Set();
DD._dragElements.forEach((elem, key) => {
const { node } = elem;
// we need to find pointer relative to that node
const stage = node.getStage();
// a pointer position of another window is relative to another viewport,
// it would drag the node to a random place
if (win && stage._getOwnerWindow() !== win) {
return;
}
if (!positioned.has(stage)) {
stage.setPointersPositions(evt);
positioned.add(stage);
}
if (elem.pointerEventType &&
elem.pointerEventType !== stage._pointerEventType) {
return;
}
// it is possible that user call startDrag without any event
// it that case we need to detect first movable pointer and attach it into the node
if (elem.pointerId === undefined) {
elem.pointerId = Util._getFirstPointerId(evt);
elem.pointerEventType = stage._pointerEventType;
if (elem.dragStatus === 'dragging') {
stage._cancelClick(elem.pointerId, elem.pointerEventType);
}
}
const pos = stage._changedPointerPositions.find((pos) => pos.id === elem.pointerId);
// not related pointer
if (!pos) {
return;
}
if (elem.dragStatus !== 'dragging') {
const dragDistance = node.dragDistance();
const distance = Math.max(Math.abs(pos.x - elem.startPointerPos.x), Math.abs(pos.y - elem.startPointerPos.y));
if (distance < dragDistance) {
return;
}
node.startDrag({ evt });
// a user can stop dragging inside `dragstart`
if (!node.isDragging()) {
return;
}
}
node._setDragPosition(evt, elem);
nodesToFireEvents.push(node);
});
// call dragmove only after ALL positions are changed
nodesToFireEvents.forEach((node) => {
// node may have been destroyed during a previous dragmove handler
if (!node.getStage()) {
return;
}
node.fire('dragmove', {
type: 'dragmove',
target: node,
evt: evt,
}, true);
});
},
// dragBefore and dragAfter allows us to set correct order of events
// setup all in dragbefore, and stop dragging only after pointerup triggered.
_endDragBefore(evt, win, only) {
const drawNodes = [];
const positioned = new Set();
DD._dragElements.forEach((elem, key) => {
const { node } = elem;
// node.stopDrag() ends only the drags of the same gesture: a Transformer
// drags its other nodes with the same pointer, another pointer is its own
if (only && elem !== only && elem.pointerId !== only.pointerId) {
return;
}
// we need to find pointer relative to that node
const stage = node.getStage();
// a pointer released in another window ends the drag too - the pointer
// is up everywhere - but its position is not used for this stage
if (evt &&
!positioned.has(stage) &&
(!win || stage._getOwnerWindow() === win)) {
stage.setPointersPositions(evt);
positioned.add(stage);
}
if (!only) {
if (evt &&
elem.pointerEventType &&
elem.pointerEventType !== Util._getEventType(evt.type)) {
return;
}
// a drag started without an event (node.startDrag()) that has not
// moved yet has no pointer: any release ends it
const released = elem.pointerId === undefined ||
stage._changedPointerPositions.some((pos) => pos.id === elem.pointerId);
// that pointer is not related: a "ready" element of another pointer
// waits for its own release
if (!released) {
return;
}
}
if (elem.dragStatus === 'ready') {
DD._dragElements.delete(key);
}
else {
// if a node is stopped manually we still need to reset events:
stage._cancelClick(elem.pointerId, elem.pointerEventType);
elem.dragStatus = 'stopped';
// a node that has not moved needs no redraw
const drawNode = node.getLayer() || stage;
if (drawNode && drawNodes.indexOf(drawNode) === -1) {
drawNodes.push(drawNode);
}
}
});
// draw in a sync way
// because mousemove event may trigger BEFORE batch draw is called
// but as we have not hit canvas updated yet, it will trigger incorrect mouseover/mouseout events
drawNodes.forEach((drawNode) => {
drawNode.draw();
});
},
_endDragAfter(evt, only) {
DD._dragElements.forEach((elem, key) => {
if (elem.dragStatus !== 'stopped' ||
(only && elem !== only && elem.pointerId !== only.pointerId)) {
return;
}
elem.node.fire('dragend', {
type: 'dragend',
target: elem.node,
evt: evt,
}, true);
// a dragend handler may have started a new drag of the node
if (elem.dragStatus !== 'dragging') {
DD._dragElements.delete(key);
}
});
},
};
+46
View File
@@ -0,0 +1,46 @@
import type { Node } from './Node.ts';
import type { GetSet } from './types.ts';
/**
* Enforces that a type is a string.
*/
type EnforceString<T> = T extends string ? T : never;
/**
* Represents a class.
*/
type Constructor = abstract new (...args: any) => any;
/**
* An attribute of an instance of the provided class. Attributes names be strings.
*/
type Attr<T extends Constructor> = EnforceString<keyof InstanceType<T>>;
/**
* A function that is called after a setter is called.
*/
type AfterFunc<T extends Constructor> = (this: InstanceType<T>) => void;
/**
* Extracts the type a GetSet returns. The type the setter accepts can be wider,
* so it is matched with `any` instead of being inferred from the getter.
*/
type ExtractGetSet<T> = T extends GetSet<infer U, any, any> ? U : never;
/**
* Extracts the type of a GetSet class attribute.
*/
type Value<T extends Constructor, U extends Attr<T>> = ExtractGetSet<InstanceType<T>[U]>;
/**
* A function that validates a value.
*/
type ValidatorFunc<T> = (val: T, attr: string) => T;
/**
* Extracts the "components" (keys) of a GetSet value. The value must be an object.
*/
type ExtractComponents<T extends Constructor, U extends Attr<T>> = Value<T, U> extends Record<string, any> ? EnforceString<keyof Value<T, U>>[] : never;
export declare const Factory: {
addGetterSetter<T extends Constructor, U extends Attr<T>>(constructor: T, attr: U, def?: Value<T, U>, validator?: ValidatorFunc<Value<T, U>>, after?: AfterFunc<T>): void;
addGetter<T extends Constructor, U extends Attr<T>>(constructor: T, attr: U, def?: Value<T, U>): void;
addSetter<T extends Constructor, U extends Attr<T>>(constructor: T, attr: U, validator?: ValidatorFunc<Value<T, U>>, after?: AfterFunc<T>): void;
overWriteSetter<T extends Constructor, U extends Attr<T>>(constructor: T, attr: U, validator?: ValidatorFunc<Value<T, U>>, after?: AfterFunc<T>): void;
addComponentsGetterSetter<T extends Constructor, U extends Attr<T>>(constructor: T, attr: U, components: ExtractComponents<T, U>, validator?: ValidatorFunc<Value<T, U>>, after?: AfterFunc<T>): void;
addOverloadedGetterSetter<T extends Constructor, U extends Attr<T>>(constructor: T, attr: U): void;
backCompat<T extends Constructor>(constructor: T, methods: Record<string, string>): void;
afterSetFilter(this: Node): void;
};
export {};
+118
View File
@@ -0,0 +1,118 @@
import { Util } from "./Util.js";
import { getComponentValidator } from "./Validators.js";
const GET = 'get';
const SET = 'set';
export const Factory = {
addGetterSetter(constructor, attr, def, validator, after) {
Factory.addGetter(constructor, attr, def);
Factory.addSetter(constructor, attr, validator, after);
Factory.addOverloadedGetterSetter(constructor, attr);
},
addGetter(constructor, attr, def) {
const method = GET + Util._capitalize(attr);
// array defaults are shared between instances, so hand out a copy
const isArr = Array.isArray(def);
const getter = function () {
const val = this.attrs[attr];
return val === undefined ? (isArr ? def.slice() : def) : val;
};
// toObject() compares an attr with the default of the generated getter
getter._def = def;
constructor.prototype[method] = constructor.prototype[method] || getter;
},
addSetter(constructor, attr, validator, after) {
const method = SET + Util._capitalize(attr);
if (!constructor.prototype[method]) {
Factory.overWriteSetter(constructor, attr, validator, after);
}
},
overWriteSetter(constructor, attr, validator, after) {
const method = SET + Util._capitalize(attr);
constructor.prototype[method] = function (val) {
if (validator && val !== undefined && val !== null) {
val = validator.call(this, val, attr);
}
this._setAttr(attr, val);
if (after) {
after.call(this);
}
return this;
};
},
addComponentsGetterSetter(constructor, attr, components, validator, after) {
const len = components.length, capitalize = Util._capitalize, getter = GET + capitalize(attr), setter = SET + capitalize(attr), keys = components.map((c) => attr + capitalize(c)),
// built-in components have their own getters, e.g. getOffsetX
getters = keys.map((key) => GET + capitalize(key));
// getter
constructor.prototype[getter] = function () {
const ret = {};
for (let n = 0; n < len; n++) {
const get = this[getters[n]];
ret[components[n]] = get ? get.call(this) : this.attrs[keys[n]];
}
return ret;
};
const basicValidator = getComponentValidator(components);
// setter
constructor.prototype[setter] = function (val) {
const oldVal = this[getter]();
if (validator) {
val = validator.call(this, val, attr);
}
if (basicValidator) {
basicValidator.call(this, val, attr);
}
for (const key in val) {
if (!val.hasOwnProperty(key)) {
continue;
}
this._setAttr(attr + capitalize(key), val[key]);
}
if (!val) {
components.forEach((component) => {
this._setAttr(attr + capitalize(component), undefined);
});
}
this._fireChangeEvent(attr, oldVal, val);
if (after) {
after.call(this);
}
return this;
};
Factory.addOverloadedGetterSetter(constructor, attr);
},
addOverloadedGetterSetter(constructor, attr) {
const capitalizedAttr = Util._capitalize(attr), setter = SET + capitalizedAttr, getter = GET + capitalizedAttr;
const accessor = function () {
// setting
if (arguments.length) {
this[setter](arguments[0]);
return this;
}
// getting
return this[getter]();
};
constructor.prototype[attr] = accessor;
},
backCompat(constructor, methods) {
Util.each(methods, function (oldMethodName, newMethodName) {
const method = constructor.prototype[newMethodName];
const oldGetter = GET + Util._capitalize(oldMethodName);
const oldSetter = SET + Util._capitalize(oldMethodName);
function deprecated() {
Util.error('"' +
oldMethodName +
'" method is deprecated and will be removed soon. Use ""' +
newMethodName +
'" instead.');
return method.apply(this, arguments);
}
constructor.prototype[oldMethodName] = deprecated;
constructor.prototype[oldGetter] = deprecated;
constructor.prototype[oldSetter] = deprecated;
});
},
afterSetFilter() {
this._filterUpToDate = false;
},
};
+17
View File
@@ -0,0 +1,17 @@
import { Layer } from './Layer.ts';
/**
* FastLayer constructor. **DEPRECATED!** Please use `Konva.Layer({ listening: false})` instead. Layers are tied to their own canvas element and are used
* to contain shapes only. If you don't need node nesting, mouse and touch interactions,
* or event pub/sub, you should use FastLayer instead of Layer to create your layers.
* It renders about 2x faster than normal layers.
*
* @constructor
* @memberof Konva
* @augments Konva.Layer
@@containerParams
* @example
* var layer = new Konva.FastLayer();
*/
export declare class FastLayer extends Layer {
constructor(attrs?: any);
}
+25
View File
@@ -0,0 +1,25 @@
import { Util } from "./Util.js";
import { Layer } from "./Layer.js";
import { _registerNode } from "./Global.js";
/**
* FastLayer constructor. **DEPRECATED!** Please use `Konva.Layer({ listening: false})` instead. Layers are tied to their own canvas element and are used
* to contain shapes only. If you don't need node nesting, mouse and touch interactions,
* or event pub/sub, you should use FastLayer instead of Layer to create your layers.
* It renders about 2x faster than normal layers.
*
* @constructor
* @memberof Konva
* @augments Konva.Layer
@@containerParams
* @example
* var layer = new Konva.FastLayer();
*/
export class FastLayer extends Layer {
constructor(attrs) {
super(attrs);
this.listening(false);
Util.warn('Konva.FastLayer is deprecated. Please use "new Konva.Layer({ listening: false })" instead.');
}
}
FastLayer.prototype.nodeType = 'FastLayer';
_registerNode(FastLayer);
+143
View File
@@ -0,0 +1,143 @@
export declare const glob: any;
export declare const Konva: {
_global: any;
version: string;
isBrowser: boolean;
isUnminified: boolean;
dblClickWindow: number;
getAngle(angle: number): number;
enableTrace: boolean;
pointerEventsEnabled: boolean;
/**
* Should Konva automatically update canvas on any changes. Default is true.
* @property autoDrawEnabled
* @default true
* @name autoDrawEnabled
* @memberof Konva
* @example
* Konva.autoDrawEnabled = true;
*/
autoDrawEnabled: boolean;
/**
* Should we enable hit detection while dragging? For performance reasons, by default it is false.
* But on some rare cases you want to see hit graph and check intersections. Just set it to true.
* @property hitOnDragEnabled
* @default false
* @name hitOnDragEnabled
* @memberof Konva
* @example
* Konva.hitOnDragEnabled = true;
*/
hitOnDragEnabled: boolean;
/**
* Should we capture touch events and bind them to the touchstart target? That is how it works on DOM elements.
* The case: we touchstart on div1, then touchmove out of that element into another element div2.
* DOM will continue trigger touchmove events on div1 (not div2). Because events are "captured" into initial target.
* By default Konva do not do that and will trigger touchmove on another element, while pointer is moving.
* @property capturePointerEventsEnabled
* @default false
* @name capturePointerEventsEnabled
* @memberof Konva
* @example
* Konva.capturePointerEventsEnabled = true;
*/
capturePointerEventsEnabled: boolean;
_renderBackend: string;
/**
* Use legacy text rendering. with "middle" baseline by default.
* @property legacyTextRendering
* @default false
* @name legacyTextRendering
* @memberof Konva
* @example
* Konva.legacyTextRendering = true;
*/
legacyTextRendering: boolean;
/**
* Global pixel ratio configuration. KonvaJS automatically detect pixel ratio of current device.
* But you may override such property, if you want to use your value. Set this value before any components initializations.
* @property pixelRatio
* @default window.devicePixelRatio || 1
* @name pixelRatio
* @memberof Konva
* @example
* // before any Konva code:
* Konva.pixelRatio = 1;
*/
pixelRatio: number;
/**
* Drag distance property. If you start to drag a node you may want to wait until pointer is moved to some distance from start point
* on either axis, only then start dragging. Default is 3px.
* @property dragDistance
* @default 3
* @memberof Konva
* @example
* Konva.dragDistance = 10;
*/
dragDistance: number;
/**
* Use degree values for angle properties. You may set this property to false if you want to use radian values.
* @property angleDeg
* @default true
* @memberof Konva
* @example
* node.rotation(45); // 45 degrees
* Konva.angleDeg = false;
* node.rotation(Math.PI / 2); // PI/2 radian
*/
angleDeg: boolean;
/**
* Show warnings about wrong API usage (`Konva.Util.warn`). Errors are always printed
* @property showWarnings
* @default true
* @memberof Konva
* @example
* Konva.showWarnings = false;
*/
showWarnings: boolean;
/**
* Configure what mouse buttons can be used for drag and drop.
* Default value is [0, 1] - left and middle mouse buttons.
* @property dragButtons
* @default [0, 1]
* @memberof Konva
* @example
* // enable left and right mouse buttons
* Konva.dragButtons = [0, 2];
*/
dragButtons: number[];
/**
* returns whether or not drag and drop is currently active
* @method
* @memberof Konva
*/
isDragging(): boolean;
/**
* returns whether or not a Transformer is currently transforming a node
* @method
* @memberof Konva
*/
isTransforming(): boolean;
/**
* returns whether or not a drag and drop operation is ready, but may
* not necessarily have started
* @method
* @memberof Konva
*/
isDragReady(): boolean;
/**
* Should Konva release canvas elements on destroy. Default is true.
* Useful to avoid memory leak issues in Safari on macOS/iOS.
* @property releaseCanvasOnDestroy
* @default true
* @name releaseCanvasOnDestroy
* @memberof Konva
* @example
* Konva.releaseCanvasOnDestroy = true;
*/
releaseCanvasOnDestroy: boolean;
document: any;
_injectGlobal(Konva: any): void;
};
export declare const _boundedShapes: Set<unknown>;
export declare const _registerNode: (NodeClass: any, boundsCoverPaint?: boolean) => void;
+194
View File
@@ -0,0 +1,194 @@
/*
* Konva JavaScript Framework v10.9.0
* http://konvajs.org/
* Licensed under the MIT
* Date: @@date
*
* Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS)
* Modified work Copyright (C) 2014 - present by Anton Lavrenov (Konva)
*
* @license
*/
const PI_OVER_180 = Math.PI / 180;
/**
* @namespace Konva
*/
function detectBrowser() {
return (typeof window !== 'undefined' &&
// browser case
({}.toString.call(window) === '[object Window]' ||
// electron case
{}.toString.call(window) === '[object global]'));
}
export const glob = typeof global !== 'undefined'
? global
: typeof window !== 'undefined'
? window
: typeof WorkerGlobalScope !== 'undefined'
? self
: {};
export const Konva = {
_global: glob,
version: '10.9.0',
isBrowser: detectBrowser(),
isUnminified: /param/.test(function (param) { }.toString()),
dblClickWindow: 400,
getAngle(angle) {
return Konva.angleDeg ? angle * PI_OVER_180 : angle;
},
enableTrace: false,
pointerEventsEnabled: true,
/**
* Should Konva automatically update canvas on any changes. Default is true.
* @property autoDrawEnabled
* @default true
* @name autoDrawEnabled
* @memberof Konva
* @example
* Konva.autoDrawEnabled = true;
*/
autoDrawEnabled: true,
/**
* Should we enable hit detection while dragging? For performance reasons, by default it is false.
* But on some rare cases you want to see hit graph and check intersections. Just set it to true.
* @property hitOnDragEnabled
* @default false
* @name hitOnDragEnabled
* @memberof Konva
* @example
* Konva.hitOnDragEnabled = true;
*/
hitOnDragEnabled: false,
/**
* Should we capture touch events and bind them to the touchstart target? That is how it works on DOM elements.
* The case: we touchstart on div1, then touchmove out of that element into another element div2.
* DOM will continue trigger touchmove events on div1 (not div2). Because events are "captured" into initial target.
* By default Konva do not do that and will trigger touchmove on another element, while pointer is moving.
* @property capturePointerEventsEnabled
* @default false
* @name capturePointerEventsEnabled
* @memberof Konva
* @example
* Konva.capturePointerEventsEnabled = true;
*/
capturePointerEventsEnabled: false,
_renderBackend: 'web', // web, node-canvas, skia-canvas
/**
* Use legacy text rendering. with "middle" baseline by default.
* @property legacyTextRendering
* @default false
* @name legacyTextRendering
* @memberof Konva
* @example
* Konva.legacyTextRendering = true;
*/
legacyTextRendering: false,
/**
* Global pixel ratio configuration. KonvaJS automatically detect pixel ratio of current device.
* But you may override such property, if you want to use your value. Set this value before any components initializations.
* @property pixelRatio
* @default window.devicePixelRatio || 1
* @name pixelRatio
* @memberof Konva
* @example
* // before any Konva code:
* Konva.pixelRatio = 1;
*/
pixelRatio: (typeof window !== 'undefined' && window.devicePixelRatio) || 1,
/**
* Drag distance property. If you start to drag a node you may want to wait until pointer is moved to some distance from start point
* on either axis, only then start dragging. Default is 3px.
* @property dragDistance
* @default 3
* @memberof Konva
* @example
* Konva.dragDistance = 10;
*/
dragDistance: 3,
/**
* Use degree values for angle properties. You may set this property to false if you want to use radian values.
* @property angleDeg
* @default true
* @memberof Konva
* @example
* node.rotation(45); // 45 degrees
* Konva.angleDeg = false;
* node.rotation(Math.PI / 2); // PI/2 radian
*/
angleDeg: true,
/**
* Show warnings about wrong API usage (`Konva.Util.warn`). Errors are always printed
* @property showWarnings
* @default true
* @memberof Konva
* @example
* Konva.showWarnings = false;
*/
showWarnings: true,
/**
* Configure what mouse buttons can be used for drag and drop.
* Default value is [0, 1] - left and middle mouse buttons.
* @property dragButtons
* @default [0, 1]
* @memberof Konva
* @example
* // enable left and right mouse buttons
* Konva.dragButtons = [0, 2];
*/
dragButtons: [0, 1],
/**
* returns whether or not drag and drop is currently active
* @method
* @memberof Konva
*/
isDragging() {
return Konva['DD'].isDragging;
},
/**
* returns whether or not a Transformer is currently transforming a node
* @method
* @memberof Konva
*/
isTransforming() {
var _a, _b;
return (_b = (_a = Konva['Transformer']) === null || _a === void 0 ? void 0 : _a.isTransforming()) !== null && _b !== void 0 ? _b : false;
},
/**
* returns whether or not a drag and drop operation is ready, but may
* not necessarily have started
* @method
* @memberof Konva
*/
isDragReady() {
return !!Konva['DD'].node;
},
/**
* Should Konva release canvas elements on destroy. Default is true.
* Useful to avoid memory leak issues in Safari on macOS/iOS.
* @property releaseCanvasOnDestroy
* @default true
* @name releaseCanvasOnDestroy
* @memberof Konva
* @example
* Konva.releaseCanvasOnDestroy = true;
*/
releaseCanvasOnDestroy: true,
document: glob.document,
// insert Konva into global namespace (window)
// it is required for npm packages
_injectGlobal(Konva) {
if (typeof glob.Konva !== 'undefined') {
console.error('Several Konva instances detected. It is not recommended to use multiple Konva instances in the same environment.');
}
glob.Konva = Konva;
},
};
// Konva's own shapes whose bounds cover everything they paint. Plugins can
// import _registerNode, so they opt in explicitly.
export const _boundedShapes = new Set();
export const _registerNode = (NodeClass, boundsCoverPaint = false) => {
Konva[NodeClass.prototype.getClassName()] = NodeClass;
if (boundsCoverPaint)
_boundedShapes.add(NodeClass);
};
Konva._injectGlobal(Konva);
+26
View File
@@ -0,0 +1,26 @@
import type { ContainerConfig } from './Container.ts';
import { Container } from './Container.ts';
import type { Node } from './Node.ts';
import type { Shape } from './Shape.ts';
import type { SceneCanvas } from './Canvas.ts';
import type { GetSet } from './types.ts';
export interface GroupConfig extends ContainerConfig {
isolated?: boolean;
}
/**
* Group constructor. Groups are used to contain shapes or other groups.
* @constructor
* @memberof Konva
* @augments Konva.Container
* @param {Object} config
* @param {Boolean} [config.isolated=false] composite children as one live image
* @@nodeParams
* @@containerParams
* @example
* var group = new Konva.Group();
*/
export declare class Group extends Container<Group | Shape, GroupConfig> {
_validateAdd(child: Node): void;
_drawChildNodes(drawMethod: any, canvas: SceneCanvas, top?: Node): void;
isolated: GetSet<boolean, this>;
}
+85
View File
@@ -0,0 +1,85 @@
import { Util } from "./Util.js";
import { Container } from "./Container.js";
import { _registerNode } from "./Global.js";
import { Factory } from "./Factory.js";
import { getBooleanValidator } from "./Validators.js";
/**
* Group constructor. Groups are used to contain shapes or other groups.
* @constructor
* @memberof Konva
* @augments Konva.Container
* @param {Object} config
* @param {Boolean} [config.isolated=false] composite children as one live image
* @@nodeParams
* @@containerParams
* @example
* var group = new Konva.Group();
*/
export class Group extends Container {
_validateAdd(child) {
const type = child.getType();
if (type !== 'Group' && type !== 'Shape') {
Util.throw('You may only add groups and shapes to groups.');
}
}
_drawChildNodes(drawMethod, canvas, top) {
// cache() already provides an isolated surface and owns its snapshot.
if (drawMethod !== 'drawScene' || !this.isolated() || top === this) {
return super._drawChildNodes(drawMethod, canvas, top);
}
if (!canvas.width || !canvas.height) {
return;
}
const context = canvas.getContext();
const surface = canvas._prepareIsolationCanvas(this.getClientRect({ _forDrawing: true, relativeTo: top }));
const surfaceContext = surface.getContext();
surfaceContext._opacityRoot = this;
context.save();
try {
super._drawChildNodes(drawMethod, surface, top);
context._applyOpacity(this);
context._drawDeviceBuffer(surface);
}
catch (error) {
// A throwing sceneFunc can leave unbalanced saves or a clip on the
// scratch context. Discard it rather than contaminate the next draw.
canvas._releaseIsolationCanvas();
throw error;
}
finally {
context.restore();
surfaceContext._opacityRoot = undefined;
}
}
}
/**
* Get or set group isolation. Default is false.
* An isolated group draws its children into a transparent canvas on each scene draw.
* The group opacity and globalCompositeOperation apply once to the completed image.
* Child blend modes and erasing affect only the content inside that canvas.
*
* Explicit cache() calls keep their snapshot behavior. clearCache() resumes live drawing.
* Hit testing keeps its normal behavior, including hits on erased content.
* For video or canvas sources, schedule draws when the source changes.
*
* Buffers use conservative content bounds at the destination pixel ratio.
* Moving the buffer origin changes native edge coverage. A few edge pixels
* can differ substantially; isolation does not promise pixel-identical antialiasing.
* Like cache(), isolation requires custom drawing (sceneFunc, charRenderFunc) to provide accurate
* bounds, including any pixels it paints outside its size: selfRectFunc or a
* getSelfRect() override.
* Text is bounded by its lines plus one font size, so a glyph with many stacked
* marks can be clipped. node-canvas shadows can change with bitmap origin.
* Siblings reuse a buffer; unused or oversized buffers are freed after each layer draw.
* @name Konva.Group#isolated
* @method
* @param {Boolean} isolated
* @returns {Boolean}
* @example
* const group = new Konva.Group({ isolated: true, opacity: 0.5 });
* group.add(shape1, shape2);
* group.isolated(false);
*/
Factory.addGetterSetter(Group, 'isolated', false, getBooleanValidator());
Group.prototype.nodeType = 'Group';
_registerNode(Group);
+188
View File
@@ -0,0 +1,188 @@
import type { ContainerConfig } from './Container.ts';
import { Container } from './Container.ts';
import { Node } from './Node.ts';
import { SceneCanvas, HitCanvas } from './Canvas.ts';
import type { Stage } from './Stage.ts';
import type { GetSet, IRect, Vector2d } from './types.ts';
import type { Group } from './Group.ts';
import type { Shape } from './Shape.ts';
export interface LayerConfig extends ContainerConfig {
clearBeforeDraw?: boolean;
hitGraphEnabled?: boolean;
imageSmoothingEnabled?: boolean;
}
/**
* Layer constructor. Layers are tied to their own canvas element and are used
* to contain groups or shapes.
* @constructor
* @memberof Konva
* @augments Konva.Container
* @param {Object} config
* @param {Boolean} [config.clearBeforeDraw] set this property to false if you don't want
* to clear the canvas before each layer draw. The default value is true.
* @@nodeParams
* @@containerParams
* @example
* var layer = new Konva.Layer();
* stage.add(layer);
* // now you can add shapes, groups into the layer
*/
export declare class Layer extends Container<Group | Shape> {
canvas: SceneCanvas;
hitCanvas: HitCanvas;
_waitingForDraw: boolean;
constructor(config?: LayerConfig);
createPNGStream(): any;
/**
* get layer canvas wrapper
* @method
* @name Konva.Layer#getCanvas
*/
getCanvas(): SceneCanvas;
/**
* get native canvas element
* @method
* @name Konva.Layer#getNativeCanvasElement
*/
getNativeCanvasElement(): HTMLCanvasElement;
/**
* get layer hit canvas
* @method
* @name Konva.Layer#getHitCanvas
*/
getHitCanvas(): HitCanvas;
/**
* get layer canvas context
* @method
* @name Konva.Layer#getContext
*/
getContext(): import("./Context.ts").Context;
/**
* clear the scene and hit canvas of the layer. The nodes stay and are
* drawn again by the next `draw()`
* @method
* @name Konva.Layer#clear
* @param {Object} [bounds] clear only this rectangle: `{ x, y, width, height }`
* @returns {Konva.Layer}
* @example
* layer.clear();
* layer.clear({ x: 0, y: 0, width: 100, height: 100 });
*/
clear(bounds?: IRect): this;
setZIndex(index: number): this;
moveToTop(): boolean;
moveUp(): boolean;
moveDown(): boolean;
moveToBottom(): boolean;
getLayer(): this;
remove(): this;
getStage(): Stage;
_setSize({ width, height }: {
width: any;
height: any;
}): this;
_syncHitCanvasSize(): void;
_validateAdd(child: any): void;
_toKonvaCanvas(config: any): SceneCanvas;
_checkVisibility(): void;
_setSmoothEnabled(): void;
/**
* get/set width of layer. getter return width of stage. setter doing nothing.
* if you want change width use `stage.width(value);`
* @name Konva.Layer#width
* @method
* @returns {Number}
* @example
* var width = layer.width();
*/
getWidth(): number | undefined;
setWidth(): void;
/**
* get/set height of layer.getter return height of stage. setter doing nothing.
* if you want change height use `stage.height(value);`
* @name Konva.Layer#height
* @method
* @returns {Number}
* @example
* var height = layer.height();
*/
getHeight(): number | undefined;
setHeight(): void;
/**
* batch draw. this function will not do immediate draw
* but it will schedule drawing to next tick (requestAnimFrame)
* @method
* @name Konva.Layer#batchDraw
* @return {Konva.Layer} this
*/
batchDraw(): this;
/**
* get visible intersection shape. This is the preferred
* method for determining if a point intersects a shape or not.
* It reads the hit canvas, so it finds what pointer events would: nodes with
* listening set to false or invisible nodes are not detected, a shape with opacity 0
* is, and `hitStrokeWidth` counts. The position is relative to the top left corner of the
* stage container, like `stage.getPointerPosition()`, without the stage transform
* @method
* @name Konva.Layer#getIntersection
* @param {Object} pos
* @param {Number} pos.x
* @param {Number} pos.y
* @returns {Konva.Node}
* @example
* var shape = layer.getIntersection({x: 50, y: 50});
*/
getIntersection(pos: Vector2d): Shape<import("./Shape.ts").ShapeConfig> | null;
_getIntersection(pos: Vector2d): {
shape?: Shape;
antialiased?: boolean;
};
drawScene(can?: SceneCanvas, top?: Node): this;
shouldDrawHit(top?: Node): boolean;
drawHit(can?: HitCanvas, top?: Node): this;
/**
* enable hit graph. **DEPRECATED!** Use `layer.listening(true)` instead.
* @name Konva.Layer#enableHitGraph
* @method
* @returns {Layer}
*/
enableHitGraph(): this;
/**
* disable hit graph. **DEPRECATED!** Use `layer.listening(false)` instead.
* @name Konva.Layer#disableHitGraph
* @method
* @returns {Layer}
*/
disableHitGraph(): this;
setHitGraphEnabled(val: any): void;
getHitGraphEnabled(val: any): boolean;
/**
* Show or hide hit canvas over the stage. May be useful for debugging custom hitFunc
* @name Konva.Layer#toggleHitCanvas
* @method
*/
toggleHitCanvas(): void;
destroy(): this;
hitGraphEnabled: GetSet<boolean, this>;
clearBeforeDraw: GetSet<boolean, this>;
imageSmoothingEnabled: GetSet<boolean, this>;
}
/**
* get/set hitGraphEnabled flag. **DEPRECATED!** Use `layer.listening(false)` instead.
* Disabling the hit graph will greatly increase
* draw performance because the hit graph will not be redrawn each time the layer is
* drawn. This, however, also disables mouse/touch event detection
* @name Konva.Layer#hitGraphEnabled
* @method
* @param {Boolean} enabled
* @returns {Boolean}
* @example
* // get hitGraphEnabled flag
* var hitGraphEnabled = layer.hitGraphEnabled();
*
* // disable hit graph
* layer.hitGraphEnabled(false);
*
* // enable hit graph
* layer.hitGraphEnabled(true);
*/
+532
View File
@@ -0,0 +1,532 @@
import { Util } from "./Util.js";
import { Container } from "./Container.js";
import { Node } from "./Node.js";
import { Factory } from "./Factory.js";
import { SceneCanvas, HitCanvas } from "./Canvas.js";
import { shapes } from "./Shape.js";
import { _registerNode, Konva } from "./Global.js";
import { DD } from "./DragAndDrop.js";
// constants
const BEFORE_DRAW = 'beforeDraw', DRAW = 'draw',
// getIntersection() reads the hit canvas around an edge pixel in blocks
// of this radius, doubling it as long as the search goes on
HIT_SEARCH_RADIUS = 10, HIT_SEARCH_MAX_DISTANCE = 256;
// the shape drawn at a pixel of the hit graph: an opaque pixel, or an edge
// pixel that the shape covers by at least half, so that its colour read back
// premultiplied still rounds to the key of the shape (see getHitColorKey)
function getHitShape(data, i) {
return data[i + 3] >= 128
? shapes[Util.getHitColorKey(data[i], data[i + 1], data[i + 2])]
: undefined;
}
/**
* Layer constructor. Layers are tied to their own canvas element and are used
* to contain groups or shapes.
* @constructor
* @memberof Konva
* @augments Konva.Container
* @param {Object} config
* @param {Boolean} [config.clearBeforeDraw] set this property to false if you don't want
* to clear the canvas before each layer draw. The default value is true.
* @@nodeParams
* @@containerParams
* @example
* var layer = new Konva.Layer();
* stage.add(layer);
* // now you can add shapes, groups into the layer
*/
export class Layer extends Container {
constructor(config) {
super(config);
this.canvas = new SceneCanvas();
this.hitCanvas = new HitCanvas({
pixelRatio: 1,
});
this._waitingForDraw = false;
this.on('visibleChange.konva', this._checkVisibility);
this._checkVisibility();
this.on('imageSmoothingEnabledChange.konva', this._setSmoothEnabled);
this._setSmoothEnabled();
}
// for nodejs?
createPNGStream() {
const c = this.canvas._canvas;
return c.createPNGStream();
}
/**
* get layer canvas wrapper
* @method
* @name Konva.Layer#getCanvas
*/
getCanvas() {
return this.canvas;
}
/**
* get native canvas element
* @method
* @name Konva.Layer#getNativeCanvasElement
*/
getNativeCanvasElement() {
return this.canvas._canvas;
}
/**
* get layer hit canvas
* @method
* @name Konva.Layer#getHitCanvas
*/
getHitCanvas() {
return this.hitCanvas;
}
/**
* get layer canvas context
* @method
* @name Konva.Layer#getContext
*/
getContext() {
return this.getCanvas().getContext();
}
/**
* clear the scene and hit canvas of the layer. The nodes stay and are
* drawn again by the next `draw()`
* @method
* @name Konva.Layer#clear
* @param {Object} [bounds] clear only this rectangle: `{ x, y, width, height }`
* @returns {Konva.Layer}
* @example
* layer.clear();
* layer.clear({ x: 0, y: 0, width: 100, height: 100 });
*/
clear(bounds) {
this.getContext().clear(bounds);
this.getHitCanvas().getContext().clear(bounds);
return this;
}
// extend Node.prototype.setZIndex
setZIndex(index) {
super.setZIndex(index);
const stage = this.getStage();
if (stage && stage.content) {
stage.content.removeChild(this.getNativeCanvasElement());
if (index < stage.children.length - 1) {
stage.content.insertBefore(this.getNativeCanvasElement(), stage.children[index + 1].getCanvas()._canvas);
}
else {
stage.content.appendChild(this.getNativeCanvasElement());
}
}
return this;
}
moveToTop() {
Node.prototype.moveToTop.call(this);
const stage = this.getStage();
if (stage && stage.content) {
stage.content.removeChild(this.getNativeCanvasElement());
stage.content.appendChild(this.getNativeCanvasElement());
}
return true;
}
moveUp() {
const moved = Node.prototype.moveUp.call(this);
if (!moved) {
return false;
}
const stage = this.getStage();
if (!stage || !stage.content) {
return false;
}
stage.content.removeChild(this.getNativeCanvasElement());
if (this.index < stage.children.length - 1) {
stage.content.insertBefore(this.getNativeCanvasElement(), stage.children[this.index + 1].getCanvas()._canvas);
}
else {
stage.content.appendChild(this.getNativeCanvasElement());
}
return true;
}
// extend Node.prototype.moveDown
moveDown() {
if (Node.prototype.moveDown.call(this)) {
const stage = this.getStage();
if (stage) {
const children = stage.children;
if (stage.content) {
stage.content.removeChild(this.getNativeCanvasElement());
stage.content.insertBefore(this.getNativeCanvasElement(), children[this.index + 1].getCanvas()._canvas);
}
}
return true;
}
return false;
}
// extend Node.prototype.moveToBottom
moveToBottom() {
if (Node.prototype.moveToBottom.call(this)) {
const stage = this.getStage();
if (stage) {
const children = stage.children;
if (stage.content) {
stage.content.removeChild(this.getNativeCanvasElement());
stage.content.insertBefore(this.getNativeCanvasElement(), children[1].getCanvas()._canvas);
}
}
return true;
}
return false;
}
getLayer() {
return this;
}
remove() {
var _a, _b;
super.remove();
// both canvases live in stage.content: the scene one always, the hit one
// while toggleHitCanvas() shows it
const scene = this.getNativeCanvasElement();
const hit = this.getHitCanvas()._canvas;
(_a = scene.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(scene);
(_b = hit.parentNode) === null || _b === void 0 ? void 0 : _b.removeChild(hit);
return this;
}
getStage() {
return this.parent;
}
// the canvases follow the stage size. The public size() of a layer, like
// width() and height(), warns and does nothing
_setSize({ width, height }) {
this.canvas.setSize(width, height);
this._syncHitCanvasSize();
this._setSmoothEnabled();
return this;
}
// the hit canvas is only allocated while the layer is listening;
// for a non-listening layer it is kept released (0x0), so a purely
// presentational layer does not pay for a stage-sized hit bitmap
// https://github.com/konvajs/konva/issues/2009
_syncHitCanvasSize() {
const listening = this.isListening();
this.hitCanvas.setSizeIfChanged((listening ? this.getWidth() : 0) || 0, (listening ? this.getHeight() : 0) || 0);
}
_validateAdd(child) {
const type = child.getType();
if (type !== 'Group' && type !== 'Shape') {
Util.throw('You may only add groups and shapes to a layer.');
}
}
_toKonvaCanvas(config) {
config = { ...config };
config.width = config.width || this.getWidth();
config.height = config.height || this.getHeight();
config.x = config.x !== undefined ? config.x : this.x();
config.y = config.y !== undefined ? config.y : this.y();
return Node.prototype._toKonvaCanvas.call(this, config);
}
_checkVisibility() {
const visible = this.visible();
if (visible) {
this.canvas._canvas.style.display = 'block';
}
else {
this.canvas._canvas.style.display = 'none';
}
}
_setSmoothEnabled() {
this.getContext()._context.imageSmoothingEnabled =
this.imageSmoothingEnabled();
}
/**
* get/set width of layer. getter return width of stage. setter doing nothing.
* if you want change width use `stage.width(value);`
* @name Konva.Layer#width
* @method
* @returns {Number}
* @example
* var width = layer.width();
*/
getWidth() {
if (this.parent) {
return this.parent.width();
}
}
setWidth() {
Util.warn('Can not change width of layer. Use "stage.width(value)" function instead.');
}
/**
* get/set height of layer.getter return height of stage. setter doing nothing.
* if you want change height use `stage.height(value);`
* @name Konva.Layer#height
* @method
* @returns {Number}
* @example
* var height = layer.height();
*/
getHeight() {
if (this.parent) {
return this.parent.height();
}
}
setHeight() {
Util.warn('Can not change height of layer. Use "stage.height(value)" function instead.');
}
/**
* batch draw. this function will not do immediate draw
* but it will schedule drawing to next tick (requestAnimFrame)
* @method
* @name Konva.Layer#batchDraw
* @return {Konva.Layer} this
*/
batchDraw() {
var _a;
if (!this._waitingForDraw) {
this._waitingForDraw = true;
// ask the window the stage is rendered in for the frame: a stage in a
// popout window keeps drawing while the opener window is not visible
Util.requestAnimFrame(() => {
// reset before drawing, so a throwing draw does not freeze the layer
// and a draw handler can request the next frame
this._waitingForDraw = false;
this.draw();
}, (_a = this.getStage()) === null || _a === void 0 ? void 0 : _a._getOwnerWindow());
}
return this;
}
/**
* get visible intersection shape. This is the preferred
* method for determining if a point intersects a shape or not.
* It reads the hit canvas, so it finds what pointer events would: nodes with
* listening set to false or invisible nodes are not detected, a shape with opacity 0
* is, and `hitStrokeWidth` counts. The position is relative to the top left corner of the
* stage container, like `stage.getPointerPosition()`, without the stage transform
* @method
* @name Konva.Layer#getIntersection
* @param {Object} pos
* @param {Number} pos.x
* @param {Number} pos.y
* @returns {Konva.Node}
* @example
* var shape = layer.getIntersection({x: 50, y: 50});
*/
getIntersection(pos) {
if (!this.isListening() || !this.isVisible()) {
return null;
}
const hit = this._getIntersection(pos);
if (hit.shape) {
return hit.shape;
}
if (!hit.antialiased) {
return null;
}
// the pointer is on the outer part of an edge, or on an opaque pixel with
// the colours of two shapes mixed. Walk outward ring by ring to the
// nearest pixel that resolves to a shape. The smoothed edge of a cached
// node that is scaled up can be many pixels wide, so keep going while the
// alpha still rises (the ramp leads into the shape) or the ring holds
// mixed pixels. A thin line has a flat alpha, so the search stops at once
// instead of walking along it to an unrelated shape nearby
const ratio = this.hitCanvas.pixelRatio, cx = Math.floor(pos.x * ratio), cy = Math.floor(pos.y * ratio);
const read = (r) => this.hitCanvas.context.getImageData(cx - r, cy - r, r * 2 + 1, r * 2 + 1)
.data;
let r = HIT_SEARCH_RADIUS, size = r * 2 + 1, data = read(r), previousMax = data[(r * size + r) * 4 + 3];
for (let d = 1; d <= HIT_SEARCH_MAX_DISTANCE; d++) {
if (d > r) {
r = Math.min(HIT_SEARCH_MAX_DISTANCE, r * 2);
size = r * 2 + 1;
data = read(r);
}
let max = 0;
for (let y = -d; y <= d; y++) {
// whole top and bottom rows, only the two side pixels of the others
const step = Math.abs(y) === d ? 1 : d * 2;
for (let x = -d; x <= d; x += step) {
const i = ((y + r) * size + x + r) * 4;
const shape = getHitShape(data, i);
if (shape) {
return shape;
}
max = Math.max(max, data[i + 3]);
}
}
// 255 here is a mixed pixel, as an opaque one resolved above
if (max <= previousMax && max !== 255) {
return null;
}
previousMax = max;
}
return null;
}
_getIntersection(pos) {
// a non-listening layer keeps its hit canvas released (0x0),
// and a just-enabled layer may not have drawn its hit graph yet
if (!this.hitCanvas.width || !this.hitCanvas.height) {
return {};
}
const ratio = this.hitCanvas.pixelRatio;
const p = this.hitCanvas.context.getImageData(Math.floor(pos.x * ratio), Math.floor(pos.y * ratio), 1, 1).data;
const shape = getHitShape(p, 0);
if (shape) {
return { shape };
}
if (p[3] > 0) {
// an edge pixel, or an opaque one with the colours of two shapes mixed
return { antialiased: true };
}
return {};
}
drawScene(can, top) {
const layer = this.getLayer(), canvas = can || (layer && layer.getCanvas());
this._fire(BEFORE_DRAW, {
node: this,
});
if (this.clearBeforeDraw()) {
canvas.getContext().clear();
}
Container.prototype.drawScene.call(this, canvas, top);
canvas._trimIsolationCanvas();
this._fire(DRAW, {
node: this,
});
return this;
}
// the hit graph is skipped while a node of the layer (or the stage) is
// dragged or a transformer on the layer, or of a node on it, is
// transforming, as the layer is redrawn every frame; the draw that follows
// restores it
shouldDrawHit(top) {
var _a;
if (!super.shouldDrawHit(top)) {
return false;
}
if (top || Konva.hitOnDragEnabled) {
return true;
}
let underDrag = false;
DD._dragElements.forEach((elem) => {
if (elem.dragStatus === 'dragging' &&
(elem.node === this.getStage() || elem.node.getLayer() === this)) {
underDrag = true;
}
});
return !underDrag && !((_a = Konva['Transformer']) === null || _a === void 0 ? void 0 : _a._isLayerTransforming(this));
}
drawHit(can, top) {
const layer = this.getLayer(), canvas = can || (layer && layer.hitCanvas);
if (!can && layer) {
// allocate or release the hit canvas to match the current listening
// state - this also picks up a listening change made after the last draw
layer._syncHitCanvasSize();
if (layer.clearBeforeDraw()) {
layer.getHitCanvas().getContext().clear();
}
}
Container.prototype.drawHit.call(this, canvas, top);
return this;
}
/**
* enable hit graph. **DEPRECATED!** Use `layer.listening(true)` instead.
* @name Konva.Layer#enableHitGraph
* @method
* @returns {Layer}
*/
enableHitGraph() {
this.hitGraphEnabled(true);
return this;
}
/**
* disable hit graph. **DEPRECATED!** Use `layer.listening(false)` instead.
* @name Konva.Layer#disableHitGraph
* @method
* @returns {Layer}
*/
disableHitGraph() {
this.hitGraphEnabled(false);
return this;
}
setHitGraphEnabled(val) {
Util.warn('hitGraphEnabled method is deprecated. Please use layer.listening() instead.');
this.listening(val);
}
getHitGraphEnabled(val) {
Util.warn('hitGraphEnabled method is deprecated. Please use layer.listening() instead.');
return this.listening();
}
/**
* Show or hide hit canvas over the stage. May be useful for debugging custom hitFunc
* @name Konva.Layer#toggleHitCanvas
* @method
*/
toggleHitCanvas() {
if (!this.parent || !this.parent['content']) {
return;
}
const parent = this.parent;
const added = !!this.hitCanvas._canvas.parentNode;
if (added) {
parent.content.removeChild(this.hitCanvas._canvas);
}
else {
parent.content.appendChild(this.hitCanvas._canvas);
}
}
destroy() {
this.getCanvas()._releaseIsolationCanvas();
Util.releaseCanvas(this.getNativeCanvasElement(), this.getHitCanvas()._canvas);
return super.destroy();
}
}
Layer.prototype.nodeType = 'Layer';
_registerNode(Layer);
/**
* get/set imageSmoothingEnabled flag
* For more info see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled
* @name Konva.Layer#imageSmoothingEnabled
* @method
* @param {Boolean} imageSmoothingEnabled
* @returns {Boolean}
* @example
* // get imageSmoothingEnabled flag
* var imageSmoothingEnabled = layer.imageSmoothingEnabled();
*
* layer.imageSmoothingEnabled(false);
*
* layer.imageSmoothingEnabled(true);
*/
Factory.addGetterSetter(Layer, 'imageSmoothingEnabled', true);
/**
* get/set clearBeforeDraw flag which determines if the layer is cleared or not
* before drawing
* @name Konva.Layer#clearBeforeDraw
* @method
* @param {Boolean} clearBeforeDraw
* @returns {Boolean}
* @example
* // get clearBeforeDraw flag
* var clearBeforeDraw = layer.clearBeforeDraw();
*
* // disable clear before draw
* layer.clearBeforeDraw(false);
*
* // enable clear before draw
* layer.clearBeforeDraw(true);
*/
Factory.addGetterSetter(Layer, 'clearBeforeDraw', true);
// the getter and setter are the deprecated ones above
Factory.addOverloadedGetterSetter(Layer, 'hitGraphEnabled');
/**
* get/set hitGraphEnabled flag. **DEPRECATED!** Use `layer.listening(false)` instead.
* Disabling the hit graph will greatly increase
* draw performance because the hit graph will not be redrawn each time the layer is
* drawn. This, however, also disables mouse/touch event detection
* @name Konva.Layer#hitGraphEnabled
* @method
* @param {Boolean} enabled
* @returns {Boolean}
* @example
* // get hitGraphEnabled flag
* var hitGraphEnabled = layer.hitGraphEnabled();
*
* // disable hit graph
* layer.hitGraphEnabled(false);
*
* // enable hit graph
* layer.hitGraphEnabled(true);
*/
+1017
View File
File diff suppressed because it is too large Load Diff
+3004
View File
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
import type { KonvaEventObject, Node } from './Node.ts';
import type { Shape } from './Shape.ts';
import type { Stage } from './Stage.ts';
export interface KonvaPointerEvent extends KonvaEventObject<PointerEvent> {
pointerId: number;
}
export declare function getCapturedShape(pointerId: number, stage?: Stage): Shape<import("./Shape.ts").ShapeConfig> | Stage | undefined;
export declare function hasPointerCapture(pointerId: number, shape: Shape | Stage): boolean;
export declare function setPointerCapture(pointerId: number, shape: Shape | Stage): void;
export declare function releaseCapturesOf(node: Node): void;
export declare function releaseCapture(pointerId: number, target?: Shape | Stage): void;
+67
View File
@@ -0,0 +1,67 @@
import { Konva } from "./Global.js";
const Captures = new Map();
// we may use this module for capturing touch events too
// so make sure we don't do something super specific to pointer
const SUPPORT_POINTER_EVENTS = Konva._global['PointerEvent'] !== undefined;
export function getCapturedShape(pointerId, stage) {
const capture = Captures.get(pointerId);
return capture && (!stage || capture.stage === stage)
? capture.shape
: undefined;
}
function fireCapture(shape, type, pointerId) {
shape._fire(type, { evt: new PointerEvent(type, { pointerId }), pointerId });
}
export function hasPointerCapture(pointerId, shape) {
var _a;
return ((_a = Captures.get(pointerId)) === null || _a === void 0 ? void 0 : _a.shape) === shape;
}
export function setPointerCapture(pointerId, shape) {
var _a;
releaseCapture(pointerId);
const stage = shape.getStage();
if (!stage)
return;
// Native capture belongs to this stage even if the shape is reparented.
Captures.set(pointerId, { shape, stage });
if (SUPPORT_POINTER_EVENTS) {
// capture on the DOM level too, so the stage keeps receiving the events
// of that pointer even when it moves outside of the stage container
// https://github.com/konvajs/konva/issues/1992
try {
(_a = stage.content) === null || _a === void 0 ? void 0 : _a.setPointerCapture(pointerId);
}
catch (e) {
// capture is possible only for an active pointer;
// ids of mouse and touch events (999 and touch identifiers) and
// programmatic calls outside of a pointer event land here
}
fireCapture(shape, 'gotpointercapture', pointerId);
}
}
// Destroying either the captured node or its native stage ends capture.
export function releaseCapturesOf(node) {
Captures.forEach(({ shape, stage }, pointerId) => {
if (shape === node || stage === node) {
releaseCapture(pointerId);
}
});
}
export function releaseCapture(pointerId, target) {
var _a;
const capture = Captures.get(pointerId);
// a node can only release its own capture
if (!capture || (target && capture.shape !== target))
return;
const { shape, stage } = capture;
Captures.delete(pointerId);
if (SUPPORT_POINTER_EVENTS) {
try {
(_a = stage.content) === null || _a === void 0 ? void 0 : _a.releasePointerCapture(pointerId);
}
catch (e) {
// same as in setPointerCapture: the pointer may not be active
}
fireCapture(shape, 'lostpointercapture', pointerId);
}
}
+275
View File
@@ -0,0 +1,275 @@
import type { GetClientRectConfig, NodeConfig } from './Node.ts';
import { Node } from './Node.ts';
import type { Context } from './Context.ts';
import type { GetSet, Vector2d, IRect } from './types.ts';
import type { HitCanvas, SceneCanvas } from './Canvas.ts';
export type ShapeConfigHandler<TTarget> = {
bivarianceHack(ctx: Context, shape: TTarget): void;
}['bivarianceHack'];
export type LineJoin = 'round' | 'bevel' | 'miter';
export type LineCap = 'butt' | 'round' | 'square';
export type ShapeConfig = NodeConfig & {
fill?: string | CanvasGradient;
fillPatternImage?: HTMLImageElement;
fillPatternX?: number;
fillPatternY?: number;
fillPatternOffset?: Vector2d;
fillPatternOffsetX?: number;
fillPatternOffsetY?: number;
fillPatternScale?: Vector2d;
fillPatternScaleX?: number;
fillPatternScaleY?: number;
fillPatternRotation?: number;
fillPatternRepeat?: string;
fillLinearGradientStartPoint?: Vector2d;
fillLinearGradientStartPointX?: number;
fillLinearGradientStartPointY?: number;
fillLinearGradientEndPoint?: Vector2d;
fillLinearGradientEndPointX?: number;
fillLinearGradientEndPointY?: number;
fillLinearGradientColorStops?: Array<number | string>;
strokeLinearGradientStartPoint?: Vector2d;
strokeLinearGradientStartPointX?: number;
strokeLinearGradientStartPointY?: number;
strokeLinearGradientEndPoint?: Vector2d;
strokeLinearGradientEndPointX?: number;
strokeLinearGradientEndPointY?: number;
strokeLinearGradientColorStops?: Array<number | string>;
fillRadialGradientStartPoint?: Vector2d;
fillRadialGradientStartPointX?: number;
fillRadialGradientStartPointY?: number;
fillRadialGradientEndPoint?: Vector2d;
fillRadialGradientEndPointX?: number;
fillRadialGradientEndPointY?: number;
fillRadialGradientStartRadius?: number;
fillRadialGradientEndRadius?: number;
fillRadialGradientColorStops?: Array<number | string>;
fillEnabled?: boolean;
fillPriority?: string;
fillRule?: CanvasFillRule;
stroke?: string | CanvasGradient;
strokeWidth?: number;
fillAfterStrokeEnabled?: boolean;
hitStrokeWidth?: number | string;
strokeScaleEnabled?: boolean;
strokeHitEnabled?: boolean;
strokeEnabled?: boolean;
lineJoin?: LineJoin;
lineCap?: LineCap;
miterLimit?: number;
sceneFunc?: (con: Context, shape: Shape) => void;
hitFunc?: (con: Context, shape: Shape) => void;
selfRectFunc?: (shape: Shape) => IRect;
shadowColor?: string;
shadowBlur?: number;
shadowOffset?: Vector2d;
shadowOffsetX?: number;
shadowOffsetY?: number;
shadowOpacity?: number;
shadowEnabled?: boolean;
shadowForStrokeEnabled?: boolean;
dash?: number[];
dashOffset?: number;
dashEnabled?: boolean;
perfectDrawEnabled?: boolean;
};
export type ShapeGetClientRectConfig = GetClientRectConfig;
export type FillFuncOutput = void | [Path2D | CanvasFillRule] | [Path2D, CanvasFillRule];
export declare const shapes: {
[key: string]: Shape;
};
/**
* Shape constructor. Shapes are primitive objects such as rectangles,
* circles, text, lines, etc.
* @constructor
* @memberof Konva
* @augments Konva.Node
* @param {Object} config
* @@shapeParams
* @@nodeParams
* @example
* var customShape = new Konva.Shape({
* x: 5,
* y: 10,
* fill: 'red',
* // a Konva.Canvas renderer is passed into the sceneFunc function
* sceneFunc (context, shape) {
* context.beginPath();
* context.moveTo(200, 50);
* context.lineTo(420, 80);
* context.quadraticCurveTo(300, 100, 260, 170);
* context.closePath();
* // Konva specific method
* context.fillStrokeShape(shape);
* }
*});
*/
export declare class Shape<Config extends ShapeConfig = ShapeConfig> extends Node<Config> {
_centroid: boolean;
colorKey: string;
_fillFunc: (ctx: Context) => FillFuncOutput;
_strokeFunc: (ctx: Context) => void;
_fillFuncHit: (ctx: Context) => void;
_strokeFuncHit: (ctx: Context) => void;
constructor(config?: Config);
/**
* @deprecated
*/
getContext(): Context;
/**
* @deprecated
*/
getCanvas(): SceneCanvas;
getSceneFunc(): any;
getHitFunc(): any;
/**
* returns whether or not a shadow will be rendered
* @method
* @name Konva.Shape#hasShadow
* @returns {Boolean}
*/
hasShadow(): any;
_hasShadow(): boolean;
_getFillPattern(): any;
__getFillPattern(): CanvasPattern | null | undefined;
_getLinearGradient(): any;
__getLinearGradient(): CanvasGradient | undefined;
_getRadialGradient(): any;
__getRadialGradient(): CanvasGradient | undefined;
getShadowRGBA(): any;
_getShadowRGBA(): string | undefined;
/**
* returns whether or not the shape will be filled
* @method
* @name Konva.Shape#hasFill
* @returns {Boolean}
*/
hasFill(): any;
_hasFill(): boolean;
/**
* returns whether or not the shape will be stroked
* @method
* @name Konva.Shape#hasStroke
* @returns {Boolean}
*/
hasStroke(): any;
_hasStroke(): boolean;
hasHitStroke(): any;
/**
* determines if point is in the shape, regardless if other shapes are on top of it. Note: because
* this method clears a temporary canvas and then redraws the shape, it performs very poorly if executed many times
* consecutively. Please use the {@link Konva.Stage#getIntersection} method if at all possible
* because it performs much better. A shape that is invisible, not listening or not on a stage
* never intersects
* @method
* @name Konva.Shape#intersects
* @param {Object} point
* @param {Number} point.x
* @param {Number} point.y
* @returns {Boolean}
*/
intersects(point: Vector2d): boolean;
destroy(): this;
_useBufferCanvas(forceFill?: boolean, opacity?: any): boolean;
setStrokeHitEnabled(val: number): void;
getStrokeHitEnabled(): boolean;
/**
* return self rectangle (x, y, width, height) of shape.
* This method are not taken into account transformation and styles.
* @method
* @name Konva.Shape#getSelfRect
* @returns {Object} rect with {x, y, width, height} properties
* @example
*
* rect.getSelfRect(); // return {x:0, y:0, width:rect.width(), height:rect.height()}
* circle.getSelfRect(); // return {x: - circle.width() / 2, y: - circle.height() / 2, width:circle.width(), height:circle.height()}
*
*/
getSelfRect(): IRect;
_getSelfRectForDrawing(): IRect;
getClientRect(config?: ShapeGetClientRectConfig): IRect;
_getStrokePadding(miterLimit?: number): number;
drawScene(can?: SceneCanvas, top?: Node): this;
drawHit(can?: HitCanvas, top?: Node): this;
/**
* draw hit graph using the cached scene canvas
* @method
* @name Konva.Shape#drawHitFromCache
* @param {Integer} alphaThreshold alpha channel threshold that determines whether or not
* a pixel should be drawn onto the hit graph. Must be a value between 0 and 255.
* The default is 0
* @returns {Konva.Shape}
* @example
* shape.cache();
* shape.drawHitFromCache();
*/
drawHitFromCache(alphaThreshold?: number): this;
hasPointerCapture(pointerId: number): boolean;
setPointerCapture(pointerId: number): void;
releaseCapture(pointerId: number): void;
draggable: GetSet<boolean, this>;
embossBlend: GetSet<boolean, this>;
dash: GetSet<number[], this>;
dashEnabled: GetSet<boolean, this>;
dashOffset: GetSet<number, this>;
fill: GetSet<string | CanvasGradient, this>;
fillEnabled: GetSet<boolean, this>;
fillLinearGradientColorStops: GetSet<Array<number | string>, this>;
fillLinearGradientStartPoint: GetSet<Vector2d, this>;
fillLinearGradientStartPointX: GetSet<number, this>;
fillLinearGradientStartPointY: GetSet<number, this>;
fillLinearGradientEndPoint: GetSet<Vector2d, this>;
fillLinearGradientEndPointX: GetSet<number, this>;
fillLinearGradientEndPointY: GetSet<number, this>;
fillPatternImage: GetSet<HTMLImageElement | HTMLCanvasElement, this>;
fillRadialGradientStartRadius: GetSet<number, this>;
fillRadialGradientEndRadius: GetSet<number, this>;
fillRadialGradientColorStops: GetSet<Array<number | string>, this>;
fillRadialGradientStartPoint: GetSet<Vector2d, this>;
fillRadialGradientStartPointX: GetSet<number, this>;
fillRadialGradientStartPointY: GetSet<number, this>;
fillRadialGradientEndPoint: GetSet<Vector2d, this>;
fillRadialGradientEndPointX: GetSet<number, this>;
fillRadialGradientEndPointY: GetSet<number, this>;
fillPatternOffset: GetSet<Vector2d, this>;
fillPatternOffsetX: GetSet<number, this>;
fillPatternOffsetY: GetSet<number, this>;
fillPatternRepeat: GetSet<string, this>;
fillPatternRotation: GetSet<number, this>;
fillPatternScale: GetSet<Vector2d, this>;
fillPatternScaleX: GetSet<number, this>;
fillPatternScaleY: GetSet<number, this>;
fillPatternX: GetSet<number, this>;
fillPatternY: GetSet<number, this>;
fillPriority: GetSet<string, this>;
hitFunc: GetSet<ShapeConfigHandler<this>, this>;
lineCap: GetSet<LineCap, this>;
lineJoin: GetSet<LineJoin, this>;
miterLimit: GetSet<number, this>;
perfectDrawEnabled: GetSet<boolean, this>;
sceneFunc: GetSet<ShapeConfigHandler<this>, this>;
selfRectFunc: GetSet<((shape: Shape) => IRect) | undefined, this>;
shadowColor: GetSet<string, this>;
shadowEnabled: GetSet<boolean, this>;
shadowForStrokeEnabled: GetSet<boolean, this>;
shadowOffset: GetSet<Vector2d, this>;
shadowOffsetX: GetSet<number, this>;
shadowOffsetY: GetSet<number, this>;
shadowOpacity: GetSet<number, this>;
shadowBlur: GetSet<number, this>;
stroke: GetSet<string | CanvasGradient, this>;
strokeEnabled: GetSet<boolean, this>;
fillAfterStrokeEnabled: GetSet<boolean, this>;
strokeScaleEnabled: GetSet<boolean, this>;
strokeHitEnabled: GetSet<boolean, this>;
strokeWidth: GetSet<number, this>;
hitStrokeWidth: GetSet<number | 'auto', this>;
strokeLinearGradientStartPoint: GetSet<Vector2d, this>;
strokeLinearGradientEndPoint: GetSet<Vector2d, this>;
strokeLinearGradientColorStops: GetSet<Array<number | string>, this>;
strokeLinearGradientStartPointX: GetSet<number, this>;
strokeLinearGradientStartPointY: GetSet<number, this>;
strokeLinearGradientEndPointX: GetSet<number, this>;
strokeLinearGradientEndPointY: GetSet<number, this>;
fillRule: GetSet<CanvasFillRule, this>;
}
+1706
View File
File diff suppressed because it is too large Load Diff
+175
View File
@@ -0,0 +1,175 @@
import type { ContainerConfig } from './Container.ts';
import { Container } from './Container.ts';
import { SceneCanvas, HitCanvas } from './Canvas.ts';
import type { GetSet, Vector2d } from './types.ts';
import type { Shape } from './Shape.ts';
import type { Layer } from './Layer.ts';
export interface StageConfig extends ContainerConfig {
container?: HTMLDivElement | string;
eventBatchFunc?: (callback: () => void) => void;
}
type Click = {
shape: Shape | null;
time: number;
};
type EventType = 'mouse' | 'touch' | 'pointer';
type PointerState = {
targetShape?: Shape;
downPosition?: Vector2d;
pointerType?: string;
relatedPointer?: PointerState;
clickStartShape?: Shape | null;
lastClick?: Click;
};
export declare const stages: Stage[];
/**
* Stage constructor. A stage is used to contain multiple layers
* @constructor
* @memberof Konva
* @augments Konva.Container
* @param {Object} config
* @param {String|Element} config.container Container selector or DOM element
* @@nodeParams
* @example
* var stage = new Konva.Stage({
* width: 500,
* height: 800,
* container: 'containerId' // or "#containerId" or ".containerClass"
* });
*/
export declare class Stage extends Container<Layer, StageConfig> {
content: HTMLDivElement;
pointerPos: Vector2d | null;
_pointerPositions: (Vector2d & {
id?: number;
})[];
_changedPointerPositions: (Vector2d & {
id: number;
})[];
_pointerEventType?: EventType;
_pointerStates: Map<string, PointerState>;
_lastClicks: Partial<Record<EventType, Click>>;
bufferHitCanvas: HitCanvas;
constructor(config: StageConfig);
_validateAdd(child: any): void;
_checkVisibility(): void;
/**
* The window the stage is rendered in. It is not always the window Konva was
* imported into: the container may belong to an iframe, or to a window
* opened with `window.open`.
*/
_getOwnerWindow(): Window | null;
/**
* set container dom element which contains the stage wrapper div element
* @method
* @name Konva.Stage#setContainer
* @param {DomElement} container can pass in a dom element or id string
*/
setContainer(container: any): this;
shouldDrawHit(): boolean;
/**
* clear all layers
* @method
* @name Konva.Stage#clear
*/
clear(): this;
clone(obj?: any): this;
destroy(): this;
/**
* returns ABSOLUTE pointer position which can be a touch position or mouse position
* pointer position doesn't include any transforms (such as scale) of the stage
* it is just a plain position of pointer relative to top-left corner of the canvas
* @method
* @name Konva.Stage#getPointerPosition
* @returns {Vector2d|null}
*/
getPointerPosition(): Vector2d | null;
_getPointerById(id?: number): (Vector2d & {
id?: number;
}) | undefined;
getPointersPositions(): (Vector2d & {
id?: number;
})[];
getStage(): this;
getContent(): HTMLDivElement;
_toKonvaCanvas(config: any): SceneCanvas;
/**
* get visible intersection shape. This is the preferred
* method for determining if a point intersects a shape or not.
* It reads the hit canvas, so it finds what pointer events would: nodes with
* listening set to false or invisible nodes are not detected, a shape with opacity 0
* is, and `hitStrokeWidth` counts. The position is relative to the top left corner of the
* stage container, like `stage.getPointerPosition()`, without the stage transform
* @method
* @name Konva.Stage#getIntersection
* @param {Object} pos
* @param {Number} pos.x
* @param {Number} pos.y
* @returns {Konva.Node}
* @example
* var shape = stage.getIntersection({x: 50, y: 50});
*/
getIntersection(pos: Vector2d): Shape<import("./Shape.ts").ShapeConfig> | null;
_resizeDOM(): void;
add(layer: Layer, ...rest: any[]): this;
getParent(): null;
getLayer(): null;
hasPointerCapture(pointerId: number): boolean;
setPointerCapture(pointerId: number): void;
releaseCapture(pointerId: number): void;
/**
* returns an array of layers
* @method
* @name Konva.Stage#getLayers
*/
getLayers(): Layer[];
_bindContentEvents(): void;
_batchEvents(callback: () => void): void;
_pointerenter(evt: PointerEvent): void;
_pointerover(evt: any): void;
_pointerEventsEnabled(): boolean;
_cancelClick(pointerId: number | undefined, eventType?: EventType): void;
_pointerleave(evt: any): void;
_pointerdown(evt: TouchEvent | MouseEvent | PointerEvent): void;
_pointermove(evt: TouchEvent | MouseEvent | PointerEvent): void;
_pointerup(evt: any): void;
_contextmenu(evt: any): void;
_wheel(evt: any): void;
_pointercancel(evt: PointerEvent | TouchEvent): void;
_lostpointercapture(evt: PointerEvent): void;
/**
* manually register pointers positions (mouse/touch) in the stage.
* So you can use stage.getPointerPosition(). Usually you don't need to use that method
* because all internal events are automatically registered. It may be useful if event
* is triggered outside of the stage, but you still want to use Konva methods to get pointers position.
* @method
* @name Konva.Stage#setPointersPositions
* @param {Object} evt Event object
* @example
*
* window.addEventListener('mousemove', (e) => {
* stage.setPointersPositions(e);
* });
*/
setPointersPositions(evt: any): void;
_setPointerPosition(evt: any): void;
_getContentPosition(): {
top: number;
left: number;
scaleX: number;
scaleY: number;
};
_buildDOM(): void;
cache(): this;
clearCache(): this;
/**
* batch draw
* @method
* @name Konva.Stage#batchDraw
* @return {Konva.Stage} this
*/
batchDraw(): this;
container: GetSet<HTMLDivElement, this, HTMLDivElement | string>;
eventBatchFunc: GetSet<((callback: () => void) => void) | undefined, this>;
}
export {};
+1006
View File
File diff suppressed because it is too large Load Diff
+245
View File
@@ -0,0 +1,245 @@
import { Animation } from './Animation.ts';
import type { NodeConfig } from './Node.ts';
import { Node } from './Node.ts';
declare class TweenEngine {
prop: string;
propFunc: Function;
begin: number;
_pos: number;
duration: number;
prevPos: number;
yoyo: boolean;
_time: number;
_position: number;
_startTime: number;
_finish: number;
func: Function;
_change: number;
state: number;
onPlay: Function;
onReverse: Function;
onPause: Function;
onReset: Function;
onFinish: Function;
onUpdate: Function;
constructor(prop: any, propFunc: any, func: any, begin: any, finish: any, duration: any, yoyo: any);
fire(str: any): void;
setTime(t: any): void;
getTime(): number;
setPosition(p: any): void;
getPosition(t: any): any;
play(): void;
reverse(): void;
seek(t: any): void;
reset(): void;
finish(): void;
update(): void;
onEnterFrame(): void;
pause(): void;
getTimer(): number;
}
export interface TweenOwnConfig {
node: Node;
duration?: number;
easing?: (typeof Easings)[keyof typeof Easings];
yoyo?: boolean;
onFinish?: Function;
onUpdate?: Function;
onReset?: Function;
}
export interface TweenConfig extends NodeConfig, TweenOwnConfig {
}
/**
* Tween constructor. Tweens enable you to animate a node between the current state and a new state.
* You can play, pause, reverse, seek, reset, and finish tweens. By default, tweens are animated using
* a linear easing. For more tweening options, check out {@link Konva.Easings}
* @constructor
* @memberof Konva
* @example
* // instantiate new tween which fully rotates a node in 1 second
* var tween = new Konva.Tween({
* // list of tween specific properties
* node: node,
* duration: 1,
* easing: Konva.Easings.EaseInOut,
* yoyo: false, // play back and forth
* onUpdate: () => console.log('node attrs updated'),
* onFinish: () => console.log('finished'),
* // set new values for any attributes of a passed node
* rotation: 360,
* fill: 'red'
* });
*
* // play tween
* tween.play();
*
* // pause tween
* tween.pause();
*/
export declare class Tween {
static attrs: {};
static tweens: {};
node: Node;
anim: Animation;
tween: TweenEngine;
_id: number;
onFinish: Function | undefined;
onReset: Function | undefined;
onUpdate: Function | undefined;
constructor(config: TweenConfig);
_addAttr(key: any, end: any): void;
_tweenFunc(i: any): void;
_addListeners(): void;
/**
* play
* @method
* @name Konva.Tween#play
* @returns {Tween}
*/
play(): this;
/**
* reverse
* @method
* @name Konva.Tween#reverse
* @returns {Tween}
*/
reverse(): this;
/**
* reset
* @method
* @name Konva.Tween#reset
* @returns {Tween}
*/
reset(): this;
/**
* seek
* @method
* @name Konva.Tween#seek
* @param {Number} t time in seconds between 0 and the duration
* @returns {Tween}
*/
seek(t: any): this;
/**
* pause
* @method
* @name Konva.Tween#pause
* @returns {Tween}
*/
pause(): this;
/**
* finish
* @method
* @name Konva.Tween#finish
* @returns {Tween}
*/
finish(): this;
/**
* destroy
* @method
* @name Konva.Tween#destroy
*/
destroy(): void;
}
/**
* @namespace Easings
* @memberof Konva
*/
export declare const Easings: {
/**
* back ease in
* @function
* @memberof Konva.Easings
*/
BackEaseIn(t: any, b: any, c: any, d: any): any;
/**
* back ease out
* @function
* @memberof Konva.Easings
*/
BackEaseOut(t: any, b: any, c: any, d: any): any;
/**
* back ease in out
* @function
* @memberof Konva.Easings
*/
BackEaseInOut(t: any, b: any, c: any, d: any): any;
/**
* elastic ease in
* @function
* @memberof Konva.Easings
*/
ElasticEaseIn(t: any, b: any, c: any, d: any, a: any, p: any): any;
/**
* elastic ease out
* @function
* @memberof Konva.Easings
*/
ElasticEaseOut(t: any, b: any, c: any, d: any, a: any, p: any): any;
/**
* elastic ease in out
* @function
* @memberof Konva.Easings
*/
ElasticEaseInOut(t: any, b: any, c: any, d: any, a: any, p: any): any;
/**
* bounce ease out
* @function
* @memberof Konva.Easings
*/
BounceEaseOut(t: any, b: any, c: any, d: any): any;
/**
* bounce ease in
* @function
* @memberof Konva.Easings
*/
BounceEaseIn(t: any, b: any, c: any, d: any): any;
/**
* bounce ease in out
* @function
* @memberof Konva.Easings
*/
BounceEaseInOut(t: any, b: any, c: any, d: any): any;
/**
* ease in
* @function
* @memberof Konva.Easings
*/
EaseIn(t: any, b: any, c: any, d: any): any;
/**
* ease out
* @function
* @memberof Konva.Easings
*/
EaseOut(t: any, b: any, c: any, d: any): any;
/**
* ease in out
* @function
* @memberof Konva.Easings
*/
EaseInOut(t: any, b: any, c: any, d: any): any;
/**
* strong ease in
* @function
* @memberof Konva.Easings
*/
StrongEaseIn(t: any, b: any, c: any, d: any): any;
/**
* strong ease out
* @function
* @memberof Konva.Easings
*/
StrongEaseOut(t: any, b: any, c: any, d: any): any;
/**
* strong ease in out
* @function
* @memberof Konva.Easings
*/
StrongEaseInOut(t: any, b: any, c: any, d: any): any;
/**
* linear
* @function
* @memberof Konva.Easings
*/
Linear(t: any, b: any, c: any, d: any): any;
};
export {};
+749
View File
@@ -0,0 +1,749 @@
import { Util } from "./Util.js";
import { Animation } from "./Animation.js";
import { Node } from "./Node.js";
import { Konva } from "./Global.js";
// every config key that is not an attribute to tween
const blacklist = {
node: 1,
duration: 1,
easing: 1,
onFinish: 1,
onUpdate: 1,
onReset: 1,
yoyo: 1,
}, PAUSED = 1, PLAYING = 2, REVERSING = 3, colorAttrs = ['fill', 'stroke', 'shadowColor'];
let idCounter = 0;
// the diff below reads r/g/b/a directly, so a color we can not parse must fail
// with its own message instead of "Cannot read properties of undefined"
function colorToRGBA(color) {
return (Util.colorToRGBA(color) ||
Util.throw('can not tween the color "' +
color +
'", because it is not a valid color.'));
}
function colorDiff(start, end) {
return {
r: end.r - start.r,
g: end.g - start.g,
b: end.b - start.b,
a: end.a - start.a,
};
}
function tweenColor(start, diff, i) {
return ('rgba(' +
Math.round(start.r + diff.r * i) +
',' +
Math.round(start.g + diff.g * i) +
',' +
Math.round(start.b + diff.b * i) +
',' +
(start.a + diff.a * i) +
')');
}
class TweenEngine {
constructor(prop, propFunc, func, begin, finish, duration, yoyo) {
this.prop = prop;
this.propFunc = propFunc;
this.begin = begin;
this._pos = begin;
this.duration = duration;
this._change = 0;
this.prevPos = 0;
this.yoyo = yoyo;
this._time = 0;
this._position = 0;
this._startTime = 0;
this._finish = 0;
this.func = func;
this._change = finish - this.begin;
this.pause();
}
fire(str) {
const handler = this[str];
if (handler) {
handler();
}
}
setTime(t) {
if (t > this.duration) {
if (this.yoyo) {
this._time = this.duration;
this.reverse();
}
else {
this.finish();
}
}
else if (t < 0) {
if (this.yoyo) {
this._time = 0;
this.play();
}
else {
this.reset();
}
}
else {
this._time = t;
this.update();
}
}
getTime() {
return this._time;
}
setPosition(p) {
this.prevPos = this._pos;
this.propFunc(p);
this._pos = p;
}
getPosition(t) {
if (t === undefined) {
t = this._time;
}
return this.func(t, this.begin, this._change, this.duration);
}
play() {
this.state = PLAYING;
this._startTime = this.getTimer() - this._time;
this.fire('onPlay');
this.onEnterFrame();
}
reverse() {
this.state = REVERSING;
this._time = this.duration - this._time;
this._startTime = this.getTimer() - this._time;
this.fire('onReverse');
this.onEnterFrame();
}
seek(t) {
this.pause();
this._time = t;
this.update();
this.fire('onSeek');
}
reset() {
this.pause();
this._time = 0;
this.update();
this.fire('onReset');
}
finish() {
this.pause();
this._time = this.duration;
this.update();
this.fire('onFinish');
}
update() {
this.setPosition(this.getPosition(this._time));
this.fire('onUpdate');
}
onEnterFrame() {
const t = this.getTimer() - this._startTime;
if (this.state === PLAYING) {
this.setTime(t);
}
else if (this.state === REVERSING) {
this.setTime(this.duration - t);
}
}
pause() {
this.state = PAUSED;
this.fire('onPause');
}
getTimer() {
return Date.now();
}
}
/**
* Tween constructor. Tweens enable you to animate a node between the current state and a new state.
* You can play, pause, reverse, seek, reset, and finish tweens. By default, tweens are animated using
* a linear easing. For more tweening options, check out {@link Konva.Easings}
* @constructor
* @memberof Konva
* @example
* // instantiate new tween which fully rotates a node in 1 second
* var tween = new Konva.Tween({
* // list of tween specific properties
* node: node,
* duration: 1,
* easing: Konva.Easings.EaseInOut,
* yoyo: false, // play back and forth
* onUpdate: () => console.log('node attrs updated'),
* onFinish: () => console.log('finished'),
* // set new values for any attributes of a passed node
* rotation: 360,
* fill: 'red'
* });
*
* // play tween
* tween.play();
*
* // pause tween
* tween.pause();
*/
export class Tween {
constructor(config) {
const that = this, node = config.node, nodeId = node._id, easing = config.easing || Easings.Linear, yoyo = !!config.yoyo;
let duration, key;
if (typeof config.duration === 'undefined') {
duration = 0.3;
}
else if (config.duration === 0) {
// zero is bad value for duration
duration = 0.001;
}
else {
duration = config.duration;
}
this.node = node;
this._id = idCounter++;
const layers = node.getLayer() ||
(node instanceof Konva['Stage'] ? node.getLayers() : null);
if (!layers) {
Util.error('Tween constructor have `node` that is not in a layer. Please add node into layer first.');
}
this.anim = new Animation(function () {
that.tween.onEnterFrame();
}, layers);
this.tween = new TweenEngine(key, function (i) {
that._tweenFunc(i);
}, easing, 0, 1, duration * 1000, yoyo);
this._addListeners();
// init attrs map
if (!Tween.attrs[nodeId]) {
Tween.attrs[nodeId] = {};
}
if (!Tween.attrs[nodeId][this._id]) {
Tween.attrs[nodeId][this._id] = {};
}
// init tweens map
if (!Tween.tweens[nodeId]) {
Tween.tweens[nodeId] = {};
}
for (key in config) {
if (blacklist[key] === undefined) {
this._addAttr(key, config[key]);
}
}
this.reset();
// callbacks
this.onFinish = config.onFinish;
this.onReset = config.onReset;
this.onUpdate = config.onUpdate;
}
_addAttr(key, end) {
// a component attribute ({x, y} of scale, offset, a gradient point...)
// is tweened through its components: scale -> scaleX, scaleY
if (Util._isPlainObject(end)) {
for (const component in end) {
this._addAttr(key + Util._capitalize(component), end[component]);
}
return;
}
const node = this.node, nodeId = node._id;
let diff, len, trueEnd, trueStart;
// remove conflict from tween map if it exists
const tweenId = Tween.tweens[nodeId][key];
if (tweenId !== undefined) {
delete Tween.attrs[nodeId][tweenId][key];
}
// add to tween map
let start = node.getAttr(key);
if (Util._isArray(end) || Util._isArray(start)) {
diff = [];
// only normalized arrays are interpolated, so the values the user asked
// for are restored when the tween ends
trueStart = start;
trueEnd = end;
// an attribute the node does not have yet starts from zeros, a scalar
// side tweens every entry. A copy of start, as the colour stops are
// replaced by RGBA objects below
start = Util._isArray(start)
? start.slice()
: new Array(end.length).fill(start || 0);
if (!Util._isArray(end)) {
end = new Array(start.length).fill(end);
}
len = Math.max(end.length, start.length);
if (key === 'points' && end.length !== start.length) {
// before tweening points we need to make sure that start.length === end.length
// Util._prepareArrayForTween thinking that end.length > start.length
if (end.length > start.length) {
// so in this case we will increase number of starting points
start = Util._prepareArrayForTween(start, end, node.closed());
}
else {
// in this case we will increase number of eding points
end = Util._prepareArrayForTween(end, start, node.closed());
}
}
if (key.endsWith('ColorStops')) {
for (let n = 0; n < len; n++) {
if (n % 2 === 0) {
diff.push((end[n] || 0) - (start[n] || 0));
}
else {
const startRGBA = colorToRGBA(start[n]);
start[n] = startRGBA;
diff.push(colorDiff(startRGBA, colorToRGBA(end[n])));
}
}
}
else {
for (let n = 0; n < len; n++) {
diff.push((end[n] || 0) - (start[n] || 0));
}
}
}
else if (colorAttrs.indexOf(key) !== -1) {
start = colorToRGBA(start);
diff = colorDiff(start, colorToRGBA(end));
}
else {
diff = end - start;
}
Tween.attrs[nodeId][this._id][key] = {
start: start,
diff: diff,
end: end,
trueEnd: trueEnd,
trueStart: trueStart,
};
Tween.tweens[nodeId][key] = this._id;
}
_tweenFunc(i) {
var _a;
const node = this.node, attrs = (_a = Tween.attrs[node._id]) === null || _a === void 0 ? void 0 : _a[this._id];
let key, attr, start, diff, newVal, n, len, end;
for (key in attrs) {
attr = attrs[key];
start = attr.start;
diff = attr.diff;
end = attr.end;
if (Util._isArray(start)) {
newVal = [];
len = Math.max(start.length, end.length);
if (key.endsWith('ColorStops')) {
for (n = 0; n < len; n++) {
if (n % 2 === 0) {
newVal.push((start[n] || 0) + diff[n] * i);
}
else {
newVal.push(tweenColor(start[n], diff[n], i));
}
}
}
else {
for (n = 0; n < len; n++) {
newVal.push((start[n] || 0) + diff[n] * i);
}
}
}
else if (colorAttrs.indexOf(key) !== -1) {
newVal = tweenColor(start, diff, i);
}
else {
newVal = start + diff * i;
}
node.setAttr(key, newVal);
}
}
_addListeners() {
// a running tween is destroyed with its node (the animation loop would
// keep both alive); a stopped one is not held by the node at all.
// The namespace contains "konva" so clone() does not copy the listener
const destroyEvent = `destroy.konva-tween${this._id}`;
const onDestroy = () => this.destroy();
const start = () => {
this.node.off(destroyEvent).on(destroyEvent, onDestroy);
this.anim.start();
};
this.tween.onPlay = start;
this.tween.onReverse = start;
// stop listeners
this.tween.onPause = () => {
this.node.off(destroyEvent);
this.anim.stop();
};
const end = (edge, callback) => {
var _a;
// no attributes means the tween was destroyed inside the last onUpdate
const attrs = (_a = Tween.attrs[this.node._id]) === null || _a === void 0 ? void 0 : _a[this._id];
if (!attrs) {
return;
}
for (const key in attrs) {
if (attrs[key][edge] !== undefined) {
this.node.setAttr(key, attrs[key][edge]);
}
}
callback === null || callback === void 0 ? void 0 : callback.call(this);
};
this.tween.onFinish = () => end('trueEnd', this.onFinish);
this.tween.onReset = () => end('trueStart', this.onReset);
this.tween.onUpdate = () => {
if (this.onUpdate) {
this.onUpdate.call(this);
}
};
}
/**
* play
* @method
* @name Konva.Tween#play
* @returns {Tween}
*/
play() {
this.tween.play();
return this;
}
/**
* reverse
* @method
* @name Konva.Tween#reverse
* @returns {Tween}
*/
reverse() {
this.tween.reverse();
return this;
}
/**
* reset
* @method
* @name Konva.Tween#reset
* @returns {Tween}
*/
reset() {
this.tween.reset();
return this;
}
/**
* seek
* @method
* @name Konva.Tween#seek
* @param {Number} t time in seconds between 0 and the duration
* @returns {Tween}
*/
seek(t) {
this.tween.seek(t * 1000);
return this;
}
/**
* pause
* @method
* @name Konva.Tween#pause
* @returns {Tween}
*/
pause() {
this.tween.pause();
return this;
}
/**
* finish
* @method
* @name Konva.Tween#finish
* @returns {Tween}
*/
finish() {
this.tween.finish();
return this;
}
/**
* destroy
* @method
* @name Konva.Tween#destroy
*/
destroy() {
var _a;
const nodeId = this.node._id, thisId = this._id, owned = (_a = Tween.attrs[nodeId]) === null || _a === void 0 ? void 0 : _a[thisId], owners = Tween.tweens[nodeId];
this.pause();
if (this.anim) {
this.anim.stop();
}
// release only the attributes this tween still owns, other tweens on
// the node keep theirs (nothing left to do when destroyed twice)
if (owned) {
if (owners) {
for (const key in owned) {
delete owners[key];
}
if (Object.keys(owners).length === 0) {
delete Tween.tweens[nodeId];
}
}
delete Tween.attrs[nodeId][thisId];
if (Object.keys(Tween.attrs[nodeId]).length === 0) {
delete Tween.attrs[nodeId];
}
}
}
}
Tween.attrs = {};
Tween.tweens = {};
/**
* Tween node properties. Shorter usage of {@link Konva.Tween} object.
*
* @method Konva.Node#to
* @param {Object} [params] tween params
* @returns {Konva.Tween}
* @example
*
* circle.to({
* x : 50,
* duration : 0.5,
* onUpdate: () => console.log('props updated'),
* onFinish: () => console.log('finished'),
* });
*/
Node.prototype.to = function (params) {
const onFinish = params.onFinish;
// params stay untouched, so the same object can animate several nodes
const tween = new Tween({
...params,
node: this,
onFinish() {
tween.destroy();
if (onFinish) {
onFinish();
}
},
});
tween.play();
return tween;
};
/*
* These eases were ported from an Adobe Flash tweening library to JavaScript
* by Xaric
*/
/**
* @namespace Easings
* @memberof Konva
*/
export const Easings = {
/**
* back ease in
* @function
* @memberof Konva.Easings
*/
BackEaseIn(t, b, c, d) {
const s = 1.70158;
return c * (t /= d) * t * ((s + 1) * t - s) + b;
},
/**
* back ease out
* @function
* @memberof Konva.Easings
*/
BackEaseOut(t, b, c, d) {
const s = 1.70158;
return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
},
/**
* back ease in out
* @function
* @memberof Konva.Easings
*/
BackEaseInOut(t, b, c, d) {
let s = 1.70158;
if ((t /= d / 2) < 1) {
return (c / 2) * (t * t * (((s *= 1.525) + 1) * t - s)) + b;
}
return (c / 2) * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b;
},
/**
* elastic ease in
* @function
* @memberof Konva.Easings
*/
ElasticEaseIn(t, b, c, d, a, p) {
// added s = 0
let s = 0;
if (t === 0) {
return b;
}
if ((t /= d) === 1) {
return b + c;
}
if (!p) {
p = d * 0.3;
}
if (!a || a < Math.abs(c)) {
a = c;
s = p / 4;
}
else {
s = (p / (2 * Math.PI)) * Math.asin(c / a);
}
return (-(a *
Math.pow(2, 10 * (t -= 1)) *
Math.sin(((t * d - s) * (2 * Math.PI)) / p)) + b);
},
/**
* elastic ease out
* @function
* @memberof Konva.Easings
*/
ElasticEaseOut(t, b, c, d, a, p) {
// added s = 0
let s = 0;
if (t === 0) {
return b;
}
if ((t /= d) === 1) {
return b + c;
}
if (!p) {
p = d * 0.3;
}
if (!a || a < Math.abs(c)) {
a = c;
s = p / 4;
}
else {
s = (p / (2 * Math.PI)) * Math.asin(c / a);
}
return (a * Math.pow(2, -10 * t) * Math.sin(((t * d - s) * (2 * Math.PI)) / p) +
c +
b);
},
/**
* elastic ease in out
* @function
* @memberof Konva.Easings
*/
ElasticEaseInOut(t, b, c, d, a, p) {
// added s = 0
let s = 0;
if (t === 0) {
return b;
}
if ((t /= d / 2) === 2) {
return b + c;
}
if (!p) {
p = d * (0.3 * 1.5);
}
if (!a || a < Math.abs(c)) {
a = c;
s = p / 4;
}
else {
s = (p / (2 * Math.PI)) * Math.asin(c / a);
}
if (t < 1) {
return (-0.5 *
(a *
Math.pow(2, 10 * (t -= 1)) *
Math.sin(((t * d - s) * (2 * Math.PI)) / p)) +
b);
}
return (a *
Math.pow(2, -10 * (t -= 1)) *
Math.sin(((t * d - s) * (2 * Math.PI)) / p) *
0.5 +
c +
b);
},
/**
* bounce ease out
* @function
* @memberof Konva.Easings
*/
BounceEaseOut(t, b, c, d) {
if ((t /= d) < 1 / 2.75) {
return c * (7.5625 * t * t) + b;
}
else if (t < 2 / 2.75) {
return c * (7.5625 * (t -= 1.5 / 2.75) * t + 0.75) + b;
}
else if (t < 2.5 / 2.75) {
return c * (7.5625 * (t -= 2.25 / 2.75) * t + 0.9375) + b;
}
else {
return c * (7.5625 * (t -= 2.625 / 2.75) * t + 0.984375) + b;
}
},
/**
* bounce ease in
* @function
* @memberof Konva.Easings
*/
BounceEaseIn(t, b, c, d) {
return c - Easings.BounceEaseOut(d - t, 0, c, d) + b;
},
/**
* bounce ease in out
* @function
* @memberof Konva.Easings
*/
BounceEaseInOut(t, b, c, d) {
if (t < d / 2) {
return Easings.BounceEaseIn(t * 2, 0, c, d) * 0.5 + b;
}
else {
return Easings.BounceEaseOut(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
}
},
/**
* ease in
* @function
* @memberof Konva.Easings
*/
EaseIn(t, b, c, d) {
return c * (t /= d) * t + b;
},
/**
* ease out
* @function
* @memberof Konva.Easings
*/
EaseOut(t, b, c, d) {
return -c * (t /= d) * (t - 2) + b;
},
/**
* ease in out
* @function
* @memberof Konva.Easings
*/
EaseInOut(t, b, c, d) {
if ((t /= d / 2) < 1) {
return (c / 2) * t * t + b;
}
return (-c / 2) * (--t * (t - 2) - 1) + b;
},
/**
* strong ease in
* @function
* @memberof Konva.Easings
*/
StrongEaseIn(t, b, c, d) {
return c * (t /= d) * t * t * t * t + b;
},
/**
* strong ease out
* @function
* @memberof Konva.Easings
*/
StrongEaseOut(t, b, c, d) {
return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
},
/**
* strong ease in out
* @function
* @memberof Konva.Easings
*/
StrongEaseInOut(t, b, c, d) {
if ((t /= d / 2) < 1) {
return (c / 2) * t * t * t * t * t + b;
}
return (c / 2) * ((t -= 2) * t * t * t * t + 2) + b;
},
/**
* linear
* @function
* @memberof Konva.Easings
*/
Linear(t, b, c, d) {
return (c * t) / d + b;
},
};
+276
View File
@@ -0,0 +1,276 @@
import type { Context } from './Context.ts';
import type { IRect, RGB, Vector2d } from './types.ts';
/**
* Transform constructor.
* In most of the cases you don't need to use it in your app. Because it is for internal usage in Konva core.
* But there is a documentation for that class in case you still want
* to make some manual calculations.
* @constructor
* @param {Array} [m] Optional six-element matrix
* @memberof Konva
*/
export declare class Transform {
m: Array<number>;
dirty: boolean;
constructor(m?: Array<number>);
reset(): void;
/**
* Copy Konva.Transform object
* @method
* @name Konva.Transform#copy
* @returns {Konva.Transform}
* @example
* const tr = shape.getTransform().copy()
*/
copy(): Transform;
copyInto(tr: Transform): void;
/**
* Transform point
* @method
* @name Konva.Transform#point
* @param {Object} point 2D point(x, y)
* @returns {Object} 2D point(x, y)
*/
point(point: Vector2d): {
x: number;
y: number;
};
_getTransformedRect(rect: IRect): IRect;
/**
* Apply translation
* @method
* @name Konva.Transform#translate
* @param {Number} x
* @param {Number} y
* @returns {Konva.Transform}
*/
translate(x: number, y: number): this;
/**
* Apply scale
* @method
* @name Konva.Transform#scale
* @param {Number} sx
* @param {Number} sy
* @returns {Konva.Transform}
*/
scale(sx: number, sy: number): this;
/**
* Apply rotation
* @method
* @name Konva.Transform#rotate
* @param {Number} rad Angle in radians
* @returns {Konva.Transform}
*/
rotate(rad: number): this;
/**
* Returns the translation
* @method
* @name Konva.Transform#getTranslation
* @returns {Object} 2D point(x, y)
*/
getTranslation(): {
x: number;
y: number;
};
/**
* Apply skew
* @method
* @name Konva.Transform#skew
* @param {Number} sx
* @param {Number} sy
* @returns {Konva.Transform}
*/
skew(sx: number, sy: number): this;
/**
* Transform multiplication
* @method
* @name Konva.Transform#multiply
* @param {Konva.Transform} matrix
* @returns {Konva.Transform}
*/
multiply(matrix: Transform): this;
/**
* Invert the matrix
* @method
* @name Konva.Transform#invert
* @returns {Konva.Transform}
*/
isInvertible(): boolean;
invert(): this;
/**
* return matrix
* @method
* @name Konva.Transform#getMatrix
*/
getMatrix(): number[];
/**
* convert transformation matrix back into node's attributes
* @method
* @name Konva.Transform#decompose
* @returns {Konva.Transform}
*/
decompose(): {
x: number;
y: number;
rotation: number;
scaleX: number;
scaleY: number;
skewX: number;
skewY: number;
};
}
export declare const TypedArray: any;
export declare const Util: {
_isElement(obj: any): obj is Element;
_isFunction(obj: any): boolean;
_isPlainObject(obj: any): boolean;
_isArray(obj: any): obj is Array<any>;
_isNumber(obj: any): obj is number;
_isString(obj: any): obj is string;
_isBoolean(obj: any): obj is boolean;
isObject(val: any): val is object;
isValidSelector(selector: any): boolean;
_sign(number: number): 1 | -1;
requestAnimFrame(callback: Function, win?: Window | null): void;
createCanvasElement(): HTMLCanvasElement;
createImageElement(): HTMLImageElement;
_urlToImage(url: string, callback: Function, onError?: (error: Error) => void): void;
_rgbToHex(r: number, g: number, b: number): string;
_hexToRgb(hex: string): RGB;
/**
* return random hex color
* @method
* @memberof Konva.Util
* @example
* shape.fill(Konva.Util.getRandomColor());
*/
getRandomColor(): string;
/**
* Check if canvas farbling is active (e.g., Brave browser fingerprinting protection)
* @method
* @memberof Konva.Util
* @returns {Boolean}
*/
isCanvasFarblingActive(): boolean;
/**
* Get a random color for hit detection (snapped to the hit color grid)
* @method
* @memberof Konva.Util
* @returns {String} hex color string
*/
getHitColor(): string;
/**
* Get hit color key from RGB values (snapped to the hit color grid)
* @method
* @memberof Konva.Util
* @param {Number} r - red component (0-255)
* @param {Number} g - green component (0-255)
* @param {Number} b - blue component (0-255)
* @returns {String} hex color key string
*/
getHitColorKey(r: number, g: number, b: number): string;
/**
* Snap a hex color to the hit color grid
* @method
* @memberof Konva.Util
* @param {String} hex - hex color string (e.g., "#ff00ff")
* @returns {String} snapped hex color string
*/
getSnappedHexColor(hex: string): string;
/**
* get RGB components of a color
* @method
* @memberof Konva.Util
* @param {String} color
* @example
* // each of the following examples return {r:0, g:0, b:255}
* var rgb = Konva.Util.getRGB('blue');
* var rgb = Konva.Util.getRGB('#0000ff');
* var rgb = Konva.Util.getRGB('rgb(0,0,255)');
*/
getRGB(color: string): RGB;
colorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_namedColorToRBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | null;
_rgbColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_hex8ColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_hex6ColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_hex4ColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_hex3ColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_hslColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_getPointsRect(points: ArrayLike<number>): IRect;
/**
* check intersection of two client rectangles
* @method
* @memberof Konva.Util
* @param {Object} r1 - { x, y, width, height } client rectangle
* @param {Object} r2 - { x, y, width, height } client rectangle
* @example
* const overlapping = Konva.Util.haveIntersection(shape1.getClientRect(), shape2.getClientRect());
*/
haveIntersection(r1: IRect, r2: IRect): boolean;
cloneObject<Any>(obj: Any): Any;
_cloneValue(val: any): any;
cloneArray(arr: Array<any>): any[];
degToRad(deg: number): number;
radToDeg(rad: number): number;
_getRotation(radians: number): number;
_capitalize(str: string): string;
throw(str: string): never;
error(str: string): void;
warn(str: string): void;
_batchEvents(batch: ((run: () => void) => void) | undefined, run: () => void): void;
each(obj: object, func: Function): void;
_inRange(val: number, left: number, right: number): boolean;
_getProjectionToSegment(x1: any, y1: any, x2: any, y2: any, x3: any, y3: any): any[];
_getProjectionToLine(pt: Vector2d, line: Array<Vector2d>, isClosed: boolean): Vector2d;
_prepareArrayForTween(startArray: any, endArray: any, isClosed: any): number[];
_prepareToStringify<T>(obj: any, ancestors?: Set<object>): T;
_assign<T, U>(target: T, source: U): T & U;
_getEventType(type: string): "mouse" | "touch" | "pointer";
_getFirstPointerId(evt: any): any;
releaseCanvas(...canvases: HTMLCanvasElement[]): void;
_cornerRadii(cornerRadius: number | number[], width: number, height: number): [number, number, number, number];
drawRoundedRectPath(context: Context, width: number, height: number, cornerRadius: number | number[]): void;
drawRoundedPolygonPath(context: Context, points: Vector2d[], sides: number, radius: number, cornerRadius: number | number[]): void;
};
export type AnyString<T> = T | (string & {});
+1220
View File
File diff suppressed because it is too large Load Diff
+9
View File
@@ -0,0 +1,9 @@
export declare function RGBComponent(val: number): number;
export declare function getNumberValidator<T>(): ((val: T, attr: string) => T) | undefined;
export declare function getNumberOrArrayOfNumbersValidator<T>(noOfElements: number): ((val: T, attr: string) => T) | undefined;
export declare function getNumberOrAutoValidator<T>(): ((val: T, attr: string) => T) | undefined;
export declare function getStringValidator<T>(): ((val: T, attr: string) => T) | undefined;
export declare function getStringOrGradientValidator<T>(): ((val: T, attr: string) => T) | undefined;
export declare function getNumberArrayValidator<T>(): ((val: T, attr: string) => T) | undefined;
export declare function getBooleanValidator<T>(): ((val: T, attr: string) => T) | undefined;
export declare function getComponentValidator<T>(components: string[]): ((val: T, attr: string) => T) | undefined;
+156
View File
@@ -0,0 +1,156 @@
import { Konva } from "./Global.js";
import { TypedArray, Util } from "./Util.js";
function _formatValue(val) {
if (Util._isString(val)) {
return '"' + val + '"';
}
if (Object.prototype.toString.call(val) === '[object Number]') {
return val;
}
if (Util._isBoolean(val)) {
return val;
}
return Object.prototype.toString.call(val);
}
export function RGBComponent(val) {
if (val > 255) {
return 255;
}
else if (val < 0) {
return 0;
}
return Math.round(val);
}
export function getNumberValidator() {
if (Konva.isUnminified) {
return function (val, attr) {
if (!Util._isNumber(val)) {
Util.warn(_formatValue(val) +
' is a not valid value for "' +
attr +
'" attribute. The value should be a number.');
}
return val;
};
}
}
export function getNumberOrArrayOfNumbersValidator(noOfElements) {
if (Konva.isUnminified) {
return function (val, attr) {
let isNumber = Util._isNumber(val);
let isValidArray = Util._isArray(val) && val.length == noOfElements;
if (!isNumber && !isValidArray) {
Util.warn(_formatValue(val) +
' is a not valid value for "' +
attr +
'" attribute. The value should be a number or Array<number>(' +
noOfElements +
')');
}
return val;
};
}
}
export function getNumberOrAutoValidator() {
if (Konva.isUnminified) {
return function (val, attr) {
const isNumber = Util._isNumber(val);
const isAuto = val === 'auto';
if (!(isNumber || isAuto)) {
Util.warn(_formatValue(val) +
' is a not valid value for "' +
attr +
'" attribute. The value should be a number or "auto".');
}
return val;
};
}
}
export function getStringValidator() {
if (Konva.isUnminified) {
return function (val, attr) {
if (!Util._isString(val)) {
Util.warn(_formatValue(val) +
' is a not valid value for "' +
attr +
'" attribute. The value should be a string.');
}
return val;
};
}
}
export function getStringOrGradientValidator() {
if (Konva.isUnminified) {
return function (val, attr) {
const isString = Util._isString(val);
const isGradient = Object.prototype.toString.call(val) === '[object CanvasGradient]' ||
(val && val['addColorStop']);
if (!(isString || isGradient)) {
Util.warn(_formatValue(val) +
' is a not valid value for "' +
attr +
'" attribute. The value should be a string or a native gradient.');
}
return val;
};
}
}
export function getNumberArrayValidator() {
if (Konva.isUnminified) {
return function (val, attr) {
if (val instanceof TypedArray) {
return val;
}
if (!Util._isArray(val)) {
Util.warn(_formatValue(val) +
' is a not valid value for "' +
attr +
'" attribute. The value should be a array of numbers.');
}
else {
val.forEach(function (item) {
if (!Util._isNumber(item)) {
Util.warn('"' +
attr +
'" attribute has non numeric element ' +
item +
'. Make sure that all elements are numbers.');
}
});
}
return val;
};
}
}
export function getBooleanValidator() {
if (Konva.isUnminified) {
return function (val, attr) {
const isBool = val === true || val === false;
if (!isBool) {
Util.warn(_formatValue(val) +
' is a not valid value for "' +
attr +
'" attribute. The value should be a boolean.');
}
return val;
};
}
}
export function getComponentValidator(components) {
if (Konva.isUnminified) {
return function (val, attr) {
// ignore validation on undefined value, because it will reset to defalt
if (val === undefined || val === null) {
return val;
}
if (!Util.isObject(val)) {
Util.warn(_formatValue(val) +
' is a not valid value for "' +
attr +
'" attribute. The value should be an object with properties ' +
components);
}
return val;
};
}
}
+264
View File
@@ -0,0 +1,264 @@
import { Transform } from './Util.ts';
import { Node } from './Node.ts';
import { Container } from './Container.ts';
import { Stage } from './Stage.ts';
import { Layer } from './Layer.ts';
import { FastLayer } from './FastLayer.ts';
import { Group } from './Group.ts';
import { Shape } from './Shape.ts';
import { Animation } from './Animation.ts';
import { Tween } from './Tween.ts';
import { Context } from './Context.ts';
import { Canvas } from './Canvas.ts';
export declare const Konva: {
_global: any;
version: string;
isBrowser: boolean;
isUnminified: boolean;
dblClickWindow: number;
getAngle(angle: number): number;
enableTrace: boolean;
pointerEventsEnabled: boolean;
autoDrawEnabled: boolean;
hitOnDragEnabled: boolean;
capturePointerEventsEnabled: boolean;
_renderBackend: string;
legacyTextRendering: boolean;
pixelRatio: number;
dragDistance: number;
angleDeg: boolean;
showWarnings: boolean;
dragButtons: number[];
isDragging(): boolean;
isTransforming(): boolean;
isDragReady(): boolean;
releaseCanvasOnDestroy: boolean;
document: any;
_injectGlobal(Konva: any): void;
} & {
/**
* Defer automatic Konva.Transformer updates across many synchronous node changes
* and run them once when the returned function is called.
* A Transformer normally updates after every change of an attached node, so a loop
* over N selected nodes costs N updates, each measuring all N nodes. In a batch it costs one.
* A single setAttrs(), position(), destroyChildren(), removeChildren() call or animation frame is
* already batched; use this for anything else, for example a React commit or an undo.
* Attributes and change events stay synchronous. Inside the batch, the Transformer's own
* x(), y(), width(), height() and getClientRect() are computed on read; its anchors are laid out
* when the batch finishes, or earlier if something draws or measures them.
* Change events fire inside the batch, so a listener sees the new attributes and the
* Transformer's box, but not its anchors; call transformer.update() there if it needs them.
* Batches nest. Call the returned function in a finally block, and do not keep a batch
* open across an await or an animation frame.
* @method
* @name Konva.beginUpdateBatch
* @returns {Function} finishes the batch and runs the pending updates
* @example
* const finish = Konva.beginUpdateBatch();
* try {
* selectedNodes.forEach((node) => node.width(100));
* } finally {
* finish();
* }
*/
beginUpdateBatch: typeof Node._beginUpdateBatch;
Util: {
_isElement(obj: any): obj is Element;
_isFunction(obj: any): boolean;
_isPlainObject(obj: any): boolean;
_isArray(obj: any): obj is Array<any>;
_isNumber(obj: any): obj is number;
_isString(obj: any): obj is string;
_isBoolean(obj: any): obj is boolean;
isObject(val: any): val is object;
isValidSelector(selector: any): boolean;
_sign(number: number): 1 | -1;
requestAnimFrame(callback: Function, win?: Window | null): void;
createCanvasElement(): HTMLCanvasElement;
createImageElement(): HTMLImageElement;
_urlToImage(url: string, callback: Function, onError?: (error: Error) => void): void;
_rgbToHex(r: number, g: number, b: number): string;
_hexToRgb(hex: string): import("./types.ts").RGB;
getRandomColor(): string;
isCanvasFarblingActive(): boolean;
getHitColor(): string;
getHitColorKey(r: number, g: number, b: number): string;
getSnappedHexColor(hex: string): string;
getRGB(color: string): import("./types.ts").RGB;
colorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_namedColorToRBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | null;
_rgbColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_hex8ColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_hex6ColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_hex4ColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_hex3ColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_hslColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_getPointsRect(points: ArrayLike<number>): import("./types.ts").IRect;
haveIntersection(r1: import("./types.ts").IRect, r2: import("./types.ts").IRect): boolean;
cloneObject<Any>(obj: Any): Any;
_cloneValue(val: any): any;
cloneArray(arr: Array<any>): any[];
degToRad(deg: number): number;
radToDeg(rad: number): number;
_getRotation(radians: number): number;
_capitalize(str: string): string;
throw(str: string): never;
error(str: string): void;
warn(str: string): void;
_batchEvents(batch: ((run: () => void) => void) | undefined, run: () => void): void;
each(obj: object, func: Function): void;
_inRange(val: number, left: number, right: number): boolean;
_getProjectionToSegment(x1: any, y1: any, x2: any, y2: any, x3: any, y3: any): any[];
_getProjectionToLine(pt: import("./types.ts").Vector2d, line: Array<import("./types.ts").Vector2d>, isClosed: boolean): import("./types.ts").Vector2d;
_prepareArrayForTween(startArray: any, endArray: any, isClosed: any): number[];
_prepareToStringify<T>(obj: any, ancestors?: Set<object>): T;
_assign<T, U>(target: T, source: U): T & U;
_getEventType(type: string): "mouse" | "touch" | "pointer";
_getFirstPointerId(evt: any): any;
releaseCanvas(...canvases: HTMLCanvasElement[]): void;
_cornerRadii(cornerRadius: number | number[], width: number, height: number): [number, number, number, number];
drawRoundedRectPath(context: Context, width: number, height: number, cornerRadius: number | number[]): void;
drawRoundedPolygonPath(context: Context, points: import("./types.ts").Vector2d[], sides: number, radius: number, cornerRadius: number | number[]): void;
};
Transform: typeof Transform;
Node: typeof Node;
Container: typeof Container;
Stage: typeof Stage;
stages: Stage[];
Layer: typeof Layer;
FastLayer: typeof FastLayer;
Group: typeof Group;
DD: {
readonly isDragging: boolean;
readonly node: Node<import("./Node.ts").NodeConfig> | undefined;
_dragElements: Map<number, {
node: Node;
startPointerPos: import("./types.ts").Vector2d;
offset: import("./types.ts").Vector2d;
pointerId?: number;
pointerEventType?: Stage["_pointerEventType"];
lastPos?: import("./types.ts").Vector2d;
startEvent?: any;
dragStatus: "ready" | "dragging" | "stopped";
}>;
_listenToWindow(win: Window): void;
_batchEvents(callback: () => void, win?: Window): void;
_drag(evt: any, win?: Window): void;
_endDragBefore(evt?: any, win?: Window, only?: {
node: Node;
startPointerPos: import("./types.ts").Vector2d;
offset: import("./types.ts").Vector2d;
pointerId?: number;
pointerEventType?: Stage["_pointerEventType"];
lastPos?: import("./types.ts").Vector2d;
startEvent?: any;
dragStatus: "ready" | "dragging" | "stopped";
}): void;
_endDragAfter(evt: any, only?: {
node: Node;
startPointerPos: import("./types.ts").Vector2d;
offset: import("./types.ts").Vector2d;
pointerId?: number;
pointerEventType?: Stage["_pointerEventType"];
lastPos?: import("./types.ts").Vector2d;
startEvent?: any;
dragStatus: "ready" | "dragging" | "stopped";
}): void;
};
Shape: typeof Shape;
shapes: {
[key: string]: Shape<import("./Shape.ts").ShapeConfig>;
};
Animation: typeof Animation;
Tween: typeof Tween;
Easings: {
BackEaseIn(t: any, b: any, c: any, d: any): any;
BackEaseOut(t: any, b: any, c: any, d: any): any;
BackEaseInOut(t: any, b: any, c: any, d: any): any;
ElasticEaseIn(t: any, b: any, c: any, d: any, a: any, p: any): any;
ElasticEaseOut(t: any, b: any, c: any, d: any, a: any, p: any): any;
ElasticEaseInOut(t: any, b: any, c: any, d: any, a: any, p: any): any;
BounceEaseOut(t: any, b: any, c: any, d: any): any;
BounceEaseIn(t: any, b: any, c: any, d: any): any;
BounceEaseInOut(t: any, b: any, c: any, d: any): any;
EaseIn(t: any, b: any, c: any, d: any): any;
EaseOut(t: any, b: any, c: any, d: any): any;
EaseInOut(t: any, b: any, c: any, d: any): any;
StrongEaseIn(t: any, b: any, c: any, d: any): any;
StrongEaseOut(t: any, b: any, c: any, d: any): any;
StrongEaseInOut(t: any, b: any, c: any, d: any): any;
Linear(t: any, b: any, c: any, d: any): any;
};
Context: typeof Context;
Canvas: typeof Canvas;
};
export declare namespace Konva {
type Vector2d = import('./types.ts').Vector2d;
type IRect = import('./types.ts').IRect;
type GetSet<Type, This> = import('./types.ts').GetSet<Type, This>;
type Filter = import('./Node.ts').Filter;
type LineCap = import('./Shape.ts').LineCap;
type LineJoin = import('./Shape.ts').LineJoin;
type Node = import('./Node.ts').Node;
type NodeConfig = import('./Node.ts').NodeConfig;
type KonvaEventObject<EventType> = import('./Node.ts').KonvaEventObject<EventType>;
type KonvaPointerEvent = import('./PointerEvents.ts').KonvaPointerEvent;
type KonvaEventListener<This, EventType> = import('./Node.ts').KonvaEventListener<This, EventType>;
type Container = import('./Container.ts').Container<Node>;
type ContainerConfig = import('./Container.ts').ContainerConfig;
type Transform = import('./Util.ts').Transform;
type Context = import('./Context.ts').Context;
type Stage = import('./Stage.ts').Stage;
type StageConfig = import('./Stage.ts').StageConfig;
type Layer = import('./Layer.ts').Layer;
type LayerConfig = import('./Layer.ts').LayerConfig;
type FastLayer = import('./FastLayer.ts').FastLayer;
type Group = import('./Group.ts').Group;
type GroupConfig = import('./Group.ts').GroupConfig;
type Shape = import('./Shape.ts').Shape;
type ShapeConfig = import('./Shape.ts').ShapeConfig;
type Animation = import('./Animation.ts').Animation;
type Tween = import('./Tween.ts').Tween;
type TweenConfig = import('./Tween.ts').TweenConfig;
}
export default Konva;
+61
View File
@@ -0,0 +1,61 @@
// what is core parts of Konva?
import { Konva as Global } from "./Global.js";
import { Util, Transform } from "./Util.js";
import { Node } from "./Node.js";
import { Container } from "./Container.js";
import { Stage, stages } from "./Stage.js";
import { Layer } from "./Layer.js";
import { FastLayer } from "./FastLayer.js";
import { Group } from "./Group.js";
import { DD } from "./DragAndDrop.js";
import { Shape, shapes } from "./Shape.js";
import { Animation } from "./Animation.js";
import { Tween, Easings } from "./Tween.js";
import { Context } from "./Context.js";
import { Canvas } from "./Canvas.js";
export const Konva = Util._assign(Global, {
/**
* Defer automatic Konva.Transformer updates across many synchronous node changes
* and run them once when the returned function is called.
* A Transformer normally updates after every change of an attached node, so a loop
* over N selected nodes costs N updates, each measuring all N nodes. In a batch it costs one.
* A single setAttrs(), position(), destroyChildren(), removeChildren() call or animation frame is
* already batched; use this for anything else, for example a React commit or an undo.
* Attributes and change events stay synchronous. Inside the batch, the Transformer's own
* x(), y(), width(), height() and getClientRect() are computed on read; its anchors are laid out
* when the batch finishes, or earlier if something draws or measures them.
* Change events fire inside the batch, so a listener sees the new attributes and the
* Transformer's box, but not its anchors; call transformer.update() there if it needs them.
* Batches nest. Call the returned function in a finally block, and do not keep a batch
* open across an await or an animation frame.
* @method
* @name Konva.beginUpdateBatch
* @returns {Function} finishes the batch and runs the pending updates
* @example
* const finish = Konva.beginUpdateBatch();
* try {
* selectedNodes.forEach((node) => node.width(100));
* } finally {
* finish();
* }
*/
beginUpdateBatch: Node._beginUpdateBatch,
Util,
Transform,
Node,
Container,
Stage,
stages,
Layer,
FastLayer,
Group,
DD,
Shape,
shapes,
Animation,
Tween,
Easings,
Context,
Canvas,
});
export default Konva;
+326
View File
@@ -0,0 +1,326 @@
import { Konva as Core } from './_CoreInternals.ts';
import { Arc } from './shapes/Arc.ts';
import { Arrow } from './shapes/Arrow.ts';
import { Circle } from './shapes/Circle.ts';
import { Ellipse } from './shapes/Ellipse.ts';
import { Image } from './shapes/Image.ts';
import { Label, Tag } from './shapes/Label.ts';
import { Line } from './shapes/Line.ts';
import { Path } from './shapes/Path.ts';
import { Rect } from './shapes/Rect.ts';
import { RegularPolygon } from './shapes/RegularPolygon.ts';
import { Ring } from './shapes/Ring.ts';
import { Sprite } from './shapes/Sprite.ts';
import { Star } from './shapes/Star.ts';
import { Text } from './shapes/Text.ts';
import { TextPath } from './shapes/TextPath.ts';
import { Transformer } from './shapes/Transformer.ts';
import { Wedge } from './shapes/Wedge.ts';
export declare const Konva: {
_global: any;
version: string;
isBrowser: boolean;
isUnminified: boolean;
dblClickWindow: number;
getAngle(angle: number): number;
enableTrace: boolean;
pointerEventsEnabled: boolean;
autoDrawEnabled: boolean;
hitOnDragEnabled: boolean;
capturePointerEventsEnabled: boolean;
_renderBackend: string;
legacyTextRendering: boolean;
pixelRatio: number;
dragDistance: number;
angleDeg: boolean;
showWarnings: boolean;
dragButtons: number[];
isDragging(): boolean;
isTransforming(): boolean;
isDragReady(): boolean;
releaseCanvasOnDestroy: boolean;
document: any;
_injectGlobal(Konva: any): void;
} & {
beginUpdateBatch: typeof import("./Node.ts").Node._beginUpdateBatch;
Util: {
_isElement(obj: any): obj is Element;
_isFunction(obj: any): boolean;
_isPlainObject(obj: any): boolean;
_isArray(obj: any): obj is Array<any>;
_isNumber(obj: any): obj is number;
_isString(obj: any): obj is string;
_isBoolean(obj: any): obj is boolean;
isObject(val: any): val is object;
isValidSelector(selector: any): boolean;
_sign(number: number): 1 | -1;
requestAnimFrame(callback: Function, win?: Window | null): void;
createCanvasElement(): HTMLCanvasElement;
createImageElement(): HTMLImageElement;
_urlToImage(url: string, callback: Function, onError?: (error: Error) => void): void;
_rgbToHex(r: number, g: number, b: number): string;
_hexToRgb(hex: string): import("./types.ts").RGB;
getRandomColor(): string;
isCanvasFarblingActive(): boolean;
getHitColor(): string;
getHitColorKey(r: number, g: number, b: number): string;
getSnappedHexColor(hex: string): string;
getRGB(color: string): import("./types.ts").RGB;
colorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_namedColorToRBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | null;
_rgbColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_hex8ColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_hex6ColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_hex4ColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_hex3ColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_hslColorToRGBA(str: string): {
r: number;
g: number;
b: number;
a: number;
} | undefined;
_getPointsRect(points: ArrayLike<number>): import("./types.ts").IRect;
haveIntersection(r1: import("./types.ts").IRect, r2: import("./types.ts").IRect): boolean;
cloneObject<Any>(obj: Any): Any;
_cloneValue(val: any): any;
cloneArray(arr: Array<any>): any[];
degToRad(deg: number): number;
radToDeg(rad: number): number;
_getRotation(radians: number): number;
_capitalize(str: string): string;
throw(str: string): never;
error(str: string): void;
warn(str: string): void;
_batchEvents(batch: ((run: () => void) => void) | undefined, run: () => void): void;
each(obj: object, func: Function): void;
_inRange(val: number, left: number, right: number): boolean;
_getProjectionToSegment(x1: any, y1: any, x2: any, y2: any, x3: any, y3: any): any[];
_getProjectionToLine(pt: import("./types.ts").Vector2d, line: Array<import("./types.ts").Vector2d>, isClosed: boolean): import("./types.ts").Vector2d;
_prepareArrayForTween(startArray: any, endArray: any, isClosed: any): number[];
_prepareToStringify<T>(obj: any, ancestors?: Set<object>): T;
_assign<T, U>(target: T, source: U): T & U;
_getEventType(type: string): "mouse" | "touch" | "pointer";
_getFirstPointerId(evt: any): any;
releaseCanvas(...canvases: HTMLCanvasElement[]): void;
_cornerRadii(cornerRadius: number | number[], width: number, height: number): [number, number, number, number];
drawRoundedRectPath(context: import("./Context.ts").Context, width: number, height: number, cornerRadius: number | number[]): void;
drawRoundedPolygonPath(context: import("./Context.ts").Context, points: import("./types.ts").Vector2d[], sides: number, radius: number, cornerRadius: number | number[]): void;
};
Transform: typeof import("./Util.ts").Transform;
Node: typeof import("./Node.ts").Node;
Container: typeof import("./Container.ts").Container;
Stage: typeof import("./Stage.ts").Stage;
stages: import("./Stage.ts").Stage[];
Layer: typeof import("./Layer.ts").Layer;
FastLayer: typeof import("./FastLayer.ts").FastLayer;
Group: typeof import("./Group.ts").Group;
DD: {
readonly isDragging: boolean;
readonly node: import("./Node.ts").Node<import("./Node.ts").NodeConfig> | undefined;
_dragElements: Map<number, {
node: import("./Node.ts").Node;
startPointerPos: import("./types.ts").Vector2d;
offset: import("./types.ts").Vector2d;
pointerId?: number;
pointerEventType?: import("./Stage.ts").Stage["_pointerEventType"];
lastPos?: import("./types.ts").Vector2d;
startEvent?: any;
dragStatus: "ready" | "dragging" | "stopped";
}>;
_listenToWindow(win: Window): void;
_batchEvents(callback: () => void, win?: Window): void;
_drag(evt: any, win?: Window): void;
_endDragBefore(evt?: any, win?: Window, only?: {
node: import("./Node.ts").Node;
startPointerPos: import("./types.ts").Vector2d;
offset: import("./types.ts").Vector2d;
pointerId?: number;
pointerEventType?: import("./Stage.ts").Stage["_pointerEventType"];
lastPos?: import("./types.ts").Vector2d;
startEvent?: any;
dragStatus: "ready" | "dragging" | "stopped";
}): void;
_endDragAfter(evt: any, only?: {
node: import("./Node.ts").Node;
startPointerPos: import("./types.ts").Vector2d;
offset: import("./types.ts").Vector2d;
pointerId?: number;
pointerEventType?: import("./Stage.ts").Stage["_pointerEventType"];
lastPos?: import("./types.ts").Vector2d;
startEvent?: any;
dragStatus: "ready" | "dragging" | "stopped";
}): void;
};
Shape: typeof import("./Shape.ts").Shape;
shapes: {
[key: string]: import("./Shape.ts").Shape<import("./Shape.ts").ShapeConfig>;
};
Animation: typeof import("./Animation.ts").Animation;
Tween: typeof import("./Tween.ts").Tween;
Easings: {
BackEaseIn(t: any, b: any, c: any, d: any): any;
BackEaseOut(t: any, b: any, c: any, d: any): any;
BackEaseInOut(t: any, b: any, c: any, d: any): any;
ElasticEaseIn(t: any, b: any, c: any, d: any, a: any, p: any): any;
ElasticEaseOut(t: any, b: any, c: any, d: any, a: any, p: any): any;
ElasticEaseInOut(t: any, b: any, c: any, d: any, a: any, p: any): any;
BounceEaseOut(t: any, b: any, c: any, d: any): any;
BounceEaseIn(t: any, b: any, c: any, d: any): any;
BounceEaseInOut(t: any, b: any, c: any, d: any): any;
EaseIn(t: any, b: any, c: any, d: any): any;
EaseOut(t: any, b: any, c: any, d: any): any;
EaseInOut(t: any, b: any, c: any, d: any): any;
StrongEaseIn(t: any, b: any, c: any, d: any): any;
StrongEaseOut(t: any, b: any, c: any, d: any): any;
StrongEaseInOut(t: any, b: any, c: any, d: any): any;
Linear(t: any, b: any, c: any, d: any): any;
};
Context: typeof import("./Context.ts").Context;
Canvas: typeof import("./Canvas.ts").Canvas;
} & {
Arc: typeof Arc;
Arrow: typeof Arrow;
Circle: typeof Circle;
Ellipse: typeof Ellipse;
Image: typeof Image;
Label: typeof Label;
Tag: typeof Tag;
Line: typeof Line;
Path: typeof Path;
Rect: typeof Rect;
RegularPolygon: typeof RegularPolygon;
Ring: typeof Ring;
Sprite: typeof Sprite;
Star: typeof Star;
Text: typeof Text;
TextPath: typeof TextPath;
Transformer: typeof Transformer;
Wedge: typeof Wedge;
/**
* @namespace Filters
* @memberof Konva
*/
Filters: {
Blur: import("./Node.ts").Filter;
Brightness: import("./Node.ts").Filter;
Brighten: import("./Node.ts").Filter;
Contrast: import("./Node.ts").Filter;
Emboss: import("./Node.ts").Filter;
Enhance: import("./Node.ts").Filter;
Grayscale: import("./Node.ts").Filter;
HSL: import("./Node.ts").Filter;
HSV: import("./Node.ts").Filter;
Invert: import("./Node.ts").Filter;
Kaleidoscope: import("./Node.ts").Filter;
Mask: import("./Node.ts").Filter;
Noise: import("./Node.ts").Filter;
Pixelate: import("./Node.ts").Filter;
Posterize: import("./Node.ts").Filter;
RGB: import("./Node.ts").Filter;
RGBA: import("./Node.ts").Filter;
Sepia: import("./Node.ts").Filter;
Solarize: import("./Node.ts").Filter;
Threshold: import("./Node.ts").Filter;
};
};
export declare namespace Konva {
type Vector2d = Core.Vector2d;
type IRect = Core.IRect;
type GetSet<Type, This> = Core.GetSet<Type, This>;
type Filter = Core.Filter;
type LineCap = Core.LineCap;
type LineJoin = Core.LineJoin;
type Box = import('./shapes/Transformer.ts').Box;
type Node = Core.Node;
type NodeConfig = Core.NodeConfig;
type KonvaEventObject<EventType> = Core.KonvaEventObject<EventType>;
type KonvaPointerEvent = Core.KonvaPointerEvent;
type KonvaEventListener<This, EventType> = Core.KonvaEventListener<This, EventType>;
type Container = Core.Container;
type ContainerConfig = Core.ContainerConfig;
type Transform = Core.Transform;
type Context = Core.Context;
type Stage = Core.Stage;
type StageConfig = Core.StageConfig;
type Layer = Core.Layer;
type LayerConfig = Core.LayerConfig;
type FastLayer = Core.FastLayer;
type Group = Core.Group;
type GroupConfig = Core.GroupConfig;
type Shape = Core.Shape;
type ShapeConfig = Core.ShapeConfig;
type Animation = Core.Animation;
type Tween = Core.Tween;
type TweenConfig = Core.TweenConfig;
type Arc = import('./shapes/Arc.ts').Arc;
type ArcConfig = import('./shapes/Arc.ts').ArcConfig;
type Arrow = import('./shapes/Arrow.ts').Arrow;
type ArrowConfig = import('./shapes/Arrow.ts').ArrowConfig;
type Circle = import('./shapes/Circle.ts').Circle;
type CircleConfig = import('./shapes/Circle.ts').CircleConfig;
type Ellipse = import('./shapes/Ellipse.ts').Ellipse;
type EllipseConfig = import('./shapes/Ellipse.ts').EllipseConfig;
type Image = import('./shapes/Image.ts').Image;
type ImageConfig = import('./shapes/Image.ts').ImageConfig;
type Label = import('./shapes/Label.ts').Label;
type LabelConfig = import('./shapes/Label.ts').LabelConfig;
type Tag = import('./shapes/Label.ts').Tag;
type TagConfig = import('./shapes/Label.ts').TagConfig;
type Line = import('./shapes/Line.ts').Line;
type LineConfig = import('./shapes/Line.ts').LineConfig;
type Path = import('./shapes/Path.ts').Path;
type PathConfig = import('./shapes/Path.ts').PathConfig;
type Rect = import('./shapes/Rect.ts').Rect;
type RectConfig = import('./shapes/Rect.ts').RectConfig;
type RegularPolygon = import('./shapes/RegularPolygon.ts').RegularPolygon;
type RegularPolygonConfig = import('./shapes/RegularPolygon.ts').RegularPolygonConfig;
type Ring = import('./shapes/Ring.ts').Ring;
type RingConfig = import('./shapes/Ring.ts').RingConfig;
type Sprite = import('./shapes/Sprite.ts').Sprite;
type SpriteConfig = import('./shapes/Sprite.ts').SpriteConfig;
type Star = import('./shapes/Star.ts').Star;
type StarConfig = import('./shapes/Star.ts').StarConfig;
type Text = import('./shapes/Text.ts').Text;
type TextConfig = import('./shapes/Text.ts').TextConfig;
type TextPath = import('./shapes/TextPath.ts').TextPath;
type TextPathConfig = import('./shapes/TextPath.ts').TextPathConfig;
type Transformer = import('./shapes/Transformer.ts').Transformer;
type TransformerConfig = import('./shapes/Transformer.ts').TransformerConfig;
type Wedge = import('./shapes/Wedge.ts').Wedge;
type WedgeConfig = import('./shapes/Wedge.ts').WedgeConfig;
}
+87
View File
@@ -0,0 +1,87 @@
// we need to import core of the Konva and then extend it with all additional objects
import { Konva as Core } from "./_CoreInternals.js";
// shapes
import { Arc } from "./shapes/Arc.js";
import { Arrow } from "./shapes/Arrow.js";
import { Circle } from "./shapes/Circle.js";
import { Ellipse } from "./shapes/Ellipse.js";
import { Image } from "./shapes/Image.js";
import { Label, Tag } from "./shapes/Label.js";
import { Line } from "./shapes/Line.js";
import { Path } from "./shapes/Path.js";
import { Rect } from "./shapes/Rect.js";
import { RegularPolygon } from "./shapes/RegularPolygon.js";
import { Ring } from "./shapes/Ring.js";
import { Sprite } from "./shapes/Sprite.js";
import { Star } from "./shapes/Star.js";
import { Text } from "./shapes/Text.js";
import { TextPath } from "./shapes/TextPath.js";
import { Transformer } from "./shapes/Transformer.js";
import { Wedge } from "./shapes/Wedge.js";
// filters
import { Blur } from "./filters/Blur.js";
import { Brighten } from "./filters/Brighten.js";
import { Brightness } from "./filters/Brightness.js";
import { Contrast } from "./filters/Contrast.js";
import { Emboss } from "./filters/Emboss.js";
import { Enhance } from "./filters/Enhance.js";
import { Grayscale } from "./filters/Grayscale.js";
import { HSL } from "./filters/HSL.js";
import { HSV } from "./filters/HSV.js";
import { Invert } from "./filters/Invert.js";
import { Kaleidoscope } from "./filters/Kaleidoscope.js";
import { Mask } from "./filters/Mask.js";
import { Noise } from "./filters/Noise.js";
import { Pixelate } from "./filters/Pixelate.js";
import { Posterize } from "./filters/Posterize.js";
import { RGB } from "./filters/RGB.js";
import { RGBA } from "./filters/RGBA.js";
import { Sepia } from "./filters/Sepia.js";
import { Solarize } from "./filters/Solarize.js";
import { Threshold } from "./filters/Threshold.js";
export const Konva = Core.Util._assign(Core, {
Arc,
Arrow,
Circle,
Ellipse,
Image,
Label,
Tag,
Line,
Path,
Rect,
RegularPolygon,
Ring,
Sprite,
Star,
Text,
TextPath,
Transformer,
Wedge,
/**
* @namespace Filters
* @memberof Konva
*/
Filters: {
Blur,
Brightness,
Brighten,
Contrast,
Emboss,
Enhance,
Grayscale,
HSL,
HSV,
Invert,
Kaleidoscope,
Mask,
Noise,
Pixelate,
Posterize,
RGB,
RGBA,
Sepia,
Solarize,
Threshold,
},
});
+2
View File
@@ -0,0 +1,2 @@
import { Konva } from './_CoreInternals.ts';
export default Konva;
+33
View File
@@ -0,0 +1,33 @@
var _a;
var _b;
import { Konva } from "./_CoreInternals.js";
// @ts-ignore
import * as Canvas from 'canvas';
const canvas = Canvas['default'] || Canvas;
// @ts-ignore
global.DOMMatrix = canvas.DOMMatrix;
// @ts-ignore
(_a = (_b = global).Path2D) !== null && _a !== void 0 ? _a : (_b.Path2D = class Path2D {
constructor(path) {
this.path = path;
}
get [Symbol.toStringTag]() {
return `Path2D`;
}
});
Konva.Util['createCanvasElement'] = () => {
const node = canvas.createCanvas(300, 300);
if (!node['style']) {
node['style'] = {};
}
return node;
};
// create image in Node env
Konva.Util.createImageElement = () => {
const node = new canvas.Image();
return node;
};
Konva._renderBackend = 'node-canvas';
// the core Konva object, without shapes and filters. `import 'konva'` extends
// this same object with them
export default Konva;
+20
View File
@@ -0,0 +1,20 @@
import type { Filter } from '../Node.ts';
/**
* Blur Filter
* @function
* @name Blur
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Blur]);
* node.blurRadius(10);
*/
export declare const Blur: Filter;
/**
* get/set blur radius. Use with {@link Konva.Filters.Blur} filter
* @name Konva.Node#blurRadius
* @method
* @param {Integer} radius
* @returns {Integer}
*/
+312
View File
@@ -0,0 +1,312 @@
import { Factory } from "../Factory.js";
import { Node } from "../Node.js";
import { getNumberValidator } from "../Validators.js";
/*
the Gauss filter
master repo: https://github.com/pavelpower/kineticjsGaussFilter
*/
/*
StackBlur - a fast almost Gaussian Blur For Canvas
Version: 0.5
Author: Mario Klingemann
Contact: mario@quasimondo.com
Website: http://www.quasimondo.com/StackBlurForCanvas
Twitter: @quasimondo
In case you find this class useful - especially in commercial projects -
I am not totally unhappy for a small donation to my PayPal account
mario@quasimondo.de
Or support me on flattr:
https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript
Copyright (c) 2010 Mario Klingemann
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
* @license MIT
*/
function BlurStack() {
this.r = 0;
this.g = 0;
this.b = 0;
this.a = 0;
this.next = null;
}
const mul_table = [
512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292,
512, 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292,
273, 512, 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259,
496, 475, 456, 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292,
282, 273, 265, 512, 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373,
364, 354, 345, 337, 328, 320, 312, 305, 298, 291, 284, 278, 271, 265, 259,
507, 496, 485, 475, 465, 456, 446, 437, 428, 420, 412, 404, 396, 388, 381,
374, 367, 360, 354, 347, 341, 335, 329, 323, 318, 312, 307, 302, 297, 292,
287, 282, 278, 273, 269, 265, 261, 512, 505, 497, 489, 482, 475, 468, 461,
454, 447, 441, 435, 428, 422, 417, 411, 405, 399, 394, 389, 383, 378, 373,
368, 364, 359, 354, 350, 345, 341, 337, 332, 328, 324, 320, 316, 312, 309,
305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271, 268, 265, 262, 259,
257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456, 451, 446, 442,
437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388, 385, 381,
377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, 332,
329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292,
289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259,
];
const shg_table = [
9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17,
17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24,
];
// the horizontal pass blurs premultiplied colour, otherwise transparent
// pixels bleed their colour into their neighbours; the vertical pass
// unpremultiplies again
function premultiply(channel, alpha) {
return alpha === 255 ? channel : (channel * alpha) / 255;
}
// Preserve the maximum logical radius; high-DPI caches need larger kernels.
const MAX_RADIUS = 180;
function filterGaussBlurRGBA(imageData, radius) {
const pixels = imageData.data, width = imageData.width, height = imageData.height;
let p, yi, yw, r_sum, g_sum, b_sum, a_sum, r_out_sum, g_out_sum, b_out_sum, a_out_sum, r_in_sum, g_in_sum, b_in_sum, a_in_sum, pr, pg, pb, pa, rbs;
const div = radius + radius + 1, widthMinus1 = width - 1, heightMinus1 = height - 1, radiusPlus1 = radius + 1, sumFactor = (radiusPlus1 * (radiusPlus1 + 1)) / 2, stackStart = new BlurStack(),
// Use floating-point normalization to avoid 32-bit overflow at high DPI.
normalization = radius <= MAX_RADIUS
? mul_table[radius] / 2 ** shg_table[radius]
: 1 / (radiusPlus1 * radiusPlus1);
let stackEnd = null, stack = stackStart, stackIn = null, stackOut = null;
for (let i = 1; i < div; i++) {
stack = stack.next = new BlurStack();
if (i === radiusPlus1) {
stackEnd = stack;
}
}
stack.next = stackStart;
yw = yi = 0;
for (let y = 0; y < height; y++) {
r_in_sum =
g_in_sum =
b_in_sum =
a_in_sum =
r_sum =
g_sum =
b_sum =
a_sum =
0;
pa = pixels[yi + 3];
pr = premultiply(pixels[yi], pa);
pg = premultiply(pixels[yi + 1], pa);
pb = premultiply(pixels[yi + 2], pa);
r_out_sum = radiusPlus1 * pr;
g_out_sum = radiusPlus1 * pg;
b_out_sum = radiusPlus1 * pb;
a_out_sum = radiusPlus1 * pa;
r_sum += sumFactor * pr;
g_sum += sumFactor * pg;
b_sum += sumFactor * pb;
a_sum += sumFactor * pa;
stack = stackStart;
for (let i = 0; i < radiusPlus1; i++) {
stack.r = pr;
stack.g = pg;
stack.b = pb;
stack.a = pa;
stack = stack.next;
}
for (let i = 1; i < radiusPlus1; i++) {
p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);
pa = pixels[p + 3];
r_sum +=
(stack.r = pr = premultiply(pixels[p], pa)) * (rbs = radiusPlus1 - i);
g_sum += (stack.g = pg = premultiply(pixels[p + 1], pa)) * rbs;
b_sum += (stack.b = pb = premultiply(pixels[p + 2], pa)) * rbs;
a_sum += (stack.a = pa) * rbs;
r_in_sum += pr;
g_in_sum += pg;
b_in_sum += pb;
a_in_sum += pa;
stack = stack.next;
}
stackIn = stackStart;
stackOut = stackEnd;
for (let x = 0; x < width; x++) {
pixels[yi] = Math.floor(r_sum * normalization);
pixels[yi + 1] = Math.floor(g_sum * normalization);
pixels[yi + 2] = Math.floor(b_sum * normalization);
pixels[yi + 3] = Math.floor(a_sum * normalization);
r_sum -= r_out_sum;
g_sum -= g_out_sum;
b_sum -= b_out_sum;
a_sum -= a_out_sum;
r_out_sum -= stackIn.r;
g_out_sum -= stackIn.g;
b_out_sum -= stackIn.b;
a_out_sum -= stackIn.a;
p = (yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1)) << 2;
pa = pixels[p + 3];
r_in_sum += stackIn.r = premultiply(pixels[p], pa);
g_in_sum += stackIn.g = premultiply(pixels[p + 1], pa);
b_in_sum += stackIn.b = premultiply(pixels[p + 2], pa);
a_in_sum += stackIn.a = pa;
r_sum += r_in_sum;
g_sum += g_in_sum;
b_sum += b_in_sum;
a_sum += a_in_sum;
stackIn = stackIn.next;
r_out_sum += pr = stackOut.r;
g_out_sum += pg = stackOut.g;
b_out_sum += pb = stackOut.b;
a_out_sum += pa = stackOut.a;
r_in_sum -= pr;
g_in_sum -= pg;
b_in_sum -= pb;
a_in_sum -= pa;
stackOut = stackOut.next;
yi += 4;
}
yw += width;
}
for (let x = 0; x < width; x++) {
g_in_sum =
b_in_sum =
a_in_sum =
r_in_sum =
g_sum =
b_sum =
a_sum =
r_sum =
0;
yi = x << 2;
r_out_sum = radiusPlus1 * (pr = pixels[yi]);
g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]);
b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]);
a_out_sum = radiusPlus1 * (pa = pixels[yi + 3]);
r_sum += sumFactor * pr;
g_sum += sumFactor * pg;
b_sum += sumFactor * pb;
a_sum += sumFactor * pa;
stack = stackStart;
for (let i = 0; i < radiusPlus1; i++) {
stack.r = pr;
stack.g = pg;
stack.b = pb;
stack.a = pa;
stack = stack.next;
}
// the pass primes from the second row, which a 1-pixel-tall image lacks
let yp = Math.min(1, heightMinus1) * width;
for (let i = 1; i <= radius; i++) {
yi = (yp + x) << 2;
r_sum += (stack.r = pr = pixels[yi]) * (rbs = radiusPlus1 - i);
g_sum += (stack.g = pg = pixels[yi + 1]) * rbs;
b_sum += (stack.b = pb = pixels[yi + 2]) * rbs;
a_sum += (stack.a = pa = pixels[yi + 3]) * rbs;
r_in_sum += pr;
g_in_sum += pg;
b_in_sum += pb;
a_in_sum += pa;
stack = stack.next;
if (i < heightMinus1) {
yp += width;
}
}
yi = x;
stackIn = stackStart;
stackOut = stackEnd;
for (let y = 0; y < height; y++) {
p = yi << 2;
pixels[p + 3] = pa = Math.floor(a_sum * normalization);
if (pa > 0) {
pa = 255 / pa;
pixels[p] = Math.floor(r_sum * normalization) * pa;
pixels[p + 1] = Math.floor(g_sum * normalization) * pa;
pixels[p + 2] = Math.floor(b_sum * normalization) * pa;
}
else {
pixels[p] = pixels[p + 1] = pixels[p + 2] = 0;
}
r_sum -= r_out_sum;
g_sum -= g_out_sum;
b_sum -= b_out_sum;
a_sum -= a_out_sum;
r_out_sum -= stackIn.r;
g_out_sum -= stackIn.g;
b_out_sum -= stackIn.b;
a_out_sum -= stackIn.a;
p =
(x +
((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width) <<
2;
r_sum += r_in_sum += stackIn.r = pixels[p];
g_sum += g_in_sum += stackIn.g = pixels[p + 1];
b_sum += b_in_sum += stackIn.b = pixels[p + 2];
a_sum += a_in_sum += stackIn.a = pixels[p + 3];
stackIn = stackIn.next;
r_out_sum += pr = stackOut.r;
g_out_sum += pg = stackOut.g;
b_out_sum += pb = stackOut.b;
a_out_sum += pa = stackOut.a;
r_in_sum -= pr;
g_in_sum -= pg;
b_in_sum -= pb;
a_in_sum -= pa;
stackOut = stackOut.next;
yi += width;
}
}
}
/**
* Blur Filter
* @function
* @name Blur
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Blur]);
* node.blurRadius(10);
*/
export const Blur = function Blur(imageData, pixelRatio = 1) {
const radius = Math.round(Math.min(this.blurRadius(), MAX_RADIUS) * pixelRatio);
if (radius > 0) {
filterGaussBlurRGBA(imageData, radius);
}
};
Factory.addGetterSetter(Node, 'blurRadius', 0, getNumberValidator(), Factory.afterSetFilter);
/**
* get/set blur radius. Use with {@link Konva.Filters.Blur} filter
* @name Konva.Node#blurRadius
* @method
* @param {Integer} radius
* @returns {Integer}
*/
+24
View File
@@ -0,0 +1,24 @@
import type { Filter } from '../Node.ts';
/**
* Brighten Filter.
* @deprecated Use {@link Konva.Filters.Brightness} instead for CSS-compatible behavior.
* This filter uses additive brightness adjustment (adds a constant value to RGB channels).
* @function
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Brighten]);
* node.brightness(0.8);
*/
export declare const Brighten: Filter;
/**
* get/set filter brightness. With {@link Konva.Filters.Brighten} it is a number between -1 and 1
* added to every channel: positive values brighten the pixels and negative values darken them.
* With {@link Konva.Filters.Brightness} it is a CSS-like multiplier: 1 is no change, 1.5 is 50% brighter.
* Both filters leave pixels unchanged when the attribute is unset. An explicit 0 makes Brightness black.
* @name Konva.Node#brightness
* @method
* @param {Number} brightness
* @returns {Number}
*/
+37
View File
@@ -0,0 +1,37 @@
import { Factory } from "../Factory.js";
import { Node } from "../Node.js";
import { getNumberValidator } from "../Validators.js";
/**
* Brighten Filter.
* @deprecated Use {@link Konva.Filters.Brightness} instead for CSS-compatible behavior.
* This filter uses additive brightness adjustment (adds a constant value to RGB channels).
* @function
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Brighten]);
* node.brightness(0.8);
*/
export const Brighten = function (imageData) {
const brightness = this.brightness() * 255, data = imageData.data, len = data.length;
for (let i = 0; i < len; i += 4) {
// red
data[i] += brightness;
// green
data[i + 1] += brightness;
// blue
data[i + 2] += brightness;
}
};
Factory.addGetterSetter(Node, 'brightness', 0, getNumberValidator(), Factory.afterSetFilter);
/**
* get/set filter brightness. With {@link Konva.Filters.Brighten} it is a number between -1 and 1
* added to every channel: positive values brighten the pixels and negative values darken them.
* With {@link Konva.Filters.Brightness} it is a CSS-like multiplier: 1 is no change, 1.5 is 50% brighter.
* Both filters leave pixels unchanged when the attribute is unset. An explicit 0 makes Brightness black.
* @name Konva.Node#brightness
* @method
* @param {Number} brightness
* @returns {Number}
*/
+14
View File
@@ -0,0 +1,14 @@
import type { Filter } from '../Node.ts';
import './Brighten.ts';
/**
* Brightness Filter.
* CSS-compatible brightness multiplier. An unset brightness leaves pixels unchanged.
* @function
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Brightness]);
* node.brightness(1.5); // 50% brighter (CSS-compatible)
*/
export declare const Brightness: Filter;
+24
View File
@@ -0,0 +1,24 @@
// The shared brightness accessor is registered by Brighten.
import "./Brighten.js";
/**
* Brightness Filter.
* CSS-compatible brightness multiplier. An unset brightness leaves pixels unchanged.
* @function
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Brightness]);
* node.brightness(1.5); // 50% brighter (CSS-compatible)
*/
export const Brightness = function (imageData) {
const brightness = this.attrs.brightness === undefined ? 1 : this.brightness(), data = imageData.data, len = data.length;
for (let i = 0; i < len; i += 4) {
// red
data[i] = Math.min(255, data[i] * brightness);
// green
data[i + 1] = Math.min(255, data[i + 1] * brightness);
// blue
data[i + 2] = Math.min(255, data[i + 2] * brightness);
}
};
+12
View File
@@ -0,0 +1,12 @@
import type { Filter } from '../Node.ts';
/**
* Contrast Filter.
* @function
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Contrast]);
* node.contrast(10);
*/
export declare const Contrast: Filter;
+56
View File
@@ -0,0 +1,56 @@
import { Factory } from "../Factory.js";
import { Node } from "../Node.js";
import { getNumberValidator } from "../Validators.js";
/**
* Contrast Filter.
* @function
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Contrast]);
* node.contrast(10);
*/
export const Contrast = function (imageData) {
const adjust = Math.pow((this.contrast() + 100) / 100, 2);
const data = imageData.data, nPixels = data.length;
let red = 150, green = 150, blue = 150;
for (let i = 0; i < nPixels; i += 4) {
red = data[i];
green = data[i + 1];
blue = data[i + 2];
//Red channel
red /= 255;
red -= 0.5;
red *= adjust;
red += 0.5;
red *= 255;
//Green channel
green /= 255;
green -= 0.5;
green *= adjust;
green += 0.5;
green *= 255;
//Blue channel
blue /= 255;
blue -= 0.5;
blue *= adjust;
blue += 0.5;
blue *= 255;
red = red < 0 ? 0 : red > 255 ? 255 : red;
green = green < 0 ? 0 : green > 255 ? 255 : green;
blue = blue < 0 ? 0 : blue > 255 ? 255 : blue;
data[i] = red;
data[i + 1] = green;
data[i + 2] = blue;
}
};
/**
* get/set filter contrast. The contrast is a number between -100 and 100.
* Use with {@link Konva.Filters.Contrast} filter.
* @name Konva.Node#contrast
* @method
* @param {Number} contrast value between -100 and 100
* @returns {Number}
*/
Factory.addGetterSetter(Node, 'contrast', 0, getNumberValidator(), Factory.afterSetFilter);
+22
View File
@@ -0,0 +1,22 @@
import type { Filter } from '../Node.ts';
/**
* Emboss Filter.
* @function
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Emboss]);
* node.embossStrength(0.8);
* node.embossWhiteLevel(0.3);
* node.embossDirection('right');
* node.embossBlend(true);
*/
export declare const Emboss: Filter;
/**
* get/set emboss blend. Use with {@link Konva.Filters.Emboss} filter.
* @name Konva.Node#embossBlend
* @method
* @param {Boolean} embossBlend
* @returns {Boolean}
*/
+119
View File
@@ -0,0 +1,119 @@
import { Factory } from "../Factory.js";
import { Node } from "../Node.js";
import { getNumberValidator } from "../Validators.js";
/**
* Emboss Filter.
* @function
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Emboss]);
* node.embossStrength(0.8);
* node.embossWhiteLevel(0.3);
* node.embossDirection('right');
* node.embossBlend(true);
*/
export const Emboss = function (imageData) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
const data = imageData.data;
const w = imageData.width;
const h = imageData.height;
// Inputs from Konva node
const strength01 = Math.min(1, Math.max(0, (_b = (_a = this.embossStrength) === null || _a === void 0 ? void 0 : _a.call(this)) !== null && _b !== void 0 ? _b : 0.5)); // [0..1]
const whiteLevel01 = Math.min(1, Math.max(0, (_d = (_c = this.embossWhiteLevel) === null || _c === void 0 ? void 0 : _c.call(this)) !== null && _d !== void 0 ? _d : 0.5)); // [0..1]
// Convert string direction to degrees
const directionMap = {
'top-left': 315,
top: 270,
'top-right': 225,
right: 180,
'bottom-right': 135,
bottom: 90,
'bottom-left': 45,
left: 0,
};
const directionDeg = (_g = directionMap[(_f = (_e = this.embossDirection) === null || _e === void 0 ? void 0 : _e.call(this)) !== null && _f !== void 0 ? _f : 'top-left']) !== null && _g !== void 0 ? _g : 315; // degrees
const blend = !!((_j = (_h = this.embossBlend) === null || _h === void 0 ? void 0 : _h.call(this)) !== null && _j !== void 0 ? _j : false);
// Internal mapping:
// - "strength" was 0..10; we honor your 0..1 API and scale accordingly.
// - Sobel directional response is roughly in [-1020..1020] for 8-bit luminance; scale to ~±128.
const strength = strength01 * 10;
const bias = whiteLevel01 * 255;
const dirRad = (directionDeg * Math.PI) / 180;
const cx = Math.cos(dirRad);
const cy = Math.sin(dirRad);
const SCALE = (128 / 1020) * strength; // ≈0.1255 * strength
// Precompute luminance (Rec.709)
const lum = new Float32Array(w * h);
for (let p = 0, i = 0; i < data.length; i += 4, p++) {
lum[p] = 0.2126 * data[i] + 0.7152 * data[i + 1] + 0.0722 * data[i + 2];
}
// Sobel kernels (flattened)
const Gx = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
const Gy = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
const clamp8 = (v) => (v < 0 ? 0 : v > 255 ? 255 : v);
// Extend the edge pixels so the same kernel applies at every position.
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
let sx = 0, sy = 0, k = 0;
for (let dy = -1; dy <= 1; dy++) {
const row = Math.max(0, Math.min(h - 1, y + dy)) * w;
for (let dx = -1; dx <= 1; dx++, k++) {
const sample = lum[row + Math.max(0, Math.min(w - 1, x + dx))];
sx += sample * Gx[k];
sy += sample * Gy[k];
}
}
const p = y * w + x;
const r = cx * sx + cy * sy; // directional response
const outGray = clamp8(bias + r * SCALE); // biased, scaled, clamped
const o = p * 4;
if (blend) {
// Add the emboss "relief" around chosen bias to original RGB
const delta = outGray - bias; // symmetric around whiteLevel
data[o] = clamp8(data[o] + delta);
data[o + 1] = clamp8(data[o + 1] + delta);
data[o + 2] = clamp8(data[o + 2] + delta);
}
else {
// Grayscale embossed output
data[o] = data[o + 1] = data[o + 2] = outGray;
}
}
}
return imageData;
};
Factory.addGetterSetter(Node, 'embossStrength', 0.5, getNumberValidator(), Factory.afterSetFilter);
/**
* get/set emboss strength. Use with {@link Konva.Filters.Emboss} filter.
* @name Konva.Node#embossStrength
* @method
* @param {Number} level between 0 and 1. Default is 0.5
* @returns {Number}
*/
Factory.addGetterSetter(Node, 'embossWhiteLevel', 0.5, getNumberValidator(), Factory.afterSetFilter);
/**
* get/set emboss white level. Use with {@link Konva.Filters.Emboss} filter.
* @name Konva.Node#embossWhiteLevel
* @method
* @param {Number} embossWhiteLevel between 0 and 1. Default is 0.5
* @returns {Number}
*/
Factory.addGetterSetter(Node, 'embossDirection', 'top-left', undefined, Factory.afterSetFilter);
/**
* get/set emboss direction. Use with {@link Konva.Filters.Emboss} filter.
* @name Konva.Node#embossDirection
* @method
* @param {String} embossDirection can be top-left, top, top-right, right, bottom-right, bottom, bottom-left or left
* The default is top-left
* @returns {String}
*/
Factory.addGetterSetter(Node, 'embossBlend', false, undefined, Factory.afterSetFilter);
/**
* get/set emboss blend. Use with {@link Konva.Filters.Emboss} filter.
* @name Konva.Node#embossBlend
* @method
* @param {Boolean} embossBlend
* @returns {Boolean}
*/
+16
View File
@@ -0,0 +1,16 @@
import type { Filter } from '../Node.ts';
/**
* Enhance Filter. Adjusts the colors so that they span the widest
* possible range (ie 0-255). Performs w*h pixel reads and w*h pixel
* writes.
* @function
* @name Enhance
* @memberof Konva.Filters
* @param {Object} imageData
* @author ippo615
* @example
* node.cache();
* node.filters([Konva.Filters.Enhance]);
* node.enhance(0.4);
*/
export declare const Enhance: Filter;
+118
View File
@@ -0,0 +1,118 @@
import { Factory } from "../Factory.js";
import { Node } from "../Node.js";
import { getNumberValidator } from "../Validators.js";
function remap(fromValue, fromMin, fromMax, toMin, toMax) {
// Compute the range of the data
const fromRange = fromMax - fromMin, toRange = toMax - toMin;
// If either range is 0, then the value can only be mapped to 1 value
if (fromRange === 0) {
return toMin + toRange / 2;
}
if (toRange === 0) {
return toMin;
}
// (1) untranslate, (2) unscale, (3) rescale, (4) retranslate
let toValue = (fromValue - fromMin) / fromRange;
toValue = toRange * toValue + toMin;
return toValue;
}
/**
* Enhance Filter. Adjusts the colors so that they span the widest
* possible range (ie 0-255). Performs w*h pixel reads and w*h pixel
* writes.
* @function
* @name Enhance
* @memberof Konva.Filters
* @param {Object} imageData
* @author ippo615
* @example
* node.cache();
* node.filters([Konva.Filters.Enhance]);
* node.enhance(0.4);
*/
export const Enhance = function (imageData) {
const data = imageData.data, nSubPixels = data.length;
let rMin = data[0], rMax = rMin, r, gMin = data[1], gMax = gMin, g, bMin = data[2], bMax = bMin, b;
// If we are not enhancing anything - don't do any computation
const enhanceAmount = this.enhance();
if (enhanceAmount === 0) {
return;
}
// 1st Pass - find the min and max for each channel:
for (let i = 0; i < nSubPixels; i += 4) {
r = data[i + 0];
if (r < rMin) {
rMin = r;
}
else if (r > rMax) {
rMax = r;
}
g = data[i + 1];
if (g < gMin) {
gMin = g;
}
else if (g > gMax) {
gMax = g;
}
b = data[i + 2];
if (b < bMin) {
bMin = b;
}
else if (b > bMax) {
bMax = b;
}
//a = data[i + 3];
//if (a < aMin) { aMin = a; } else
//if (a > aMax) { aMax = a; }
}
// If there is only 1 level - don't remap
if (rMax === rMin) {
rMax = 255;
rMin = 0;
}
if (gMax === gMin) {
gMax = 255;
gMin = 0;
}
if (bMax === bMin) {
bMax = 255;
bMin = 0;
}
let rGoalMax, rGoalMin, gGoalMax, gGoalMin, bGoalMax, bGoalMin;
// If the enhancement is positive - stretch the histogram
if (enhanceAmount > 0) {
rGoalMax = rMax + enhanceAmount * (255 - rMax);
rGoalMin = rMin - enhanceAmount * (rMin - 0);
gGoalMax = gMax + enhanceAmount * (255 - gMax);
gGoalMin = gMin - enhanceAmount * (gMin - 0);
bGoalMax = bMax + enhanceAmount * (255 - bMax);
bGoalMin = bMin - enhanceAmount * (bMin - 0);
// If the enhancement is negative - compress the histogram
}
else {
const rMid = (rMax + rMin) * 0.5;
rGoalMax = rMax + enhanceAmount * (rMax - rMid);
rGoalMin = rMin + enhanceAmount * (rMin - rMid);
const gMid = (gMax + gMin) * 0.5;
gGoalMax = gMax + enhanceAmount * (gMax - gMid);
gGoalMin = gMin + enhanceAmount * (gMin - gMid);
const bMid = (bMax + bMin) * 0.5;
bGoalMax = bMax + enhanceAmount * (bMax - bMid);
bGoalMin = bMin + enhanceAmount * (bMin - bMid);
}
// Pass 2 - remap everything, except the alpha
for (let i = 0; i < nSubPixels; i += 4) {
data[i + 0] = remap(data[i + 0], rMin, rMax, rGoalMin, rGoalMax);
data[i + 1] = remap(data[i + 1], gMin, gMax, gGoalMin, gGoalMax);
data[i + 2] = remap(data[i + 2], bMin, bMax, bGoalMin, bGoalMax);
//data[i + 3] = remap(data[i + 3], aMin, aMax, aGoalMin, aGoalMax);
}
};
/**
* get/set enhance. Use with {@link Konva.Filters.Enhance} filter. -1 to 1 values
* @name Konva.Node#enhance
* @method
* @param {Float} amount
* @returns {Float}
*/
Factory.addGetterSetter(Node, 'enhance', 0, getNumberValidator(), Factory.afterSetFilter);
+11
View File
@@ -0,0 +1,11 @@
import type { Filter } from '../Node.ts';
/**
* Grayscale Filter
* @function
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Grayscale]);
*/
export declare const Grayscale: Filter;
+21
View File
@@ -0,0 +1,21 @@
/**
* Grayscale Filter
* @function
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Grayscale]);
*/
export const Grayscale = function (imageData) {
const data = imageData.data, len = data.length;
for (let i = 0; i < len; i += 4) {
const brightness = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2];
// red
data[i] = brightness;
// green
data[i + 1] = brightness;
// blue
data[i + 2] = brightness;
}
};
+19
View File
@@ -0,0 +1,19 @@
import type { Filter } from '../Node.ts';
/**
* get/set hsl luminance. Use with {@link Konva.Filters.HSL} filter.
* @name Konva.Node#luminance
* @method
* @param {Number} value from -1 to 1
* @returns {Number}
*/
/**
* HSL Filter. Adjusts the hue, saturation and luminance (or lightness)
* @function
* @memberof Konva.Filters
* @param {Object} imageData
* @author ippo615
* @example
* image.filters([Konva.Filters.HSL]);
* image.luminance(0.2);
*/
export declare const HSL: Filter;
+67
View File
@@ -0,0 +1,67 @@
import { Factory } from "../Factory.js";
import { Node } from "../Node.js";
import { getNumberValidator } from "../Validators.js";
Factory.addGetterSetter(Node, 'hue', 0, getNumberValidator(), Factory.afterSetFilter);
/**
* get/set hsv hue in degrees. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter.
* @name Konva.Node#hue
* @method
* @param {Number} hue value between 0 and 359
* @returns {Number}
*/
Factory.addGetterSetter(Node, 'saturation', 0, getNumberValidator(), Factory.afterSetFilter);
/**
* get/set hsv saturation. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter.
* @name Konva.Node#saturation
* @method
* @param {Number} saturation 0 is no change, -1.0 halves the saturation, 1.0 doubles, etc..
* @returns {Number}
*/
Factory.addGetterSetter(Node, 'luminance', 0, getNumberValidator(), Factory.afterSetFilter);
/**
* get/set hsl luminance. Use with {@link Konva.Filters.HSL} filter.
* @name Konva.Node#luminance
* @method
* @param {Number} value from -1 to 1
* @returns {Number}
*/
/**
* HSL Filter. Adjusts the hue, saturation and luminance (or lightness)
* @function
* @memberof Konva.Filters
* @param {Object} imageData
* @author ippo615
* @example
* image.filters([Konva.Filters.HSL]);
* image.luminance(0.2);
*/
export const HSL = function (imageData) {
const data = imageData.data, nPixels = data.length, v = 1, s = Math.pow(2, this.saturation()), h = ((this.hue() % 360) + 360) % 360, l = this.luminance() * 127;
// Basis for the technique used:
// http://beesbuzz.biz/code/hsv_color_transforms.php
// V is the value multiplier (1 for none, 2 for double, 0.5 for half)
// S is the saturation multiplier (1 for none, 2 for double, 0.5 for half)
// H is the hue shift in degrees (0 to 360)
// vsu = V*S*cos(H*PI/180);
// vsw = V*S*sin(H*PI/180);
//[ .299V+.701vsu+.168vsw .587V-.587vsu+.330vsw .114V-.114vsu-.497vsw ] [R]
//[ .299V-.299vsu-.328vsw .587V+.413vsu+.035vsw .114V-.114vsu+.292vsw ]*[G]
//[ .299V-.300vsu+1.25vsw .587V-.588vsu-1.05vsw .114V+.886vsu-.203vsw ] [B]
// Precompute the values in the matrix:
const vsu = v * s * Math.cos((h * Math.PI) / 180), vsw = v * s * Math.sin((h * Math.PI) / 180);
// (result spot)(source spot)
const rr = 0.299 * v + 0.701 * vsu + 0.168 * vsw, rg = 0.587 * v - 0.587 * vsu + 0.33 * vsw, rb = 0.114 * v - 0.114 * vsu - 0.497 * vsw;
const gr = 0.299 * v - 0.299 * vsu - 0.328 * vsw, gg = 0.587 * v + 0.413 * vsu + 0.035 * vsw, gb = 0.114 * v - 0.114 * vsu + 0.292 * vsw;
const br = 0.299 * v - 0.3 * vsu + 1.25 * vsw, bg = 0.587 * v - 0.588 * vsu - 1.05 * vsw, bb = 0.114 * v + 0.886 * vsu - 0.203 * vsw;
let r, g, b, a;
for (let i = 0; i < nPixels; i += 4) {
r = data[i + 0];
g = data[i + 1];
b = data[i + 2];
a = data[i + 3];
data[i + 0] = rr * r + rg * g + rb * b + l;
data[i + 1] = gr * r + gg * g + gb * b + l;
data[i + 2] = br * r + bg * g + bb * b + l;
data[i + 3] = a; // alpha
}
};
+20
View File
@@ -0,0 +1,20 @@
import type { Filter } from '../Node.ts';
/**
* HSV Filter. Adjusts the hue, saturation and value
* @function
* @name HSV
* @memberof Konva.Filters
* @param {Object} imageData
* @author ippo615
* @example
* image.filters([Konva.Filters.HSV]);
* image.value(1); // twice as bright
*/
export declare const HSV: Filter;
/**
* get/set hsv value. Use with {@link Konva.Filters.HSV} filter.
* @name Konva.Node#value
* @method
* @param {Number} value 0 is no change, -1.0 halves the value, 1.0 doubles, etc..
* @returns {Number}
*/
+67
View File
@@ -0,0 +1,67 @@
import { Factory } from "../Factory.js";
import { Node } from "../Node.js";
import { getNumberValidator } from "../Validators.js";
/**
* HSV Filter. Adjusts the hue, saturation and value
* @function
* @name HSV
* @memberof Konva.Filters
* @param {Object} imageData
* @author ippo615
* @example
* image.filters([Konva.Filters.HSV]);
* image.value(1); // twice as bright
*/
export const HSV = function (imageData) {
const data = imageData.data, nPixels = data.length, v = Math.pow(2, this.value()), s = Math.pow(2, this.saturation()), h = ((this.hue() % 360) + 360) % 360;
// Basis for the technique used:
// http://beesbuzz.biz/code/hsv_color_transforms.php
// V is the value multiplier (1 for none, 2 for double, 0.5 for half)
// S is the saturation multiplier (1 for none, 2 for double, 0.5 for half)
// H is the hue shift in degrees (0 to 360)
// vsu = V*S*cos(H*PI/180);
// vsw = V*S*sin(H*PI/180);
//[ .299V+.701vsu+.168vsw .587V-.587vsu+.330vsw .114V-.114vsu-.497vsw ] [R]
//[ .299V-.299vsu-.328vsw .587V+.413vsu+.035vsw .114V-.114vsu+.292vsw ]*[G]
//[ .299V-.300vsu+1.25vsw .587V-.588vsu-1.05vsw .114V+.886vsu-.203vsw ] [B]
// Precompute the values in the matrix:
const vsu = v * s * Math.cos((h * Math.PI) / 180), vsw = v * s * Math.sin((h * Math.PI) / 180);
// (result spot)(source spot)
const rr = 0.299 * v + 0.701 * vsu + 0.168 * vsw, rg = 0.587 * v - 0.587 * vsu + 0.33 * vsw, rb = 0.114 * v - 0.114 * vsu - 0.497 * vsw;
const gr = 0.299 * v - 0.299 * vsu - 0.328 * vsw, gg = 0.587 * v + 0.413 * vsu + 0.035 * vsw, gb = 0.114 * v - 0.114 * vsu + 0.292 * vsw;
const br = 0.299 * v - 0.3 * vsu + 1.25 * vsw, bg = 0.587 * v - 0.588 * vsu - 1.05 * vsw, bb = 0.114 * v + 0.886 * vsu - 0.203 * vsw;
for (let i = 0; i < nPixels; i += 4) {
const r = data[i + 0];
const g = data[i + 1];
const b = data[i + 2];
const a = data[i + 3];
data[i + 0] = rr * r + rg * g + rb * b;
data[i + 1] = gr * r + gg * g + gb * b;
data[i + 2] = br * r + bg * g + bb * b;
data[i + 3] = a; // alpha
}
};
Factory.addGetterSetter(Node, 'hue', 0, getNumberValidator(), Factory.afterSetFilter);
/**
* get/set hsv hue in degrees. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter.
* @name Konva.Node#hue
* @method
* @param {Number} hue value between 0 and 359
* @returns {Number}
*/
Factory.addGetterSetter(Node, 'saturation', 0, getNumberValidator(), Factory.afterSetFilter);
/**
* get/set hsv saturation. Use with {@link Konva.Filters.HSV} or {@link Konva.Filters.HSL} filter.
* @name Konva.Node#saturation
* @method
* @param {Number} saturation 0 is no change, -1.0 halves the saturation, 1.0 doubles, etc..
* @returns {Number}
*/
Factory.addGetterSetter(Node, 'value', 0, getNumberValidator(), Factory.afterSetFilter);
/**
* get/set hsv value. Use with {@link Konva.Filters.HSV} filter.
* @name Konva.Node#value
* @method
* @param {Number} value 0 is no change, -1.0 halves the value, 1.0 doubles, etc..
* @returns {Number}
*/
+11
View File
@@ -0,0 +1,11 @@
import type { Filter } from '../Node.ts';
/**
* Invert Filter
* @function
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Invert]);
*/
export declare const Invert: Filter;
+20
View File
@@ -0,0 +1,20 @@
/**
* Invert Filter
* @function
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Invert]);
*/
export const Invert = function (imageData) {
const data = imageData.data, len = data.length;
for (let i = 0; i < len; i += 4) {
// red
data[i] = 255 - data[i];
// green
data[i + 1] = 255 - data[i + 1];
// blue
data[i + 2] = 255 - data[i + 2];
}
};
+14
View File
@@ -0,0 +1,14 @@
import type { Filter } from '../Node.ts';
/**
* Kaleidoscope Filter.
* @function
* @name Kaleidoscope
* @author ippo615
* @memberof Konva.Filters
* @example
* node.cache();
* node.filters([Konva.Filters.Kaleidoscope]);
* node.kaleidoscopePower(3);
* node.kaleidoscopeAngle(45);
*/
export declare const Kaleidoscope: Filter;
+209
View File
@@ -0,0 +1,209 @@
import { Factory } from "../Factory.js";
import { Node } from "../Node.js";
import { getNumberValidator } from "../Validators.js";
/*
* ToPolar Filter. Converts image data to polar coordinates. Performs
* w*h*4 pixel reads and w*h pixel writes. The r axis is placed along
* what would be the y axis and the theta axis along the x axis.
* @function
* @author ippo615
* @memberof Konva.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt
* @param {Number} [opt.polarCenterX] horizontal location for the center of the circle,
* default is in the middle
* @param {Number} [opt.polarCenterY] vertical location for the center of the circle,
* default is in the middle
*/
const ToPolar = function (src, dst, opt) {
const srcPixels = src.data, dstPixels = dst.data, xSize = src.width, ySize = src.height, xMid = opt.polarCenterX || xSize / 2, yMid = opt.polarCenterY || ySize / 2;
// Find the largest radius
let rMax = Math.sqrt(xMid * xMid + yMid * yMid);
let x = xSize - xMid;
let y = ySize - yMid;
const rad = Math.sqrt(x * x + y * y);
rMax = rad > rMax ? rad : rMax;
// We'll be using y as the radius, and x as the angle (theta=t)
const rSize = ySize, tSize = xSize;
// We want to cover all angles (0-360) and we need to convert to
// radians (*PI/180)
const conversion = ((360 / tSize) * Math.PI) / 180;
// var x1, x2, x1i, x2i, y1, y2, y1i, y2i, scale;
for (let theta = 0; theta < tSize; theta += 1) {
const sin = Math.sin(theta * conversion);
const cos = Math.cos(theta * conversion);
for (let radius = 0; radius < rSize; radius += 1) {
x = Math.floor(xMid + ((rMax * radius) / rSize) * cos);
y = Math.floor(yMid + ((rMax * radius) / rSize) * sin);
let i = (y * xSize + x) * 4;
const r = srcPixels[i + 0];
const g = srcPixels[i + 1];
const b = srcPixels[i + 2];
const a = srcPixels[i + 3];
// Store it
//i = (theta * xSize + radius) * 4;
i = (theta + radius * xSize) * 4;
dstPixels[i + 0] = r;
dstPixels[i + 1] = g;
dstPixels[i + 2] = b;
dstPixels[i + 3] = a;
}
}
};
/*
* FromPolar Filter. Converts image data from polar coordinates back to rectangular.
* Performs w*h*4 pixel reads and w*h pixel writes.
* @function
* @author ippo615
* @memberof Konva.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt
* @param {Number} [opt.polarCenterX] horizontal location for the center of the circle,
* default is in the middle
* @param {Number} [opt.polarCenterY] vertical location for the center of the circle,
* default is in the middle
* @param {Number} [opt.polarRotation] amount to rotate the image counterclockwis,
* 0 is no rotation, 360 degrees is a full rotation
*/
const FromPolar = function (src, dst, opt) {
const srcPixels = src.data, dstPixels = dst.data, xSize = src.width, ySize = src.height, xMid = opt.polarCenterX || xSize / 2, yMid = opt.polarCenterY || ySize / 2;
// Find the largest radius
let rMax = Math.sqrt(xMid * xMid + yMid * yMid);
let x = xSize - xMid;
let y = ySize - yMid;
const rad = Math.sqrt(x * x + y * y);
rMax = rad > rMax ? rad : rMax;
// We'll be uisng x as the radius, and y as the angle (theta=t)
const rSize = ySize, tSize = xSize, phaseShift = opt.polarRotation || 0;
// We need to convert to degrees and we need to make sure
// it's between (0-360)
// var conversion = tSize/360*180/Math.PI;
//var conversion = tSize/360*180/Math.PI;
let x1, y1;
for (x = 0; x < xSize; x += 1) {
for (y = 0; y < ySize; y += 1) {
const dx = x - xMid;
const dy = y - yMid;
const radius = (Math.sqrt(dx * dx + dy * dy) * rSize) / rMax;
let theta = ((Math.atan2(dy, dx) * 180) / Math.PI + 360 + phaseShift) % 360;
theta = (theta * tSize) / 360;
x1 = Math.floor(theta);
y1 = Math.floor(radius);
let i = (y1 * xSize + x1) * 4;
const r = srcPixels[i + 0];
const g = srcPixels[i + 1];
const b = srcPixels[i + 2];
const a = srcPixels[i + 3];
// Store it
i = (y * xSize + x) * 4;
dstPixels[i + 0] = r;
dstPixels[i + 1] = g;
dstPixels[i + 2] = b;
dstPixels[i + 3] = a;
}
}
};
/**
* Kaleidoscope Filter.
* @function
* @name Kaleidoscope
* @author ippo615
* @memberof Konva.Filters
* @example
* node.cache();
* node.filters([Konva.Filters.Kaleidoscope]);
* node.kaleidoscopePower(3);
* node.kaleidoscopeAngle(45);
*/
export const Kaleidoscope = function (imageData) {
const xSize = imageData.width, ySize = imageData.height;
let x, y, xoff, i, r, g, b, a, srcPos, dstPos;
let power = Math.round(this.kaleidoscopePower());
const angle = Math.round(this.kaleidoscopeAngle());
const offset = Math.floor((xSize * (((angle % 360) + 360) % 360)) / 360);
if (power < 1) {
return;
}
const scratchData = {
width: xSize,
height: ySize,
data: new Uint8ClampedArray(xSize * ySize * 4),
};
// Convert the original to polar coordinates.
ToPolar(imageData, scratchData, {
polarCenterX: xSize / 2,
polarCenterY: ySize / 2,
});
// Determine how big each section will be, if it's too small
// make it bigger
let minSectionSize = xSize / Math.pow(2, power);
while (minSectionSize <= 8) {
minSectionSize = minSectionSize * 2;
power -= 1;
}
minSectionSize = Math.ceil(minSectionSize);
let sectionSize = minSectionSize;
// Copy the offset region to 0
// Depending on the size of filter and location of the offset we may need
// to copy the section backwards to prevent it from rewriting itself
let xStart = 0, xEnd = sectionSize, xDelta = 1;
if (offset + minSectionSize > xSize) {
xStart = sectionSize;
xEnd = 0;
xDelta = -1;
}
for (y = 0; y < ySize; y += 1) {
for (x = xStart; x !== xEnd; x += xDelta) {
xoff = Math.round(x + offset) % xSize;
srcPos = (xSize * y + xoff) * 4;
r = scratchData.data[srcPos + 0];
g = scratchData.data[srcPos + 1];
b = scratchData.data[srcPos + 2];
a = scratchData.data[srcPos + 3];
dstPos = (xSize * y + x) * 4;
scratchData.data[dstPos + 0] = r;
scratchData.data[dstPos + 1] = g;
scratchData.data[dstPos + 2] = b;
scratchData.data[dstPos + 3] = a;
}
}
// Perform the actual effect
for (y = 0; y < ySize; y += 1) {
sectionSize = Math.floor(minSectionSize);
for (i = 0; i < power; i += 1) {
for (x = 0; x < sectionSize + 1; x += 1) {
srcPos = (xSize * y + x) * 4;
r = scratchData.data[srcPos + 0];
g = scratchData.data[srcPos + 1];
b = scratchData.data[srcPos + 2];
a = scratchData.data[srcPos + 3];
dstPos = (xSize * y + sectionSize * 2 - x - 1) * 4;
scratchData.data[dstPos + 0] = r;
scratchData.data[dstPos + 1] = g;
scratchData.data[dstPos + 2] = b;
scratchData.data[dstPos + 3] = a;
}
sectionSize *= 2;
}
}
// Convert back from polar coordinates
FromPolar(scratchData, imageData, { polarRotation: 0 });
};
/**
* get/set kaleidoscope power. Use with {@link Konva.Filters.Kaleidoscope} filter.
* @name Konva.Node#kaleidoscopePower
* @method
* @param {Integer} power of kaleidoscope
* @returns {Integer}
*/
Factory.addGetterSetter(Node, 'kaleidoscopePower', 2, getNumberValidator(), Factory.afterSetFilter);
/**
* get/set kaleidoscope angle. Use with {@link Konva.Filters.Kaleidoscope} filter.
* @name Konva.Node#kaleidoscopeAngle
* @method
* @param {Integer} degrees
* @returns {Integer}
*/
Factory.addGetterSetter(Node, 'kaleidoscopeAngle', 0, getNumberValidator(), Factory.afterSetFilter);
+13
View File
@@ -0,0 +1,13 @@
import type { Filter } from '../Node.ts';
/**
* Mask Filter
* @function
* @name Mask
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Mask]);
* node.threshold(200);
*/
export declare const Mask: Filter;
+169
View File
@@ -0,0 +1,169 @@
import { Factory } from "../Factory.js";
import { Node } from "../Node.js";
import { getNumberValidator } from "../Validators.js";
function pixelAt(idata, x, y) {
let idx = (y * idata.width + x) * 4;
const d = [];
d.push(idata.data[idx++], idata.data[idx++], idata.data[idx++], idata.data[idx++]);
return d;
}
function rgbDistance(p1, p2) {
return Math.sqrt(Math.pow(p1[0] - p2[0], 2) +
Math.pow(p1[1] - p2[1], 2) +
Math.pow(p1[2] - p2[2], 2));
}
function rgbMean(pTab) {
const m = [0, 0, 0];
for (let i = 0; i < pTab.length; i++) {
m[0] += pTab[i][0];
m[1] += pTab[i][1];
m[2] += pTab[i][2];
}
m[0] /= pTab.length;
m[1] /= pTab.length;
m[2] /= pTab.length;
return m;
}
function backgroundMask(idata, threshold) {
const rgbv_no = pixelAt(idata, 0, 0);
const rgbv_ne = pixelAt(idata, idata.width - 1, 0);
const rgbv_so = pixelAt(idata, 0, idata.height - 1);
const rgbv_se = pixelAt(idata, idata.width - 1, idata.height - 1);
if (rgbDistance(rgbv_no, rgbv_ne) < threshold &&
rgbDistance(rgbv_ne, rgbv_se) < threshold &&
rgbDistance(rgbv_se, rgbv_so) < threshold &&
rgbDistance(rgbv_so, rgbv_no) < threshold) {
// Mean color
const mean = rgbMean([rgbv_ne, rgbv_no, rgbv_se, rgbv_so]);
// Mask based on color distance
const mask = [];
for (let i = 0; i < idata.width * idata.height; i++) {
const d = rgbDistance(mean, [
idata.data[i * 4],
idata.data[i * 4 + 1],
idata.data[i * 4 + 2],
]);
mask[i] = d < threshold ? 0 : 255;
}
return mask;
}
}
function applyMask(idata, mask) {
for (let i = 0; i < idata.width * idata.height; i++) {
idata.data[4 * i + 3] = mask[i];
}
}
function erodeMask(mask, sw, sh) {
const weights = [1, 1, 1, 1, 0, 1, 1, 1, 1];
const side = Math.round(Math.sqrt(weights.length));
const halfSide = Math.floor(side / 2);
const maskResult = [];
for (let y = 0; y < sh; y++) {
for (let x = 0; x < sw; x++) {
const so = y * sw + x;
let a = 0;
for (let cy = 0; cy < side; cy++) {
for (let cx = 0; cx < side; cx++) {
const scy = y + cy - halfSide;
const scx = x + cx - halfSide;
if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
const srcOff = scy * sw + scx;
const wt = weights[cy * side + cx];
a += mask[srcOff] * wt;
}
}
}
maskResult[so] = a === 255 * 8 ? 255 : 0;
}
}
return maskResult;
}
function dilateMask(mask, sw, sh) {
const weights = [1, 1, 1, 1, 1, 1, 1, 1, 1];
const side = Math.round(Math.sqrt(weights.length));
const halfSide = Math.floor(side / 2);
const maskResult = [];
for (let y = 0; y < sh; y++) {
for (let x = 0; x < sw; x++) {
const so = y * sw + x;
let a = 0;
for (let cy = 0; cy < side; cy++) {
for (let cx = 0; cx < side; cx++) {
const scy = y + cy - halfSide;
const scx = x + cx - halfSide;
if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
const srcOff = scy * sw + scx;
const wt = weights[cy * side + cx];
a += mask[srcOff] * wt;
}
}
}
maskResult[so] = a >= 255 * 4 ? 255 : 0;
}
}
return maskResult;
}
function smoothEdgeMask(mask, sw, sh) {
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 halfSide = Math.floor(side / 2);
const maskResult = [];
for (let y = 0; y < sh; y++) {
for (let x = 0; x < sw; x++) {
const so = y * sw + x;
let a = 0;
for (let cy = 0; cy < side; cy++) {
for (let cx = 0; cx < side; cx++) {
const scy = y + cy - halfSide;
const scx = x + cx - halfSide;
if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
const srcOff = scy * sw + scx;
const wt = weights[cy * side + cx];
a += mask[srcOff] * wt;
}
}
}
maskResult[so] = a;
}
}
return maskResult;
}
/**
* Mask Filter
* @function
* @name Mask
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Mask]);
* node.threshold(200);
*/
export const Mask = function (imageData) {
var _a;
// Detect pixels close to the background color
const threshold = (_a = this.attrs.threshold) !== null && _a !== void 0 ? _a : 10;
let mask = backgroundMask(imageData, threshold);
if (mask) {
// Erode
mask = erodeMask(mask, imageData.width, imageData.height);
// Dilate
mask = dilateMask(mask, imageData.width, imageData.height);
// Gradient
mask = smoothEdgeMask(mask, imageData.width, imageData.height);
// Apply mask
applyMask(imageData, mask);
}
return imageData;
};
Factory.addGetterSetter(Node, 'threshold', 0.5, getNumberValidator(), Factory.afterSetFilter);
+21
View File
@@ -0,0 +1,21 @@
import type { Filter } from '../Node.ts';
/**
* Noise Filter. Randomly adds or substracts to the color channels
* @function
* @name Noise
* @memberof Konva.Filters
* @param {Object} imageData
* @author ippo615
* @example
* node.cache();
* node.filters([Konva.Filters.Noise]);
* node.noise(0.8);
*/
export declare const Noise: Filter;
/**
* get/set noise amount. Must be a value between 0 and 1. Use with {@link Konva.Filters.Noise} filter.
* @name Konva.Node#noise
* @method
* @param {Number} noise
* @returns {Number}
*/
+31
View File
@@ -0,0 +1,31 @@
import { Factory } from "../Factory.js";
import { Node } from "../Node.js";
import { getNumberValidator } from "../Validators.js";
/**
* Noise Filter. Randomly adds or substracts to the color channels
* @function
* @name Noise
* @memberof Konva.Filters
* @param {Object} imageData
* @author ippo615
* @example
* node.cache();
* node.filters([Konva.Filters.Noise]);
* node.noise(0.8);
*/
export const Noise = function (imageData) {
const amount = this.noise() * 255, data = imageData.data, nPixels = data.length, half = amount / 2;
for (let i = 0; i < nPixels; i += 4) {
data[i + 0] += half - 2 * half * Math.random();
data[i + 1] += half - 2 * half * Math.random();
data[i + 2] += half - 2 * half * Math.random();
}
};
Factory.addGetterSetter(Node, 'noise', 0.2, getNumberValidator(), Factory.afterSetFilter);
/**
* get/set noise amount. Must be a value between 0 and 1. Use with {@link Konva.Filters.Noise} filter.
* @name Konva.Node#noise
* @method
* @param {Number} noise
* @returns {Number}
*/
+22
View File
@@ -0,0 +1,22 @@
import type { Filter } from '../Node.ts';
/**
* Pixelate Filter. Averages groups of pixels and redraws
* them as larger pixels
* @function
* @name Pixelate
* @memberof Konva.Filters
* @param {Object} imageData
* @author ippo615
* @example
* node.cache();
* node.filters([Konva.Filters.Pixelate]);
* node.pixelSize(10);
*/
export declare const Pixelate: Filter;
/**
* get/set pixel size. Use with {@link Konva.Filters.Pixelate} filter.
* @name Konva.Node#pixelSize
* @method
* @param {Integer} pixelSize
* @returns {Integer}
*/
+74
View File
@@ -0,0 +1,74 @@
import { Factory } from "../Factory.js";
import { Util } from "../Util.js";
import { Node } from "../Node.js";
import { getNumberValidator } from "../Validators.js";
/**
* Pixelate Filter. Averages groups of pixels and redraws
* them as larger pixels
* @function
* @name Pixelate
* @memberof Konva.Filters
* @param {Object} imageData
* @author ippo615
* @example
* node.cache();
* node.filters([Konva.Filters.Pixelate]);
* node.pixelSize(10);
*/
export const Pixelate = function (imageData, pixelRatio = 1) {
let pixelSize = Math.ceil(this.pixelSize() * pixelRatio), width = imageData.width, height = imageData.height, nBinsX = Math.ceil(width / pixelSize), nBinsY = Math.ceil(height / pixelSize), data = imageData.data;
if (pixelSize <= 0) {
Util.error('pixelSize value can not be <= 0');
return;
}
for (let xBin = 0; xBin < nBinsX; xBin += 1) {
for (let yBin = 0; yBin < nBinsY; yBin += 1) {
// Initialize the color accumlators to 0
let red = 0;
let green = 0;
let blue = 0;
let alpha = 0;
// Determine which pixels are included in this bin
const xBinStart = xBin * pixelSize;
const xBinEnd = Math.min(xBinStart + pixelSize, width);
const yBinStart = yBin * pixelSize;
const yBinEnd = Math.min(yBinStart + pixelSize, height);
// Add all of the pixels to this bin!
for (let x = xBinStart; x < xBinEnd; x += 1) {
for (let y = yBinStart; y < yBinEnd; y += 1) {
const i = (width * y + x) * 4;
const a = data[i + 3];
red += data[i + 0] * a;
green += data[i + 1] * a;
blue += data[i + 2] * a;
alpha += a;
}
}
// average premultiplied colour so transparent pixels do not darken the block
if (alpha) {
red = red / alpha;
green = green / alpha;
blue = blue / alpha;
}
alpha = alpha / ((xBinEnd - xBinStart) * (yBinEnd - yBinStart));
// Draw this bin
for (let x = xBinStart; x < xBinEnd; x += 1) {
for (let y = yBinStart; y < yBinEnd; y += 1) {
const i = (width * y + x) * 4;
data[i + 0] = red;
data[i + 1] = green;
data[i + 2] = blue;
data[i + 3] = alpha;
}
}
}
}
};
Factory.addGetterSetter(Node, 'pixelSize', 8, getNumberValidator(), Factory.afterSetFilter);
/**
* get/set pixel size. Use with {@link Konva.Filters.Pixelate} filter.
* @name Konva.Node#pixelSize
* @method
* @param {Integer} pixelSize
* @returns {Integer}
*/
+23
View File
@@ -0,0 +1,23 @@
import type { Filter } from '../Node.ts';
/**
* Posterize Filter. Adjusts the channels so that there are no more
* than n different values for that channel. This is also applied
* to the alpha channel.
* @function
* @name Posterize
* @author ippo615
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Posterize]);
* node.levels(0.8); // between 0 and 1
*/
export declare const Posterize: Filter;
/**
* get/set levels. Must be a number between 0 and 1. Use with {@link Konva.Filters.Posterize} filter.
* @name Konva.Node#levels
* @method
* @param {Number} level between 0 and 1
* @returns {Number}
*/
+32
View File
@@ -0,0 +1,32 @@
import { Factory } from "../Factory.js";
import { Node } from "../Node.js";
import { getNumberValidator } from "../Validators.js";
/**
* Posterize Filter. Adjusts the channels so that there are no more
* than n different values for that channel. This is also applied
* to the alpha channel.
* @function
* @name Posterize
* @author ippo615
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Posterize]);
* node.levels(0.8); // between 0 and 1
*/
export const Posterize = function (imageData) {
// level must be between 1 and 255
const levels = Math.round(this.levels() * 254) + 1, data = imageData.data, len = data.length, scale = 255 / levels;
for (let i = 0; i < len; i += 1) {
data[i] = Math.floor(data[i] / scale) * scale;
}
};
Factory.addGetterSetter(Node, 'levels', 0.5, getNumberValidator(), Factory.afterSetFilter);
/**
* get/set levels. Must be a number between 0 and 1. Use with {@link Konva.Filters.Posterize} filter.
* @name Konva.Node#levels
* @method
* @param {Number} level between 0 and 1
* @returns {Number}
*/
+23
View File
@@ -0,0 +1,23 @@
import type { Filter } from '../Node.ts';
/**
* RGB Filter
* @function
* @name RGB
* @memberof Konva.Filters
* @param {Object} imageData
* @author ippo615
* @example
* node.cache();
* node.filters([Konva.Filters.RGB]);
* node.blue(120);
* node.green(200);
*/
export declare const RGB: Filter;
/**
* get/set filter blue value. Use with {@link Konva.Filters.RGB} or {@link Konva.Filters.RGBA} filter.
* @name blue
* @method
* @memberof Konva.Node.prototype
* @param {Integer} blue value between 0 and 255
* @returns {Integer}
*/
+53
View File
@@ -0,0 +1,53 @@
import { Factory } from "../Factory.js";
import { Node } from "../Node.js";
import { RGBComponent } from "../Validators.js";
/**
* RGB Filter
* @function
* @name RGB
* @memberof Konva.Filters
* @param {Object} imageData
* @author ippo615
* @example
* node.cache();
* node.filters([Konva.Filters.RGB]);
* node.blue(120);
* node.green(200);
*/
export const RGB = function (imageData) {
const data = imageData.data, nPixels = data.length, red = this.red(), green = this.green(), blue = this.blue();
for (let i = 0; i < nPixels; i += 4) {
const brightness = (0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2]) / 255;
data[i] = brightness * red; // r
data[i + 1] = brightness * green; // g
data[i + 2] = brightness * blue; // b
data[i + 3] = data[i + 3]; // alpha
}
};
Factory.addGetterSetter(Node, 'red', 0, RGBComponent, Factory.afterSetFilter);
/**
* get/set filter red value. Use with {@link Konva.Filters.RGB} or {@link Konva.Filters.RGBA} filter.
* @name red
* @method
* @memberof Konva.Node.prototype
* @param {Integer} red value between 0 and 255
* @returns {Integer}
*/
Factory.addGetterSetter(Node, 'green', 0, RGBComponent, Factory.afterSetFilter);
/**
* get/set filter green value. Use with {@link Konva.Filters.RGB} or {@link Konva.Filters.RGBA} filter.
* @name green
* @method
* @memberof Konva.Node.prototype
* @param {Integer} green value between 0 and 255
* @returns {Integer}
*/
Factory.addGetterSetter(Node, 'blue', 0, RGBComponent, Factory.afterSetFilter);
/**
* get/set filter blue value. Use with {@link Konva.Filters.RGB} or {@link Konva.Filters.RGBA} filter.
* @name blue
* @method
* @memberof Konva.Node.prototype
* @param {Integer} blue value between 0 and 255
* @returns {Integer}
*/
+25
View File
@@ -0,0 +1,25 @@
import type { Filter } from '../Node.ts';
import './RGB.ts';
/**
* RGBA Filter
* @function
* @name RGBA
* @memberof Konva.Filters
* @param {Object} imageData
* @author codefo
* @example
* node.cache();
* node.filters([Konva.Filters.RGBA]);
* node.blue(120);
* node.green(200);
* node.alpha(0.3);
*/
export declare const RGBA: Filter;
/**
* get/set filter alpha value. Use with {@link Konva.Filters.RGBA} filter.
* @name alpha
* @method
* @memberof Konva.Node.prototype
* @param {Float} alpha value between 0 and 1
* @returns {Float}
*/
+36
View File
@@ -0,0 +1,36 @@
import { Factory } from "../Factory.js";
import { Node } from "../Node.js";
// red, green and blue are registered there
import "./RGB.js";
/**
* RGBA Filter
* @function
* @name RGBA
* @memberof Konva.Filters
* @param {Object} imageData
* @author codefo
* @example
* node.cache();
* node.filters([Konva.Filters.RGBA]);
* node.blue(120);
* node.green(200);
* node.alpha(0.3);
*/
export const RGBA = function (imageData) {
const data = imageData.data, nPixels = data.length, red = this.red(), green = this.green(), blue = this.blue(), alpha = this.alpha();
for (let i = 0; i < nPixels; i += 4) {
const ia = 1 - alpha;
data[i] = red * alpha + data[i] * ia; // r
data[i + 1] = green * alpha + data[i + 1] * ia; // g
data[i + 2] = blue * alpha + data[i + 2] * ia; // b
}
};
Factory.addGetterSetter(Node, 'alpha', 1, (val) => Math.min(1, Math.max(0, val)), Factory.afterSetFilter);
/**
* get/set filter alpha value. Use with {@link Konva.Filters.RGBA} filter.
* @name alpha
* @method
* @memberof Konva.Node.prototype
* @param {Float} alpha value between 0 and 1
* @returns {Float}
*/
+11
View File
@@ -0,0 +1,11 @@
import type { Filter } from '../Node.ts';
/**
* @function
* @name Sepia
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Sepia]);
*/
export declare const Sepia: Filter;
+21
View File
@@ -0,0 +1,21 @@
// based on https://stackoverflow.com/questions/1061093/how-is-a-sepia-tone-created
/**
* @function
* @name Sepia
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Sepia]);
*/
export const Sepia = function (imageData) {
const data = imageData.data, nPixels = data.length;
for (let i = 0; i < nPixels; i += 4) {
const r = data[i + 0];
const g = data[i + 1];
const b = data[i + 2];
data[i + 0] = Math.min(255, r * 0.393 + g * 0.769 + b * 0.189);
data[i + 1] = Math.min(255, r * 0.349 + g * 0.686 + b * 0.168);
data[i + 2] = Math.min(255, r * 0.272 + g * 0.534 + b * 0.131);
}
};
+12
View File
@@ -0,0 +1,12 @@
import type { Filter } from '../Node.ts';
/**
* Solarize Filter
* @function
* @name Solarize
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Solarize]);
*/
export declare const Solarize: Filter;
+25
View File
@@ -0,0 +1,25 @@
/**
* Solarize Filter
* @function
* @name Solarize
* @memberof Konva.Filters
* @param {Object} imageData
* @example
* node.cache();
* node.filters([Konva.Filters.Solarize]);
*/
export const Solarize = function (imageData) {
const threshold = 128;
const d = imageData.data;
for (let i = 0; i < d.length; i += 4) {
const r = d[i], g = d[i + 1], b = d[i + 2];
// sRGB luma
const L = 0.2126 * r + 0.7152 * g + 0.0722 * b;
if (L >= threshold) {
d[i] = 255 - r;
d[i + 1] = 255 - g;
d[i + 2] = 255 - b;
}
}
return imageData;
};
+24
View File
@@ -0,0 +1,24 @@
import type { Filter } from '../Node.ts';
/**
* Threshold Filter. Pushes any value above the mid point to
* the max and any value below the mid point to the min.
* This affects the alpha channel.
* @function
* @name Threshold
* @memberof Konva.Filters
* @param {Object} imageData
* @author ippo615
* @example
* node.cache();
* node.filters([Konva.Filters.Threshold]);
* node.threshold(0.1);
*/
export declare const Threshold: Filter;
/**
* get/set threshold. With {@link Konva.Filters.Threshold} it is a value between 0 and 1, default 0.5. With {@link Konva.Filters.Mask} it is the colour distance to the background, 0 to 255, default 10.
* @name threshold
* @method
* @memberof Konva.Node.prototype
* @param {Number} threshold
* @returns {Number}
*/
+32
View File
@@ -0,0 +1,32 @@
import { Factory } from "../Factory.js";
import { Node } from "../Node.js";
import { getNumberValidator } from "../Validators.js";
/**
* Threshold Filter. Pushes any value above the mid point to
* the max and any value below the mid point to the min.
* This affects the alpha channel.
* @function
* @name Threshold
* @memberof Konva.Filters
* @param {Object} imageData
* @author ippo615
* @example
* node.cache();
* node.filters([Konva.Filters.Threshold]);
* node.threshold(0.1);
*/
export const Threshold = function (imageData) {
const level = this.threshold() * 255, data = imageData.data, len = data.length;
for (let i = 0; i < len; i += 1) {
data[i] = data[i] < level ? 0 : 255;
}
};
Factory.addGetterSetter(Node, 'threshold', 0.5, getNumberValidator(), Factory.afterSetFilter);
/**
* get/set threshold. With {@link Konva.Filters.Threshold} it is a value between 0 and 1, default 0.5. With {@link Konva.Filters.Mask} it is the colour distance to the background, 0 to 255, default 10.
* @name threshold
* @method
* @memberof Konva.Node.prototype
* @param {Number} threshold
* @returns {Number}
*/
+2
View File
@@ -0,0 +1,2 @@
import { Konva } from './_FullInternals.ts';
export default Konva;
+2
View File
@@ -0,0 +1,2 @@
import { Konva } from "./_FullInternals.js";
export default Konva;
+67
View File
@@ -0,0 +1,67 @@
import type { ShapeConfig } from '../Shape.ts';
import { Shape } from '../Shape.ts';
import type { GetSet } from '../types.ts';
import type { Context } from '../Context.ts';
export interface ArcConfig extends ShapeConfig {
angle: number;
innerRadius: number;
outerRadius: number;
clockwise?: boolean;
}
/**
* Arc constructor
* @constructor
* @memberof Konva
* @augments Konva.Shape
* @param {Object} config
* @param {Number} config.angle in degrees
* @param {Number} config.innerRadius
* @param {Number} config.outerRadius
* @param {Boolean} [config.clockwise]
* @@shapeParams
* @@nodeParams
* @example
* // draw a Arc that's pointing downwards
* var arc = new Konva.Arc({
* innerRadius: 40,
* outerRadius: 80,
* fill: 'red',
* stroke: 'black',
* strokeWidth: 5,
* angle: 60,
* rotation: -120
* });
*/
export declare class Arc extends Shape<ArcConfig> {
_sceneFunc(context: Context): void;
getWidth(): number;
getHeight(): number;
setWidth(width: number): void;
setHeight(height: number): void;
getSelfRect(): {
x: number;
y: number;
width: number;
height: number;
};
innerRadius: GetSet<number, this>;
outerRadius: GetSet<number, this>;
angle: GetSet<number, this>;
clockwise: GetSet<boolean, this>;
}
/**
* get/set clockwise flag
* @name Konva.Arc#clockwise
* @method
* @param {Boolean} clockwise
* @returns {Boolean}
* @example
* // get clockwise flag
* var clockwise = arc.clockwise();
*
* // draw arc counter-clockwise
* arc.clockwise(false);
*
* // draw arc clockwise
* arc.clockwise(true);
*/
+156
View File
@@ -0,0 +1,156 @@
import { Factory } from "../Factory.js";
import { Shape } from "../Shape.js";
import { Konva } from "../Global.js";
import { getNumberValidator, getBooleanValidator } from "../Validators.js";
import { _registerNode } from "../Global.js";
/**
* Arc constructor
* @constructor
* @memberof Konva
* @augments Konva.Shape
* @param {Object} config
* @param {Number} config.angle in degrees
* @param {Number} config.innerRadius
* @param {Number} config.outerRadius
* @param {Boolean} [config.clockwise]
* @@shapeParams
* @@nodeParams
* @example
* // draw a Arc that's pointing downwards
* var arc = new Konva.Arc({
* innerRadius: 40,
* outerRadius: 80,
* fill: 'red',
* stroke: 'black',
* strokeWidth: 5,
* angle: 60,
* rotation: -120
* });
*/
export class Arc extends Shape {
_sceneFunc(context) {
const angle = Konva.getAngle(this.angle()), clockwise = this.clockwise();
context.beginPath();
context.arc(0, 0, Math.abs(this.outerRadius()), 0, angle, clockwise);
context.arc(0, 0, Math.abs(this.innerRadius()), angle, 0, !clockwise);
context.closePath();
context.fillStrokeShape(this);
}
getWidth() {
return Math.abs(this.outerRadius()) * 2;
}
getHeight() {
return Math.abs(this.outerRadius()) * 2;
}
setWidth(width) {
this.outerRadius(width / 2);
}
setHeight(height) {
this.outerRadius(height / 2);
}
getSelfRect() {
const r1 = Math.abs(this.innerRadius()), r2 = Math.abs(this.outerRadius());
const innerRadius = Math.min(r1, r2), outerRadius = Math.max(r1, r2);
const clockwise = this.clockwise();
// canvas draws a full circle for any non-zero full-turn end angle regardless
// of direction, and nothing for angle 0. Keep exact turns distinct from
// the modulo-normalized partial sweep below.
const rawAngle = Konva.getAngle(this.angle());
if (rawAngle % (Math.PI * 2) === 0) {
return rawAngle !== 0
? {
x: -outerRadius,
y: -outerRadius,
width: outerRadius * 2,
height: outerRadius * 2,
}
: { x: innerRadius, y: 0, width: outerRadius - innerRadius, height: 0 };
}
const turn = Math.PI * 2;
const sweep = clockwise ? -rawAngle : rawAngle;
const angle = sweep >= turn ? turn : ((sweep % turn) + turn) % turn;
const boundLeftRatio = Math.cos(Math.min(angle, Math.PI));
const boundRightRatio = 1;
const boundTopRatio = Math.sin(Math.min(Math.max(Math.PI, angle), (3 * Math.PI) / 2));
const boundBottomRatio = Math.sin(Math.min(angle, Math.PI / 2));
const boundLeft = boundLeftRatio * (boundLeftRatio > 0 ? innerRadius : outerRadius);
const boundRight = boundRightRatio * (boundRightRatio > 0 ? outerRadius : innerRadius);
const boundTop = boundTopRatio * (boundTopRatio > 0 ? innerRadius : outerRadius);
const boundBottom = boundBottomRatio * (boundBottomRatio > 0 ? outerRadius : innerRadius);
return {
x: boundLeft,
y: clockwise ? -1 * boundBottom : boundTop,
width: boundRight - boundLeft,
height: boundBottom - boundTop,
};
}
}
Arc.prototype._centroid = true;
Arc.prototype.className = 'Arc';
Arc.prototype._attrsAffectingSize = [
'innerRadius',
'outerRadius',
'angle',
'clockwise',
];
_registerNode(Arc, true);
// add getters setters
Factory.addGetterSetter(Arc, 'innerRadius', 0, getNumberValidator());
/**
* get/set innerRadius
* @name Konva.Arc#innerRadius
* @method
* @param {Number} innerRadius
* @returns {Number}
* @example
* // get inner radius
* var innerRadius = arc.innerRadius();
*
* // set inner radius
* arc.innerRadius(20);
*/
Factory.addGetterSetter(Arc, 'outerRadius', 0, getNumberValidator());
/**
* get/set outerRadius
* @name Konva.Arc#outerRadius
* @method
* @param {Number} outerRadius
* @returns {Number}
* @example
* // get outer radius
* var outerRadius = arc.outerRadius();
*
* // set outer radius
* arc.outerRadius(20);
*/
Factory.addGetterSetter(Arc, 'angle', 0, getNumberValidator());
/**
* get/set angle in degrees
* @name Konva.Arc#angle
* @method
* @param {Number} angle
* @returns {Number}
* @example
* // get angle
* var angle = arc.angle();
*
* // set angle
* arc.angle(20);
*/
Factory.addGetterSetter(Arc, 'clockwise', false, getBooleanValidator());
/**
* get/set clockwise flag
* @name Konva.Arc#clockwise
* @method
* @param {Boolean} clockwise
* @returns {Boolean}
* @example
* // get clockwise flag
* var clockwise = arc.clockwise();
*
* // draw arc counter-clockwise
* arc.clockwise(false);
*
* // draw arc clockwise
* arc.clockwise(true);
*/
+47
View File
@@ -0,0 +1,47 @@
import type { LineConfig } from './Line.ts';
import { Line } from './Line.ts';
import type { GetSet } from '../types.ts';
import type { Context } from '../Context.ts';
export interface ArrowConfig extends LineConfig {
tension?: number;
closed?: boolean;
pointerLength?: number;
pointerWidth?: number;
pointerAtBeginning?: boolean;
pointerAtEnding?: boolean;
}
/**
* Arrow constructor
* @constructor
* @memberof Konva
* @augments Konva.Line
* @param {Object} config
* @param {Array} config.points Flat array of points coordinates. You should define them as [x1, y1, x2, y2, x3, y3].
* @param {Number} [config.tension] Higher values will result in a more curvy line. A value of 0 will result in no interpolation.
* The default is 0
* @param {Number} config.pointerLength Arrow pointer length. Default value is 10.
* @param {Number} config.pointerWidth Arrow pointer width. Default value is 10.
* @param {Boolean} config.pointerAtBeginning Do we need to draw pointer on beginning position?. Default false.
* @param {Boolean} config.pointerAtEnding Do we need to draw pointer on ending position?. Default true.
* @@shapeParams
* @@nodeParams
* @example
* var line = new Konva.Line({
* points: [73, 70, 340, 23, 450, 60, 500, 20],
* stroke: 'red',
* tension: 1,
* pointerLength : 10,
* pointerWidth : 12
* });
*/
export declare class Arrow extends Line<ArrowConfig> {
_getStrokePadding(): number;
_sceneFunc(ctx: Context): void;
_getPointerAngle(atBeginning: boolean): number;
__fillStroke(ctx: Context): void;
getSelfRect(): import("../types.ts").IRect;
pointerLength: GetSet<number, this>;
pointerWidth: GetSet<number, this>;
pointerAtEnding: GetSet<boolean, this>;
pointerAtBeginning: GetSet<boolean, this>;
}
+210
View File
@@ -0,0 +1,210 @@
import { Factory } from "../Factory.js";
import { Line } from "./Line.js";
import { getNumberValidator } from "../Validators.js";
import { _registerNode } from "../Global.js";
import { Path } from "./Path.js";
/**
* Arrow constructor
* @constructor
* @memberof Konva
* @augments Konva.Line
* @param {Object} config
* @param {Array} config.points Flat array of points coordinates. You should define them as [x1, y1, x2, y2, x3, y3].
* @param {Number} [config.tension] Higher values will result in a more curvy line. A value of 0 will result in no interpolation.
* The default is 0
* @param {Number} config.pointerLength Arrow pointer length. Default value is 10.
* @param {Number} config.pointerWidth Arrow pointer width. Default value is 10.
* @param {Boolean} config.pointerAtBeginning Do we need to draw pointer on beginning position?. Default false.
* @param {Boolean} config.pointerAtEnding Do we need to draw pointer on ending position?. Default true.
* @@shapeParams
* @@nodeParams
* @example
* var line = new Konva.Line({
* points: [73, 70, 340, 23, 450, 60, 500, 20],
* stroke: 'red',
* tension: 1,
* pointerLength : 10,
* pointerWidth : 12
* });
*/
export class Arrow extends Line {
_getStrokePadding() {
// Arrowheads have joins even when the shaft is a single straight line.
return super._getStrokePadding(this.pointerAtBeginning() || this.pointerAtEnding()
? this.miterLimit() || 10
: undefined);
}
_sceneFunc(ctx) {
super._sceneFunc(ctx);
const points = this.points();
const n = points.length;
if (n < 4)
return;
const length = this.pointerLength();
const width = this.pointerWidth();
if (this.pointerAtEnding()) {
ctx.save();
ctx.beginPath();
ctx.translate(points[n - 2], points[n - 1]);
ctx.rotate(this._getPointerAngle(false));
ctx.moveTo(0, 0);
ctx.lineTo(-length, width / 2);
ctx.lineTo(-length, -width / 2);
ctx.closePath();
ctx.restore();
this.__fillStroke(ctx);
}
if (this.pointerAtBeginning()) {
ctx.save();
ctx.beginPath();
ctx.translate(points[0], points[1]);
ctx.rotate(this._getPointerAngle(true));
ctx.moveTo(0, 0);
ctx.lineTo(-length, width / 2);
ctx.lineTo(-length, -width / 2);
ctx.closePath();
ctx.restore();
this.__fillStroke(ctx);
}
}
_getPointerAngle(atBeginning) {
const points = this.points();
const n = points.length;
const fromTension = this.tension() !== 0 && n > 4;
const tp = fromTension ? this.getTensionPoints() : points;
const ex = atBeginning ? points[0] : points[n - 2], ey = atBeginning ? points[1] : points[n - 1];
let dx = 0, dy = 0;
if (fromTension && atBeginning) {
dx = ex - (tp[0] + tp[2]) / 2;
dy = ey - (tp[1] + tp[3]) / 2;
}
else if (fromTension) {
const x = tp[tp.length - 4], y = tp[tp.length - 3];
const controlX = tp[tp.length - 2], controlY = tp[tp.length - 1];
const lastLength = Path.calcLength(x, y, 'Q', [
controlX,
controlY,
ex,
ey,
]);
const previous = Path.getPointOnQuadraticBezier(lastLength ? Math.max(0, 1 - this.pointerLength() / lastLength) : 0, x, y, controlX, controlY, ex, ey);
dx = ex - previous.x;
dy = ey - previous.y;
}
// repeated endpoints give a zero tangent, so walk towards the other end
// of the line until a distinct point is found
for (let i = atBeginning ? 0 : tp.length - 2; !dx && !dy && i >= 0 && i < tp.length; i += atBeginning ? 2 : -2) {
dx = ex - tp[i];
dy = ey - tp[i + 1];
}
const turn = Math.PI * 2;
return (Math.atan2(dy, dx) + turn) % turn;
}
__fillStroke(ctx) {
// here is a tricky part
// we need to disable dash for arrow pointers
const isDashEnabled = this.dashEnabled();
if (isDashEnabled) {
// manually disable dash for head
// it is better not to use setter here,
// because it will trigger attr change event
this.attrs.dashEnabled = false;
ctx.setLineDash([]);
}
ctx.fillStrokeShape(this);
// restore old value
if (isDashEnabled) {
this.attrs.dashEnabled = true;
}
}
getSelfRect() {
const lineRect = super.getSelfRect();
const points = this.points();
if (points.length < 4)
return lineRect;
let minX = lineRect.x, minY = lineRect.y;
let maxX = minX + lineRect.width, maxY = minY + lineRect.height;
for (const beginning of [false, true]) {
if (!(beginning ? this.pointerAtBeginning() : this.pointerAtEnding()))
continue;
const index = beginning ? 0 : points.length - 2;
const angle = this._getPointerAngle(beginning);
const cos = Math.cos(angle), sin = Math.sin(angle);
const x = points[index] - this.pointerLength() * cos;
const y = points[index + 1] - this.pointerLength() * sin;
const dx = Math.abs((this.pointerWidth() * sin) / 2);
const dy = Math.abs((this.pointerWidth() * cos) / 2);
minX = Math.min(minX, x - dx);
minY = Math.min(minY, y - dy);
maxX = Math.max(maxX, x + dx);
maxY = Math.max(maxY, y + dy);
}
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
}
}
Arrow.prototype.className = 'Arrow';
Arrow.prototype._attrsAffectingSize = [
...Line.prototype._attrsAffectingSize,
'pointerLength',
'pointerWidth',
'pointerAtBeginning',
'pointerAtEnding',
];
_registerNode(Arrow, true);
/**
* get/set pointerLength
* @name Konva.Arrow#pointerLength
* @method
* @param {Number} Length of pointer of arrow. The default is 10.
* @returns {Number}
* @example
* // get length
* var pointerLength = line.pointerLength();
*
* // set length
* line.pointerLength(15);
*/
Factory.addGetterSetter(Arrow, 'pointerLength', 10, getNumberValidator());
/**
* get/set pointerWidth
* @name Konva.Arrow#pointerWidth
* @method
* @param {Number} Width of pointer of arrow.
* The default is 10.
* @returns {Number}
* @example
* // get width
* var pointerWidth = line.pointerWidth();
*
* // set width
* line.pointerWidth(15);
*/
Factory.addGetterSetter(Arrow, 'pointerWidth', 10, getNumberValidator());
/**
* get/set pointerAtBeginning
* @name Konva.Arrow#pointerAtBeginning
* @method
* @param {Boolean} pointerAtBeginning Whether to draw a pointer at the beginning of the arrow. The default is false.
* @returns {Boolean}
* @example
* // get value
* var pointerAtBeginning = line.pointerAtBeginning();
*
* // set value
* line.pointerAtBeginning(true);
*/
Factory.addGetterSetter(Arrow, 'pointerAtBeginning', false);
/**
* get/set pointerAtEnding
* @name Konva.Arrow#pointerAtEnding
* @method
* @param {Boolean} pointerAtEnding Whether to draw a pointer at the end of the arrow. The default is true.
* @returns {Boolean}
* @example
* // get value
* var pointerAtEnding = line.pointerAtEnding();
*
* // set value
* line.pointerAtEnding(false);
*/
Factory.addGetterSetter(Arrow, 'pointerAtEnding', true);
+34
View File
@@ -0,0 +1,34 @@
import type { ShapeConfig } from '../Shape.ts';
import { Shape } from '../Shape.ts';
import type { GetSet } from '../types.ts';
import type { Context } from '../Context.ts';
export interface CircleConfig extends ShapeConfig {
radius?: number;
}
/**
* Circle constructor
* @constructor
* @memberof Konva
* @augments Konva.Shape
* @param {Object} config
* @param {Number} config.radius
* @@shapeParams
* @@nodeParams
* @example
* // create circle
* var circle = new Konva.Circle({
* radius: 40,
* fill: 'red',
* stroke: 'black',
* strokeWidth: 5
* });
*/
export declare class Circle extends Shape<CircleConfig> {
_getStrokePadding(): number;
_sceneFunc(context: Context): void;
getWidth(): number;
getHeight(): number;
setWidth(width: number): void;
setHeight(height: number): void;
radius: GetSet<number, this>;
}
+63
View File
@@ -0,0 +1,63 @@
import { Factory } from "../Factory.js";
import { Shape } from "../Shape.js";
import { getNumberValidator } from "../Validators.js";
import { _registerNode } from "../Global.js";
/**
* Circle constructor
* @constructor
* @memberof Konva
* @augments Konva.Shape
* @param {Object} config
* @param {Number} config.radius
* @@shapeParams
* @@nodeParams
* @example
* // create circle
* var circle = new Konva.Circle({
* radius: 40,
* fill: 'red',
* stroke: 'black',
* strokeWidth: 5
* });
*/
export class Circle extends Shape {
_getStrokePadding() {
return super._getStrokePadding(1);
}
_sceneFunc(context) {
context.beginPath();
context.arc(0, 0, Math.abs(this.attrs.radius || 0), 0, Math.PI * 2, false);
context.closePath();
context.fillStrokeShape(this);
}
getWidth() {
return Math.abs(this.radius()) * 2;
}
getHeight() {
return Math.abs(this.radius()) * 2;
}
setWidth(width) {
this.radius(width / 2);
}
setHeight(height) {
this.radius(height / 2);
}
}
Circle.prototype._centroid = true;
Circle.prototype.className = 'Circle';
Circle.prototype._attrsAffectingSize = ['radius'];
_registerNode(Circle, true);
/**
* get/set radius
* @name Konva.Circle#radius
* @method
* @param {Number} radius
* @returns {Number}
* @example
* // get radius
* var radius = circle.radius();
*
* // set radius
* circle.radius(10);
*/
Factory.addGetterSetter(Circle, 'radius', 0, getNumberValidator());
+51
View File
@@ -0,0 +1,51 @@
import type { ShapeConfig } from '../Shape.ts';
import { Shape } from '../Shape.ts';
import type { Context } from '../Context.ts';
import type { GetSet, Vector2d } from '../types.ts';
export interface EllipseConfig extends ShapeConfig {
radius?: Vector2d;
radiusX?: number;
radiusY?: number;
}
/**
* Ellipse constructor
* @constructor
* @memberof Konva
* @augments Konva.Shape
* @param {Object} config
* @param {Object} config.radius defines x and y radius
* @@shapeParams
* @@nodeParams
* @example
* var ellipse = new Konva.Ellipse({
* radius : {
* x : 50,
* y : 50
* },
* fill: 'red'
* });
*/
export declare class Ellipse extends Shape<EllipseConfig> {
_getStrokePadding(): number;
_sceneFunc(context: Context): void;
getWidth(): number;
getHeight(): number;
setWidth(width: number): void;
setHeight(height: number): void;
radius: GetSet<Vector2d, this>;
radiusX: GetSet<number, this>;
radiusY: GetSet<number, this>;
}
/**
* get/set radius y
* @name Konva.Ellipse#radiusY
* @method
* @param {Number} y
* @returns {Number}
* @example
* // get radius y
* var radiusY = ellipse.radiusY();
*
* // set radius y
* ellipse.radiusY(200);
*/
+98
View File
@@ -0,0 +1,98 @@
import { Factory } from "../Factory.js";
import { Shape } from "../Shape.js";
import { getNumberValidator } from "../Validators.js";
import { _registerNode } from "../Global.js";
/**
* Ellipse constructor
* @constructor
* @memberof Konva
* @augments Konva.Shape
* @param {Object} config
* @param {Object} config.radius defines x and y radius
* @@shapeParams
* @@nodeParams
* @example
* var ellipse = new Konva.Ellipse({
* radius : {
* x : 50,
* y : 50
* },
* fill: 'red'
* });
*/
export class Ellipse extends Shape {
_getStrokePadding() {
return super._getStrokePadding(1);
}
_sceneFunc(context) {
const rx = Math.abs(this.radiusX()), ry = Math.abs(this.radiusY());
context.beginPath();
context.ellipse(0, 0, rx, ry, 0, 0, Math.PI * 2, false);
context.closePath();
context.fillStrokeShape(this);
}
getWidth() {
return Math.abs(this.radiusX()) * 2;
}
getHeight() {
return Math.abs(this.radiusY()) * 2;
}
setWidth(width) {
this.radiusX(width / 2);
}
setHeight(height) {
this.radiusY(height / 2);
}
}
Ellipse.prototype.className = 'Ellipse';
Ellipse.prototype._centroid = true;
Ellipse.prototype._attrsAffectingSize = ['radiusX', 'radiusY'];
_registerNode(Ellipse, true);
// add getters setters
Factory.addComponentsGetterSetter(Ellipse, 'radius', ['x', 'y']);
/**
* get/set radius
* @name Konva.Ellipse#radius
* @method
* @param {Object} radius
* @param {Number} radius.x
* @param {Number} radius.y
* @returns {Object}
* @example
* // get radius
* var radius = ellipse.radius();
*
* // set radius
* ellipse.radius({
* x: 200,
* y: 100
* });
*/
Factory.addGetterSetter(Ellipse, 'radiusX', 0, getNumberValidator());
/**
* get/set radius x
* @name Konva.Ellipse#radiusX
* @method
* @param {Number} x
* @returns {Number}
* @example
* // get radius x
* var radiusX = ellipse.radiusX();
*
* // set radius x
* ellipse.radiusX(200);
*/
Factory.addGetterSetter(Ellipse, 'radiusY', 0, getNumberValidator());
/**
* get/set radius y
* @name Konva.Ellipse#radiusY
* @method
* @param {Number} y
* @returns {Number}
* @example
* // get radius y
* var radiusY = ellipse.radiusY();
*
* // set radius y
* ellipse.radiusY(200);
*/
+79
View File
@@ -0,0 +1,79 @@
import type { ShapeConfig } from '../Shape.ts';
import { Shape } from '../Shape.ts';
import type { GetSet, IRect } from '../types.ts';
import type { Context } from '../Context.ts';
export interface ImageConfig extends ShapeConfig {
image: CanvasImageSource | undefined;
crop?: IRect;
cornerRadius?: number | number[];
}
/**
* Image constructor
* @constructor
* @memberof Konva
* @augments Konva.Shape
* @param {Object} config
* @param {Image} config.image
* @param {Object} [config.crop]
* @@shapeParams
* @@nodeParams
* @example
* var imageObj = new Image();
* imageObj.onload = function() {
* var image = new Konva.Image({
* x: 200,
* y: 50,
* image: imageObj,
* width: 100,
* height: 100
* });
* };
* imageObj.src = '/path/to/image.jpg'
*/
export declare class Image extends Shape<ImageConfig> {
private _loadListener;
constructor(attrs?: ImageConfig);
_setImageLoad(): void;
_removeImageLoad(image: any): void;
destroy(): this;
_useBufferCanvas(forceFill?: boolean, opacity?: any): boolean;
_getStrokePadding(): number;
_sceneFunc(context: Context): void;
_hitFunc(context: Context): void;
getWidth(): any;
getHeight(): any;
/**
* load image from given url and create `Konva.Image` instance
* @method
* @memberof Konva.Image
* @param {String} url image source
* @param {Function} callback with Konva.Image instance as first argument
* @param {Function} onError optional error handler
* @example
* Konva.Image.fromURL(imageURL, function(image){
* // image is Konva.Image instance
* layer.add(image);
* });
*/
static fromURL(url: string, callback: (img: Image) => void, onError?: OnErrorEventHandler): void;
image: GetSet<CanvasImageSource | undefined, this>;
crop: GetSet<IRect, this>;
cropX: GetSet<number, this>;
cropY: GetSet<number, this>;
cropWidth: GetSet<number, this>;
cropHeight: GetSet<number, this>;
cornerRadius: GetSet<number | number[], this>;
}
/**
* get/set crop height
* @name Konva.Image#cropHeight
* @method
* @param {Number} height
* @returns {Number}
* @example
* // get crop height
* var cropHeight = image.cropHeight();
*
* // set crop height
* image.cropHeight(20);
*/
+260
View File
@@ -0,0 +1,260 @@
import { Util } from "../Util.js";
import { Factory } from "../Factory.js";
import { Shape } from "../Shape.js";
import { _registerNode } from "../Global.js";
import { getNumberOrArrayOfNumbersValidator, getNumberValidator, } from "../Validators.js";
/**
* Image constructor
* @constructor
* @memberof Konva
* @augments Konva.Shape
* @param {Object} config
* @param {Image} config.image
* @param {Object} [config.crop]
* @@shapeParams
* @@nodeParams
* @example
* var imageObj = new Image();
* imageObj.onload = function() {
* var image = new Konva.Image({
* x: 200,
* y: 50,
* image: imageObj,
* width: 100,
* height: 100
* });
* };
* imageObj.src = '/path/to/image.jpg'
*/
export class Image extends Shape {
constructor(attrs) {
super(attrs);
this._loadListener = () => {
this._requestDraw();
};
this.on('imageChange.konva', (props) => {
this._removeImageLoad(props.oldVal);
this._setImageLoad();
});
this._setImageLoad();
}
_setImageLoad() {
var _a;
const image = this.image();
// A fresh `new Image()` with no src already reports complete === true,
// so it still needs the listener.
if (((image === null || image === void 0 ? void 0 : image.complete) && image.src) || (image === null || image === void 0 ? void 0 : image.readyState) >= 2) {
return;
}
(_a = image === null || image === void 0 ? void 0 : image.addEventListener) === null || _a === void 0 ? void 0 : _a.call(image, 'videoWidth' in image ? 'loadeddata' : 'load', this._loadListener);
}
_removeImageLoad(image) {
var _a;
(_a = image === null || image === void 0 ? void 0 : image.removeEventListener) === null || _a === void 0 ? void 0 : _a.call(image, 'videoWidth' in image ? 'loadeddata' : 'load', this._loadListener);
}
destroy() {
this._removeImageLoad(this.image());
super.destroy();
return this;
}
_useBufferCanvas(forceFill, opacity = this.getAbsoluteOpacity()) {
if (this.attrs.perfectDrawEnabled === false)
return false;
const hasCornerRadius = !!this.cornerRadius();
const hasShadow = this.hasShadow();
if (hasCornerRadius && hasShadow) {
return true;
}
return super._useBufferCanvas(true, opacity);
}
_getStrokePadding() {
return super._getStrokePadding(this.strokeScaleEnabled() ? 1 : undefined);
}
_sceneFunc(context) {
const width = this.getWidth();
const height = this.getHeight();
const cornerRadius = this.cornerRadius();
const image = this.attrs.image;
if (this.hasFill() || this.hasStroke() || cornerRadius) {
context.beginPath();
cornerRadius
? Util.drawRoundedRectPath(context, width, height, cornerRadius)
: context.rect(0, 0, width, height);
context.closePath();
context.fillStrokeShape(this);
}
if (image) {
if (cornerRadius) {
context.clip();
}
const cropWidth = this.attrs.cropWidth;
const cropHeight = this.attrs.cropHeight;
if (cropWidth && cropHeight) {
context.drawImage(image, this.cropX(), this.cropY(), cropWidth, cropHeight, 0, 0, width, height);
}
else {
context.drawImage(image, 0, 0, width, height);
}
}
// If you need to draw later, you need to execute save/restore
}
_hitFunc(context) {
const width = this.width(), height = this.height(), cornerRadius = this.cornerRadius();
context.beginPath();
if (!cornerRadius) {
context.rect(0, 0, width, height);
}
else {
Util.drawRoundedRectPath(context, width, height, cornerRadius);
}
context.closePath();
context.fillStrokeShape(this);
}
getWidth() {
var _a, _b, _c;
const image = this.image();
return (_c = (_b = (_a = this.attrs.width) !== null && _a !== void 0 ? _a : image === null || image === void 0 ? void 0 : image.videoWidth) !== null && _b !== void 0 ? _b : image === null || image === void 0 ? void 0 : image.width) !== null && _c !== void 0 ? _c : 0;
}
getHeight() {
var _a, _b, _c;
const image = this.image();
return (_c = (_b = (_a = this.attrs.height) !== null && _a !== void 0 ? _a : image === null || image === void 0 ? void 0 : image.videoHeight) !== null && _b !== void 0 ? _b : image === null || image === void 0 ? void 0 : image.height) !== null && _c !== void 0 ? _c : 0;
}
/**
* load image from given url and create `Konva.Image` instance
* @method
* @memberof Konva.Image
* @param {String} url image source
* @param {Function} callback with Konva.Image instance as first argument
* @param {Function} onError optional error handler
* @example
* Konva.Image.fromURL(imageURL, function(image){
* // image is Konva.Image instance
* layer.add(image);
* });
*/
static fromURL(url, callback, onError = null) {
const img = Util.createImageElement();
img.onload = () => {
img.onload = img.onerror = null;
callback(new Image({ image: img }));
};
img.onerror = onError;
img.crossOrigin = 'Anonymous';
img.src = url;
}
}
Image.prototype.className = 'Image';
Image.prototype._attrsAffectingSize = ['image'];
_registerNode(Image, true);
/**
* get/set corner radius
* @method
* @name Konva.Image#cornerRadius
* @param {Number} cornerRadius
* @returns {Number}
* @example
* // get corner radius
* var cornerRadius = image.cornerRadius();
*
* // set corner radius
* image.cornerRadius(10);
*
* // set different corner radius values
* // top-left, top-right, bottom-right, bottom-left
* image.cornerRadius([0, 10, 20, 30]);
*/
Factory.addGetterSetter(Image, 'cornerRadius', 0, getNumberOrArrayOfNumbersValidator(4));
/**
* get/set image source. It can be image, canvas or video element
* @name Konva.Image#image
* @method
* @param {Object} image source
* @returns {Object}
* @example
* // get value
* var image = shape.image();
*
* // set value
* shape.image(img);
*/
Factory.addGetterSetter(Image, 'image');
Factory.addComponentsGetterSetter(Image, 'crop', ['x', 'y', 'width', 'height']);
/**
* get/set crop
* @method
* @name Konva.Image#crop
* @param {Object} crop
* @param {Number} crop.x
* @param {Number} crop.y
* @param {Number} crop.width
* @param {Number} crop.height
* @returns {Object}
* @example
* // get crop
* var crop = image.crop();
*
* // set crop
* image.crop({
* x: 20,
* y: 20,
* width: 20,
* height: 20
* });
*/
Factory.addGetterSetter(Image, 'cropX', 0, getNumberValidator());
/**
* get/set crop x
* @method
* @name Konva.Image#cropX
* @param {Number} x
* @returns {Number}
* @example
* // get crop x
* var cropX = image.cropX();
*
* // set crop x
* image.cropX(20);
*/
Factory.addGetterSetter(Image, 'cropY', 0, getNumberValidator());
/**
* get/set crop y
* @name Konva.Image#cropY
* @method
* @param {Number} y
* @returns {Number}
* @example
* // get crop y
* var cropY = image.cropY();
*
* // set crop y
* image.cropY(20);
*/
Factory.addGetterSetter(Image, 'cropWidth', 0, getNumberValidator());
/**
* get/set crop width
* @name Konva.Image#cropWidth
* @method
* @param {Number} width
* @returns {Number}
* @example
* // get crop width
* var cropWidth = image.cropWidth();
*
* // set crop width
* image.cropWidth(20);
*/
Factory.addGetterSetter(Image, 'cropHeight', 0, getNumberValidator());
/**
* get/set crop height
* @name Konva.Image#cropHeight
* @method
* @param {Number} height
* @returns {Number}
* @example
* // get crop height
* var cropHeight = image.cropHeight();
*
* // set crop height
* image.cropHeight(20);
*/
+102
View File
@@ -0,0 +1,102 @@
import type { ShapeConfig } from '../Shape.ts';
import { Shape } from '../Shape.ts';
import { Group } from '../Group.ts';
import type { Context } from '../Context.ts';
import type { ContainerConfig } from '../Container.ts';
import type { GetSet } from '../types.ts';
import { Text } from './Text.ts';
export interface LabelConfig extends ContainerConfig {
}
declare const NONE = "none";
/**
* Label constructor.&nbsp; Labels are groups that contain a Text and Tag shape
* @constructor
* @memberof Konva
* @param {Object} config
* @@nodeParams
* @example
* // create label
* var label = new Konva.Label({
* x: 100,
* y: 100,
* draggable: true
* });
*
* // add a tag to the label
* label.add(new Konva.Tag({
* fill: '#bbb',
* stroke: '#333',
* shadowColor: 'black',
* shadowBlur: 10,
* shadowOffset: { x: 10, y: 10 },
* shadowOpacity: 0.2,
* lineJoin: 'round',
* pointerDirection: 'up',
* pointerWidth: 20,
* pointerHeight: 20,
* cornerRadius: 5
* }));
*
* // add text to the label
* label.add(new Konva.Text({
* text: 'Hello World!',
* fontSize: 50,
* lineHeight: 1.2,
* padding: 10,
* fill: 'green'
* }));
*/
export declare class Label extends Group {
constructor(config?: LabelConfig);
/**
* get Text shape for the label. You need to access the Text shape in order to update
* the text properties
* @name Konva.Label#getText
* @method
* @example
* label.getText().fill('red')
*/
getText(): Text;
/**
* get Tag shape for the label. You need to access the Tag shape in order to update
* the pointer properties and the corner radius
* @name Konva.Label#getTag
* @method
*/
getTag(): Tag;
getWidth(): number;
getHeight(): number;
_sync(): void;
}
export interface TagConfig extends ShapeConfig {
pointerDirection?: string;
pointerWidth?: number;
pointerHeight?: number;
cornerRadius?: number | Array<number>;
}
/**
* Tag constructor.&nbsp; A Tag can be configured
* to have a pointer element that points up, right, down, or left
* @constructor
* @memberof Konva
* @param {Object} config
* @param {String} [config.pointerDirection] can be up, right, down, left, or none; the default
* is none. When a pointer is present, the positioning of the label is relative to the tip of the pointer.
* @param {Number} [config.pointerWidth]
* @param {Number} [config.pointerHeight]
* @param {Number} [config.cornerRadius]
*/
export declare class Tag extends Shape<TagConfig> {
_sceneFunc(context: Context): void;
getSelfRect(): {
x: number;
y: number;
width: number;
height: number;
};
pointerDirection: GetSet<'left' | 'up' | 'right' | 'down' | typeof NONE, this>;
pointerWidth: GetSet<number, this>;
pointerHeight: GetSet<number, this>;
cornerRadius: GetSet<number | number[], this>;
}
export {};
+250
View File
@@ -0,0 +1,250 @@
import { Factory } from "../Factory.js";
import { Util } from "../Util.js";
import { Shape } from "../Shape.js";
import { Group } from "../Group.js";
import { getNumberOrArrayOfNumbersValidator, getNumberValidator, } from "../Validators.js";
import { _registerNode } from "../Global.js";
import { Text } from "./Text.js";
// constants
const ATTR_CHANGE_LIST = [
...Text.prototype._attrsAffectingSize,
'width',
'height',
'pointerDirection',
'pointerWidth',
'pointerHeight',
], CHANGE_EVENTS = ATTR_CHANGE_LIST.map((attr) => attr + 'Change.konva').join(' '), NONE = 'none', UP = 'up', RIGHT = 'right', DOWN = 'down', LEFT = 'left';
/**
* Label constructor.&nbsp; Labels are groups that contain a Text and Tag shape
* @constructor
* @memberof Konva
* @param {Object} config
* @@nodeParams
* @example
* // create label
* var label = new Konva.Label({
* x: 100,
* y: 100,
* draggable: true
* });
*
* // add a tag to the label
* label.add(new Konva.Tag({
* fill: '#bbb',
* stroke: '#333',
* shadowColor: 'black',
* shadowBlur: 10,
* shadowOffset: { x: 10, y: 10 },
* shadowOpacity: 0.2,
* lineJoin: 'round',
* pointerDirection: 'up',
* pointerWidth: 20,
* pointerHeight: 20,
* cornerRadius: 5
* }));
*
* // add text to the label
* label.add(new Konva.Text({
* text: 'Hello World!',
* fontSize: 50,
* lineHeight: 1.2,
* padding: 10,
* fill: 'green'
* }));
*/
export class Label extends Group {
constructor(config) {
super(config);
this._sync = this._sync.bind(this);
this.on('add.konva', function (evt) {
// re-adding the same child must not stack another listener
evt.child.off(CHANGE_EVENTS, this._sync).on(CHANGE_EVENTS, this._sync);
this._sync();
});
}
/**
* get Text shape for the label. You need to access the Text shape in order to update
* the text properties
* @name Konva.Label#getText
* @method
* @example
* label.getText().fill('red')
*/
getText() {
return this.find('Text')[0];
}
/**
* get Tag shape for the label. You need to access the Tag shape in order to update
* the pointer properties and the corner radius
* @name Konva.Label#getTag
* @method
*/
getTag() {
return this.find('Tag')[0];
}
getWidth() {
return this.getText().width();
}
getHeight() {
return this.getText().height();
}
_sync() {
let text = this.getText(), tag = this.getTag(), width, height, pointerDirection, pointerWidth, x, y, pointerHeight;
if (text && tag) {
width = text.width();
height = text.height();
pointerDirection = tag.pointerDirection();
pointerWidth = tag.pointerWidth();
pointerHeight = tag.pointerHeight();
x = 0;
y = 0;
switch (pointerDirection) {
case UP:
x = width / 2;
y = -1 * pointerHeight;
break;
case RIGHT:
x = width + pointerWidth;
y = height / 2;
break;
case DOWN:
x = width / 2;
y = height + pointerHeight;
break;
case LEFT:
x = -1 * pointerWidth;
y = height / 2;
break;
}
tag.setAttrs({
x: -1 * x,
y: -1 * y,
width: width,
height: height,
});
text.setAttrs({
x: -1 * x,
y: -1 * y,
});
}
}
}
Label.prototype.className = 'Label';
_registerNode(Label);
/**
* Tag constructor.&nbsp; A Tag can be configured
* to have a pointer element that points up, right, down, or left
* @constructor
* @memberof Konva
* @param {Object} config
* @param {String} [config.pointerDirection] can be up, right, down, left, or none; the default
* is none. When a pointer is present, the positioning of the label is relative to the tip of the pointer.
* @param {Number} [config.pointerWidth]
* @param {Number} [config.pointerHeight]
* @param {Number} [config.cornerRadius]
*/
export class Tag extends Shape {
_sceneFunc(context) {
const width = this.width(), height = this.height(), pointerDirection = this.pointerDirection(), pointerWidth = this.pointerWidth(), pointerHeight = this.pointerHeight(), cornerRadius = this.cornerRadius();
const [topLeft, topRight, bottomRight, bottomLeft] = Util._cornerRadii(cornerRadius, width, height);
context.beginPath();
context.moveTo(topLeft, 0);
if (pointerDirection === UP) {
context.lineTo((width - pointerWidth) / 2, 0);
context.lineTo(width / 2, -1 * pointerHeight);
context.lineTo((width + pointerWidth) / 2, 0);
}
context.lineTo(width - topRight, 0);
context.arc(width - topRight, topRight, topRight, (Math.PI * 3) / 2, 0, false);
if (pointerDirection === RIGHT) {
context.lineTo(width, (height - pointerHeight) / 2);
context.lineTo(width + pointerWidth, height / 2);
context.lineTo(width, (height + pointerHeight) / 2);
}
context.lineTo(width, height - bottomRight);
context.arc(width - bottomRight, height - bottomRight, bottomRight, 0, Math.PI / 2, false);
if (pointerDirection === DOWN) {
context.lineTo((width + pointerWidth) / 2, height);
context.lineTo(width / 2, height + pointerHeight);
context.lineTo((width - pointerWidth) / 2, height);
}
context.lineTo(bottomLeft, height);
context.arc(bottomLeft, height - bottomLeft, bottomLeft, Math.PI / 2, Math.PI, false);
if (pointerDirection === LEFT) {
context.lineTo(0, (height + pointerHeight) / 2);
context.lineTo(-1 * pointerWidth, height / 2);
context.lineTo(0, (height - pointerHeight) / 2);
}
context.lineTo(0, topLeft);
context.arc(topLeft, topLeft, topLeft, Math.PI, (Math.PI * 3) / 2, false);
context.closePath();
context.fillStrokeShape(this);
}
getSelfRect() {
const pointerWidth = this.pointerWidth(), pointerHeight = this.pointerHeight(), direction = this.pointerDirection(), width = this.width(), height = this.height();
// The pointer base is centred on its side and can be longer than it.
if (direction === UP || direction === DOWN) {
return {
x: Math.min(0, (width - pointerWidth) / 2),
y: direction === UP ? -pointerHeight : 0,
width: Math.max(width, pointerWidth),
height: height + pointerHeight,
};
}
if (direction === LEFT || direction === RIGHT) {
return {
x: direction === LEFT ? -pointerWidth : 0,
y: Math.min(0, (height - pointerHeight) / 2),
width: width + pointerWidth,
height: Math.max(height, pointerHeight),
};
}
return { x: 0, y: 0, width, height };
}
}
Tag.prototype.className = 'Tag';
_registerNode(Tag, true);
/**
* get/set pointer direction
* @name Konva.Tag#pointerDirection
* @method
* @param {String} pointerDirection can be up, right, down, left, or none. The default is none.
* @returns {String}
* @example
* tag.pointerDirection('right');
*/
Factory.addGetterSetter(Tag, 'pointerDirection', NONE);
/**
* get/set pointer width
* @name Konva.Tag#pointerWidth
* @method
* @param {Number} pointerWidth
* @returns {Number}
* @example
* tag.pointerWidth(20);
*/
Factory.addGetterSetter(Tag, 'pointerWidth', 0, getNumberValidator());
/**
* get/set pointer height
* @method
* @name Konva.Tag#pointerHeight
* @param {Number} pointerHeight
* @returns {Number}
* @example
* tag.pointerHeight(20);
*/
Factory.addGetterSetter(Tag, 'pointerHeight', 0, getNumberValidator());
/**
* get/set cornerRadius
* @name Konva.Tag#cornerRadius
* @method
* @param {Number} cornerRadius
* @returns {Number}
* @example
* tag.cornerRadius(20);
*
* // set different corner radius values
* // top-left, top-right, bottom-right, bottom-left
* tag.cornerRadius([0, 10, 20, 30]);
*/
Factory.addGetterSetter(Tag, 'cornerRadius', 0, getNumberOrArrayOfNumbersValidator(4));
+74
View File
@@ -0,0 +1,74 @@
import type { ShapeConfig } from '../Shape.ts';
import { Shape } from '../Shape.ts';
import type { Context } from '../Context.ts';
import type { GetSet } from '../types.ts';
export type LinePoints = number[] | Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array;
export interface LineConfig extends ShapeConfig {
points?: LinePoints;
tension?: number;
closed?: boolean;
bezier?: boolean;
}
/**
* Line constructor.&nbsp; Lines are defined by an array of points and
* a tension
* @constructor
* @memberof Konva
* @augments Konva.Shape
* @param {Object} config
* @param {Array} config.points Flat array of points coordinates. You should define them as [x1, y1, x2, y2, x3, y3].
* @param {Number} [config.tension] Higher values will result in a more curvy line. A value of 0 will result in no interpolation.
* The default is 0
* @param {Boolean} [config.closed] defines whether or not the line shape is closed, creating a polygon or blob
* @param {Boolean} [config.bezier] if no tension is provided but bezier=true, we draw the line as a bezier using the passed points
* @@shapeParams
* @@nodeParams
* @example
* var line = new Konva.Line({
* x: 100,
* y: 50,
* points: [73, 70, 340, 23, 450, 60, 500, 20],
* stroke: 'red',
* tension: 1
* });
*/
export declare class Line<Config extends LineConfig = LineConfig> extends Shape<Config> {
constructor(config?: Config);
_hasTension(): boolean;
/**
* Report every curve segment of a line with a tension, in draw order. Both
* the scene function and the bounding rect read the shape through this, so
* that they can not disagree about which curve the line is.
*
* The handlers take plain numbers, because this runs on every frame.
*/
_eachTensionSegment(onQuadratic: (x0: number, y0: number, cpx: number, cpy: number, x: number, y: number) => void, onCubic: (x0: number, y0: number, cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number) => void): void;
_getStrokePadding(miterLimit?: number | undefined): number;
_sceneFunc(context: Context): void;
getTensionPoints(): any;
_getTensionPoints(): number[];
_getTensionPointsClosed(): number[];
getWidth(): number;
getHeight(): number;
getSelfRect(): import("../types.ts").IRect;
closed: GetSet<boolean, this>;
bezier: GetSet<boolean, this>;
tension: GetSet<number, this>;
points: GetSet<number[], this, LinePoints | null | undefined>;
}
/**
* get/set points array. Points is a flat array [x1, y1, x2, y2]. It is flat for performance reasons.
* @name Konva.Line#points
* @method
* @param {Array} points
* @returns {Array}
* @example
* // get points
* var points = line.points();
*
* // set points
* line.points([10, 20, 30, 40, 50, 60]);
*
* // push a new point
* line.points(line.points().concat([70, 80]));
*/
+273
View File
@@ -0,0 +1,273 @@
import { getCubicExtremaPoints, getQuadraticExtremaPoints, } from "../BezierFunctions.js";
import { Factory } from "../Factory.js";
import { _registerNode } from "../Global.js";
import { Shape } from "../Shape.js";
import { Util } from "../Util.js";
import { getNumberArrayValidator, getNumberValidator } from "../Validators.js";
function getControlPoints(x0, y0, x1, y1, x2, y2, t) {
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)), dSum = d01 + d12;
// all three points are the same, so the control points collapse into that
// point. Without this the divisions below are 0 / 0 = NaN.
if (dSum === 0) {
return [x1, y1, x1, y1];
}
const fa = (t * d01) / dSum, fb = (t * d12) / dSum, p1x = x1 - fa * (x2 - x0), p1y = y1 - fa * (y2 - y0), p2x = x1 + fb * (x2 - x0), p2y = y1 + fb * (y2 - y0);
return [p1x, p1y, p2x, p2y];
}
function expandPoints(p, tension) {
const len = p.length, allPoints = [];
for (let n = 2; n < len - 2; n += 2) {
const cp = getControlPoints(p[n - 2], p[n - 1], p[n], p[n + 1], p[n + 2], p[n + 3], tension);
if (isNaN(cp[0])) {
continue;
}
allPoints.push(cp[0]);
allPoints.push(cp[1]);
allPoints.push(p[n]);
allPoints.push(p[n + 1]);
allPoints.push(cp[2]);
allPoints.push(cp[3]);
}
return allPoints;
}
function getBezierExtremaPoints(points) {
const extrema = [];
// points[0], points[1] is the starting point; every subsequent group of
// 6 values is one cubic bezier segment (cp1x, cp1y, cp2x, cp2y, x, y),
// whose start is the previous segment's end point (matching the layout
// Line#_sceneFunc feeds to context.bezierCurveTo in a loop).
for (let n = 0; n + 7 < points.length; n += 6) {
// the end point of the segment. It is a joint with the next segment, and
// the curve can reach its highest or lowest value there without the
// derivative going to zero on either side.
extrema.push(points[n + 6], points[n + 7], ...getCubicExtremaPoints(points[n], points[n + 1], points[n + 2], points[n + 3], points[n + 4], points[n + 5], points[n + 6], points[n + 7]));
}
return extrema;
}
/**
* Line constructor.&nbsp; Lines are defined by an array of points and
* a tension
* @constructor
* @memberof Konva
* @augments Konva.Shape
* @param {Object} config
* @param {Array} config.points Flat array of points coordinates. You should define them as [x1, y1, x2, y2, x3, y3].
* @param {Number} [config.tension] Higher values will result in a more curvy line. A value of 0 will result in no interpolation.
* The default is 0
* @param {Boolean} [config.closed] defines whether or not the line shape is closed, creating a polygon or blob
* @param {Boolean} [config.bezier] if no tension is provided but bezier=true, we draw the line as a bezier using the passed points
* @@shapeParams
* @@nodeParams
* @example
* var line = new Konva.Line({
* x: 100,
* y: 50,
* points: [73, 70, 340, 23, 450, 60, 500, 20],
* stroke: 'red',
* tension: 1
* });
*/
export class Line extends Shape {
constructor(config) {
super(config);
this.on('pointsChange.konva tensionChange.konva closedChange.konva bezierChange.konva', function () {
this._clearCache('tensionPoints');
});
}
_hasTension() {
return this.tension() !== 0 && this.points().length > 4;
}
/**
* Report every curve segment of a line with a tension, in draw order. Both
* the scene function and the bounding rect read the shape through this, so
* that they can not disagree about which curve the line is.
*
* The handlers take plain numbers, because this runs on every frame.
*/
_eachTensionSegment(onQuadratic, onCubic) {
const points = this.points(), length = points.length, closed = this.closed(), tp = this.getTensionPoints(), len = tp.length;
let x0 = points[0], y0 = points[1],
// a closed line has a curve on both sides of the first point, so it
// starts on a full cubic. An open one is capped by a quadratic instead
n = closed ? 0 : 4;
if (!closed) {
onQuadratic(x0, y0, tp[0], tp[1], tp[2], tp[3]);
x0 = tp[2];
y0 = tp[3];
}
while (n < len - 2) {
const cp1x = tp[n++], cp1y = tp[n++], cp2x = tp[n++], cp2y = tp[n++], x = tp[n++], y = tp[n++];
onCubic(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y);
x0 = x;
y0 = y;
}
if (!closed) {
onQuadratic(x0, y0, tp[len - 2], tp[len - 1], points[length - 2], points[length - 1]);
}
}
_getStrokePadding(miterLimit = this.points().length === 4 && !this.closed() ? 1 : undefined) {
return super._getStrokePadding(miterLimit);
}
_sceneFunc(context) {
const points = this.points(), length = points.length, closed = this.closed(), bezier = this.bezier();
if (!length) {
return;
}
let n = 0;
context.beginPath();
context.moveTo(points[0], points[1]);
// tension
if (this._hasTension()) {
this._eachTensionSegment((_x0, _y0, cpx, cpy, x, y) => context.quadraticCurveTo(cpx, cpy, x, y), (_x0, _y0, cp1x, cp1y, cp2x, cp2y, x, y) => context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y));
}
else if (bezier) {
// no tension but bezier
n = 2;
while (n < length) {
context.bezierCurveTo(points[n++], points[n++], points[n++], points[n++], points[n++], points[n++]);
}
}
else {
// no tension
for (n = 2; n < length; n += 2) {
context.lineTo(points[n], points[n + 1]);
}
}
// closed e.g. polygons and blobs
if (closed) {
context.closePath();
context.fillStrokeShape(this);
}
else {
// open e.g. lines and splines
context.strokeShape(this);
}
}
getTensionPoints() {
return this._getCache('tensionPoints', this._getTensionPoints);
}
_getTensionPoints() {
if (this.closed()) {
return this._getTensionPointsClosed();
}
else {
return expandPoints(this.points(), this.tension());
}
}
_getTensionPointsClosed() {
const p = this.points(), len = p.length, tension = this.tension(), firstControlPoints = getControlPoints(p[len - 2], p[len - 1], p[0], p[1], p[2], p[3], tension), lastControlPoints = getControlPoints(p[len - 4], p[len - 3], p[len - 2], p[len - 1], p[0], p[1], tension), middle = expandPoints(p, tension), tp = [firstControlPoints[2], firstControlPoints[3]]
.concat(middle)
.concat([
lastControlPoints[0],
lastControlPoints[1],
p[len - 2],
p[len - 1],
lastControlPoints[2],
lastControlPoints[3],
firstControlPoints[0],
firstControlPoints[1],
p[0],
p[1],
]);
return tp;
}
getWidth() {
return this.getSelfRect().width;
}
getHeight() {
return this.getSelfRect().height;
}
// overload size detection
getSelfRect() {
let points = this.points();
if (points.length < 4) {
return {
x: points[0] || 0,
y: points[1] || 0,
width: 0,
height: 0,
};
}
if (this._hasTension()) {
// the two end points of every segment, plus the points where it turns
// back on either axis. Together they are the exact bounds of the curve
const bounds = [points[0], points[1]];
this._eachTensionSegment((x0, y0, cpx, cpy, x, y) => bounds.push(x, y, ...getQuadraticExtremaPoints(x0, y0, cpx, cpy, x, y)), (x0, y0, cp1x, cp1y, cp2x, cp2y, x, y) => bounds.push(x, y, ...getCubicExtremaPoints(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y)));
points = bounds;
}
else if (this.bezier()) {
// no trailing point here: the extrema already carry the end point of
// every segment that is drawn. Adding the last pair of the array back
// would include a control point of a trailing partial segment, which
// _sceneFunc never draws.
points = [points[0], points[1], ...getBezierExtremaPoints(points)];
}
return Util._getPointsRect(points);
}
}
Line.prototype.className = 'Line';
Line.prototype._attrsAffectingSize = ['points', 'bezier', 'tension', 'closed'];
_registerNode(Line, true);
// add getters setters
Factory.addGetterSetter(Line, 'closed', false);
/**
* get/set closed flag. The default is false
* @name Konva.Line#closed
* @method
* @param {Boolean} closed
* @returns {Boolean}
* @example
* // get closed flag
* var closed = line.closed();
*
* // close the shape
* line.closed(true);
*
* // open the shape
* line.closed(false);
*/
Factory.addGetterSetter(Line, 'bezier', false);
/**
* get/set bezier flag. The default is false
* @name Konva.Line#bezier
* @method
* @param {Boolean} bezier
* @returns {Boolean}
* @example
* // get whether the line is a bezier
* var isBezier = line.bezier();
*
* // set whether the line is a bezier
* line.bezier(true);
*/
Factory.addGetterSetter(Line, 'tension', 0, getNumberValidator());
/**
* get/set tension
* @name Konva.Line#tension
* @method
* @param {Number} tension Higher values will result in a more curvy line. A value of 0 will result in no interpolation. The default is 0
* @returns {Number}
* @example
* // get tension
* var tension = line.tension();
*
* // set tension
* line.tension(3);
*/
Factory.addGetterSetter(Line, 'points', [], getNumberArrayValidator());
/**
* get/set points array. Points is a flat array [x1, y1, x2, y2]. It is flat for performance reasons.
* @name Konva.Line#points
* @method
* @param {Array} points
* @returns {Array}
* @example
* // get points
* var points = line.points();
*
* // set points
* line.points([10, 20, 30, 40, 50, 60]);
*
* // push a new point
* line.points(line.points().concat([70, 80]));
*/

Some files were not shown because too many files have changed in this diff Show More