@@ -2,21 +2,60 @@ import Element from './elements';
22import imageManager from '../common/imageManager' ;
33import { IElementOptions } from './types' ;
44
5+ /**
6+ * 图片类型
7+ */
8+ export enum ImageType {
9+ /**
10+ * 普通类型
11+ */
12+ SIMPLE = 'simple' ,
13+ /**
14+ * 切片(九宫格)类型。
15+ */
16+ SLICED = 'sliced' ,
17+ /**
18+ * 平铺类型
19+ */
20+ TILED = 'tiled' ,
21+ }
22+
23+ /**
24+ * 九宫格配置参数
25+ */
26+ export interface IInsetParams {
27+ /** 左边界距离 */
28+ left : number ;
29+ /** 上边界距离 */
30+ top : number ;
31+ /** 右边界距离 */
32+ right : number ;
33+ /** 下边界距离 */
34+ bottom : number ;
35+ }
36+
537interface IImageOptions extends IElementOptions {
638 src ?: string ;
39+ type ?: ImageType ;
40+ /** 九宫格配置 */
41+ inset ?: string ;
742}
843
944export default class Image extends Element {
1045 private imgsrc : string ;
1146 public type = 'Image' ;
1247 public img : HTMLImageElement | null ;
48+ public imageType : ImageType = ImageType . SIMPLE ;
49+ private insetParams ?: IInsetParams ;
1350
1451 constructor ( opts : IImageOptions ) {
1552 const {
1653 style = { } ,
1754 idName = '' ,
1855 className = '' ,
1956 src = '' ,
57+ type = ImageType . SIMPLE ,
58+ inset = '0 0 0 0' ,
2059 dataset,
2160 } = opts ;
2261
@@ -27,15 +66,31 @@ export default class Image extends Element {
2766 style,
2867 } ) ;
2968
69+ // 解析inset参数并验证
70+ if ( inset ) {
71+ const values = inset . split ( ' ' ) . map ( Number ) ;
72+ if ( values . length === 4 && values . every ( v => ! isNaN ( v ) && v >= 0 ) ) {
73+ const [ left , top , right , bottom ] = values ;
74+ this . insetParams = {
75+ left,
76+ top,
77+ right,
78+ bottom,
79+ } ;
80+ } else {
81+ console . warn ( 'Invalid inset parameter format. Expected: "left top right bottom"' ) ;
82+ }
83+ }
84+
3085 this . imgsrc = src ;
86+ this . imageType = type ;
3187
3288 this . img = imageManager . loadImage ( this . src , ( img , fromCache ) => {
3389 if ( fromCache ) {
3490 this . img = img ;
3591 } else {
3692 if ( ! this . isDestroyed ) {
3793 this . img = img ;
38- // 当图片加载完成,实例可能已经被销毁了
3994 this . root ?. emit ( 'repaint' ) ;
4095 }
4196 }
@@ -59,6 +114,43 @@ export default class Image extends Element {
59114 }
60115 }
61116
117+ /**
118+ * 设置九宫格参数
119+ */
120+ setInsetParams ( params : IInsetParams ) {
121+ // 验证参数
122+ if ( this . validateInsetParams ( params ) ) {
123+ this . insetParams = { ...params } ;
124+ this . root ?. emit ( 'repaint' ) ;
125+ }
126+ }
127+
128+ /**
129+ * 验证九宫格参数
130+ */
131+ private validateInsetParams ( params : IInsetParams ) : boolean {
132+ if ( ! params ) return false ;
133+
134+ const { left, top, right, bottom } = params ;
135+
136+ // 检查参数是否为非负数
137+ if ( left < 0 || top < 0 || right < 0 || bottom < 0 ) {
138+ console . warn ( 'Inset parameters must be non-negative' ) ;
139+ return false ;
140+ }
141+
142+ // 如果有图片,检查参数是否超出图片尺寸
143+ if ( this . img ) {
144+ const { width : imgWidth , height : imgHeight } = this . img ;
145+ if ( left + right >= imgWidth || top + bottom >= imgHeight ) {
146+ console . warn ( 'Inset parameters exceed image dimensions' ) ;
147+ return false ;
148+ }
149+ }
150+
151+ return true ;
152+ }
153+
62154 repaint ( ) {
63155 this . render ( ) ;
64156 }
@@ -82,9 +174,18 @@ export default class Image extends Element {
82174
83175 const { needStroke, needClip, originX, originY, drawX, drawY, width, height } = this . baseRender ( ) ;
84176
85- // 自定义渲染逻辑 开始
86- ctx . drawImage ( this . img , drawX - originX , drawY - originY , width , height ) ;
87- // 自定义渲染逻辑 结束
177+ // 根据图片类型选择渲染方式
178+ switch ( this . imageType ) {
179+ case ImageType . SIMPLE :
180+ this . renderSimple ( ctx , drawX , drawY , originX , originY , width , height ) ;
181+ break ;
182+ case ImageType . SLICED :
183+ this . renderSliced ( ctx , drawX , drawY , originX , originY , width , height ) ;
184+ break ;
185+ case ImageType . TILED :
186+ this . renderTiled ( ctx , drawX , drawY , originX , originY , width , height ) ;
187+ break ;
188+ }
88189
89190 if ( needClip ) {
90191 this . renderBorder ( ctx , originX , originY ) ;
@@ -95,8 +196,135 @@ export default class Image extends Element {
95196 }
96197
97198 ctx . translate ( - originX , - originY ) ;
199+ ctx . restore ( ) ;
200+ }
201+
202+ /**
203+ * 普通渲染
204+ */
205+ private renderSimple ( ctx : CanvasRenderingContext2D , drawX : number , drawY : number , originX : number , originY : number , width : number , height : number ) {
206+ ctx . drawImage ( this . img ! , drawX - originX , drawY - originY , width , height ) ;
207+ }
208+
209+ /**
210+ * 九宫格渲染 - 图像会被拉伸以适应尺寸
211+ */
212+ private renderSliced ( ctx : CanvasRenderingContext2D , drawX : number , drawY : number , originX : number , originY : number , width : number , height : number ) {
213+ if ( ! this . insetParams ) {
214+ console . warn ( '九宫格渲染需要设置 insetParams 参数' ) ;
215+ this . renderSimple ( ctx , drawX , drawY , originX , originY , width , height ) ;
216+ return ;
217+ }
98218
219+ const { left, top, right, bottom } = this . insetParams ;
220+ const img = this . img ! ;
221+ const imgWidth = img . width ;
222+ const imgHeight = img . height ;
223+
224+ // 计算源区域尺寸
225+ const centerSrcWidth = imgWidth - left - right ;
226+ const centerSrcHeight = imgHeight - top - bottom ;
227+
228+ // 计算目标区域尺寸
229+ const targetCenterWidth = Math . max ( 0 , width - left - right ) ;
230+ const targetCenterHeight = Math . max ( 0 , height - top - bottom ) ;
231+
232+ const x = drawX - originX ;
233+ const y = drawY - originY ;
234+
235+ // 1. 渲染四个角(保持原样)
236+ this . renderSlicedCorners ( ctx , img , x , y , width , height , imgWidth , imgHeight , left , top , right , bottom ) ;
237+
238+ // 2. 渲染四条边(拉伸)
239+ this . renderSlicedEdges ( ctx , img , x , y , width , height , imgWidth , imgHeight , left , top , right , bottom , centerSrcWidth , centerSrcHeight , targetCenterWidth , targetCenterHeight ) ;
240+
241+ // 3. 渲染中心区域(拉伸)
242+ if ( targetCenterWidth > 0 && targetCenterHeight > 0 && centerSrcWidth > 0 && centerSrcHeight > 0 ) {
243+ ctx . drawImage ( img , left , top , centerSrcWidth , centerSrcHeight ,
244+ x + left , y + top , targetCenterWidth , targetCenterHeight ) ;
245+ }
246+ }
247+
248+ /**
249+ * 平铺渲染 - 图像按原始大小重复平铺
250+ */
251+ private renderTiled ( ctx : CanvasRenderingContext2D , drawX : number , drawY : number , originX : number , originY : number , width : number , height : number ) {
252+ const img = this . img ! ;
253+ const x = drawX - originX ;
254+ const y = drawY - originY ;
255+
256+ // 使用 createPattern 进行平铺渲染
257+ const pattern = ctx . createPattern ( img , 'repeat' ) ;
258+ if ( ! pattern ) return ;
259+
260+ ctx . save ( ) ;
261+
262+ // 设置裁剪区域
263+ ctx . beginPath ( ) ;
264+ ctx . rect ( x , y , width , height ) ;
265+ ctx . clip ( ) ;
266+
267+ // 设置pattern并填充
268+ ctx . fillStyle = pattern ;
269+ ctx . fillRect ( x , y , width , height ) ;
270+
99271 ctx . restore ( ) ;
100272 }
101- }
102273
274+ /**
275+ * 渲染九宫格的四个角
276+ */
277+ private renderSlicedCorners ( ctx : CanvasRenderingContext2D , img : HTMLImageElement , x : number , y : number , width : number , height : number , imgWidth : number , imgHeight : number , left : number , top : number , right : number , bottom : number ) {
278+ // 左上角
279+ if ( left > 0 && top > 0 ) {
280+ ctx . drawImage ( img , 0 , 0 , left , top , x , y , left , top ) ;
281+ }
282+
283+ // 右上角
284+ if ( right > 0 && top > 0 ) {
285+ ctx . drawImage ( img , imgWidth - right , 0 , right , top ,
286+ x + width - right , y , right , top ) ;
287+ }
288+
289+ // 左下角
290+ if ( left > 0 && bottom > 0 ) {
291+ ctx . drawImage ( img , 0 , imgHeight - bottom , left , bottom ,
292+ x , y + height - bottom , left , bottom ) ;
293+ }
294+
295+ // 右下角
296+ if ( right > 0 && bottom > 0 ) {
297+ ctx . drawImage ( img , imgWidth - right , imgHeight - bottom , right , bottom ,
298+ x + width - right , y + height - bottom , right , bottom ) ;
299+ }
300+ }
301+
302+ /**
303+ * 渲染九宫格的四条边
304+ */
305+ private renderSlicedEdges ( ctx : CanvasRenderingContext2D , img : HTMLImageElement , x : number , y : number , width : number , height : number , imgWidth : number , imgHeight : number , left : number , top : number , right : number , bottom : number , centerSrcWidth : number , centerSrcHeight : number , targetCenterWidth : number , targetCenterHeight : number ) {
306+ // 上边 - 水平拉伸
307+ if ( top > 0 && targetCenterWidth > 0 ) {
308+ ctx . drawImage ( img , left , 0 , centerSrcWidth , top ,
309+ x + left , y , targetCenterWidth , top ) ;
310+ }
311+
312+ // 下边 - 水平拉伸
313+ if ( bottom > 0 && targetCenterWidth > 0 ) {
314+ ctx . drawImage ( img , left , imgHeight - bottom , centerSrcWidth , bottom ,
315+ x + left , y + height - bottom , targetCenterWidth , bottom ) ;
316+ }
317+
318+ // 左边 - 垂直拉伸
319+ if ( left > 0 && targetCenterHeight > 0 ) {
320+ ctx . drawImage ( img , 0 , top , left , centerSrcHeight ,
321+ x , y + top , left , targetCenterHeight ) ;
322+ }
323+
324+ // 右边 - 垂直拉伸
325+ if ( right > 0 && targetCenterHeight > 0 ) {
326+ ctx . drawImage ( img , imgWidth - right , top , right , centerSrcHeight ,
327+ x + width - right , y + top , right , targetCenterHeight ) ;
328+ }
329+ }
330+ }
0 commit comments