c# - 如何使用半透明选择器选择屏幕上任意位置的颜色?

标签 c# winforms graphics drawing gdi+

小免责声明:这是我第一次使用表单中的图形,因此我对这里的概念不太熟悉

好吧,所以我一直在尝试制作一个应用程序来跟踪光标在整个屏幕中的位置并在其周围绘制一个椭圆。我借用的代码来自this问题(我更改了椭圆的 X 和 Y 位置,以便在光标周围自动调整自身,无论其大小如何)到目前为止,一切都很完美。这是到目前为止的代码:

        public static float width;
        public static float height;

        public Main(float w, float h)
        {
            InitializeComponent();
            this.DoubleBuffered = true;
            width = w;
            height = h;
            BackColor = Color.White;
            FormBorderStyle = FormBorderStyle.None;
            Bounds = Screen.PrimaryScreen.Bounds;
            TopMost = true;
            TransparencyKey = BackColor;
            this.ShowInTaskbar = false;
            timer1.Tick += timer1_Tick;
        }

        Timer timer1 = new Timer() { Interval = 1, Enabled = true };

        protected override void OnPaint(PaintEventArgs e)
        {
            DrawTest(e.Graphics);
            base.OnPaint(e);
        }

        private void DrawTest(Graphics g)
        {
            var p = PointToClient(Cursor.Position);
            g.DrawEllipse(Pens.DeepSkyBlue, p.X - (width / 2), p.Y - (height / 2), width, height);
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            Invalidate();
        }

所以现在我希望应用程序检查椭圆区域内是否存在预先分配的颜色,如果存在,则获取距具有该颜色的光标最近的像素的位置。我到处搜索过,但没有找到任何方法。

我知道它背后的逻辑是获取椭圆内的所有像素,检查颜色是否存在并找到最接近光标的具有该颜色的一个像素,但我无法实现它。

任何帮助将不胜感激。

最佳答案

这是一种简化的方法(它不需要 PInvoking、鼠标跟踪/ Hook 或其他低级操作)。
如果您不需要对窗口后面发生的事情进行太多控制,您不想记录动画图像,只需执行问题描述中的操作:捕获当前在鼠标指针。

这里使用了一个技巧:表单的BackColor及其 TransparencyKey设置为蓝色 ( Color.Navy )。这允许拥有透明但实体的表单。
在实践中,MouseMove即使表单完全透明并且可以点击,也会引发事件。

另一个准技巧是使用标准 DoubleBuffer 对表单进行双缓冲属性(property),而不是 OptimizedDoubleBuffer可以调用 SetStyle() 来启用它方法。

ResizeRedraw属性设置为 true,因此如果/调整大小时,表单会重新绘制自身。

通过此设置,要获取光标位置下的颜色,您只需使用大小为 (1, 1) 的位图拍摄当前屏幕的单像素快照。 (我们只需要那个像素)并使用(不是很快但功能齐全) GetPixel() 方法从位图中读取颜色。

当单击鼠标右键时,光标下的颜色将保存在 List<Color> 中(可以使用公共(public)/只读 SavedColors 属性进行访问),然后在用作此调色板 Canvas 的 PictureBox 中进行绘制。

构建此示例:

  • 创建新表单
  • 添加一个 PictureBox(此处名为 picColor )并将其锚定在右上角。该控件用于当鼠标指针移动时显示光标下的当前颜色。
  • 在前一个 PictureBox 下添加第二个 PictureBox(此处名为 picPalette ),并将其固定在“上-右-下”位置。这用于绘制已保存颜色的当前调色板。
    在设计器中,使用事件面板通过您可以在此代码中找到的处理程序方法订阅 Paint 事件(即,不要添加另一个)。

using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

public partial class frmColorPicker : Form
{
    Color m_CurrentColor = Color.Empty;
    List<Color> m_SavedColors = new List<Color>();

    public frmColorPicker()
    {
        InitializeComponent();
        ResizeRedraw = true;
        DoubleBuffered = true;

        TopMost = true;
        BackColor = Color.Navy;
        TransparencyKey = Color.Navy;
    }

    public Color CursorEllipseColor { get; set; } = Color.Orange;

    public List<Color> SavedColors => m_SavedColors;

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        GetColorUnderCursor();
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        var rect = GetCursorEllipse();
        using (var pen = new Pen(CursorEllipseColor, 2)) {
            e.Graphics.DrawEllipse(pen, rect);
        }
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);
        if (e.Button == MouseButtons.Right) {
            m_SavedColors.Add(m_CurrentColor);
            picPalette.Invalidate();
        }
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        base.OnMouseMove(e);
        Invalidate();
    }

    private Rectangle GetCursorEllipse()
    {
        var cursorEllipse = new Rectangle(PointToClient(Cursor.Position), Cursor.Size);
        cursorEllipse.Offset(-cursorEllipse.Width / 2, -cursorEllipse.Height / 2);
        return cursorEllipse;
    }

    private void GetColorUnderCursor()
    {
        using (var bmp = new Bitmap(1, 1))
        using (var g = Graphics.FromImage(bmp)) {
            g.CopyFromScreen(Cursor.Position, Point.Empty, new Size(1, 1));
            m_CurrentColor = bmp.GetPixel(0, 0);
            picColor.BackColor = m_CurrentColor;
        }
    }

    private void picPalette_Paint(object sender, PaintEventArgs e)
    {
        int rectsCount = 0;
        int rectsLines = 0;
        int rectsPerLine = picPalette.Width / 20;

        foreach (var color in m_SavedColors) {
            using (var brush = new SolidBrush(color)) {
                var rect = new Rectangle(new Point(rectsCount * 20, rectsLines * 20), new Size(20, 20));
                e.Graphics.FillRectangle(brush, rect);
                e.Graphics.DrawRectangle(Pens.DarkGray, rect);
                rectsCount += 1;
                if (rectsCount == rectsPerLine) {
                    rectsCount = 0;
                    rectsLines += 1;
                }
            }
        }
    }
}

它是这样工作的:

Transparent Form GetPixel

关于c# - 如何使用半透明选择器选择屏幕上任意位置的颜色?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62428888/

相关文章:

c# - 使用非空构造函数初始化泛型

c# - 是否有可能使 WinForms 选项卡控件能够像 IE 或 Firefox 一样进行选项卡重新排序?

c# - MSI 为安装人员提出问题

math - 为什么通过添加坐标来添加两个点是错误的?

c# - 如何将多行文本添加到 ListBox 项?

c# - 向后播放动画( Storyboard)

c# - 使用 dataGridView 和 BindingSource 的奇怪问题

C# 类不能伪装成另一个类,因为无法覆盖 GetType 方法

c# - 为什么调用 ClearSelected 并附加数据源后 SelectedIndex 可能为 0?

c# - 将 .net 图形转换为 java