@@ -3,7 +3,8 @@ import { ImageAsset } from '../image-asset';
33import { path as fsPath , knownFolders } from '../file-system' ;
44import { isFileOrResourcePath } from '../utils' ;
55import { Trace } from '../trace' ;
6-
6+ import { isMainThread , dispatchToMainThread } from '../utils/mainthread-helper' ;
7+ import { requestInternal as httpRequest } from '../http/http-request-internal' ;
78export { isFileOrResourcePath } ;
89
910// Cached constants for resource name handling
@@ -20,13 +21,9 @@ function bitmapFromUriAsync(uriStr: string): Promise<Windows.UI.Xaml.Media.Imagi
2021 try {
2122 const bmp = makeBitmapImage ( ) ;
2223 const onOpened = ( ) => {
23- bmp . ImageOpened = null ;
24- bmp . ImageFailed = null ;
2524 resolve ( bmp ) ;
2625 } ;
2726 const onFailed = ( s : any , e : any ) => {
28- bmp . ImageOpened = null ;
29- bmp . ImageFailed = null ;
3027 reject ( e || new Error ( 'Image load failed' ) ) ;
3128 } ;
3229 bmp . ImageOpened = onOpened ;
@@ -58,28 +55,34 @@ function bytesToStream(bytes: Uint8Array): Promise<any> {
5855
5956function bitmapFromStream ( stream : any ) : Promise < any > {
6057 return new Promise ( ( resolve , reject ) => {
61- try {
62- const bmp = makeBitmapImage ( ) ;
63- NSWinRT . toPromise ( bmp . SetSourceAsync ( stream ) ) . then (
64- ( ) => resolve ( bmp ) ,
65- reject
66- ) ;
67- } catch ( e ) { reject ( e ) ; }
58+ // BitmapImage has UI thread affinity — create and load on the UI thread.
59+ const create = ( ) => {
60+ try {
61+ ( stream as any ) . Seek ( 0 ) ;
62+ const bmp = makeBitmapImage ( ) ;
63+ NSWinRT . toPromise ( bmp . SetSourceAsync ( stream ) ) . then (
64+ ( ) => resolve ( bmp ) ,
65+ reject
66+ ) ;
67+ } catch ( e ) { reject ( e ) ; }
68+ } ;
69+ if ( isMainThread ( ) ) {
70+ create ( ) ;
71+ } else {
72+ dispatchToMainThread ( create ) ;
73+ }
6874 } ) ;
6975}
7076
7177function bitmapFromBytesAsync ( bytes : Uint8Array ) : Promise < any > {
72- try { console . log ( `[Image.Windows] bitmapFromBytesAsync: bytes.length=${ bytes ?. length ?? 0 } ` ) ; } catch ( _e ) { }
7378 return bytesToStream ( bytes ) . then ( ( stream ) => {
74- try { console . log ( `[Image.Windows] bitmapFromBytesAsync: streamReady` ) ; } catch ( _e ) { }
7579 return bitmapFromStream ( stream ) ;
7680 } ) ;
7781}
7882
7983function fetchBytesAsync ( url : string ) : Promise < Uint8Array > {
8084 return new Promise ( ( resolve , reject ) => {
8185 try {
82- try { console . log ( `[Image.Windows] fetchBytesAsync: fetching ${ url } ` ) ; } catch ( _e ) { }
8386 const httpClient = new Windows . Web . Http . HttpClient ( ) ;
8487 const uri = new Windows . Foundation . Uri ( url ) ;
8588 NSWinRT . toPromise ( httpClient . GetBufferAsync ( uri ) ) . then (
@@ -88,13 +91,12 @@ function fetchBytesAsync(url: string): Promise<Uint8Array> {
8891 const reader = Windows . Storage . Streams . DataReader . FromBuffer ( buffer ) ;
8992 const bytes = new Uint8Array ( buffer . Length ) ;
9093 reader . ReadBytes ( bytes as never ) ;
91- try { console . log ( `[Image.Windows] fetchBytesAsync: fetched ${ bytes . length } bytes for ${ url } ` ) ; } catch ( _e ) { }
9294 resolve ( bytes ) ;
93- } catch ( e ) { try { console . log ( `[Image.Windows] fetchBytesAsync: error reading buffer -> ${ e } ` ) ; } catch ( _ee ) { } ; reject ( e ) ; }
95+ } catch ( e ) { reject ( e ) ; }
9496 } ,
95- ( err : any ) => { try { console . log ( `[Image.Windows] fetchBytesAsync: http error -> ${ err } ` ) ; } catch ( _e ) { } ; reject ( err ) ; }
97+ ( err : any ) => { reject ( err ) ; }
9698 ) ;
97- } catch ( e ) { try { console . log ( `[Image.Windows] fetchBytesAsync: exception -> ${ e } ` ) ; } catch ( _e ) { } ; reject ( e ) ; }
99+ } catch ( e ) { reject ( e ) ; }
98100 } ) ;
99101}
100102
@@ -198,60 +200,27 @@ export class ImageSource implements ImageSourceDefinition {
198200 }
199201
200202 static fromUrl ( url : string ) : Promise < ImageSource > {
201- // Use the JS-side HttpClient + bitmap creation path to avoid using
202- // the native ImageHelper URL loader which can surface unobserved
203- // Task exceptions in some network failure scenarios.
204- return fetchBytesAsync ( url ) . then ( ( bytes ) => {
205- return bitmapFromBytesAsync ( bytes ) . then ( ( bmp ) => {
206- const src = new ImageSource ( bmp ) ;
207- src . _rawBytes = bytes ;
208- src . _width = bmp . PixelWidth ?? 0 ;
209- src . _height = bmp . PixelHeight ?? 0 ;
210- return src ;
211- } ) ;
212- } ) . catch ( ( err ) => {
213- try { console . log ( `[Image.Windows] fromUrl: fetch/bitmap path failed -> ${ err } . Trying uri-based loader fallback for ${ url } ` ) ; } catch ( _e ) { }
214- // Try the native Uri loader as a fallback
215- return bitmapFromUriAsync ( url ) . then ( ( bmp ) => {
216- const src = new ImageSource ( bmp ) ;
217- src . _width = bmp . PixelWidth ?? 0 ;
218- src . _height = bmp . PixelHeight ?? 0 ;
219- return src ;
220- } ) ;
221- } ) ;
203+ return httpRequest ( { url, method : 'GET' } ) . then ( ( response ) => response . content . toNativeImage ( ) . then ( ( value ) => new ImageSource ( value ) ) ) ;
222204 }
223205
224206 static fromResourceSync ( name : string ) : ImageSource {
225207 if ( ! name ) return null as any ;
226208 try {
227- // Do not mutate the input `name`. Normalize into a local variable.
228209 const resourceName = name . startsWith ( RES_PREFIX ) ? name . slice ( RES_PREFIX . length ) : name ;
229- // Strip leading slashes using slice in a small loop (avoids regex)
230- let normalized = resourceName ;
231- while ( normalized . length && ( normalized . charAt ( 0 ) === '/' || normalized . charAt ( 0 ) === '\\' ) ) {
232- normalized = normalized . slice ( 1 ) ;
233- }
234- // Replace backslashes with forward slashes without regex
235- if ( normalized . indexOf ( '\\' ) !== - 1 ) {
236- normalized = normalized . split ( '\\' ) . join ( '/' ) ;
237- }
210+ const normalized = resourceName . replace ( LEADING_SLASHES_RE , '' ) . replace ( BACKSLASH_RE , '/' ) ;
238211 const exts = [ '' , '.png' , '.jpg' , '.jpeg' , '.gif' , '.bmp' , '.ico' , '.svg' , '.webp' ] ;
239-
240- // Always map to the packaged app assets so Windows picks scale-qualified files
241- for ( const ext of exts ) {
242- try {
243- const msAppx = `ms-appx:///Assets/${ normalized } ${ ext } ` ;
244- try { console . log ( `[Image.Windows] fromResourceSync: trying ${ msAppx } ` ) ; } catch ( _e ) { }
245- const src = ImageSource . fromFileSync ( msAppx ) ;
246- if ( src ) {
247- try { console . log ( `[Image.Windows] fromResourceSync: loaded ${ msAppx } ` ) ; } catch ( _e ) { }
248- return src ;
212+ const resolve = ( globalThis as any ) . __nsMsAppxResolve ;
213+ if ( typeof resolve === 'function' ) {
214+ for ( const ext of exts ) {
215+ const uri = `ms-appx:///Assets/${ normalized } ${ ext } ` ;
216+ if ( resolve ( uri ) != null ) {
217+ return ImageSource . fromFileSync ( uri ) ;
249218 }
250- } catch { /* ignore and try next */ }
219+ }
220+ return null as any ;
251221 }
252- try { console . log ( `[Image.Windows] fromResourceSync: no resource found for ${ name } ` ) ; } catch ( _e ) { }
253-
254- return null as any ;
222+ // Fallback when runtime hasn't registered the resolver yet
223+ return ImageSource . fromFileSync ( `ms-appx:///Assets/${ normalized } ` ) ;
255224 } catch {
256225 return null as any ;
257226 }
@@ -264,15 +233,16 @@ export class ImageSource implements ImageSourceDefinition {
264233 const normalized = resourceName . replace ( LEADING_SLASHES_RE , '' ) . replace ( BACKSLASH_RE , '/' ) ;
265234 const exts = [ '' , '.png' , '.jpg' , '.jpeg' , '.gif' , '.bmp' , '.ico' , '.svg' , '.webp' ] ;
266235
267- console . log ( `ImageSource.fromResource: loading resource '${ name } ' (normalized: '${ normalized } ')` ) ;
268236 for ( const ext of exts ) {
269- const msAppx = `ms-appx:///Assets/${ normalized } ${ ext } ` ;
270237 try {
271- const result = await ImageSource . fromFile ( msAppx ) ;
272- if ( result ) return result ;
273- } catch ( err ) {
274- try { console . log ( `[Image.Windows] fromResource: failed to load ${ msAppx } -> ${ err } ` ) ; } catch ( _e ) { }
275- }
238+ const bmp = await bitmapFromUriAsync ( `ms-appx:///Assets/${ normalized } ${ ext } ` ) ;
239+ if ( bmp ) {
240+ const src = new ImageSource ( bmp ) ;
241+ src . _width = bmp . PixelWidth ?? 0 ;
242+ src . _height = bmp . PixelHeight ?? 0 ;
243+ return src ;
244+ }
245+ } catch { /* try next extension */ }
276246 }
277247
278248 return null as any ;
@@ -288,8 +258,15 @@ export class ImageSource implements ImageSourceDefinition {
288258 bmp . ImageOpened = ( ) => {
289259 src . _width = bmp . PixelWidth ?? 0 ;
290260 src . _height = bmp . PixelHeight ?? 0 ;
261+ //@ts -ignore
291262 bmp . ImageOpened = null ;
292263 } ;
264+ bmp . ImageFailed = ( ) => {
265+ //@ts -ignore
266+ bmp . ImageOpened = null ;
267+ //@ts -ignore
268+ bmp . ImageFailed = null ;
269+ }
293270 bmp . UriSource = new Windows . Foundation . Uri ( fileUri ( filePath ) ) ;
294271 src . windows = bmp ;
295272 return src ;
@@ -354,6 +331,14 @@ export class ImageSource implements ImageSourceDefinition {
354331 }
355332 }
356333
334+ static fromSystemImageSync ( name : string ) : ImageSource {
335+ return ImageSource . fromResourceSync ( name ) ;
336+ }
337+
338+ static fromSystemImage ( name : string ) : Promise < ImageSource > {
339+ return ImageSource . fromResource ( name ) ;
340+ }
341+
357342 static fromFontIconCodeSync ( _source : string , _font : any , _color : any ) : ImageSource {
358343 return null as any ;
359344 }
0 commit comments