Наше приложение делает некоторые манипуляции с курсором, чтобы включить "относительно" красивую анимацию перетаскивания на WinForms (в то время, когда WPF не был вариантом). Однако при использовании приложения через сеанс RDP он генерирует общее исключение GDI+.
Метод, который вызывает это:
[DllImport("user32")]
private static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO pIconInfo);
[DllImport("user32.dll")]
private static extern IntPtr LoadCursorFromFile(string lpFileName);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool DestroyIcon(IntPtr hIcon);
[DllImport("gdi32.dll", SetLastError = true)]
private static extern bool DeleteObject(IntPtr hObject);
public static Bitmap BitmapFromCursor(Cursor cur)
{
ICONINFO iInfo;
GetIconInfo(cur.Handle, out iInfo);
Bitmap bmp = Bitmap.FromHbitmap(iInfo.hbmColor);
DeleteObject(iInfo.hbmColor);
DeleteObject(iInfo.hbmMask);
BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
Bitmap dstBitmap = new Bitmap(bmData.Width, bmData.Height, bmData.Stride, PixelFormat.Format32bppArgb, bmData.Scan0);
bmp.UnlockBits(bmData);
return new Bitmap(dstBitmap);
}
В частности, строка:
Bitmap bmp = Bitmap.FromHbitmap(iInfo.hbmColor);
Когда отладка hbmColor
равна 0
, это означает, что при запуске RDP вызов GetIconInfo
не возвращает требуемую информацию.
Я могу проверить на 0
и обрабатывать специальный случай, но есть ли что-нибудь, что я могу сделать, чтобы сделать эту работу над RDP, как это было бы нормально?
редактировать
Здесь структура ICONINFO
:
[StructLayout(LayoutKind.Sequential)]
struct ICONINFO
{
public bool fIcon; // Specifies whether this structure defines an icon or a cursor. A value of TRUE specifies
// an icon; FALSE specifies a cursor.
public Int32 xHotspot; // Specifies the x-coordinate of a cursor hot spot. If this structure defines an icon, the hot
// spot is always in the center of the icon, and this member is ignored.
public Int32 yHotspot; // Specifies the y-coordinate of the cursor hot spot. If this structure defines an icon, the hot
// spot is always in the center of the icon, and this member is ignored.
public IntPtr hbmMask; // (HBITMAP) Specifies the icon bitmask bitmap. If this structure defines a black and white icon,
// this bitmask is formatted so that the upper half is the icon AND bitmask and the lower half is
// the icon XOR bitmask. Under this condition, the height should be an even multiple of two. If
// this structure defines a color icon, this mask only defines the AND bitmask of the icon.
public IntPtr hbmColor; // (HBITMAP) Handle to the icon color bitmap. This member can be optional if this
// structure defines a black and white icon. The AND bitmask of hbmMask is applied with the SRCAND
// flag to the destination; subsequently, the color bitmap is applied (using XOR) to the
// destination by using the SRCINVERT flag.
}
Из ответа HABJAN ниже я добавил комментарии из p/Invoke к структуре выше. Похоже, hbmMask
содержит ссылку на растровое изображение, за которым я нахожусь, но я боюсь, что мои навыки манипуляции битками довольно ржавые. Когда p/Invoke говорит верхнюю половину/нижнюю половину - что он выводит?
Можно ли получить черно-белое растровое изображение?
Я думаю, что это связано с вашей глубиной цвета RDP. Если ваш курсор только черный и белый (через RDP), вы не получите значение hbmColor
поскольку этот параметр является необязательным.
MSDN говорит:
hbmColor
Type: HBITMAP
Описание: дескриптор растрового изображения цвета значка. Этот член может быть необязательным, если эта структура определяет черно-белый значок. Битовая маска AND hbmMask применяется с флагом SRCAND к месту назначения; впоследствии цветовая растровая карта применяется (с использованием XOR) к месту назначения с использованием флага SRCINVERT.
РЕДАКТИРОВАТЬ:
public static Bitmap BitmapFromCursor(Cursor cur)
{
ICONINFO iInfo;
GetIconInfo(cur.Handle, out iInfo);
Bitmap bmpColor = null;
if (iInfo.hbmColor != IntPtr.Zero) {
bmpColor = Bitmap.FromHbitmap(iInfo.hbmColor);
}
else {
bmpColor = new Bitmap(w,h);
// fill bmpColor with white colour
}
Bitmap bmpMask = Bitmap.FromHbitmap(iInfo.hbmMask);
DeleteObject(iInfo.hbmColor);
DeleteObject(iInfo.hbmMask);
// apply mask bitmap to color bitmap:
// http://stackoverflow.com/questions/3654220/alpha-masking-in-c-sharp-system-drawing
BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
Bitmap dstBitmap = new Bitmap(bmData.Width, bmData.Height, bmData.Stride, PixelFormat.Format32bppArgb, bmData.Scan0);
bmp.UnlockBits(bmData);
return new Bitmap(dstBitmap);
}
... я не тестировал этот код, он просто дал вам краткую информацию о том, что делать...
С помощью HABJAN я смог придумать способ выполнить эту работу. Причина, по которой я пишу ответ, заключается в том, что маска растрового изображения, которую вы получаете из дескриптора, содержит две маски, поэтому вам нужно выбрать, какую версию вы хотите (согласно документации).
public static Bitmap GetBitmapFromMask(IntPtr maskH)
{
using (var bothMasks = Bitmap.FromHbitmap(maskH))
{
int midY = bothMasks.Height / 2;
using (var mask = bothMasks.Clone(new Rectangle(0, midY, bothMasks.Width, midY), bothMasks.PixelFormat))
{
using (var input = new Bitmap(mask.Width, mask.Height))
{
using (var g = Graphics.FromImage(input))
{
using (var b = new SolidBrush(Color.FromArgb(255, 255, 255, 255)))
g.FillRectangle(b, 0, 0, input.Width, input.Height);
}
var output = new Bitmap(mask.Width, mask.Height, PixelFormat.Format32bppArgb);
var rect = new Rectangle(0, 0, input.Width, input.Height);
var bitsMask = mask.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var bitsInput = input.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var bitsOutput = output.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
unsafe
{
for (int y = 0; y < input.Height; y++)
{
byte* ptrMask = (byte*)bitsMask.Scan0 + y * bitsMask.Stride;
byte* ptrInput = (byte*)bitsInput.Scan0 + y * bitsInput.Stride;
byte* ptrOutput = (byte*)bitsOutput.Scan0 + y * bitsOutput.Stride;
for (int x = 0; x < input.Width; x++)
{
ptrOutput[4 * x] = ptrInput[4 * x]; // blue
ptrOutput[4 * x + 1] = ptrInput[4 * x + 1]; // green
ptrOutput[4 * x + 2] = ptrInput[4 * x + 2]; // red
ptrOutput[4 * x + 3] = ptrMask[4 * x]; // alpha
}
}
}
mask.UnlockBits(bitsMask);
input.UnlockBits(bitsInput);
output.UnlockBits(bitsOutput);
return output;
}
}
}
}
Это базовая копия ответа, связанного с HABJAN, - он, похоже, не делает логического ИЛИ или логического XOR в результирующих байтах - тем не менее, похоже, делает требуемую работу.