11<script setup lang="ts">
2- import type { Color } from ' culori'
3-
4- import html2canvas from ' html2canvas'
5-
2+ import { colorFromElement } from ' @proj-airi/stage-ui/libs'
63import { BasicInputFile } from ' @proj-airi/ui'
7- import { average } from ' culori'
8- import { Vibrant } from ' node-vibrant/browser'
94import { computed , nextTick , onMounted , onUnmounted , ref , useTemplateRef , watch } from ' vue'
105
116import defaultBackgroundImage from ' ../../assets/backgrounds/fairy-forest.e17cbc2774.ko-fi.com.avif'
127
138import { useThemeColor } from ' ../../composables/theme-color'
14-
159// Reactive state
1610const isCapturing = ref (false )
1711const extractedColors = ref <string []>([])
@@ -53,113 +47,60 @@ const topBar = computed(() => {
5347 return ' '
5448})
5549
56- // Color extraction from image
57- async function extractColorsFromImage() {
58- if (images .value .length === 0 ) {
50+ async function refreshColors() {
51+ if (! imageRef .value || images .value .length === 0 ) {
5952 return
6053 }
6154
6255 try {
6356 isCapturing .value = true
6457
65- // Load the image
66- const img = new window .Image ()
67- img .crossOrigin = ' anonymous'
68- img .src = images .value [0 ]
69-
70- await new Promise ((resolve , reject ) => {
71- img .onload = resolve
72- img .onerror = reject
73- })
74-
75- // Create a canvas and draw only the top portion
76- const cropHeight = Math .floor (img .naturalHeight * 0.2 ) // top 20% of the image
77- const canvas = document .createElement (' canvas' )
78- canvas .width = img .naturalWidth
79- canvas .height = cropHeight
80- const ctx = canvas .getContext (' 2d' )
81- if (ctx ) {
82- ctx .drawImage (img , 0 , 0 , img .naturalWidth , cropHeight , 0 , 0 , img .naturalWidth , cropHeight )
58+ if (imageRef .value instanceof HTMLImageElement && ! imageRef .value .complete ) {
59+ await new Promise <void >((resolve , reject ) => {
60+ imageRef .value ?.addEventListener (' load' , () => resolve (), { once: true })
61+ imageRef .value ?.addEventListener (' error' , () => reject (new Error (' Image failed to load' )), { once: true })
62+ })
8363 }
8464
85- // Convert canvas to data URL and use Vibrant on the cropped region
86- const dataUrl = canvas .toDataURL ()
87- const vibrant = new Vibrant (dataUrl )
88- const palette = await vibrant .getPalette ()
89-
90- const colors = Object .values (palette )
91- .map (color => color ?.hex )
92- .filter ((color ): color is string => typeof color === ' string' )
93-
94- extractedColors .value = colors
95- dominantColor .value = palette .Vibrant ?.hex || palette .DarkVibrant ?.hex || colors [0 ]
96-
97- // Update PWA theme color
98- await updateThemeColor ()
99- }
100- catch (error ) {
101- console .error (' Color extraction failed:' , error )
102- }
103- finally {
104- isCapturing .value = false
105- }
106- }
107-
108- // Capture top edge colors using html2canvas
109- async function captureTopEdgeColors() {
110- if (! imageRef .value )
111- return
112-
113- try {
114- isCapturing .value = true
115-
116- // Capture the background element
117- const canvas = await html2canvas (imageRef .value , {
118- allowTaint: true ,
119- useCORS: true ,
120- backgroundColor: null ,
121- scale: 0.5 , // Reduce quality for performance
122- height: 100 , // Only capture top portion
123- width: imageRef .value .offsetWidth ,
124- logging: false ,
65+ const result = await colorFromElement (imageRef .value , {
66+ mode: ' both' ,
67+ vibrant: {
68+ imageSource: images .value [0 ],
69+ sampleTopRatio: 0.2 ,
70+ },
71+ html2canvas: {
72+ region: {
73+ x: 0 ,
74+ y: 0 ,
75+ width: imageRef .value .offsetWidth ,
76+ height: 100 ,
77+ },
78+ sampleHeight: 20 ,
79+ sampleStride: 10 ,
80+ scale: 0.5 ,
81+ backgroundColor: null ,
82+ allowTaint: true ,
83+ useCORS: true ,
84+ },
12585 })
12686
127- // Display captured canvas for debugging
128- if (canvasRef .value ) {
87+ extractedColors .value = result .vibrant ?.palette ?? []
88+ dominantColor .value = result .vibrant ?.dominant ?? ' '
89+ topEdgeColors .value = result .html2canvas ?.average ?? ' '
90+
91+ if (canvasRef .value && result .html2canvas ?.canvas ) {
12992 const ctx = canvasRef .value .getContext (' 2d' )
13093 if (ctx ) {
131- canvasRef .value .width = canvas .width
132- canvasRef .value .height = canvas .height
133- ctx .drawImage (canvas , 0 , 0 )
94+ canvasRef .value .width = result . html2canvas . canvas .width
95+ canvasRef .value .height = result . html2canvas . canvas .height
96+ ctx .drawImage (result . html2canvas . canvas , 0 , 0 )
13497 }
13598 }
13699
137- // Sample colors from top edge
138- const ctx = canvas .getContext (' 2d' )
139- if (ctx ) {
140- const imageData = ctx .getImageData (0 , 0 , canvas .width , 20 ) // Top 20px
141- const colors: Color [] = []
142-
143- // Sample every 10th pixel to get representative colors
144- for (let i = 0 ; i < imageData .data .length ; i += 40 ) { // RGBA = 4 bytes, sample every 10 pixels
145- const r = imageData .data [i ]
146- const g = imageData .data [i + 1 ]
147- const b = imageData .data [i + 2 ]
148- const a = imageData .data [i + 3 ]
149-
150- if (a > 0 ) { // Skip transparent pixels
151- colors .push ({ mode: ' rgb' , r , g , b })
152- }
153- }
154-
155- if (colors .length > 0 ) {
156- const c = average (colors as [Color , ... Color []])
157- topEdgeColors .value = ` rgb(${c .r }, ${c .g }, ${c .b }) `
158- }
159- }
100+ await updateThemeColor ()
160101 }
161102 catch (error ) {
162- console .error (' Canvas capture failed:' , error )
103+ console .error (' Color extraction failed:' , error )
163104 }
164105 finally {
165106 isCapturing .value = false
@@ -169,14 +110,12 @@ async function captureTopEdgeColors() {
169110// Auto-extract colors on mount
170111onMounted (async () => {
171112 await nextTick ()
172- await extractColorsFromImage ()
173- await captureTopEdgeColors ()
113+ await refreshColors ()
174114})
175115
176116watch (images , async () => {
177117 await nextTick ()
178- await extractColorsFromImage ()
179- await captureTopEdgeColors ()
118+ await refreshColors ()
180119})
181120
182121onUnmounted (() => {
0 commit comments