292 lines
10 KiB
C#
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);
|
|
}
|
|
}
|
|
} |