I have issues on erasing old lines after ControlPaint.DrawReversibleLine and DrawReversibleFrame. I did erase, but screen (usercontrol and picturebox) retains old lines while Mouse_Move. How to erase old lines completely?
I have searched such stuff on stackoverflow, all answers are similar but no functioning for my case.
//a picturebox1 on usercontrol.
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
//...other code
ControlPaint.DrawReversibleLine(HorizontalSelectionPT1, HorizontalSelectionPT2, Color.Red); //erase old Horizontal line
ControlPaint.DrawReversibleLine(VerticalSelectionPT1, VerticalSelectionPT2, Color.Red); //erase old vertical line
Point point = PointToScreen(base.Location);
HorizontalSelectionPT1 = new Point(point.X, Cursor.Position.Y);
HorizontalSelectionPT2 = new Point(point.X + base.Width, Cursor.Position.Y);
VerticalSelectionPT1 = new Point(Cursor.Position.X, point.Y);
VerticalSelectionPT2 = new Point(Cursor.Position.X, point.Y + base.Height);
ControlPaint.DrawReversibleLine(HorizontalSelectionPT1, HorizontalSelectionPT2, Color.Red); //Draw new line
ControlPaint.DrawReversibleLine(VerticalSelectionPT1, VerticalSelectionPT2, Color.Red); //Draw new line
//draw selected frame
ControlPaint.DrawReversibleFrame(pbCanvas.RectangleToScreen(rectangle_0), Color.Black, FrameStyle.Dashed); //erase old frame
rectangle_0.Width = e.X - rectangle_0.X;
rectangle_0.Height = e.Y - rectangle_0.Y;
ControlPaint.DrawReversibleFrame(pbCanvas.RectangleToScreen(rectangle_0), Color.Black, FrameStyle.Dashed); //Draw new frame
//...other codes
}
Hardly to capture screenshot, I used Camera.
I have replaced all ControlPaint.DrawReverseFrame with GDI32 APIs to solve the erase old line problem. Whatever I tried using ControlPaint.DrawReversibleLine produced either undesired effect or flickering.
private static int ArgbToRGB(int rgb)
{
return ((rgb >> 16 & 0x0000FF) | (rgb & 0x00FF00) | (rgb << 16 & 0xFF0000));
}
public enum PenStyles
{
PS_OLID = 0,
PS_DASH = 1,
PS_DOT = 2,
PS_DASHDOT = 3,
PS_DASHDOTDOT = 4,
PS_NULL = 5,
PS_INSIDEFRAME = 6,
PS_USERSTYLE = 7,
PS_ALTERNATE = 8,
PS_STYLE_MASK = 0x0000000F,
PS_ENDCAP_ROUND = 0x00000000,
PS_ENDCAP_SQUARE = 0x00000100,
PS_ENDCAP_FLAT = 0x00000200,
PS_ENDCAP_MASK = 0x00000F00,
PS_JOIN_ROUND = 0x00000000,
PS_JOIN_BEVEL = 0x00001000,
PS_JOIN_MITER = 0x00002000,
PS_JOIN_MASK = 0x0000F000,
PS_COSMETIC = 0x00000000,
PS_GEOMETRIC = 0x00010000
PS_TYPE_MASK = 0x000F0000
}
public enum GDIDrawingMode
{
R2_BLACK = 1,
R2_NOTMERGEPEN = 2,
R2_MASKNOTPEN = 3,
R2_NOTCOPYPEN = 4,
R2_MASKPENNOT = 5,
R2_NOT = 6,
R2_XORPEN = 7,
R2_NOTMASKPEN = 8,
R2_MASKPEN = 9,
R2_NOTXORPEN = 10,
R2_NOP = 11,
R2_MERGENOTPEN = 12,
R2_COPYPEN = 13,
R2_MERGEPENNOT = 14,
R2_MERGEPEN = 15,
R2_WHITE = 16
}
[DllImport("gdi32.dll", CharSet = CharSet.Auto, EntryPoint = "Rectangle", ExactSpelling = true, SetLastError = true)]
public static extern bool GDIDrawRectangle(IntPtr hDC, int left, int top, int right, int bottom);
[DllImport("gdi32.dll")]
public static extern int SetROP2(IntPtr hDC, int fnDrawMode);
[DllImport("gdi32.dll")]
public static extern bool MoveToEx(IntPtr hDC, int x, int y, ref Point p);
[DllImport("gdi32.dll")]
public static extern bool LineTo(IntPtr hdc, int x, int y);
[DllImport("gdi32.dll")]
public static extern IntPtr CreatePen(int fnPenStyle, int nWidth, int crColor);
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObj);
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObj);
public static void DrawXORRectangle(Graphics graphics, Color penColor, Rectangle rectangle)
{
IntPtr hDC = graphics.GetHdc();
IntPtr hPen = CreatePen((int)PenStyles.PS_DOT, 1, ArgbToRGB(penColor.ToArgb()));
IntPtr hOldPen = SelectObject(hDC, hPen);
SetROP2(hDC, (int)GDIDrawingMode.R2_NOTXORPEN);
GDIDrawRectangle(hDC, rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom);
SelectObject(hDC, hOldPen);
DeleteObject(hPen);
graphics.ReleaseHdc(hDC);
}
Related
I added this class :
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests
{
class WindowUtility
{
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
const uint SWP_NOSIZE = 0x0001;
const uint SWP_NOZORDER = 0x0004;
private static Size GetScreenSize() => new Size(GetSystemMetrics(0), GetSystemMetrics(1));
private struct Size
{
public int Width { get; set; }
public int Height { get; set; }
public Size(int width, int height)
{
Width = width;
Height = height;
}
}
[DllImport("User32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
private static extern int GetSystemMetrics(int nIndex);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(HandleRef hWnd, out Rect lpRect);
[StructLayout(LayoutKind.Sequential)]
private struct Rect
{
public int Left; // x position of upper-left corner
public int Top; // y position of upper-left corner
public int Right; // x position of lower-right corner
public int Bottom; // y position of lower-right corner
}
private static Size GetWindowSize(IntPtr window)
{
if (!GetWindowRect(new HandleRef(null, window), out Rect rect))
throw new Exception("Unable to get window rect!");
int width = rect.Right - rect.Left;
int height = rect.Bottom - rect.Top;
return new Size(width, height);
}
public static void MoveWindowToCenter()
{
IntPtr window = Process.GetCurrentProcess().MainWindowHandle;
if (window == IntPtr.Zero)
throw new Exception("Couldn't find a window to center!");
Size screenSize = GetScreenSize();
Size windowSize = GetWindowSize(window);
int x = (screenSize.Width - windowSize.Width) / 2;
int y = (screenSize.Height - windowSize.Height) / 2;
SetWindowPos(window, IntPtr.Zero, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
}
}
Using it in Program.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Tests
{
class Program
{
static void Main(string[] args)
{
WindowUtility.MoveWindowToCenter();
The problem is that it's showing for a second the window in some random position when running the application and then move it to the screen center.
Is there a way to make that when starting the application the window already will be in the center of the screen ?
I tried to use the accepted answer in this question in the link :
Show/Hide the console window of a C# console application
but then when i'm hiding the window then try to center it then to show it again when it's trying to center it can't find the window because it's hidden so it's throwing this message in the WindowUtility class :
"Couldn't find a window to center!"
I know you have your answer but this is I created for your question.
internal class WindowUtility
{
// P/Invoke declarations.
[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);
const int MONITOR_DEFAULTTOPRIMARY = 1;
[DllImport("user32.dll")]
static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);
[StructLayout(LayoutKind.Sequential)]
struct MONITORINFO
{
public uint cbSize;
public RECT rcMonitor;
public RECT rcWork;
public uint dwFlags;
public static MONITORINFO Default
{
get { var inst = new MONITORINFO(); inst.cbSize = (uint)Marshal.SizeOf(inst); return inst; }
}
}
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int Left, Top, Right, Bottom;
}
[StructLayout(LayoutKind.Sequential)]
struct POINT
{
public int x, y;
}
[DllImport("user32.dll", SetLastError = true)]
static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);
const uint SW_RESTORE = 9;
[StructLayout(LayoutKind.Sequential)]
struct WINDOWPLACEMENT
{
public uint Length;
public uint Flags;
public uint ShowCmd;
public POINT MinPosition;
public POINT MaxPosition;
public RECT NormalPosition;
public static WINDOWPLACEMENT Default
{
get
{
var instance = new WINDOWPLACEMENT();
instance.Length = (uint)Marshal.SizeOf(instance);
return instance;
}
}
}
internal enum AnchorWindow
{
None = 0x0,
Top = 0x1,
Bottom = 0x2,
Left = 0x4,
Right = 0x8,
Center = 0x10,
Fill = 0x20
}
internal static void SetConsoleWindowPosition(AnchorWindow position)
{
// Get this console window's hWnd (window handle).
IntPtr hWnd = GetConsoleWindow();
// Get information about the monitor (display) that the window is (mostly) displayed on.
// The .rcWork field contains the monitor's work area, i.e., the usable space excluding
// the taskbar (and "application desktop toolbars" - see https://msdn.microsoft.com/en-us/library/windows/desktop/ms724947(v=vs.85).aspx)
var mi = MONITORINFO.Default;
GetMonitorInfo(MonitorFromWindow(hWnd, MONITOR_DEFAULTTOPRIMARY), ref mi);
// Get information about this window's current placement.
var wp = WINDOWPLACEMENT.Default;
GetWindowPlacement(hWnd, ref wp);
// Calculate the window's new position: lower left corner.
// !! Inexplicably, on W10, work-area coordinates (0,0) appear to be (7,7) pixels
// !! away from the true edge of the screen / taskbar.
int fudgeOffset = 7;
int _left = 0, _top = 0;
switch (position)
{
case AnchorWindow.Left|AnchorWindow.Top:
wp.NormalPosition = new RECT()
{
Left = -fudgeOffset,
Top = mi.rcWork.Top,
Right = (wp.NormalPosition.Right - wp.NormalPosition.Left) - fudgeOffset,
Bottom = (wp.NormalPosition.Bottom - wp.NormalPosition.Top)
};
break;
case AnchorWindow.Right| AnchorWindow.Top:
wp.NormalPosition = new RECT()
{
Left = mi.rcWork.Right - wp.NormalPosition.Right + wp.NormalPosition.Left + fudgeOffset,
Top = mi.rcWork.Top,
Right = mi.rcWork.Right + fudgeOffset,
Bottom = (wp.NormalPosition.Bottom - wp.NormalPosition.Top)
};
break;
case AnchorWindow.Left | AnchorWindow.Bottom:
wp.NormalPosition = new RECT()
{
Left = -fudgeOffset,
Top = mi.rcWork.Bottom - (wp.NormalPosition.Bottom - wp.NormalPosition.Top),
Right = (wp.NormalPosition.Right - wp.NormalPosition.Left) - fudgeOffset,
Bottom = fudgeOffset + mi.rcWork.Bottom
};
break;
case AnchorWindow.Right | AnchorWindow.Bottom:
wp.NormalPosition = new RECT()
{
Left = mi.rcWork.Right - wp.NormalPosition.Right + wp.NormalPosition.Left + fudgeOffset,
Top = mi.rcWork.Bottom - (wp.NormalPosition.Bottom - wp.NormalPosition.Top),
Right = mi.rcWork.Right + fudgeOffset,
Bottom = fudgeOffset + mi.rcWork.Bottom
};
break;
case AnchorWindow.Center|AnchorWindow.Top:
_left = mi.rcWork.Right / 2 - (wp.NormalPosition.Right - wp.NormalPosition.Left) / 2;
wp.NormalPosition = new RECT()
{
Left = _left,
Top = mi.rcWork.Top,
Right = mi.rcWork.Right + fudgeOffset - _left,
Bottom = (wp.NormalPosition.Bottom - wp.NormalPosition.Top)
};
break;
case AnchorWindow.Center | AnchorWindow.Bottom:
_left = mi.rcWork.Right / 2 - (wp.NormalPosition.Right - wp.NormalPosition.Left) / 2;
wp.NormalPosition = new RECT()
{
Left = _left,
Top = mi.rcWork.Bottom - (wp.NormalPosition.Bottom - wp.NormalPosition.Top),
Right = mi.rcWork.Right + fudgeOffset - _left,
Bottom = fudgeOffset + mi.rcWork.Bottom
};
break;
case AnchorWindow.Center:
_left = mi.rcWork.Right / 2 - (wp.NormalPosition.Right - wp.NormalPosition.Left) / 2;
_top = mi.rcWork.Bottom / 2 - (wp.NormalPosition.Bottom - wp.NormalPosition.Top) / 2;
wp.NormalPosition = new RECT()
{
Left = _left,
Top = _top,
Right = mi.rcWork.Right + fudgeOffset - _left,
Bottom = mi.rcWork.Bottom + fudgeOffset - _top
};
break;
case AnchorWindow.Fill:
wp.NormalPosition = new RECT()
{
Left = -fudgeOffset,
Top = mi.rcWork.Top,
Right = mi.rcWork.Right + fudgeOffset,
Bottom = mi.rcWork.Bottom + fudgeOffset
};
break;
default:
return;
}
// Place the window at the new position.
SetWindowPlacement(hWnd, ref wp);
}
}
You can use it like this.
WindowUtility.SetConsoleWindowPosition(WindowUtility.AnchorWindow.Left | WindowUtility.AnchorWindow.Top);
// or
WindowUtility.SetConsoleWindowPosition(WindowUtility.AnchorWindow.Center);
// or
WindowUtility.SetConsoleWindowPosition(WindowUtility.AnchorWindow.Fill);
I want to create a button and a container with rounded corners.
I'm using the Region to paint the corners, code attached below.
However the corners doesn't seem smooth, is there any way to fix this, any help would be appreciated.
Image attached below as im not allowed to upload images yet.
[DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
private static extern IntPtr CreateRoundRectRgn(
int nLeftRect,
int nTopRect,
int nRightRect,
int nBottomRect,
int nWidthEllipse,
int nHeightEllipse
);
public Login()
{
InitializeComponent();
this.Region = System.Drawing.Region.FromHrgn(CreateRoundRectRgn(0, 0, Width, Height, 30, 30));
this.logo.Image = Properties.Resources.logo;
this.btn_login.Region = System.Drawing.Region.FromHrgn(CreateRoundRectRgn(0, 0, this.btn_login.Width, this.btn_login.Height, 10, 10));
}
Once the function is not implemented using the normal WinForm function.
Therefore we must implement it using win32Api.
The code is created referring to this and this.
First, you have to draw a round rectangle.
public static GraphicsPath RoundedRect(Rectangle bounds, int radius)
{
int diameter = radius * 2;
Size size = new Size(diameter, diameter);
Rectangle arc = new Rectangle(bounds.Location, size);
GraphicsPath path = new GraphicsPath();
if (radius == 0)
{
path.AddRectangle(bounds);
return path;
}
// top left arc
path.AddArc(arc, 180, 90);
// top right arc
arc.X = bounds.Right - diameter;
path.AddArc(arc, 270, 90);
// bottom right arc
arc.Y = bounds.Bottom - diameter;
path.AddArc(arc, 0, 90);
// bottom left arc
arc.X = bounds.Left;
path.AddArc(arc, 90, 90);
path.CloseFigure();
return path;
}
public static void FillRoundedRectangle(Graphics graphics, Brush brush, Rectangle bounds, int cornerRadius)
{
if (graphics == null)
throw new ArgumentNullException("graphics");
if (brush == null)
throw new ArgumentNullException("brush");
using (GraphicsPath path = RoundedRect(bounds, cornerRadius))
{
graphics.FillPath(brush, path);
}
}
And let's add it to the drawing call.
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics graphics = e.Graphics;
Rectangle gradientRectangle = new Rectangle(0, 0, this.Width - 1, this.Height - 1);
Brush b = new LinearGradientBrush(gradientRectangle, Color.DarkSlateBlue, Color.MediumPurple, 0.0f);
graphics.SmoothingMode = SmoothingMode.HighQuality;
FillRoundedRectangle(graphics, b, gradientRectangle, 35);
}
Then we can draw the same form as the picture above.
Second, draw a form using Per Pixel Alpha Blend.
public void SetBitmap(Bitmap bitmap)
{
SetBitmap(bitmap, 255);
}
public void SetBitmap(Bitmap bitmap, byte opacity)
{
if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
throw new ApplicationException("The bitmap must be 32ppp with alpha-channel.");
IntPtr screenDc = Win32.GetDC(IntPtr.Zero);
IntPtr memDc = Win32.CreateCompatibleDC(screenDc);
IntPtr hBitmap = IntPtr.Zero;
IntPtr oldBitmap = IntPtr.Zero;
try
{
hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
oldBitmap = Win32.SelectObject(memDc, hBitmap);
Win32.Size size = new Win32.Size(bitmap.Width, bitmap.Height);
Win32.Point pointSource = new Win32.Point(0, 0);
Win32.Point topPos = new Win32.Point(Left, Top);
Win32.BLENDFUNCTION blend = new Win32.BLENDFUNCTION();
blend.BlendOp = Win32.AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = opacity;
blend.AlphaFormat = Win32.AC_SRC_ALPHA;
Win32.UpdateLayeredWindow(Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, Win32.ULW_ALPHA);
}
finally
{
Win32.ReleaseDC(IntPtr.Zero, screenDc);
if (hBitmap != IntPtr.Zero)
{
Win32.SelectObject(memDc, oldBitmap);
Win32.DeleteObject(hBitmap);
}
Win32.DeleteDC(memDc);
}
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00080000;
return cp;
}
}
Finally, call SetBitmap when loading a form.
private void Form1_Load(object sender, EventArgs e)
{
Bitmap myBitmap = new Bitmap(this.Width, this.Height);
Graphics graphics = Graphics.FromImage(myBitmap);
Rectangle gradientRectangle = new Rectangle(0, 0, this.Width - 1, this.Height - 1);
Brush b = new LinearGradientBrush(gradientRectangle, Color.DarkSlateBlue, Color.MediumPurple, 0.0f);
graphics.SmoothingMode = SmoothingMode.HighQuality;
FillRoundedRectangle(graphics, b, gradientRectangle, 35);
SetBitmap(myBitmap);
}
When you finish the above tasks, you can finally get Smooth Round Corners in WinForm Applications.
Full code of Form
public class RoundedForm : Form
{
private Timer drawTimer = new Timer();
public NanoRoundedForm()
{
this.FormBorderStyle = FormBorderStyle.None;
}
protected override void OnLoad(EventArgs e)
{
if (!DesignMode)
{
drawTimer.Interval = 1000 / 60;
drawTimer.Tick += DrawForm;
drawTimer.Start();
}
base.OnLoad(e);
}
private void DrawForm(object pSender, EventArgs pE)
{
using (Bitmap backImage = new Bitmap(this.Width, this.Height))
{
using (Graphics graphics = Graphics.FromImage(backImage))
{
Rectangle gradientRectangle = new Rectangle(0, 0, this.Width - 1, this.Height - 1);
using (Brush b = new LinearGradientBrush(gradientRectangle, Color.DarkSlateBlue, Color.MediumPurple, 0.0f))
{
graphics.SmoothingMode = SmoothingMode.HighQuality;
RoundedRectangle.FillRoundedRectangle(graphics, b, gradientRectangle, 35);
foreach (Control ctrl in this.Controls)
{
using (Bitmap bmp = new Bitmap(ctrl.Width, ctrl.Height))
{
Rectangle rect = new Rectangle(0, 0, ctrl.Width, ctrl.Height);
ctrl.DrawToBitmap(bmp, rect);
graphics.DrawImage(bmp, ctrl.Location);
}
}
PerPixelAlphaBlend.SetBitmap(backImage, Left, Top, Handle);
}
}
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (DesignMode)
{
Graphics graphics = e.Graphics;
Rectangle gradientRectangle = new Rectangle(0, 0, this.Width - 1, this.Height - 1);
Brush b = new LinearGradientBrush(gradientRectangle, Color.DarkSlateBlue, Color.MediumPurple, 0.0f);
graphics.SmoothingMode = SmoothingMode.HighQuality;
RoundedRectangle.FillRoundedRectangle(graphics, b, gradientRectangle, 35);
}
base.OnPaint(e);
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
if (!DesignMode)
{
cp.ExStyle |= 0x00080000;
}
return cp;
}
}
}
public static class RoundedRectangle
{
public static GraphicsPath RoundedRect(Rectangle bounds, int radius)
{
int diameter = radius * 2;
Size size = new Size(diameter, diameter);
Rectangle arc = new Rectangle(bounds.Location, size);
GraphicsPath path = new GraphicsPath();
if (radius == 0)
{
path.AddRectangle(bounds);
return path;
}
// top left arc
path.AddArc(arc, 180, 90);
// top right arc
arc.X = bounds.Right - diameter;
path.AddArc(arc, 270, 90);
// bottom right arc
arc.Y = bounds.Bottom - diameter;
path.AddArc(arc, 0, 90);
// bottom left arc
arc.X = bounds.Left;
path.AddArc(arc, 90, 90);
path.CloseFigure();
return path;
}
public static void FillRoundedRectangle(Graphics graphics, Brush brush, Rectangle bounds, int cornerRadius)
{
if (graphics == null)
throw new ArgumentNullException("graphics");
if (brush == null)
throw new ArgumentNullException("brush");
using (GraphicsPath path = RoundedRect(bounds, cornerRadius))
{
graphics.FillPath(brush, path);
}
}
}
internal static class PerPixelAlphaBlend
{
public static void SetBitmap(Bitmap bitmap, int left, int top, IntPtr handle)
{
SetBitmap(bitmap, 255, left, top, handle);
}
public static void SetBitmap(Bitmap bitmap, byte opacity, int left, int top, IntPtr handle)
{
if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
throw new ApplicationException("The bitmap must be 32ppp with alpha-channel.");
IntPtr screenDc = Win32.GetDC(IntPtr.Zero);
IntPtr memDc = Win32.CreateCompatibleDC(screenDc);
IntPtr hBitmap = IntPtr.Zero;
IntPtr oldBitmap = IntPtr.Zero;
try
{
hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
oldBitmap = Win32.SelectObject(memDc, hBitmap);
Win32.Size size = new Win32.Size(bitmap.Width, bitmap.Height);
Win32.Point pointSource = new Win32.Point(0, 0);
Win32.Point topPos = new Win32.Point(left, top);
Win32.BLENDFUNCTION blend = new Win32.BLENDFUNCTION();
blend.BlendOp = Win32.AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = opacity;
blend.AlphaFormat = Win32.AC_SRC_ALPHA;
Win32.UpdateLayeredWindow(handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, Win32.ULW_ALPHA);
}
finally
{
Win32.ReleaseDC(IntPtr.Zero, screenDc);
if (hBitmap != IntPtr.Zero)
{
Win32.SelectObject(memDc, oldBitmap);
Win32.DeleteObject(hBitmap);
}
Win32.DeleteDC(memDc);
}
}
}
internal class Win32
{
public enum Bool
{
False = 0,
True
};
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
public Int32 x;
public Int32 y;
public Point(Int32 x, Int32 y) { this.x = x; this.y = y; }
}
[StructLayout(LayoutKind.Sequential)]
public struct Size
{
public Int32 cx;
public Int32 cy;
public Size(Int32 cx, Int32 cy) { this.cx = cx; this.cy = cy; }
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct ARGB
{
public byte Blue;
public byte Green;
public byte Red;
public byte Alpha;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BLENDFUNCTION
{
public byte BlendOp;
public byte BlendFlags;
public byte SourceConstantAlpha;
public byte AlphaFormat;
}
public const Int32 ULW_COLORKEY = 0x00000001;
public const Int32 ULW_ALPHA = 0x00000002;
public const Int32 ULW_OPAQUE = 0x00000004;
public const byte AC_SRC_OVER = 0x00;
public const byte AC_SRC_ALPHA = 0x01;
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags);
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll", ExactSpelling = true)]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll", ExactSpelling = true)]
public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool DeleteObject(IntPtr hObject);
}
I'm trying to read a pixel from an external program and then get its RGB colors.
This works flawlessly whenever I find the location with the mouse and extract the pixel colors. However when I try to do it from a console program, the RBG colors comes back.. differently than what I expected.
I believe it could be an offset missing, so whenever I find the location using my mouse it's using my screens pixel, and whenever I activate the external program using the function below it will take the pixel locations from that window handle.
It could also be something about it being a game, and it's getting drawn differently, any tips? If I try to get the pixel colors from notepad it works.
[DllImport("user32.dll")] static extern bool SetForegroundWindow(IntPtr hWnd);
public static void Activate(string processName = "CookieGame")
{
var processes = Process.GetProcessesByName(processName);
var process = processes.FirstOrDefault();
if (process != null)
{
SetForegroundWindow(process.MainWindowHandle);
}
}
I use the following function for extracting pixel colors from a location, this is run after I've set the program as active window (function above):
public class MailReader
{
[DllImport("user32.dll")] public static extern bool GetCursorPos(ref Point lpPoint);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)] public static extern int BitBlt(IntPtr hDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);
static Bitmap screenPixel = new Bitmap(1, 1, PixelFormat.Format32bppArgb);
public static Color GetColorAt(Point location)
{
using (Graphics gdest = Graphics.FromImage(screenPixel))
{
using (Graphics gsrc = Graphics.FromHwnd(IntPtr.Zero)){
IntPtr hSrcDC = gsrc.GetHdc();
IntPtr hDC = gdest.GetHdc();
int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, location.X, location.Y, (int)CopyPixelOperation.SourceCopy);
gdest.ReleaseHdc();
gsrc.ReleaseHdc();
}
}
return screenPixel.GetPixel(0, 0);
}
}
Conole program being run, this returns the correct X and Y pixels that I told it to, but the colors come back off:
namespace TestProgram.TestConsole
{
class Program
{
static void Main(string[] args)
{
var model = new PixelInformation
{
X = 505,
Y = 27,
R = 117,
G = 208,
B = 50
};
var point = new Point();
point.X = model.X;
point.Y = model.Y;
ActivateWindow.Activate("cookieGame");
var location = PixelReader.GetColorAt(point);
Console.WriteLine("Position X: " + point.X + " Y: " + point.Y);
Console.WriteLine("R:" + location.R + " " + "G:" + location.G + " B:" + location.B);
Console.ReadKey();
}
}
}
These symptoms are consistent with your system using font scaling other than 100% and the process not being DPI aware. Thus the process is subject to DPI virtualization.
I have created a class change the appearance of the calander.
The class is based on these previous stackoverflow questions:
Source1: Setting calendar size when overriding DateTimePicker to add week numbers
Source2: Increase Font Size of DateTimePicker Calender in Win7 Aero Theme
This is the class:
class DateTimePickerImpl : DateTimePicker
{
private const int McmFirst = 0x1000;
private const int McmGetminreqrect = (McmFirst + 9);
private const int McsWeeknumbers = 0x4;
private const int DtmFirst = 0x1000;
private const int DtmGetmonthcal = (DtmFirst + 8);
[DllImport("User32.dll")]
private static extern int GetWindowLong(IntPtr handleToWindow, int offsetToValueToGet);
[DllImport("User32.dll")]
private static extern int SetWindowLong(IntPtr h, int index, int value);
[DllImport("uxtheme.dll")]
private static extern int SetWindowTheme(IntPtr hWnd, string appname, string idlist);
[DllImport("User32.dll")]
private static extern IntPtr SendMessage(IntPtr h, int msg, int param, int data);
[DllImport("User32.dll")]
private static extern IntPtr GetParent(IntPtr h);
[DllImport("User32.dll")]
private static extern int SendMessage(IntPtr h, int msg, int param, ref Rectangle data);
[DllImport("User32.dll")]
private static extern int MoveWindow(IntPtr h, int x, int y, int width, int height, bool repaint);
protected override void OnDropDown(EventArgs e)
{
CalendarFont = new Font("Microsoft Sans Serif", 20F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
const int offsetToGetWindowsStyles = (-16);
IntPtr pointerToCalenderWindow = SendMessage(Handle, DtmGetmonthcal, 0, 0);
SetWindowTheme(pointerToCalenderWindow, "", "");
int styleForWindow = GetWindowLong(pointerToCalenderWindow, offsetToGetWindowsStyles);
styleForWindow = styleForWindow | McsWeeknumbers;
Rectangle rect = new Rectangle();
SendMessage(pointerToCalenderWindow, McmGetminreqrect, 0, ref rect);
rect.Width = rect.Width + 30;
rect.Height = rect.Height + 10;
SetWindowLong(pointerToCalenderWindow, offsetToGetWindowsStyles, styleForWindow);
MoveWindow(pointerToCalenderWindow, 0, 0, rect.Width, rect.Height, true);
IntPtr parentWindow = GetParent(pointerToCalenderWindow);
MoveWindow(parentWindow, 0, 0, rect.Width, rect.Height, true);
base.OnDropDown(e);
}
}
Afther implementing this class the visible text of the datetimepicker changes to whatever you choose. But when using dateTimePickerImpl.Value it always returns the it began with (the time the datetimpicker loaded). I already found out by adding a method to the ValueChanged event that this event never happens. I found some similar problems online but these were all solved by setting the checked property of the datetimepicker to true. In my case the checked property is already true.
What did i do to break the datetimepicker?
I had to create the datetime picker manually instead of dragging it from the toolbox into the designer.
DateTimePickerImpl dtp = new DateTimePickerImpl();
dtp.Location = new Point(3, 254);
dtp.Name = "dtp";
dtp.Size = new Size(271, 26);
dtp.TabIndex = 25;
panel1.Controls.Add(dtp);
I'm currently working on creating an Ambilight for my computer monitor with C#, an arduino, and an Ikea Dioder. Currently the hardware portion runs flawlessly; however, I'm having a problem with detecting the average color of a section of screen.
I have two issues with the implementations that I'm using:
Performance - Both of these algorithms add a somewhat noticeable stutter to the screen. Nothing showstopping, but it's annoying while watching video.
No Fullscreen Game Support - When a game is in fullscreen mode both of these methods just return white.
public class DirectxColorProvider : IColorProvider
{
private static Device d;
private static Collection<long> colorPoints;
public DirectxColorProvider()
{
PresentParameters present_params = new PresentParameters();
if (d == null)
{
d = new Device(new Direct3D(), 0, DeviceType.Hardware, IntPtr.Zero, CreateFlags.SoftwareVertexProcessing, present_params);
}
if (colorPoints == null)
{
colorPoints = GetColorPoints();
}
}
public byte[] GetColors()
{
var color = new byte[4];
using (var screen = this.CaptureScreen())
{
DataRectangle dr = screen.LockRectangle(LockFlags.None);
using (var gs = dr.Data)
{
color = avcs(gs, colorPoints);
}
}
return color;
}
private Surface CaptureScreen()
{
Surface s = Surface.CreateOffscreenPlain(d, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, Format.A8R8G8B8, Pool.Scratch);
d.GetFrontBufferData(0, s);
return s;
}
private static byte[] avcs(DataStream gs, Collection<long> positions)
{
byte[] bu = new byte[4];
int r = 0;
int g = 0;
int b = 0;
int i = 0;
foreach (long pos in positions)
{
gs.Position = pos;
gs.Read(bu, 0, 4);
r += bu[2];
g += bu[1];
b += bu[0];
i++;
}
byte[] result = new byte[3];
result[0] = (byte)(r / i);
result[1] = (byte)(g / i);
result[2] = (byte)(b / i);
return result;
}
private Collection<long> GetColorPoints()
{
const long offset = 20;
const long Bpp = 4;
var box = GetBox();
var colorPoints = new Collection<long>();
for (var x = box.X; x < (box.X + box.Length); x += offset)
{
for (var y = box.Y; y < (box.Y + box.Height); y += offset)
{
long pos = (y * Screen.PrimaryScreen.Bounds.Width + x) * Bpp;
colorPoints.Add(pos);
}
}
return colorPoints;
}
private ScreenBox GetBox()
{
var box = new ScreenBox();
int m = 8;
box.X = (Screen.PrimaryScreen.Bounds.Width - m) / 3;
box.Y = (Screen.PrimaryScreen.Bounds.Height - m) / 3;
box.Length = box.X * 2;
box.Height = box.Y * 2;
return box;
}
private class ScreenBox
{
public long X { get; set; }
public long Y { get; set; }
public long Length { get; set; }
public long Height { get; set; }
}
}
You can find the file for the directX implmentation here.
public class GDIColorProvider : Form, IColorProvider
{
private static Rectangle box;
private readonly IColorHelper _colorHelper;
public GDIColorProvider()
{
_colorHelper = new ColorHelper();
box = _colorHelper.GetCenterBox();
}
public byte[] GetColors()
{
var colors = new byte[3];
IntPtr hDesk = GetDesktopWindow();
IntPtr hSrce = GetDC(IntPtr.Zero);
IntPtr hDest = CreateCompatibleDC(hSrce);
IntPtr hBmp = CreateCompatibleBitmap(hSrce, box.Width, box.Height);
IntPtr hOldBmp = SelectObject(hDest, hBmp);
bool b = BitBlt(hDest, box.X, box.Y, (box.Width - box.X), (box.Height - box.Y), hSrce, 0, 0, CopyPixelOperation.SourceCopy);
using(var bmp = Bitmap.FromHbitmap(hBmp))
{
colors = _colorHelper.AverageColors(bmp);
}
SelectObject(hDest, hOldBmp);
DeleteObject(hBmp);
DeleteDC(hDest);
ReleaseDC(hDesk, hSrce);
return colors;
}
// P/Invoke declarations
[DllImport("gdi32.dll")]
static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int
wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, CopyPixelOperation rop);
[DllImport("user32.dll")]
static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteDC(IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteObject(IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp);
[DllImport("user32.dll")]
private static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
private static extern IntPtr GetWindowDC(IntPtr ptr);
[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr ptr);
}
You Can Find the File for the GDI implementation Here.
The Full Codebase Can be Found Here.
Updated Answer
The problem of slow screen capture performance most likely is caused by BitBlt() doing a pixel conversion when the pixel formats of source and destination don't match. From the docs:
If the color formats of the source and destination device contexts do not match, the BitBlt function converts the source color format to match the destination format.
This is what caused slow performance in my code, especially in higher resolutions.
The default pixel format seems to be PixelFormat.Format32bppArgb, so that's what you should use for the buffer:
var screen = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb);
var gfx = Graphics.FromImage(screen);
gfx.CopyFromScreen(bounds.Location, new Point(0, 0), bounds.Size);
The next source for slow performance is Bitmap.GetPixel() which does boundary checks. Never use it when analyzing every pixel. Instead lock the bitmap data and get a pointer to it:
public unsafe Color GetAverageColor(Bitmap image, int sampleStep = 1) {
var data = image.LockBits(
new Rectangle(Point.Empty, Image.Size),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb);
var row = (int*)data.Scan0.ToPointer();
var (sumR, sumG, sumB) = (0L, 0L, 0L);
var stride = data.Stride / sizeof(int) * sampleStep;
for (var y = 0; y < data.Height; y += sampleStep) {
for (var x = 0; x < data.Width; x += sampleStep) {
var argb = row[x];
sumR += (argb & 0x00FF0000) >> 16;
sumG += (argb & 0x0000FF00) >> 8;
sumB += argb & 0x000000FF;
}
row += stride;
}
image.UnlockBits(data);
var numSamples = data.Width / sampleStep * data.Height / sampleStep;
var avgR = sumR / numSamples;
var avgG = sumG / numSamples;
var avgB = sumB / numSamples;
return Color.FromArgb((int)avgR, (int)avgG, (int)avgB);
}
This should get you well below 10 ms, depending on the screen size. In case it is still too slow you can increase the sampleStep parameter of GetAverageColor().
Original Answer
I recently did the same thing and came up with something that worked surprisingly good.
The trick is to create an additional bitmap that is 1x1 pixels in size and set a good interpolation mode on its graphics context (bilinear or bicubic, but not nearest neighbor).
Then you draw your captured bitmap into that 1x1 bitmap exploiting the interpolation and retrieve that pixel to get the average color.
I'm doing that at a rate of ~30 fps. When the screen shows a GPU rendering (e.g. watching YouTube full screen with enabled hardware acceleration in Chrome) there is no visible stuttering or anything. In fact, CPU utilization of the application is way below 10%. However, if I turn off Chrome's hardware acceleration then there is definitely some slight stuttering noticeable if you watch close enough.
Here are the relevant parts of the code:
using var screen = new Bitmap(width, height);
using var screenGfx = Graphics.FromImage(screen);
using var avg = new Bitmap(1, 1);
using var avgGfx = Graphics.FromImage(avg);
avgGfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
while (true) {
screenGfx.CopyFromScreen(left, top, 0, 0, screen.Size);
avgGfx.DrawImage(screen, 0, 0, avg.Width, avg.Height);
var color = avg.GetPixel(0, 0);
var bright = (int)Math.Round(Math.Clamp(color.GetBrightness() * 100, 1, 100));
// set color and brightness on your device
// wait 1000/fps milliseconds
}
Note that this works for GPU renderings, because System.Drawing.Common uses GDI+ nowadays. However, it does not work when the content is DRM protected. So it won't work with Netflix for example :(
I published the code on GitHub. Even though I abandoned the project due to Netflix' DRM protection it might help someone else.