Merge branch 'iaosee-image-corner-radius' into master

This commit is contained in:
Anton Lavrenov 2023-01-05 23:44:03 -05:00
commit 6ca59aace1
5 changed files with 164 additions and 54 deletions

View File

@ -1,4 +1,5 @@
import { Konva } from './Global'; import { Konva } from './Global';
import { Context } from './Context';
import { IRect, RGB, RGBA, Vector2d } from './types'; import { IRect, RGB, RGBA, Vector2d } from './types';
/* /*
@ -988,5 +989,53 @@ export const Util = {
c.width = 0; c.width = 0;
c.height = 0; c.height = 0;
}) })
},
drawRoundedRectPath(context: Context, width: number, height: number, cornerRadius: number | number[]) {
let topLeft = 0;
let topRight = 0;
let bottomLeft = 0;
let bottomRight = 0;
if (typeof cornerRadius === 'number') {
topLeft = topRight = bottomLeft = bottomRight = Math.min(
cornerRadius,
width / 2,
height / 2
);
} else {
topLeft = Math.min(cornerRadius[0] || 0, width / 2, height / 2);
topRight = Math.min(cornerRadius[1] || 0, width / 2, height / 2);
bottomRight = Math.min(cornerRadius[2] || 0, width / 2, height / 2);
bottomLeft = Math.min(cornerRadius[3] || 0, width / 2, height / 2);
}
context.moveTo(topLeft, 0);
context.lineTo(width - topRight, 0);
context.arc(
width - topRight,
topRight,
topRight,
(Math.PI * 3) / 2,
0,
false
);
context.lineTo(width, height - bottomRight);
context.arc(
width - bottomRight,
height - bottomRight,
bottomRight,
0,
Math.PI / 2,
false
);
context.lineTo(bottomLeft, height);
context.arc(
bottomLeft,
height - bottomLeft,
bottomLeft,
Math.PI / 2,
Math.PI,
false
);
context.lineTo(0, topLeft);
context.arc(topLeft, topLeft, topLeft, Math.PI, (Math.PI * 3) / 2, false);
} }
}; };

View File

@ -1,8 +1,11 @@
import { Util } from '../Util'; import { Util } from '../Util';
import { Factory } from '../Factory'; import { Factory } from '../Factory';
import { Shape, ShapeConfig } from '../Shape'; import { Shape, ShapeConfig } from '../Shape';
import { getNumberValidator } from '../Validators';
import { _registerNode } from '../Global'; import { _registerNode } from '../Global';
import {
getNumberOrArrayOfNumbersValidator,
getNumberValidator,
} from '../Validators';
import { GetSet, IRect } from '../types'; import { GetSet, IRect } from '../types';
import { Context } from '../Context'; import { Context } from '../Context';
@ -10,6 +13,7 @@ import { Context } from '../Context';
export interface ImageConfig extends ShapeConfig { export interface ImageConfig extends ShapeConfig {
image: CanvasImageSource | undefined; image: CanvasImageSource | undefined;
crop?: IRect; crop?: IRect;
cornerRadius?: number | number[];
} }
/** /**
@ -66,6 +70,7 @@ export class Image extends Shape<ImageConfig> {
_sceneFunc(context: Context) { _sceneFunc(context: Context) {
const width = this.getWidth(); const width = this.getWidth();
const height = this.getHeight(); const height = this.getHeight();
const cornerRadius = this.cornerRadius();
const image = this.attrs.image; const image = this.attrs.image;
let params; let params;
@ -89,23 +94,34 @@ export class Image extends Shape<ImageConfig> {
} }
} }
if (this.hasFill() || this.hasStroke()) { if (this.hasFill() || this.hasStroke() || cornerRadius) {
context.beginPath(); context.beginPath();
context.rect(0, 0, width, height); cornerRadius
? Util.drawRoundedRectPath(context, width, height, cornerRadius)
: context.rect(0, 0, width, height);
context.closePath(); context.closePath();
context.fillStrokeShape(this); context.fillStrokeShape(this);
} }
if (image) { if (image) {
if (cornerRadius) {
context.clip();
}
context.drawImage.apply(context, params); context.drawImage.apply(context, params);
} }
// If you need to draw later, you need to execute save/restore
} }
_hitFunc(context) { _hitFunc(context) {
var width = this.width(), var width = this.width(),
height = this.height(); height = this.height(),
cornerRadius = this.cornerRadius();
context.beginPath(); context.beginPath();
context.rect(0, 0, width, height); if (!cornerRadius) {
context.rect(0, 0, width, height);
} else {
Util.drawRoundedRectPath(context, width, height, cornerRadius);
}
context.closePath(); context.closePath();
context.fillStrokeShape(this); context.fillStrokeShape(this);
} }
@ -149,10 +165,36 @@ export class Image extends Shape<ImageConfig> {
cropY: GetSet<number, this>; cropY: GetSet<number, this>;
cropWidth: GetSet<number, this>; cropWidth: GetSet<number, this>;
cropHeight: GetSet<number, this>; cropHeight: GetSet<number, this>;
cornerRadius: GetSet<number | number[], this>;
} }
Image.prototype.className = 'Image'; Image.prototype.className = 'Image';
_registerNode(Image); _registerNode(Image);
/**
* 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 * get/set image source. It can be image, canvas or video element
* @name Konva.Image#image * @name Konva.Image#image

View File

@ -2,8 +2,11 @@ import { Factory } from '../Factory';
import { Shape, ShapeConfig } from '../Shape'; import { Shape, ShapeConfig } from '../Shape';
import { _registerNode } from '../Global'; import { _registerNode } from '../Global';
import { Util } from '../Util';
import { GetSet } from '../types'; import { GetSet } from '../types';
import { Context } from '../Context';
import { getNumberOrArrayOfNumbersValidator } from '../Validators'; import { getNumberOrArrayOfNumbersValidator } from '../Validators';
export interface RectConfig extends ShapeConfig { export interface RectConfig extends ShapeConfig {
cornerRadius?: number | number[]; cornerRadius?: number | number[];
} }
@ -27,7 +30,7 @@ export interface RectConfig extends ShapeConfig {
* }); * });
*/ */
export class Rect extends Shape<RectConfig> { export class Rect extends Shape<RectConfig> {
_sceneFunc(context) { _sceneFunc(context: Context) {
var cornerRadius = this.cornerRadius(), var cornerRadius = this.cornerRadius(),
width = this.width(), width = this.width(),
height = this.height(); height = this.height();
@ -38,52 +41,7 @@ export class Rect extends Shape<RectConfig> {
// simple rect - don't bother doing all that complicated maths stuff. // simple rect - don't bother doing all that complicated maths stuff.
context.rect(0, 0, width, height); context.rect(0, 0, width, height);
} else { } else {
let topLeft = 0; Util.drawRoundedRectPath(context, width, height, cornerRadius);
let topRight = 0;
let bottomLeft = 0;
let bottomRight = 0;
if (typeof cornerRadius === 'number') {
topLeft = topRight = bottomLeft = bottomRight = Math.min(
cornerRadius,
width / 2,
height / 2
);
} else {
topLeft = Math.min(cornerRadius[0] || 0, width / 2, height / 2);
topRight = Math.min(cornerRadius[1] || 0, width / 2, height / 2);
bottomRight = Math.min(cornerRadius[2] || 0, width / 2, height / 2);
bottomLeft = Math.min(cornerRadius[3] || 0, width / 2, height / 2);
}
context.moveTo(topLeft, 0);
context.lineTo(width - topRight, 0);
context.arc(
width - topRight,
topRight,
topRight,
(Math.PI * 3) / 2,
0,
false
);
context.lineTo(width, height - bottomRight);
context.arc(
width - bottomRight,
height - bottomRight,
bottomRight,
0,
Math.PI / 2,
false
);
context.lineTo(bottomLeft, height);
context.arc(
bottomLeft,
height - bottomLeft,
bottomLeft,
Math.PI / 2,
Math.PI,
false
);
context.lineTo(0, topLeft);
context.arc(topLeft, topLeft, topLeft, Math.PI, (Math.PI * 3) / 2, false);
} }
context.closePath(); context.closePath();
context.fillStrokeShape(this); context.fillStrokeShape(this);

View File

@ -52,6 +52,38 @@
stage.on('contextmenu', (e) => { stage.on('contextmenu', (e) => {
console.log('click'); console.log('click');
}); });
var imageObj = new Image();
imageObj.onload = function () {
var yoda = new Konva.Image({
x: 150,
y: 50,
image: imageObj,
width: 200,
height: 200,
draggable: true,
strokeWidth: 4,
stroke: 'blue',
cornerRadius: [10, 20, 30, 40],
});
layer.add(yoda);
};
imageObj.src = 'https://konvajs.org/assets/yoda.jpg';
Konva.Image.fromURL('https://konvajs.org/assets/darth-vader.jpg', function (darthNode) {
darthNode.setAttrs({
x: 300,
y: 50,
// scaleX: 0.5,
// scaleY: 0.5,
draggable: true,
strokeEnabled: true,
stroke: 'green',
strokeWidth: 2,
cornerRadius: 30,
});
layer.add(darthNode);
});
</script> </script>
</body> </body>
</html> </html>

View File

@ -44,8 +44,7 @@ describe('Image', function () {
assert.equal(darth.offset().x, 50); assert.equal(darth.offset().x, 50);
assert.equal(darth.offset().y, 30); assert.equal(darth.offset().y, 30);
var crop = null; var crop = darth.crop();
crop = darth.crop();
assert.equal(crop.x, 135); assert.equal(crop.x, 135);
assert.equal(crop.y, 7); assert.equal(crop.y, 7);
@ -168,6 +167,7 @@ describe('Image', function () {
crop: { x: 186, y: 211, width: 106, height: 74 }, crop: { x: 186, y: 211, width: 106, height: 74 },
draggable: true, draggable: true,
scale: { x: 0.5, y: 0.5 }, scale: { x: 0.5, y: 0.5 },
cornerRadius: 15,
}); });
layer.add(darth); layer.add(darth);
@ -209,6 +209,7 @@ describe('Image', function () {
assert.equal(darth.cropY(), 6); assert.equal(darth.cropY(), 6);
assert.equal(darth.cropWidth(), 7); assert.equal(darth.cropWidth(), 7);
assert.equal(darth.cropHeight(), 8); assert.equal(darth.cropHeight(), 8);
assert.equal(darth.cornerRadius(), 15);
done(); done();
}); });
@ -384,4 +385,32 @@ describe('Image', function () {
done(); done();
}); });
}); });
it('corner radius', function (done) {
loadImage('darth-vader.jpg', (imageObj) => {
var stage = addStage();
var layer = new Konva.Layer();
var darth = new Konva.Image({
x: 20,
y: 20,
image: imageObj,
cornerRadius: 10,
draggable: true,
stroke: 'red',
strokeWidth: 100,
strokeEnabled: false,
});
layer.add(darth);
stage.add(layer);
assert.equal(
layer.getContext().getTrace(true),
'clearRect();save();transform();beginPath();moveTo();lineTo();arc();lineTo();arc();lineTo();arc();lineTo();arc();closePath();clip();drawImage();restore();'
);
done();
});
});
}); });