@@ -25,4 +25,174 @@ export function fileToBase64(file: File): Promise<string> {
2525 reader . onerror = reject
2626 reader . readAsDataURL ( file )
2727 } )
28+ }
29+
30+ /**
31+ * Create File object from img DOM element using Canvas API
32+ */
33+ export async function getFileFromImageUrl (
34+ url : string ,
35+ filename ?: string ,
36+ quality ?: number
37+ ) : Promise < File > {
38+ return new Promise ( ( resolve , reject ) => {
39+ const imgElement = document . createElement ( "img" )
40+ imgElement . src = url
41+
42+ // Create canvas element
43+ const canvas = document . createElement ( "canvas" )
44+ const ctx = canvas . getContext ( "2d" )
45+
46+ if ( ! ctx ) {
47+ reject ( new Error ( "Cannot get 2D context" ) )
48+ return
49+ }
50+
51+ // Check if image source is cross-origin
52+ const isCrossOrigin = ( src : string ) : boolean => {
53+ try {
54+ const imgUrl = new URL ( src , window . location . href )
55+ const currentUrl = new URL ( window . location . href )
56+ return imgUrl . origin !== currentUrl . origin
57+ } catch {
58+ return false
59+ }
60+ }
61+
62+ // Create a new image element to ensure proper CORS handling
63+ const createCORSImage = ( ) : Promise < HTMLImageElement > => {
64+ return new Promise ( ( resolveImg , rejectImg ) => {
65+ const newImg = new Image ( )
66+
67+ // Set crossOrigin before setting src for cross-origin images
68+ if ( isCrossOrigin ( imgElement . src ) ) {
69+ newImg . crossOrigin = "anonymous"
70+ }
71+
72+ newImg . onload = ( ) => resolveImg ( newImg )
73+ newImg . onerror = ( ) => rejectImg ( new Error ( "Failed to load image with CORS" ) )
74+
75+ // Set src after crossOrigin
76+ newImg . src = imgElement . src
77+ } )
78+ }
79+
80+ // Process image when ready
81+ const processImage = async ( ) => {
82+ try {
83+ let imageToUse = imgElement
84+
85+ // For cross-origin images, create a new image with proper CORS
86+ if ( isCrossOrigin ( imgElement . src ) ) {
87+ try {
88+ imageToUse = await createCORSImage ( )
89+ } catch ( corsError ) {
90+ // If CORS fails, try alternative methods
91+ console . warn ( "CORS image loading failed, trying alternative methods:" , corsError )
92+ // Fall back to fetch method for cross-origin images
93+ return await getFileFromImageUsingFetch ( imgElement . src , filename )
94+ }
95+ }
96+
97+ // Set canvas dimensions to match image natural size
98+ canvas . width = imageToUse . naturalWidth
99+ canvas . height = imageToUse . naturalHeight
100+
101+ // Draw image onto canvas
102+ ctx . drawImage ( imageToUse , 0 , 0 )
103+
104+ // Convert canvas to blob
105+ canvas . toBlob ( ( blob ) => {
106+ if ( blob ) {
107+ // Create File object from blob
108+ const file = new File (
109+ [ blob ] ,
110+ filename || "image.png" ,
111+ {
112+ type : blob . type ,
113+ lastModified : Date . now ( )
114+ }
115+ )
116+ resolve ( file )
117+ } else {
118+ reject ( new Error ( "Failed to convert canvas to blob" ) )
119+ }
120+ } , "image/png" , quality || 0.9 )
121+
122+ } catch ( error ) {
123+ // If canvas method fails due to security, try fetch method
124+ if ( error instanceof DOMException && error . name === "SecurityError" ) {
125+ console . warn ( "Canvas security error, falling back to fetch method:" , error )
126+ try {
127+ const file = await getFileFromImageUsingFetch ( imgElement . src , filename )
128+ resolve ( file )
129+ } catch ( fetchError ) {
130+ const fetchErrorMessage = fetchError instanceof Error ? fetchError . message : String ( fetchError )
131+ reject ( new Error ( `Both canvas and fetch methods failed: ${ error . message } , ${ fetchErrorMessage } ` ) )
132+ }
133+ } else {
134+ reject ( error )
135+ }
136+ }
137+ }
138+
139+ // Check if image is already loaded
140+ if ( imgElement . complete && imgElement . naturalHeight !== 0 ) {
141+ processImage ( )
142+ } else {
143+ // Wait for image to load
144+ imgElement . onload = processImage
145+ imgElement . onerror = ( ) => reject ( new Error ( "Image failed to load" ) )
146+ }
147+ } )
148+ }
149+
150+ /**
151+ * Alternative method using fetch for cross-origin images
152+ */
153+ async function getFileFromImageUsingFetch (
154+ imageSrc : string ,
155+ filename ?: string
156+ ) : Promise < File > {
157+ try {
158+ const response = await fetch ( imageSrc )
159+
160+ if ( ! response . ok ) {
161+ throw new Error ( `HTTP error! status: ${ response . status } ` )
162+ }
163+
164+ const blob = await response . blob ( )
165+ const file = new File (
166+ [ blob ] ,
167+ filename || getFilenameFromUrl ( imageSrc ) ,
168+ {
169+ type : blob . type || "image/png" ,
170+ lastModified : Date . now ( )
171+ }
172+ )
173+
174+ return file
175+ } catch ( error ) {
176+ const errorMessage = error instanceof Error ? error . message : String ( error )
177+ throw new Error ( `Fetch method failed: ${ errorMessage } ` )
178+ }
179+ }
180+
181+ /**
182+ * Get filename from URL
183+ */
184+ function getFilenameFromUrl ( url : string ) : string {
185+ try {
186+ const pathname = new URL ( url ) . pathname
187+ const filename = pathname . split ( "/" ) . pop ( ) || "image"
188+
189+ // Add extension if missing
190+ if ( ! filename . includes ( "." ) ) {
191+ return filename + ".png"
192+ }
193+
194+ return filename
195+ } catch {
196+ return "image.png"
197+ }
28198}
0 commit comments