Files
HandyControl/HandyControl/Tools/Helper/IconHelper.cs
T
2019-03-25 23:50:57 +08:00

292 lines
10 KiB
C#

using System;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using HandyControl.Tools.Interop;
namespace HandyControl.Tools
{
internal static class IconHelper
{
private static Size _smallIconSize;
private static Size _iconSize;
private static int _systemBitDepth;
[SecurityCritical, SecuritySafeCritical]
public static void GetIconHandlesFromImageSource(ImageSource image, out IconHandle largeIconHandle, out IconHandle smallIconHandle)
{
EnsureSystemMetrics();
largeIconHandle = CreateIconHandleFromImageSource(image, _iconSize);
smallIconHandle = CreateIconHandleFromImageSource(image, _smallIconSize);
}
[SecurityCritical]
public static IconHandle CreateIconHandleFromImageSource(ImageSource image, Size size)
{
EnsureSystemMetrics();
var asGoodAsItGets = false;
var bf = image as BitmapFrame;
if (bf?.Decoder?.Frames != null)
{
bf = GetBestMatch(bf.Decoder.Frames, size);
asGoodAsItGets = bf.Decoder is IconBitmapDecoder || bf.PixelWidth == (int)size.Width && bf.PixelHeight == (int)size.Height;
image = bf;
}
if (!asGoodAsItGets)
{
bf = BitmapFrame.Create(GenerateBitmapSource(image, size));
}
return CreateIconHandleFromBitmapFrame(bf);
}
[SecurityCritical]
private static IconHandle CreateIconHandleFromBitmapFrame(BitmapFrame sourceBitmapFrame)
{
BitmapSource bitmapSource = sourceBitmapFrame;
if (bitmapSource.Format != PixelFormats.Bgra32 && bitmapSource.Format != PixelFormats.Pbgra32)
{
bitmapSource = new FormatConvertedBitmap(bitmapSource, PixelFormats.Bgra32, null, 0.0);
}
var w = bitmapSource.PixelWidth;
var h = bitmapSource.PixelHeight;
var bpp = bitmapSource.Format.BitsPerPixel;
var stride = (bpp * w + 31) / 32 * 4;
var sizeCopyPixels = stride * h;
var xor = new byte[sizeCopyPixels];
bitmapSource.CopyPixels(xor, stride, 0);
return CreateIconCursor(xor, w, h, 0, 0, true);
}
[SecurityCritical]
internal static IconHandle CreateIconCursor(byte[] colorArray, int width, int height, int xHotspot, int yHotspot, bool isIcon)
{
BitmapHandle colorBitmap = null;
BitmapHandle maskBitmap = null;
try
{
var bi = new BITMAPINFO(width, -height, 32)
{
bmiHeader_biCompression = NativeMethods.BI_RGB
};
var bits = IntPtr.Zero;
colorBitmap = UnsafeNativeMethods.CreateDIBSection(new HandleRef(null, IntPtr.Zero), ref bi, NativeMethods.DIB_RGB_COLORS, ref bits, null, 0);
if (colorBitmap.IsInvalid || bits == IntPtr.Zero)
{
return IconHandle.GetInvalidIcon();
}
Marshal.Copy(colorArray, 0, bits, colorArray.Length);
var maskArray = GenerateMaskArray(width, height, colorArray);
maskBitmap = UnsafeNativeMethods.CreateBitmap(width, height, 1, 1, maskArray);
if (maskBitmap.IsInvalid)
{
return IconHandle.GetInvalidIcon();
}
var iconInfo = new ICONINFO
{
fIcon = isIcon,
xHotspot = xHotspot,
yHotspot = yHotspot,
hbmMask = maskBitmap,
hbmColor = colorBitmap
};
return UnsafeNativeMethods.CreateIconIndirect(iconInfo);
}
finally
{
colorBitmap?.Dispose();
maskBitmap?.Dispose();
}
}
private static byte[] GenerateMaskArray(int width, int height, byte[] colorArray)
{
var nCount = width * height;
var bytesPerScanLine = AlignToBytes(width, 2) / 8;
var bitsMask = new byte[bytesPerScanLine * height];
for (var i = 0; i < nCount; i++)
{
var hPos = i % width;
var vPos = i / width;
var byteIndex = hPos / 8;
var offsetBit = (byte)(0x80 >> (hPos % 8));
if (colorArray[i * 4 + 3] == 0x00)
{
bitsMask[byteIndex + bytesPerScanLine * vPos] |= offsetBit;
}
else
{
bitsMask[byteIndex + bytesPerScanLine * vPos] &= (byte)~offsetBit;
}
if (hPos == width - 1 && width == 8)
{
bitsMask[1 + bytesPerScanLine * vPos] = 0xff;
}
}
return bitsMask;
}
internal static int AlignToBytes(double original, int nBytesCount)
{
var nBitsCount = 8 << (nBytesCount - 1);
return ((int)Math.Ceiling(original) + (nBitsCount - 1)) / nBitsCount * nBitsCount;
}
private static BitmapSource GenerateBitmapSource(ImageSource img, Size renderSize)
{
var drawingDimensions = new Rect(0, 0, renderSize.Width, renderSize.Height);
var renderRatio = renderSize.Width / renderSize.Height;
var aspectRatio = img.Width / img.Height;
if (img.Width <= renderSize.Width && img.Height <= renderSize.Height)
{
drawingDimensions = new Rect((renderSize.Width - img.Width) / 2, (renderSize.Height - img.Height) / 2, img.Width, img.Height);
}
else if (renderRatio > aspectRatio)
{
var scaledRenderWidth = (img.Width / img.Height) * renderSize.Width;
drawingDimensions = new Rect((renderSize.Width - scaledRenderWidth) / 2, 0, scaledRenderWidth, renderSize.Height);
}
else if (renderRatio < aspectRatio)
{
var scaledRenderHeight = img.Height / img.Width * renderSize.Height;
drawingDimensions = new Rect(0, (renderSize.Height - scaledRenderHeight) / 2, renderSize.Width, scaledRenderHeight);
}
var dv = new DrawingVisual();
var dc = dv.RenderOpen();
dc.DrawImage(img, drawingDimensions);
dc.Close();
var bmp = new RenderTargetBitmap((int)renderSize.Width, (int)renderSize.Height, 96, 96, PixelFormats.Pbgra32);
bmp.Render(dv);
return bmp;
}
private static BitmapFrame GetBestMatch(ReadOnlyCollection<BitmapFrame> frames, Size size)
{
var bestScore = int.MaxValue;
var bestBpp = 0;
var bestIndex = 0;
var isBitmapIconDecoder = frames[0].Decoder is IconBitmapDecoder;
for (int i = 0; i < frames.Count && bestScore != 0; ++i)
{
var currentIconBitDepth = isBitmapIconDecoder ? frames[i].Thumbnail.Format.BitsPerPixel : frames[i].Format.BitsPerPixel;
if (currentIconBitDepth == 0)
{
currentIconBitDepth = 8;
}
var score = MatchImage(frames[i], size, currentIconBitDepth);
if (score < bestScore)
{
bestIndex = i;
bestBpp = currentIconBitDepth;
bestScore = score;
}
else if (score == bestScore)
{
if (bestBpp < currentIconBitDepth)
{
bestIndex = i;
bestBpp = currentIconBitDepth;
}
}
}
return frames[bestIndex];
}
private static int MatchImage(BitmapFrame frame, Size size, int bpp)
{
var score = 2 * MyAbs(bpp, _systemBitDepth, false) +
MyAbs(frame.PixelWidth, (int)size.Width, true) +
MyAbs(frame.PixelHeight, (int)size.Height, true);
return score;
}
private static int MyAbs(int valueHave, int valueWant, bool fPunish)
{
var diff = (valueHave - valueWant);
if (diff < 0)
{
diff = (fPunish ? -2 : -1) * diff;
}
return diff;
}
[SecurityCritical, SecuritySafeCritical]
private static void EnsureSystemMetrics()
{
if (_systemBitDepth == 0)
{
var hdcDesktop = new HandleRef(null, UnsafeNativeMethods.GetDC(new HandleRef()));
try
{
var sysBitDepth = UnsafeNativeMethods.GetDeviceCaps(hdcDesktop, NativeMethods.BITSPIXEL);
sysBitDepth *= UnsafeNativeMethods.GetDeviceCaps(hdcDesktop, NativeMethods.PLANES);
if (sysBitDepth == 8)
{
sysBitDepth = 4;
}
var cxSmallIcon = UnsafeNativeMethods.GetSystemMetrics(SM.CXSMICON);
var cySmallIcon = UnsafeNativeMethods.GetSystemMetrics(SM.CYSMICON);
var cxIcon = UnsafeNativeMethods.GetSystemMetrics(SM.CXICON);
var cyIcon = UnsafeNativeMethods.GetSystemMetrics(SM.CYICON);
_smallIconSize = new Size(cxSmallIcon, cySmallIcon);
_iconSize = new Size(cxIcon, cyIcon);
_systemBitDepth = sysBitDepth;
}
finally
{
UnsafeNativeMethods.ReleaseDC(new HandleRef(), hdcDesktop);
}
}
}
[SecurityCritical, SecuritySafeCritical]
public static void GetDefaultIconHandles(out IconHandle largeIconHandle, out IconHandle smallIconHandle)
{
largeIconHandle = null;
smallIconHandle = null;
SecurityHelper.DemandUIWindowPermission();
var iconModuleFile = UnsafeNativeMethods.GetModuleFileName(new HandleRef());
UnsafeNativeMethods.ExtractIconEx(iconModuleFile, 0, out largeIconHandle, out smallIconHandle, 1);
}
}
}