Create a semi-transparent cursor from an image - c#

Is it possible to create a cursor from an image and have it be semi-transparent?
I'm currently taking a custom image and overylaying the mouse cursor image. It would be great if I could make this semi-transparent, but not necessary. The sales guys love shiny.
Currently doing something like this:
Image cursorImage = customImage.GetThumbnailImage(300, 100, null, IntPtr.Zero);
cursorImage.SetResolution(96.0F, 96.0F);
int midPointX = cursorImage.Width / 2;
int midPointY = cursorImage.Height / 2;
Bitmap cursorMouse = GetCursorImage(cursorOverlay);
Graphics cursorGfx = Graphics.FromImage(cursorImageCopy);
cursorGfx.DrawImageUnscaled(cursorMouse, midPointX, midPointY);
Cursor tmp = new Cursor(cursorImage.GetHicon());
alt text http://members.cox.net/dustinbrooks/drag.jpg

I've tried following example, and it was working fine...
public struct IconInfo
{
public bool fIcon;
public int xHotspot;
public int yHotspot;
public IntPtr hbmMask;
public IntPtr hbmColor;
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo);
[DllImport("user32.dll")]
public static extern IntPtr CreateIconIndirect(ref IconInfo icon);
public static Cursor CreateCursor(Bitmap bmp, int xHotSpot, int yHotSpot)
{
IntPtr ptr = bmp.GetHicon();
IconInfo tmp = new IconInfo();
GetIconInfo(ptr, ref tmp);
tmp.xHotspot = xHotSpot;
tmp.yHotspot = yHotSpot;
tmp.fIcon = false;
ptr = CreateIconIndirect(ref tmp);
return new Cursor(ptr);
}
And i've put this on button click event (you can call from where you like):
Bitmap b = new Bitmap("D:/Up.png");
this.Cursor = CreateCursor(b, 5, 5);
And the Up.png image is saved with 75% opacity in AdobePhotoshop.

On the top of my head (I would try that first):
create new bitmap with same size as original, but with ARGB structure
drawimage: existing bitmap to the new bitmap
access raw bitmap data, and replace A bytes with 128
You should have nice semitransparent bitmap there.
If performance allows, you can scan for fully transparent pixels and set A to zero for them!

If you want to set transparency of a custom mouse cursor bitmap 'on the fly' you may find this function helpful. It uses a color matrix to set the amount of transparency to any given bitmap and will return the modified one. To have just a touch of transparency the TranspFactor should be between 225 and 245, just try it out. (You need to import System.Drawing and System.Drawing.Imaging)
public static Bitmap GetBMPTransparent(Bitmap bmp, int TranspFactor)
{
Bitmap transpBmp = new Bitmap(bmp.Width, bmp.Height);
using (ImageAttributes attr = new ImageAttributes()) {
ColorMatrix matrix = new ColorMatrix { Matrix33 = Convert.ToSingle(TranspFactor / 255) };
attr.SetColorMatrix(matrix);
using (Graphics g = Graphics.FromImage(transpBmp)) {
g.DrawImage(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height), 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel, attr);
}
}
return transpBmp;
}

that is very easy, I don't use API.
the code is
Bitmap img = new Bitmap(new Bitmap(#"image.png"), 30, 30); //this is the size of cursor
Icon icono = Icon.FromHandle(img.GetHicon()); //create the Icon object
Cursor = new Cursor(icono.Handle); //the icon Object has the stream to create a Cursor.
I hope that is your solution

Related

(C#) PrintWindow api returns black or partial images. Alternative methods?

I have searched all the topics in stackoverflow and tried all the suggestions, but Printwindow keeps giving me black, partial or practically blank screens. I refer to inactive / minimized applications especially.
I was reading that it could be due to some apps that handle this functionality badly, but this also happens with explorer.exe, and I want to hope that the problem is not attributable to this.
My goal is to create a window selector similar to what appears in Zoom when you do screen sharing.
Is there any alternative method?
For completeness I report the function I used, even if it is the same as reported on some question in stackoverflow.
(two proven methods)
public static Bitmap GetScreenshot(IntPtr hwnd)
{
RECT rc;
GetWindowRect(new HandleRef(null, hwnd), out rc);
Bitmap bmp = new Bitmap(rc.Right - rc.Left, rc.Bottom - rc.Top, PixelFormat.Format32bppArgb);
Graphics gfxBmp = Graphics.FromImage(bmp);
IntPtr hdcBitmap;
try
{
hdcBitmap = gfxBmp.GetHdc();
}
catch
{
return null;
}
bool succeeded = PrintWindow(hwnd, hdcBitmap, 0);
gfxBmp.ReleaseHdc(hdcBitmap);
if (!succeeded)
{
gfxBmp.FillRectangle(new SolidBrush(Color.Gray), new Rectangle(Point.Empty, bmp.Size));
}
IntPtr hRgn = CreateRectRgn(0, 0, 0, 0);
GetWindowRgn(hwnd, hRgn);
Region region = Region.FromHrgn(hRgn);//err here once
if (!region.IsEmpty(gfxBmp))
{
gfxBmp.ExcludeClip(region);
gfxBmp.Clear(Color.Transparent);
}
gfxBmp.Dispose();
return bmp;
}
public static Bitmap PrintWindow(IntPtr hwnd)
{
RECT rc;
GetWindowRect(hwnd, out rc);
Bitmap bmp = new Bitmap(rc.Width, rc.Height, PixelFormat.Format32bppArgb);
Graphics gfxBmp = Graphics.FromImage(bmp);
IntPtr hdcBitmap = gfxBmp.GetHdc();
PrintWindow(hwnd, hdcBitmap, 0);
gfxBmp.ReleaseHdc(hdcBitmap);
gfxBmp.Dispose();
return bmp;
}

facing issue of "Parameter is not valid" inside BufferCB

I am creating desktop recording (screen recording) application using Directshow.NET and C#. I am almost done, application is able to record desktop screen. To paint mouse pointer in recording video I have implemented BufferCB from SampleGrabber and with the help of my another post Fliped cursor icon on desktop recording using directshow i am able to paint mouse pointer in correct orientation
Here is my code of BufferCB :
[System.Security.Permissions.SecurityPermission(
System.Security.Permissions.SecurityAction.LinkDemand, Flags =
System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
int ISampleGrabberCB.BufferCB(double SampleTime, IntPtr pBuffer, int BufferLen)
{
if (!wait)
{
wait = true;
Rectangle imageBounds = new Rectangle(0, 0, m_videoWidth, m_videoHeight);
Bitmap bitmap = new Bitmap(m_videoWidth, m_videoHeight, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
System.Drawing.Imaging.BitmapData bitmapData = bitmap.LockBits(imageBounds, System.Drawing.Imaging.ImageLockMode.ReadWrite, bitmap.PixelFormat);
IntPtr ptr = bitmapData.Scan0;
bitmap.UnlockBits(bitmapData);
CopyMemory(ptr, pBuffer, (uint)BufferLen);
bitmap.RotateFlip(System.Drawing.RotateFlipType.RotateNoneFlipY);
using (Graphics g = Graphics.FromImage(bitmap))
{
CURSORINFO cursorInfo;
cursorInfo.cbSize = Marshal.SizeOf(typeof(CURSORINFO));
if (GetCursorInfo(out cursorInfo) && cursorInfo.flags == CURSOR_SHOWING)
{
IntPtr iconPointer = CopyIcon(cursorInfo.hCursor);
ICONINFO iconInfo;
int iconX, iconY;
if (GetIconInfo(iconPointer, out iconInfo))
{
// calculate the correct position of the cursor
iconX = cursorInfo.ptScreenPos.x - ((int)iconInfo.xHotspot);
iconY = cursorInfo.ptScreenPos.y - ((int)iconInfo.yHotspot);
//GETTING ARGUMENTEXCEPTION AT BELOW LINE
IntPtr hdc = g.GetHdc();
DrawIcon(hdc, iconX, iconY, cursorInfo.hCursor);
g.ReleaseHdc(hdc);
}
}
g.DrawImage(companylogo, m_videoWidth - 100 , 20);
}
bitmap.RotateFlip(System.Drawing.RotateFlipType.RotateNoneFlipY);
bitmapData = bitmap.LockBits(imageBounds, System.Drawing.Imaging.ImageLockMode.ReadWrite, bitmap.PixelFormat);
ptr = bitmapData.Scan0;
bitmap.UnlockBits(bitmapData);
CopyMemory(pBuffer, ptr, (uint)BufferLen);
bitmap.Dispose();
wait = false;
}
return 0;
}
Mouse pointer is getting paint on video but after some time of recording I am getting ArgumentException "Parameter is not valid." at line of code IntPtr hdc = g.GetHdc();
Can anyone help me put to solve this?
StackTrace:
at System.Drawing.Graphics.GetHdc()
Look at the answer in this question.
They discuss the same error message.

Bitmap.FromHbitmap erroring when called over RDP

Our application does some cursor manipulation to enable "relatively" nice drag drop animation on WinForms (at the time WPF wasn't an option). However when using the application over a RDP session it throws a generic GDI+ exception.
The method which throws this is this:
[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);
}
Specifically the line:
Bitmap bmp = Bitmap.FromHbitmap(iInfo.hbmColor);
When debugging hbmColor is 0, which means when running over RDP the call to GetIconInfo doesn't return the required information.
I can check for 0 and handle the special case, but is there anything I can do to make this work over RDP as it would do normally?
Edit
Here's the ICONINFO structure:
[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's 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's 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.
}
From HABJAN's answer below I've added the comments from p/Invoke to the structure above. It looks like hbmMask contains the bitmap reference I'm after, but I'm afraid my bit manipulation skills are rather rusty. When p/Invoke says upper half / lower half - what is it inferring to?
Is it possible to get the black and white bitmap from this?
I think that this is due your RDP color depth. If your cursor is black and white only (via RDP), you will not get hbmColor value as this parameter is optional.
MSDN says:
hbmColor
Type: HBITMAP
Description: A 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.
EDIT:
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);
}
... i did not test this code, it's just to give you a brief info what to do...
With the help of HABJAN I was able to come up with a method to do the job. The reason I'm writing the answer here is because the bitmap mask you get from the handle contains two masks, so you have to select which version you want (as per the documentation).
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;
}
}
}
}
This is a basic copy of the answer linked by HABJAN - it doesn't seem to do either a logical AND or a logical XOR on the resulting bytes - none the less seems to do the required job.

Getting transparent shell-icons for files and folders in C#

I'm currently working on a small library that enables you to get icons from files and folders. Now, I don't care if it only works on win8+ (cause that's the place I'm going to use it), however, I've run in to a tiny problem with regards to transparency. If you take a look at the following image:
The one I generate (from my library) is to the left, windows explorer is to the right.
Now, as you might see, first off there is 2 black lines in the upper right of the one I generate, second, there is a difference in the background color. So what I'm wondering is this; is there no way to get the exact same image used by windows explorer, or am I simply doing it wrong?
My code (with exception to structs/externs etc. for shortness) bellow, entire code here.
public static class Icon
{
public static Image GetIcon(string fileName, int size)
{
IShellItem shellItem;
Shell32.SHCreateItemFromParsingName(fileName, IntPtr.Zero, Shell32.IShellItem_GUID, out shellItem);
IntPtr hbitmap;
((IShellItemImageFactory)shellItem).GetImage(new SIZE(size, size), 0x0, out hbitmap);
// get the info about the HBITMAP inside the IPictureDisp
DIBSECTION dibsection = new DIBSECTION();
Gdi32.GetObjectDIBSection(hbitmap, Marshal.SizeOf(dibsection), ref dibsection);
int width = dibsection.dsBm.bmWidth;
int height = dibsection.dsBm.bmHeight;
// zero out the RGB values for all pixels with A == 0
// (AlphaBlend expects them to all be zero)
for (int i = 0; i < dibsection.dsBmih.biWidth * dibsection.dsBmih.biHeight; i++)
{
IntPtr ptr = dibsection.dsBm.bmBits + (i * Marshal.SizeOf(typeof(RGBQUAD)));
var rgbquad = (RGBQUAD)Marshal.PtrToStructure(ptr, typeof(RGBQUAD));
if (rgbquad.rgbReserved == 0)
{
rgbquad.rgbBlue = 0;
rgbquad.rgbGreen = 0;
rgbquad.rgbRed = 0;
}
else
{
;
}
Marshal.StructureToPtr(rgbquad, ptr, false);
}
// create the destination Bitmap object
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
// get the HDCs and select the HBITMAP
Graphics graphics = Graphics.FromImage(bitmap);
IntPtr hdcDest = graphics.GetHdc();
IntPtr hdcSrc = Gdi32.CreateCompatibleDC(hdcDest);
IntPtr hobjOriginal = Gdi32.SelectObject(hdcSrc, hbitmap);
// render the bitmap using AlphaBlend
BLENDFUNCTION blendfunction = new BLENDFUNCTION(BLENDFUNCTION.AC_SRC_OVER, 0, 0xFF, BLENDFUNCTION.AC_SRC_ALPHA);
Gdi32.AlphaBlend(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, width, height, blendfunction);
// clean up
Gdi32.SelectObject(hdcSrc, hobjOriginal);
Gdi32.DeleteDC(hdcSrc);
graphics.ReleaseHdc(hdcDest);
graphics.Dispose();
Gdi32.DeleteObject(hbitmap);
return bitmap;
}
}
It seems copying pixel by pixel was the solution. The following seems to be pixel-perfect equal to the explorer one.
public static Image GetIcon(string fileName, int size)
{
IShellItem shellItem;
Shell32.SHCreateItemFromParsingName(fileName, IntPtr.Zero, Shell32.IShellItem_GUID, out shellItem);
IntPtr hbitmap;
((IShellItemImageFactory)shellItem).GetImage(new SIZE(size, size), 0x0, out hbitmap);
// get the info about the HBITMAP inside the IPictureDisp
DIBSECTION dibsection = new DIBSECTION();
Gdi32.GetObjectDIBSection(hbitmap, Marshal.SizeOf(dibsection), ref dibsection);
int width = dibsection.dsBm.bmWidth;
int height = dibsection.dsBm.bmHeight;
// create the destination Bitmap object
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
for (int x = 0; x < dibsection.dsBmih.biWidth; x++)
{
for (int y = 0; y < dibsection.dsBmih.biHeight; y++)
{
int i = y * dibsection.dsBmih.biWidth + x;
IntPtr ptr = dibsection.dsBm.bmBits + (i * Marshal.SizeOf(typeof(RGBQUAD)));
var rgbquad = (RGBQUAD)Marshal.PtrToStructure(ptr, typeof(RGBQUAD));
if (rgbquad.rgbReserved != 0)
bitmap.SetPixel(x, y, Color.FromArgb(rgbquad.rgbReserved, rgbquad.rgbRed, rgbquad.rgbGreen, rgbquad.rgbBlue));
}
}
Gdi32.DeleteObject(hbitmap);
return bitmap;
}

How to capture the screen and mouse pointer using Windows APIs?

I'm using the below code to capture the screen in a bitmap. The screen is captured, but I'm unable to get the mouse pointer on the screen. Could you suggest some alternative approach so that the mouse is captured as well?
private Bitmap CaptureScreen()
{
// Size size is how big an area to capture
// pointOrigin is the upper left corner of the area to capture
int width = Screen.PrimaryScreen.Bounds.X + Screen.PrimaryScreen.Bounds.Width;
int height = Screen.PrimaryScreen.Bounds.Y + Screen.PrimaryScreen.Bounds.Height;
Size size = new Size(width, height);
Point pointOfOrigin = new Point(0, 0);
Bitmap bitmap = new Bitmap(size.Width, size.Height);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(pointOfOrigin, new Point(0, 0), size);
}
return bitmap;
}
[StructLayout(LayoutKind.Sequential)]
struct CURSORINFO
{
public Int32 cbSize;
public Int32 flags;
public IntPtr hCursor;
public POINTAPI ptScreenPos;
}
[StructLayout(LayoutKind.Sequential)]
struct POINTAPI
{
public int x;
public int y;
}
[DllImport("user32.dll")]
static extern bool GetCursorInfo(out CURSORINFO pci);
[DllImport("user32.dll")]
static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon);
const Int32 CURSOR_SHOWING = 0x00000001;
public static Bitmap CaptureScreen(bool CaptureMouse)
{
Bitmap result = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format24bppRgb);
try
{
using (Graphics g = Graphics.FromImage(result))
{
g.CopyFromScreen(0, 0, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
if (CaptureMouse)
{
CURSORINFO pci;
pci.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(CURSORINFO));
if (GetCursorInfo(out pci))
{
if (pci.flags == CURSOR_SHOWING)
{
DrawIcon(g.GetHdc(), pci.ptScreenPos.x, pci.ptScreenPos.y, pci.hCursor);
g.ReleaseHdc();
}
}
}
}
}
catch
{
result = null;
}
return result;
}
If you're NOT looking for the EXACT replica of the cursor you're currently using, you could use the following code, all you have to do is add one line in your original code!
private Bitmap CaptureScreen()
{
// Size size is how big an area to capture
// pointOrigin is the upper left corner of the area to capture
int width = Screen.PrimaryScreen.Bounds.X + Screen.PrimaryScreen.Bounds.Width;
int height = Screen.PrimaryScreen.Bounds.Y + Screen.PrimaryScreen.Bounds.Height;
Size size = new Size(width, height);
Point pointOfOrigin = new Point(0, 0);
Bitmap bitmap = new Bitmap(size.Width, size.Height);
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(pointOfOrigin, new Point(0, 0), size);
//Following code is all you needed!
graphics.DrawIcon(new Icon("Sample.ico"),Cursor.Position.X-50,Cursor.Position.Y-50);
//The reason I minus 50 in the position is because you need to "offset" the position. Please go check out the post WholsRich commented.
}
return bitmap;
}
}
You could go online and download all kind of icons.
Or use ICO Convert to make your own.
Good luck!

Categories