@@ -17,8 +17,6 @@ namespace System.Drawing;
17
17
[ TypeForwardedFrom ( AssemblyRef . SystemDrawing ) ]
18
18
public sealed unsafe partial class Icon : MarshalByRefObject , ICloneable , IDisposable , ISerializable , IIcon
19
19
{
20
- private static int s_bitDepth ;
21
-
22
20
// The PNG signature is specified at https://www.w3.org/TR/PNG/#5PNG-file-signature
23
21
private const int PNGSignature1 = 137 + ( 'P' << 8 ) + ( 'N' << 16 ) + ( 'G' << 24 ) ;
24
22
private const int PNGSignature2 = 13 + ( 10 << 8 ) + ( 26 << 16 ) + ( 10 << 24 ) ;
@@ -398,6 +396,40 @@ public static Icon FromHandle(IntPtr handle)
398
396
return new Icon ( ( HICON ) handle ) ;
399
397
}
400
398
399
+ // Creates RT_GROUP_ICON resource directory from ICO file data
400
+ private static byte [ ] CreateIconResourceDirectory ( ReadOnlySpan < byte > iconData )
401
+ {
402
+ var reader = new SpanReader < byte > ( iconData ) ;
403
+
404
+ reader . TryRead ( out ICONDIR icoDir ) ;
405
+ reader . TryRead ( icoDir . idCount , out ReadOnlySpan < ICONDIRENTRY > icoEntries ) ;
406
+
407
+ byte [ ] resDir = GC . AllocateUninitializedArray < byte > ( sizeof ( GRPICONDIR ) + icoDir . idCount * sizeof ( GRPICONDIRENTRY ) ) ;
408
+ Span < byte > resDirSpan = resDir ;
409
+
410
+ MemoryMarshal . Write ( resDirSpan , new GRPICONDIR { Reserved = 0 , ResType = icoDir . idType , ResCount = icoDir . idCount } ) ;
411
+
412
+ Span < GRPICONDIRENTRY > gpEntries = MemoryMarshal . Cast < byte , GRPICONDIRENTRY > ( resDirSpan [ sizeof ( GRPICONDIR ) ..] ) ;
413
+
414
+ for ( int i = 0 ; i < icoDir . idCount ; i ++ )
415
+ {
416
+ ref readonly ICONDIRENTRY icoEntry = ref icoEntries [ i ] ;
417
+ gpEntries [ i ] = new GRPICONDIRENTRY
418
+ {
419
+ Width = icoEntry . bWidth ,
420
+ Height = icoEntry . bHeight ,
421
+ ColorCount = icoEntry . bColorCount ,
422
+ Reserved = icoEntry . bReserved ,
423
+ Planes = icoEntry . wPlanes ,
424
+ BitCount = icoEntry . wBitCount ,
425
+ BytesInRes = icoEntry . dwBytesInRes ,
426
+ IconCursorId = ( ushort ) i
427
+ } ;
428
+ }
429
+
430
+ return resDir ;
431
+ }
432
+
401
433
// Initializes this Image object. This is identical to calling the image's
402
434
// constructor with picture, but this allows non-constructor initialization,
403
435
// which may be necessary in some instances.
@@ -408,155 +440,48 @@ private void Initialize(int width, int height)
408
440
throw new InvalidOperationException ( SR . Format ( SR . IllegalState , GetType ( ) . Name ) ) ;
409
441
}
410
442
411
- SpanReader < byte > reader = new ( _iconData ) ;
412
- if ( ! reader . TryRead ( out ICONDIR dir )
413
- || dir . idReserved != 0
414
- || dir . idType != 1
415
- || dir . idCount == 0 )
416
- {
417
- throw new ArgumentException ( SR . Format ( SR . InvalidPictureType , "picture" , nameof ( Icon ) ) ) ;
418
- }
443
+ var reader = new SpanReader < byte > ( _iconData ) ;
419
444
420
- // Get the correct width and height.
421
- if ( width == 0 )
445
+ if ( ! reader . TryRead ( out ICONDIR icoDir ) || icoDir . idReserved != 0 || icoDir . idType != 1 || icoDir . idCount < 1 )
422
446
{
423
- width = PInvokeCore . GetSystemMetrics ( SYSTEM_METRICS_INDEX . SM_CXICON ) ;
424
- }
425
-
426
- if ( height == 0 )
427
- {
428
- height = PInvokeCore . GetSystemMetrics ( SYSTEM_METRICS_INDEX . SM_CYICON ) ;
429
- }
430
-
431
- if ( s_bitDepth == 0 )
432
- {
433
- using var hdc = GetDcScope . ScreenDC ;
434
- s_bitDepth = PInvokeCore . GetDeviceCaps ( hdc , GET_DEVICE_CAPS_INDEX . BITSPIXEL ) ;
435
- s_bitDepth *= PInvokeCore . GetDeviceCaps ( hdc , GET_DEVICE_CAPS_INDEX . PLANES ) ;
436
-
437
- // If the bit depth is 8, make it 4 because windows does not
438
- // choose a 256 color icon if the display is running in 256 color mode
439
- // due to palette flicker.
440
- if ( s_bitDepth == 8 )
441
- {
442
- s_bitDepth = 4 ;
443
- }
447
+ throw new ArgumentException ( SR . Format ( SR . InvalidPictureType , "picture" , nameof ( Icon ) ) ) ;
444
448
}
445
449
446
- byte bestWidth = 0 ;
447
- byte bestHeight = 0 ;
448
-
449
- if ( ! reader . TryRead ( dir . idCount , out ReadOnlySpan < ICONDIRENTRY > entries ) )
450
+ if ( ! reader . TryRead ( icoDir . idCount , out ReadOnlySpan < ICONDIRENTRY > icoEntries ) )
450
451
{
451
452
throw new ArgumentException ( SR . Format ( SR . InvalidPictureType , "picture" , nameof ( Icon ) ) ) ;
452
453
}
453
454
454
- foreach ( ICONDIRENTRY entry in entries )
455
- {
456
- bool fUpdateBestFit = false ;
457
- uint iconBitDepth ;
458
- if ( entry . bColorCount != 0 )
459
- {
460
- iconBitDepth = 4 ;
461
- if ( entry . bColorCount < 0x10 )
462
- {
463
- iconBitDepth = 1 ;
464
- }
465
- }
466
- else
467
- {
468
- iconBitDepth = entry . wBitCount ;
469
- }
455
+ // Prepare fake RT_GROUP_ICON resource for LookupIconIdFromDirectoryEx call.
456
+ var resDir = CreateIconResourceDirectory ( _iconData ) ;
470
457
471
- // If it looks like if nothing is specified at this point then set the bits per pixel to 8.
472
- if ( iconBitDepth == 0 )
473
- {
474
- iconBitDepth = 8 ;
475
- }
476
-
477
- // Windows rules for specifying an icon:
478
- //
479
- // 1. The icon with the closest size match.
480
- // 2. For matching sizes, the image with the closest bit depth.
481
- // 3. If there is no color depth match, the icon with the closest color depth that does not exceed the display.
482
- // 4. If all icon color depth > display, lowest color depth is chosen.
483
- // 5. color depth of > 8bpp are all equal.
484
- // 6. Never choose an 8bpp icon on an 8bpp system.
485
-
486
- if ( _bestBytesInRes == 0 )
487
- {
488
- fUpdateBestFit = true ;
489
- }
490
- else
491
- {
492
- int bestDelta = Math . Abs ( bestWidth - width ) + Math . Abs ( bestHeight - height ) ;
493
- int thisDelta = Math . Abs ( entry . bWidth - width ) + Math . Abs ( entry . bHeight - height ) ;
494
-
495
- if ( ( thisDelta < bestDelta )
496
- || ( thisDelta == bestDelta
497
- && ( ( iconBitDepth <= s_bitDepth && iconBitDepth > _bestBitDepth )
498
- || ( _bestBitDepth > s_bitDepth && iconBitDepth < _bestBitDepth ) ) ) )
499
- {
500
- fUpdateBestFit = true ;
501
- }
502
- }
503
-
504
- if ( fUpdateBestFit )
505
- {
506
- bestWidth = entry . bWidth ;
507
- bestHeight = entry . bHeight ;
508
- _bestImageOffset = entry . dwImageOffset ;
509
- _bestBytesInRes = entry . dwBytesInRes ;
510
- _bestBitDepth = iconBitDepth ;
511
- }
512
- }
513
-
514
- if ( _bestImageOffset > int . MaxValue )
458
+ int id = 0 ;
459
+ fixed ( byte * b = resDir )
515
460
{
516
- throw new ArgumentException ( SR . Format ( SR . InvalidPictureType , "picture" , nameof ( Icon ) ) ) ;
461
+ id = PInvoke . LookupIconIdFromDirectoryEx ( b , fIcon : true , width , height , IMAGE_FLAGS . LR_DEFAULTSIZE ) ;
517
462
}
518
463
519
- if ( _bestBytesInRes > int . MaxValue )
520
- {
521
- throw new Win32Exception ( ( int ) WIN32_ERROR . ERROR_INVALID_PARAMETER ) ;
522
- }
464
+ var entry = icoEntries [ id ] ;
465
+ _bestImageOffset = entry . dwImageOffset ;
466
+ _bestBytesInRes = entry . dwBytesInRes ;
467
+ _bestBitDepth = entry . bColorCount != 0 ? ( entry . bColorCount < 16 ? 1u : 4u )
468
+ : ( entry . wBitCount > 0 ? entry . wBitCount : 8u ) ;
523
469
524
- uint endOffset ;
525
- try
526
- {
527
- endOffset = checked ( _bestImageOffset + _bestBytesInRes ) ;
528
- }
529
- catch ( OverflowException )
470
+ if ( _bestBytesInRes > int . MaxValue )
530
471
{
531
472
throw new Win32Exception ( ( int ) WIN32_ERROR . ERROR_INVALID_PARAMETER ) ;
532
473
}
533
474
534
- if ( endOffset > _iconData . Length )
475
+ if ( checked ( _bestImageOffset + _bestBytesInRes ) > _iconData . Length )
535
476
{
536
477
throw new ArgumentException ( SR . Format ( SR . InvalidPictureType , "picture" , nameof ( Icon ) ) ) ;
537
478
}
538
479
539
- // Copy the bytes into an aligned buffer if needed.
540
-
541
- ReadOnlySpan < byte > bestImage = reader . Span . Slice ( ( int ) _bestImageOffset , ( int ) _bestBytesInRes ) ;
542
-
543
- if ( ( _bestImageOffset % sizeof ( nint ) ) != 0 )
480
+ // CreateIconFromResourceEx expects an aligned RT_ICON resource buffer.
481
+ // Just copy best image bytes into a new aligned buffer.
482
+ fixed ( byte * b = _iconData . AsSpan ( ) . Slice ( ( int ) _bestImageOffset , ( int ) _bestBytesInRes ) . ToArray ( ) )
544
483
{
545
- // Beginning of icon's content is misaligned.
546
- using BufferScope < byte > alignedBuffer = new ( ( int ) _bestBytesInRes ) ;
547
- bestImage . CopyTo ( alignedBuffer . AsSpan ( ) ) ;
548
-
549
- fixed ( byte * b = alignedBuffer )
550
- {
551
- _handle = PInvoke . CreateIconFromResourceEx ( b , ( uint ) bestImage . Length , fIcon : true , 0x00030000 , 0 , 0 , 0 ) ;
552
- }
553
- }
554
- else
555
- {
556
- fixed ( byte * b = bestImage )
557
- {
558
- _handle = PInvoke . CreateIconFromResourceEx ( b , ( uint ) bestImage . Length , fIcon : true , 0x00030000 , 0 , 0 , 0 ) ;
559
- }
484
+ _handle = PInvoke . CreateIconFromResourceEx ( b , _bestBytesInRes , fIcon : true , 0x00030000 , 0 , 0 , IMAGE_FLAGS . LR_DEFAULTCOLOR ) ;
560
485
}
561
486
562
487
if ( _handle . IsNull )
0 commit comments