11import path from 'node:path' ;
22import { AttachmentBuilder } from 'discord.js' ;
3+ import type { Image } from 'skia-canvas' ;
34
45import { createCanvas , loadAndCacheLocalImage , printWrappedText } from '@/lib/canvas/canvasUtil.js' ;
56import { OSRSCanvas } from '@/lib/canvas/OSRSCanvas.js' ;
67
7- const textBoxFile = loadAndCacheLocalImage ( './src/lib/resources/images/textbox.png' ) ;
8+ type HeadKey =
9+ | 'mejJal'
10+ | 'jane'
11+ | 'santa'
12+ | 'izzy'
13+ | 'alry'
14+ | 'ketKeh'
15+ | 'gertrude'
16+ | 'antiSanta'
17+ | 'bunny'
18+ | 'minimus'
19+ | 'partyPete'
20+ | 'mysteriousFigure'
21+ | 'rudolph'
22+ | 'pumpkin'
23+ | 'marimbo'
24+ | 'spookling'
25+ | 'magnaboy'
26+ | 'wurMuTheMonkey' ;
827
9- function loadChImg ( fileName : string ) {
10- const basePath = './src/lib/resources/images/chat_heads/' ;
11- return loadAndCacheLocalImage ( path . join ( basePath , fileName ) ) ;
12- }
28+ const basePath = './src/lib/resources/images' ;
29+ const chatBase = `${ basePath } /chat_heads` ;
1330
14- const mejJalChatHead = loadChImg ( 'mejJal.png' ) ;
15- const janeChatHead = loadChImg ( 'jane.png' ) ;
16- const santaChatHead = loadChImg ( 'santa.png' ) ;
17- const izzyChatHead = loadChImg ( 'izzy.png' ) ;
18- const alryTheAnglerChatHead = loadChImg ( 'alryTheAngler.png' ) ;
19- const ketKehChatHead = loadChImg ( 'ketKeh.png' ) ;
20- const gertrudeChatHead = loadChImg ( 'gertrude.png' ) ;
21- const antiSantaChatHead = loadChImg ( 'antisanta.png' ) ;
22- const bunnyChatHead = loadChImg ( 'bunny.png' ) ;
23- const minimusHead = loadChImg ( 'minimus.png' ) ;
24- const pumpkinHead = loadChImg ( 'pumpkin.png' ) ;
25- const spookling = loadChImg ( 'spookling.png' ) ;
26- const monkeyChildChatHead = loadChImg ( 'monkeychild.png' ) ;
27- const magnaboyChatHead = loadChImg ( 'magnaboy.png' ) ;
28- const marimboChatHead = loadChImg ( 'marimbo.png' ) ;
29- const partyPeteHead = loadChImg ( 'partyPete.png' ) ;
30- const mysteriousFigureHead = loadChImg ( 'mysteriousFigure.png' ) ;
31- const rudolphChatHead = loadChImg ( 'rudolph.png' ) ;
32-
33- const chatHeads = {
34- mejJal : mejJalChatHead ,
35- jane : janeChatHead ,
36- santa : santaChatHead ,
37- izzy : izzyChatHead ,
38- alry : alryTheAnglerChatHead ,
39- ketKeh : ketKehChatHead ,
40- gertrude : gertrudeChatHead ,
41- antiSanta : antiSantaChatHead ,
42- bunny : bunnyChatHead ,
43- minimus : minimusHead ,
31+ const chatHeadPaths : Record < HeadKey , string > = {
32+ mejJal : 'mejJal.png' ,
33+ jane : 'jane.png' ,
34+ santa : 'santa.png' ,
35+ izzy : 'izzy.png' ,
36+ alry : 'alryTheAngler.png' ,
37+ ketKeh : 'ketKeh.png' ,
38+ gertrude : 'gertrude.png' ,
39+ antiSanta : 'antisanta.png' ,
40+ bunny : 'bunny.png' ,
41+ minimus : 'minimus.png' ,
4442
4543 // BSO
46- partyPete : partyPeteHead ,
47- mysteriousFigure : mysteriousFigureHead ,
48- rudolph : rudolphChatHead ,
49- pumpkin : pumpkinHead ,
50- marimbo : marimboChatHead ,
51- spookling : spookling ,
52- magnaboy : magnaboyChatHead ,
53- wurMuTheMonkey : monkeyChildChatHead
44+ partyPete : 'partyPete.png' ,
45+ mysteriousFigure : 'mysteriousFigure.png' ,
46+ rudolph : 'rudolph.png' ,
47+ pumpkin : 'pumpkin.png' ,
48+ marimbo : 'marimbo.png' ,
49+ spookling : ' spookling.png' ,
50+ magnaboy : 'magnaboy.png' ,
51+ wurMuTheMonkey : 'monkeychild.png'
5452} ;
5553
56- const names : Record < keyof typeof chatHeads , string > = {
54+ const names : Record < HeadKey , string > = {
5755 mejJal : 'TzHaar-Mej-Jal' ,
5856 jane : 'Guildmaster Jane' ,
5957 santa : 'Santa' ,
@@ -76,20 +74,36 @@ const names: Record<keyof typeof chatHeads, string> = {
7674 wurMuTheMonkey : 'Wur Mu the Monkey'
7775} ;
7876
79- export async function newChatHeadImage ( { content, head } : { content : string ; head : keyof typeof chatHeads } ) {
77+ const imagePromiseCache = new Map < string , Promise < Image > > ( ) ;
78+
79+ const loadOnce = ( absPath : string ) : Promise < any > => {
80+ let p = imagePromiseCache . get ( absPath ) ;
81+ if ( ! p ) {
82+ p = loadAndCacheLocalImage ( absPath ) ;
83+ imagePromiseCache . set ( absPath , p ) ;
84+ }
85+ return p ;
86+ } ;
87+
88+ const getTextbox = ( ) => loadOnce ( path . join ( basePath , 'textbox.png' ) ) ;
89+ const getChatHead = ( key : HeadKey ) => loadOnce ( path . join ( chatBase , chatHeadPaths [ key ] ) ) ;
90+
91+ export async function newChatHeadImage ( { content, head } : { content : string ; head : HeadKey } ) {
8092 const canvas = createCanvas ( 519 , 142 ) ;
8193 const ctx = canvas . getContext ( '2d' ) ;
8294 ctx . imageSmoothingEnabled = false ;
83- const headImage = await chatHeads [ head ] ;
84- const bg = await textBoxFile ;
95+
96+ const [ bg , headImage ] = await Promise . all ( [ getTextbox ( ) , getChatHead ( head ) ] ) ;
8597
8698 ctx . drawImage ( bg , 0 , 0 ) ;
8799 ctx . drawImage ( headImage , 28 , bg . height / 2 - headImage . height / 2 ) ;
88100 ctx . font = '16px RuneScape Quill 8' ;
89101
90102 ctx . fillStyle = '#810303' ;
91- const nameWidth = Math . floor ( ctx . measureText ( names [ head ] ) . width ) ;
92- ctx . fillText ( names [ head ] , Math . floor ( 307 - nameWidth / 2 ) , 36 ) ;
103+ const name = names [ head ] ;
104+ const nameWidth = Math . floor ( ctx . measureText ( name ) . width ) ;
105+ ctx . fillText ( name , Math . floor ( 307 - nameWidth / 2 ) , 36 ) ;
106+
93107 ctx . fillStyle = '#000' ;
94108 printWrappedText ( ctx , content , 307 , 58 , 361 ) ;
95109
@@ -108,14 +122,12 @@ export async function newChatHeadImage({ content, head }: { content: string; hea
108122 return scaledCanvas . toBuffer ( ) ;
109123}
110124
111- export default async function chatHeadImage ( { content, head } : { content : string ; head : keyof typeof chatHeads } ) {
125+ export default async function chatHeadImage ( { content, head } : { content : string ; head : HeadKey } ) {
112126 const image = await newChatHeadImage ( { content, head } ) ;
113127 return new AttachmentBuilder ( image ) ;
114128}
115129
116- export async function mahojiChatHead ( { content, head } : { content : string ; head : keyof typeof chatHeads } ) {
130+ export async function mahojiChatHead ( { content, head } : { content : string ; head : HeadKey } ) {
117131 const image = await newChatHeadImage ( { content, head } ) ;
118- return {
119- files : [ { attachment : image , name : 'image.jpg' } ]
120- } ;
132+ return { files : [ { attachment : image , name : 'image.jpg' } ] } ;
121133}
0 commit comments