@@ -107,30 +107,148 @@ function loadImage(url: string): Promise<HTMLImageElement> {
107107}
108108
109109type Ctx2D = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D
110+ type HookExecutor = ( ctx : Ctx2D , imageData : ImageData , size : number , args : unknown ) => void
110111
111- /**
112- * 编译 hook 函数字符串为可执行函数
113- * 函数签名: (ctx, imageData, size, args) => void
114- */
115- function compileHook ( code : string ) : (
112+ const normalizeHookCode = ( code : string ) : string => code . replace ( / \s + / g, ' ' ) . trim ( )
113+
114+ function getNumberArg ( args : unknown , key : string , fallback : number ) : number {
115+ if ( typeof args !== 'object' || args === null ) {
116+ return fallback
117+ }
118+ const value = Reflect . get ( args , key )
119+ return typeof value === 'number' && Number . isFinite ( value ) ? value : fallback
120+ }
121+
122+ function runRainbowGradientHook (
116123 ctx : Ctx2D ,
117124 imageData : ImageData ,
118125 size : number ,
119126 args : unknown
120- ) => void {
127+ ) : void {
128+ const data = imageData . data
129+ const centerX = size / 2
130+ const centerY = size / 2
131+ const opacity = getNumberArg ( args , 'opacity' , 1 )
132+
133+ for ( let index = 0 ; index < data . length ; index += 4 ) {
134+ const alpha = data [ index + 3 ] !
135+ if ( alpha < 10 ) continue
136+
137+ const pixelX = ( index / 4 ) % size
138+ const pixelY = Math . floor ( index / 4 / size )
139+
140+ const angle = ( Math . atan2 ( pixelX - centerX , centerY - pixelY ) * 180 ) / Math . PI + 180
141+ const hue = ( angle % 360 ) / 60
142+ const hueBand = Math . floor ( hue ) % 6
143+ const fraction = hue - Math . floor ( hue )
144+
145+ let red = 255
146+ let green = 0
147+ let blue = 0
148+
149+ switch ( hueBand ) {
150+ case 0 :
151+ red = 255
152+ green = Math . round ( fraction * 255 )
153+ blue = 0
154+ break
155+ case 1 :
156+ red = Math . round ( ( 1 - fraction ) * 255 )
157+ green = 255
158+ blue = 0
159+ break
160+ case 2 :
161+ red = 0
162+ green = 255
163+ blue = Math . round ( fraction * 255 )
164+ break
165+ case 3 :
166+ red = 0
167+ green = Math . round ( ( 1 - fraction ) * 255 )
168+ blue = 255
169+ break
170+ case 4 :
171+ red = Math . round ( fraction * 255 )
172+ green = 0
173+ blue = 255
174+ break
175+ default :
176+ red = 255
177+ green = 0
178+ blue = Math . round ( ( 1 - fraction ) * 255 )
179+ break
180+ }
181+
182+ data [ index ] = red
183+ data [ index + 1 ] = green
184+ data [ index + 2 ] = blue
185+ data [ index + 3 ] = Math . round ( alpha * opacity )
186+ }
187+
188+ ctx . putImageData ( imageData , 0 , 0 )
189+ }
190+
191+ function runSolidColorHook (
192+ ctx : Ctx2D ,
193+ imageData : ImageData ,
194+ _size : number ,
195+ args : unknown
196+ ) : void {
197+ const data = imageData . data
198+ const red = getNumberArg ( args , 'r' , 255 )
199+ const green = getNumberArg ( args , 'g' , 255 )
200+ const blue = getNumberArg ( args , 'b' , 255 )
201+ const opacity = getNumberArg ( args , 'opacity' , 1 )
202+
203+ for ( let index = 0 ; index < data . length ; index += 4 ) {
204+ const alpha = data [ index + 3 ] !
205+ if ( alpha < 10 ) continue
206+
207+ data [ index ] = red
208+ data [ index + 1 ] = green
209+ data [ index + 2 ] = blue
210+ data [ index + 3 ] = Math . round ( alpha * opacity )
211+ }
212+
213+ ctx . putImageData ( imageData , 0 , 0 )
214+ }
215+
216+ const builtinHookExecutors = new Map < string , HookExecutor > ( )
217+ let builtinHookExecutorsReady = false
218+
219+ function ensureBuiltinHookExecutors ( ) : void {
220+ if ( builtinHookExecutorsReady ) {
221+ return
222+ }
223+
224+ builtinHookExecutors . set ( normalizeHookCode ( RAINBOW_GRADIENT_HOOK . code ) , runRainbowGradientHook )
225+ builtinHookExecutors . set ( normalizeHookCode ( SOLID_COLOR_HOOK_CODE ) , runSolidColorHook )
226+ builtinHookExecutorsReady = true
227+ }
228+
229+ function resolveBuiltinHookExecutor ( code : string ) : HookExecutor | undefined {
230+ ensureBuiltinHookExecutors ( )
231+ return builtinHookExecutors . get ( normalizeHookCode ( code ) )
232+ }
233+
234+ /**
235+ * 编译 hook 函数字符串为可执行函数
236+ * 函数签名: (ctx, imageData, size, args) => void
237+ */
238+ function compileHook ( code : string ) : HookExecutor {
121239 // eslint-disable-next-line @typescript-eslint/no-implied-eval
122- return new Function ( 'ctx' , 'imageData' , 'size' , 'args' , code ) as (
123- ctx : Ctx2D ,
124- imageData : ImageData ,
125- size : number ,
126- args : unknown
127- ) => void
240+ return new Function ( 'ctx' , 'imageData' , 'size' , 'args' , code ) as HookExecutor
128241}
129242
130243// Hook 函数缓存(避免重复编译)
131244const hookCache = new Map < string , ReturnType < typeof compileHook > > ( )
132245
133246function getCompiledHook ( code : string ) : ReturnType < typeof compileHook > {
247+ const builtinHook = resolveBuiltinHookExecutor ( code )
248+ if ( builtinHook ) {
249+ return builtinHook
250+ }
251+
134252 let fn = hookCache . get ( code )
135253 if ( ! fn ) {
136254 fn = compileHook ( code )
0 commit comments