diff --git a/src/Util.ts b/src/Util.ts index 29063929..19da8d5a 100644 --- a/src/Util.ts +++ b/src/Util.ts @@ -1,4 +1,5 @@ import { Konva } from './Global'; +import { Context } from './Context'; import { IRect, RGB, RGBA, Vector2d } from './types'; /* @@ -988,5 +989,53 @@ export const Util = { c.width = 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); } }; diff --git a/src/shapes/Image.ts b/src/shapes/Image.ts index 7032d7d4..1bc59aa8 100644 --- a/src/shapes/Image.ts +++ b/src/shapes/Image.ts @@ -1,8 +1,8 @@ import { Util } from '../Util'; import { Factory } from '../Factory'; import { Shape, ShapeConfig } from '../Shape'; -import { getNumberValidator } from '../Validators'; import { _registerNode } from '../Global'; +import { getNumberOrArrayOfNumbersValidator, getNumberValidator } from '../Validators'; import { GetSet, IRect } from '../types'; import { Context } from '../Context'; @@ -10,6 +10,7 @@ import { Context } from '../Context'; export interface ImageConfig extends ShapeConfig { image: CanvasImageSource | undefined; crop?: IRect; + cornerRadius?: number | number[]; } /** @@ -66,6 +67,7 @@ export class Image extends Shape { _sceneFunc(context: Context) { const width = this.getWidth(); const height = this.getHeight(); + const cornerRadius = this.cornerRadius(); const image = this.attrs.image; let params; @@ -89,23 +91,32 @@ export class Image extends Shape { } } - if (this.hasFill() || this.hasStroke()) { - context.beginPath(); + context.beginPath(); + if (!cornerRadius) { context.rect(0, 0, width, height); - context.closePath(); - context.fillStrokeShape(this); + } else { + Util.drawRoundedRectPath(context, width, height, cornerRadius); } - if (image) { + context.save(); + cornerRadius && context.clip(); context.drawImage.apply(context, params); + context.restore(); } + context.closePath(); + context.fillStrokeShape(this); } _hitFunc(context) { var width = this.width(), - height = this.height(); + height = this.height(), + cornerRadius = this.cornerRadius(); 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.fillStrokeShape(this); } @@ -149,10 +160,36 @@ export class Image extends Shape { cropY: GetSet; cropWidth: GetSet; cropHeight: GetSet; + cornerRadius: GetSet; } Image.prototype.className = '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 * @name Konva.Image#image diff --git a/src/shapes/Rect.ts b/src/shapes/Rect.ts index 27549e7f..4b1dcddc 100644 --- a/src/shapes/Rect.ts +++ b/src/shapes/Rect.ts @@ -2,8 +2,11 @@ import { Factory } from '../Factory'; import { Shape, ShapeConfig } from '../Shape'; import { _registerNode } from '../Global'; +import { Util } from '../Util'; import { GetSet } from '../types'; +import { Context } from '../Context'; import { getNumberOrArrayOfNumbersValidator } from '../Validators'; + export interface RectConfig extends ShapeConfig { cornerRadius?: number | number[]; } @@ -27,7 +30,7 @@ export interface RectConfig extends ShapeConfig { * }); */ export class Rect extends Shape { - _sceneFunc(context) { + _sceneFunc(context: Context) { var cornerRadius = this.cornerRadius(), width = this.width(), height = this.height(); @@ -38,52 +41,7 @@ export class Rect extends Shape { // simple rect - don't bother doing all that complicated maths stuff. context.rect(0, 0, width, height); } else { - 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); + Util.drawRoundedRectPath(context, width, height, cornerRadius); } context.closePath(); context.fillStrokeShape(this); diff --git a/test/sandbox.html b/test/sandbox.html index 38276fcb..a136b90e 100644 --- a/test/sandbox.html +++ b/test/sandbox.html @@ -52,6 +52,38 @@ stage.on('contextmenu', (e) => { 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); + }); diff --git a/test/unit/Image-test.ts b/test/unit/Image-test.ts index 4cf4fa43..0376433d 100644 --- a/test/unit/Image-test.ts +++ b/test/unit/Image-test.ts @@ -26,6 +26,7 @@ describe('Image', function () { offset: { x: 50, y: 30 }, crop: { x: 135, y: 7, width: 167, height: 134 }, draggable: true, + cornerRadius: 15, }); layer.add(darth); @@ -43,9 +44,9 @@ describe('Image', function () { assert.equal(darth.getHeight(), 100); assert.equal(darth.offset().x, 50); assert.equal(darth.offset().y, 30); + assert.equal(darth.cornerRadius(), 15); - var crop = null; - crop = darth.crop(); + var crop = darth.crop(); assert.equal(crop.x, 135); assert.equal(crop.y, 7);