diff --git a/common/changes/@visactor/vrender-core/feat-image-enhance_2025-02-20-03-46.json b/common/changes/@visactor/vrender-core/feat-image-enhance_2025-02-20-03-46.json new file mode 100644 index 000000000..f4747e665 --- /dev/null +++ b/common/changes/@visactor/vrender-core/feat-image-enhance_2025-02-20-03-46.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender-core", + "comment": "feat: image support auto width height by rawImage wh", + "type": "none" + } + ], + "packageName": "@visactor/vrender-core" +} \ No newline at end of file diff --git a/common/changes/@visactor/vrender/feat-image-enhance_2025-02-20-03-46.json b/common/changes/@visactor/vrender/feat-image-enhance_2025-02-20-03-46.json new file mode 100644 index 000000000..d0d51583d --- /dev/null +++ b/common/changes/@visactor/vrender/feat-image-enhance_2025-02-20-03-46.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vrender", + "comment": "feat: image support auto width height by rawImage wh", + "type": "none" + } + ], + "packageName": "@visactor/vrender" +} \ No newline at end of file diff --git a/packages/vrender-core/src/graphic/config.ts b/packages/vrender-core/src/graphic/config.ts index edfd5638a..047bc31ca 100644 --- a/packages/vrender-core/src/graphic/config.ts +++ b/packages/vrender-core/src/graphic/config.ts @@ -370,6 +370,8 @@ export const DefaultImageAttribute: Required = { image: '', width: 0, height: 0, + maxWidth: 500, + maxHeight: 500, ...DefaultAttribute, fill: true, cornerRadius: 0, diff --git a/packages/vrender-core/src/graphic/graphic.ts b/packages/vrender-core/src/graphic/graphic.ts index 4c2564b85..4749d40fc 100644 --- a/packages/vrender-core/src/graphic/graphic.ts +++ b/packages/vrender-core/src/graphic/graphic.ts @@ -196,7 +196,7 @@ export abstract class Graphic = Partial; declare backgroundImg?: boolean; diff --git a/packages/vrender-core/src/graphic/image.ts b/packages/vrender-core/src/graphic/image.ts index 3b7cf63fd..6fd723c93 100644 --- a/packages/vrender-core/src/graphic/image.ts +++ b/packages/vrender-core/src/graphic/image.ts @@ -18,6 +18,8 @@ export class Image extends Graphic implements IImage { // 资源加载完成后回调,外部通过回调获取图片资源尺寸 successCallback?: () => void; failCallback?: () => void; + declare _actualWidth?: number; + declare _actualHeight?: number; static NOWORK_ANIMATE_ATTR = { image: 1, @@ -33,25 +35,39 @@ export class Image extends Graphic implements IImage { this.loadImage(this.attribute.image); } - get width(): number { - return this.attribute.width ?? 0; - } - set width(width: number) { - if (this.attribute.width === width) { - this.attribute.width = width; - this.addUpdateShapeAndBoundsTag(); + getImageElement(): HTMLImageElement | HTMLCanvasElement | null { + const { image } = this.attribute; + if (!image || !this.resources) { + return null; + } + const res = this.resources.get(image); + if (res.state !== 'success') { + return null; } + return res.data; } - get height(): number { - return this.attribute.height ?? 0; - } - set height(height: number) { - if (this.attribute.height === height) { - this.attribute.height = height; - this.addUpdateShapeAndBoundsTag(); - } + get width(): number { + this.tryUpdateAABBBounds(); + return this._actualWidth; } + // set width(width: number) { + // if (this.attribute.width === width) { + // this.attribute.width = width; + // this.addUpdateShapeAndBoundsTag(); + // } + // } + + get height(): number { + this.tryUpdateAABBBounds(); + return this._actualHeight; + } + // set height(height: number) { + // if (this.attribute.height === height) { + // this.attribute.height = height; + // this.addUpdateShapeAndBoundsTag(); + // } + // } get repeatX(): IRepeatType { return this.attribute.repeatX ?? 'no-repeat'; } @@ -85,6 +101,7 @@ export class Image extends Graphic implements IImage { this.successCallback(); } }); + this.addUpdateBoundTag(); } imageLoadFail(url: string, cb?: () => void): void { @@ -123,7 +140,37 @@ export class Image extends Graphic implements IImage { aabbBounds: IAABBBounds ) { if (!this.updatePathProxyAABBBounds(aabbBounds)) { - const { width = imageTheme.width, height = imageTheme.height } = attribute; + const { maxWidth = imageTheme.maxWidth, maxHeight = imageTheme.maxHeight } = attribute; + let { width, height } = attribute; + if (width == null || height == null) { + const imageElement = this.getImageElement(); + if (imageElement) { + // 如果给了width或者height,那就使用已给的width或者height来按比例缩放,否则就在maxWidth和maxHeight之间按比例缩放 + const imageWidth = imageElement.width; + const imageHeight = imageElement.height; + if (width != null) { + height = width * (imageHeight / imageWidth); + } else if (height != null) { + width = height * (imageWidth / imageHeight); + } else { + // 如果width和height都没有给,那就使用maxWidth和maxHeight来按比例缩放 + const imageRatio = imageWidth / imageHeight; + const maxWHRatio = maxWidth / maxHeight; + if (imageRatio > maxWHRatio) { + width = maxWidth; + height = maxWidth / imageRatio; + } else { + height = maxHeight; + width = maxHeight * imageRatio; + } + } + } else { + width = maxWidth; + height = maxHeight; + } + } + this._actualWidth = width; + this._actualHeight = height; aabbBounds.set(0, 0, width, height); } diff --git a/packages/vrender-core/src/interface/graphic/image.ts b/packages/vrender-core/src/interface/graphic/image.ts index b48c2320a..8e5d3021d 100644 --- a/packages/vrender-core/src/interface/graphic/image.ts +++ b/packages/vrender-core/src/interface/graphic/image.ts @@ -11,6 +11,14 @@ export type IImageAttribute = { * 高度 */ height: number; + /** + * 最大宽度 + */ + maxWidth?: number; + /** + * 最大高度 + */ + maxHeight?: number; /** * x方向的重复方式 */ diff --git a/packages/vrender-core/src/render/contributions/render/image-render.ts b/packages/vrender-core/src/render/contributions/render/image-render.ts index 02db999e0..49a8d7c9d 100644 --- a/packages/vrender-core/src/render/contributions/render/image-render.ts +++ b/packages/vrender-core/src/render/contributions/render/image-render.ts @@ -63,8 +63,6 @@ export class DefaultCanvasImageRender extends BaseRender implements IGra // const imageAttribute = graphicService.themeService.getCurrentTheme().imageAttribute; const imageAttribute = getTheme(image).image; const { - width = imageAttribute.width, - height = imageAttribute.height, repeatX = imageAttribute.repeatX, repeatY = imageAttribute.repeatY, x: originX = imageAttribute.x, @@ -89,6 +87,9 @@ export class DefaultCanvasImageRender extends BaseRender implements IGra return; } + const width = image.width; + const height = image.height; + // deal with cornerRadius let needRestore = false; if (cornerRadius === 0 || (isArray(cornerRadius) && (cornerRadius).every(num => num === 0))) { diff --git a/packages/vrender/__tests__/browser/src/pages/image.ts b/packages/vrender/__tests__/browser/src/pages/image.ts index e68b1e1a9..833ffe8a6 100644 --- a/packages/vrender/__tests__/browser/src/pages/image.ts +++ b/packages/vrender/__tests__/browser/src/pages/image.ts @@ -10,87 +10,38 @@ const base64 = 'data:image/png;base64,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'; // const urlSvg = 'https://replace-with-svg-link.svg'; -const svg2 = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`; +const dogImage = 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/vrender/lovely_dog.jpg'; export const page = () => { const shapes = []; shapes.push( createImage({ - x: 100, - y: 100, - width: 200, - height: 200, - image: - 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/vchart-editor/upload-images/a85cb6c4-b494-4a52-bfec-5ca557132dde' - // repeatX: 'repeat', - // repeatY: 'repeat' - // cornerRadius: 100 + x: 10, + y: 10, + image: dogImage, + _debug_bounds: true + }) + ); + shapes.push( + createImage({ + x: 10, + y: 300, + width: 100, + image: dogImage, + _debug_bounds: true + }) + ); + shapes.push( + createImage({ + x: 200, + y: 300, + width: 100, + height: 100, + image: dogImage, + _debug_bounds: true }) ); - - const svgImage = createImage({ - x: 300, - y: 100, - width: 100, - height: 100, - image: svg, - stroke: 'green' - }); - shapes.push(svgImage); - - const image = createImage({ - x: 50, - y: 50, - width: 200, - height: 200, - lineWidth: 10, - cornerRadius: 14, - stroke: '#000000', - fill: 'transparent', - image: - 'http://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/vchart-editor/upload-images/014d69b2-2731-4daa-9e60-68925fa1e54b.jpg' - }); - shapes.push(image); - - // shapes.push( - // createImage({ - // x: 100, - // y: 300, - // width: 100, - // height: 100, - // image: urlSvg - // }) - // ); const stage = createStage({ canvas: 'main', @@ -100,30 +51,9 @@ export const page = () => { viewHeight: 600 }); - addShapesToStage(stage, shapes as any, true); stage.render(); - window.updateImage1 = () => { - svgImage.setAttribute('image', svg1); - (svgImage as any).loadImage(svgImage.attribute.image); - stage.render(); - }; - - window.updateImage0 = () => { - svgImage.setAttribute('image', svg); - (svgImage as any).loadImage(svgImage.attribute.image); - stage.render(); - }; - - window.updateImage2 = () => { - image.setAttribute('image', base64); - (image as any).loadImage(image.attribute.image); - stage.render(); - }; - - window.updateImage3 = () => { - image.setAttribute('image', urlSvg); - (image as any).loadImage(image.attribute.image); - stage.render(); - }; + shapes.forEach(shape => { + stage.defaultLayer.add(shape); + }); };