IconHelper.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. using System;
  2. using System.Collections.ObjectModel;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.Runtime.InteropServices;
  5. using System.Security;
  6. using System.Windows;
  7. using System.Windows.Media;
  8. using System.Windows.Media.Imaging;
  9. using HandyControl.Tools.Interop;
  10. namespace HandyControl.Tools;
  11. [SuppressMessage("ReSharper", "ArrangeRedundantParentheses")]
  12. [SuppressMessage("ReSharper", "IntVariableOverflowInUncheckedContext")]
  13. internal static class IconHelper
  14. {
  15. private static Size SmallIconSize;
  16. private static Size IconSize;
  17. private static int SystemBitDepth;
  18. [SecurityCritical, SecuritySafeCritical]
  19. public static void GetIconHandlesFromImageSource(ImageSource image, out IconHandle largeIconHandle, out IconHandle smallIconHandle)
  20. {
  21. EnsureSystemMetrics();
  22. largeIconHandle = CreateIconHandleFromImageSource(image, IconSize);
  23. smallIconHandle = CreateIconHandleFromImageSource(image, SmallIconSize);
  24. }
  25. [SecurityCritical]
  26. public static IconHandle CreateIconHandleFromImageSource(ImageSource image, Size size)
  27. {
  28. EnsureSystemMetrics();
  29. var asGoodAsItGets = false;
  30. var bf = image as BitmapFrame;
  31. if (bf?.Decoder?.Frames != null)
  32. {
  33. bf = GetBestMatch(bf.Decoder.Frames, size);
  34. asGoodAsItGets = bf.Decoder is IconBitmapDecoder || bf.PixelWidth == (int) size.Width && bf.PixelHeight == (int) size.Height;
  35. image = bf;
  36. }
  37. if (!asGoodAsItGets)
  38. {
  39. bf = BitmapFrame.Create(GenerateBitmapSource(image, size));
  40. }
  41. return CreateIconHandleFromBitmapFrame(bf);
  42. }
  43. [SecurityCritical]
  44. private static IconHandle CreateIconHandleFromBitmapFrame(BitmapFrame sourceBitmapFrame)
  45. {
  46. BitmapSource bitmapSource = sourceBitmapFrame;
  47. if (bitmapSource.Format != PixelFormats.Bgra32 && bitmapSource.Format != PixelFormats.Pbgra32)
  48. {
  49. bitmapSource = new FormatConvertedBitmap(bitmapSource, PixelFormats.Bgra32, null, 0.0);
  50. }
  51. var w = bitmapSource.PixelWidth;
  52. var h = bitmapSource.PixelHeight;
  53. var bpp = bitmapSource.Format.BitsPerPixel;
  54. var stride = (bpp * w + 31) / 32 * 4;
  55. var sizeCopyPixels = stride * h;
  56. var xor = new byte[sizeCopyPixels];
  57. bitmapSource.CopyPixels(xor, stride, 0);
  58. return CreateIconCursor(xor, w, h, 0, 0, true);
  59. }
  60. [SecurityCritical]
  61. internal static IconHandle CreateIconCursor(byte[] colorArray, int width, int height, int xHotspot, int yHotspot, bool isIcon)
  62. {
  63. BitmapHandle colorBitmap = null;
  64. BitmapHandle maskBitmap = null;
  65. try
  66. {
  67. var bi = new InteropValues.BITMAPINFO(width, -height, 32)
  68. {
  69. biCompression = InteropValues.BI_RGB
  70. };
  71. var bits = IntPtr.Zero;
  72. colorBitmap = InteropMethods.CreateDIBSection(new HandleRef(null, IntPtr.Zero), ref bi, InteropValues.DIB_RGB_COLORS, ref bits, null, 0);
  73. if (colorBitmap.IsInvalid || bits == IntPtr.Zero)
  74. {
  75. return IconHandle.GetInvalidIcon();
  76. }
  77. Marshal.Copy(colorArray, 0, bits, colorArray.Length);
  78. var maskArray = GenerateMaskArray(width, height, colorArray);
  79. maskBitmap = InteropMethods.CreateBitmap(width, height, 1, 1, maskArray);
  80. if (maskBitmap.IsInvalid)
  81. {
  82. return IconHandle.GetInvalidIcon();
  83. }
  84. var iconInfo = new InteropValues.ICONINFO
  85. {
  86. fIcon = isIcon,
  87. xHotspot = xHotspot,
  88. yHotspot = yHotspot,
  89. hbmMask = maskBitmap,
  90. hbmColor = colorBitmap
  91. };
  92. return InteropMethods.CreateIconIndirect(iconInfo);
  93. }
  94. finally
  95. {
  96. colorBitmap?.Dispose();
  97. maskBitmap?.Dispose();
  98. }
  99. }
  100. private static byte[] GenerateMaskArray(int width, int height, byte[] colorArray)
  101. {
  102. var nCount = width * height;
  103. var bytesPerScanLine = AlignToBytes(width, 2) / 8;
  104. var bitsMask = new byte[bytesPerScanLine * height];
  105. for (var i = 0; i < nCount; i++)
  106. {
  107. var hPos = i % width;
  108. var vPos = i / width;
  109. var byteIndex = hPos / 8;
  110. var offsetBit = (byte) (0x80 >> (hPos % 8));
  111. if (colorArray[i * 4 + 3] == 0x00)
  112. {
  113. bitsMask[byteIndex + bytesPerScanLine * vPos] |= offsetBit;
  114. }
  115. else
  116. {
  117. bitsMask[byteIndex + bytesPerScanLine * vPos] &= (byte) ~offsetBit;
  118. }
  119. if (hPos == width - 1 && width == 8)
  120. {
  121. bitsMask[1 + bytesPerScanLine * vPos] = 0xff;
  122. }
  123. }
  124. return bitsMask;
  125. }
  126. internal static int AlignToBytes(double original, int nBytesCount)
  127. {
  128. var nBitsCount = 8 << (nBytesCount - 1);
  129. return ((int) Math.Ceiling(original) + (nBitsCount - 1)) / nBitsCount * nBitsCount;
  130. }
  131. private static BitmapSource GenerateBitmapSource(ImageSource img, Size renderSize)
  132. {
  133. var drawingDimensions = new Rect(0, 0, renderSize.Width, renderSize.Height);
  134. var renderRatio = renderSize.Width / renderSize.Height;
  135. var aspectRatio = img.Width / img.Height;
  136. if (img.Width <= renderSize.Width && img.Height <= renderSize.Height)
  137. {
  138. drawingDimensions = new Rect((renderSize.Width - img.Width) / 2, (renderSize.Height - img.Height) / 2, img.Width, img.Height);
  139. }
  140. else if (renderRatio > aspectRatio)
  141. {
  142. var scaledRenderWidth = (img.Width / img.Height) * renderSize.Width;
  143. drawingDimensions = new Rect((renderSize.Width - scaledRenderWidth) / 2, 0, scaledRenderWidth, renderSize.Height);
  144. }
  145. else if (renderRatio < aspectRatio)
  146. {
  147. var scaledRenderHeight = img.Height / img.Width * renderSize.Height;
  148. drawingDimensions = new Rect(0, (renderSize.Height - scaledRenderHeight) / 2, renderSize.Width, scaledRenderHeight);
  149. }
  150. var dv = new DrawingVisual();
  151. var dc = dv.RenderOpen();
  152. dc.DrawImage(img, drawingDimensions);
  153. dc.Close();
  154. var bmp = new RenderTargetBitmap((int) renderSize.Width, (int) renderSize.Height, 96, 96, PixelFormats.Pbgra32);
  155. bmp.Render(dv);
  156. return bmp;
  157. }
  158. private static BitmapFrame GetBestMatch(ReadOnlyCollection<BitmapFrame> frames, Size size)
  159. {
  160. var bestScore = int.MaxValue;
  161. var bestBpp = 0;
  162. var bestIndex = 0;
  163. var isBitmapIconDecoder = frames[0].Decoder is IconBitmapDecoder;
  164. for (var i = 0; i < frames.Count && bestScore != 0; ++i)
  165. {
  166. var currentIconBitDepth = isBitmapIconDecoder ? frames[i].Thumbnail.Format.BitsPerPixel : frames[i].Format.BitsPerPixel;
  167. if (currentIconBitDepth == 0)
  168. {
  169. currentIconBitDepth = 8;
  170. }
  171. var score = MatchImage(frames[i], size, currentIconBitDepth);
  172. if (score < bestScore)
  173. {
  174. bestIndex = i;
  175. bestBpp = currentIconBitDepth;
  176. bestScore = score;
  177. }
  178. else if (score == bestScore)
  179. {
  180. if (bestBpp < currentIconBitDepth)
  181. {
  182. bestIndex = i;
  183. bestBpp = currentIconBitDepth;
  184. }
  185. }
  186. }
  187. return frames[bestIndex];
  188. }
  189. private static int MatchImage(BitmapFrame frame, Size size, int bpp)
  190. {
  191. var score = 2 * MyAbs(bpp, SystemBitDepth, false) +
  192. MyAbs(frame.PixelWidth, (int) size.Width, true) +
  193. MyAbs(frame.PixelHeight, (int) size.Height, true);
  194. return score;
  195. }
  196. private static int MyAbs(int valueHave, int valueWant, bool fPunish)
  197. {
  198. var diff = (valueHave - valueWant);
  199. if (diff < 0)
  200. {
  201. diff = (fPunish ? -2 : -1) * diff;
  202. }
  203. return diff;
  204. }
  205. [SecurityCritical, SecuritySafeCritical]
  206. private static void EnsureSystemMetrics()
  207. {
  208. if (SystemBitDepth == 0)
  209. {
  210. var hdcDesktop = new HandleRef(null, InteropMethods.GetDC(new HandleRef()));
  211. try
  212. {
  213. var sysBitDepth = InteropMethods.GetDeviceCaps(hdcDesktop, InteropValues.BITSPIXEL);
  214. sysBitDepth *= InteropMethods.GetDeviceCaps(hdcDesktop, InteropValues.PLANES);
  215. if (sysBitDepth == 8)
  216. {
  217. sysBitDepth = 4;
  218. }
  219. var cxSmallIcon = InteropMethods.GetSystemMetrics(InteropValues.SM.CXSMICON);
  220. var cySmallIcon = InteropMethods.GetSystemMetrics(InteropValues.SM.CYSMICON);
  221. var cxIcon = InteropMethods.GetSystemMetrics(InteropValues.SM.CXICON);
  222. var cyIcon = InteropMethods.GetSystemMetrics(InteropValues.SM.CYICON);
  223. SmallIconSize = new Size(cxSmallIcon, cySmallIcon);
  224. IconSize = new Size(cxIcon, cyIcon);
  225. SystemBitDepth = sysBitDepth;
  226. }
  227. finally
  228. {
  229. InteropMethods.ReleaseDC(new HandleRef(), hdcDesktop);
  230. }
  231. }
  232. }
  233. [SecurityCritical, SecuritySafeCritical]
  234. public static void GetDefaultIconHandles(out IconHandle largeIconHandle, out IconHandle smallIconHandle)
  235. {
  236. largeIconHandle = null;
  237. smallIconHandle = null;
  238. SecurityHelper.DemandUIWindowPermission();
  239. var iconModuleFile = InteropMethods.GetModuleFileName(new HandleRef());
  240. InteropMethods.ExtractIconEx(iconModuleFile, 0, out largeIconHandle, out smallIconHandle, 1);
  241. }
  242. }