Unity实现鼠标拾取电脑屏幕指定区域像素点颜色

👉一、前言及知识点

1、前言

开发时常遇到要动态修改物体或UI的颜色的需求,而且需要像Unity编辑器一样弹出颜色选择器来选择颜色。
在这里插入图片描述
心想如果还要从0开始开发一套取色器,那工作量可不少,最后我选择使用第三方颜色选择器插件ColorPicker来完成取色的功能。但在使用ColorPicker插件的过程中我发现用取色笔拾取电脑屏幕像素时会报错:attempting to ReadPixels outside of RenderTexture bounds! Reading (2396, 2905, 2412, 2921) from (1920, 1080)(意思是试图读取的像素超出纹理边界)
排查报错的原因:
在这里插入图片描述
原来ColorPicker插件的作者写取色笔拾色的逻辑是截取鼠标坐标点指定区域的图像,然后再通过获取图像中心点位置的像素点颜色来完成取色。由于插件源代码使用Texture2D.ReadPixels方法来绘制纹理图像,这个方法只能读取到当前分辨率的Game游戏窗口范围内像素,一旦超出这个范围就无法读取且会报错。
解决方法:Unity库中我没有找到可以读取Game窗口外电脑屏幕像素的方法,于是我将方向转到了.NET类库中。可以使用.net库中System.Drawing.dll截取电脑屏幕快照,创建图像的位图数据,将其转为字节数组,再将字节数组加载到Unity的纹理Texture图像上,最后读取该Texture纹理的像素颜色。

2、知识点

1. 用.Net类库System.Drawing截取鼠标指定区域图像
2. 将位图数据Bitmap转为字节数组
3. 将字节数据转为Unity的Texture纹理图像
4. 通过Unity的Texture.GetPixel()方法获取图片上指定点像素颜色

👉二、实现鼠标拾取电脑屏幕指定区域像素颜色

1、准备工作

导入.NET类库System.Drawing.dll。(使用搜索工具查找或者从你的unity安装路径下找到当前unity运行平台下的System.Drawing.dll)
在这里插入图片描述

2、使用.Net类库System.Drawing截取图像并转为Unity支持的纹理图像Texture

新建核心脚本GetScreenPixel.cs:

//引用依赖的命名空间
using UnityEngine;
using System.Drawing;
using System;
using System.Drawing.Imaging;
//获取屏幕像素点的类
public class GetScreenPixel
{
    private static Bitmap bitmapSrc;//屏幕快照的位图数据
    private static int multiple;//屏幕快照比例系数,可用于放大缩小
    
    /// <summary>
    /// 截取鼠标点的屏幕快照,将其转为Unity的Texture2D纹理图像
    /// </summary>
    /// <param name="width"></param>
    /// <param name="height"></param>
    /// <returns></returns>
    public static Texture2D GetTexture(int width, int height)
    {
        Size size = new Size(width, height);//截取的大小
        bitmapSrc = new Bitmap(width, height);//获取的位图大小
        multiple = 1;
        BitmapReset(bitmapSrc);//重置图片,解决超出屏幕部分图像残留BUG
        System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmapSrc);//根据位图数据创建新图
        g.CopyFromScreen(new Point(System.Windows.Forms.Cursor.Position.X - width / (2 * multiple), System.Windows.Forms.Cursor.Position.Y - height / (2 * multiple)), new Point(0, 0), size);//从屏幕上传输指定区域大小的图像数据到Graphics中绘制出来
        IntPtr dc1 = g.GetHdc();
        g.ReleaseHdc(dc1);//释放当前句柄
        Texture2D tex = new Texture2D(width, height, TextureFormat.RGB24, false);
        tex.LoadImage(BitmapToByte(bitmapSrc));//加载图像字节数组到纹理。 
        return tex;
    }
    /// <summary>
    /// 将bitmap位图流转为字节流数组
    /// </summary>
    /// <param name="bitmap"></param>
    /// <returns></returns>
    public static byte[] BitmapToByte(System.Drawing.Bitmap bitmap)
    {
        // 1.先将BitMap转成内存流
        System.IO.MemoryStream ms = new System.IO.MemoryStream();
        //bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);//Unity加载时不支持bmp格式数据
        bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);//将位图数据保存为png类型的数据
        ms.Seek(0, System.IO.SeekOrigin.Begin);
        // 2.再将内存流转成byte[]并返回
        byte[] bytes = new byte[ms.Length];
        ms.Read(bytes, 0, bytes.Length);
        ms.Dispose();
        return bytes;
    }

    /// <summary>
    /// 重置位图
    /// </summary>
    /// <param name="bitmap"></param>
    private static void BitmapReset(Bitmap bitmap)
    {
        System.Drawing.Imaging.BitmapData bitmapdata = bitmap.LockBits(new Rectangle(new Point(0, 0), bitmap.Size), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
        unsafe
        {
            byte* dataPointer = (byte*)(bitmapdata.Scan0.ToPointer());//数据矩阵在内存中的地址指针
            for (int y = 0; y < bitmapdata.Height; y++)
            {
                for (int x = 0; x < bitmapdata.Width; x++)
                {
                    dataPointer[0] = 0;
                    dataPointer[1] = 0;
                    dataPointer[2] = 0;
                    dataPointer[3] = 0;
                    dataPointer += 4;
                }
            }
        }
        bitmap.UnlockBits(bitmapdata);
    }
}
3、需要注意的点

1.将位图数据转为字节流时需要指定图片格式为png或jgp类型(Unity支持的格式),否则会出现unity加载纹理时显示红色问号图像的情况。
2.在GetScreenPixel脚本中处理位图时用到了c++的指针,如果是使用Unity2018以后的版本,请在PlayerSetting/OtherSetting中勾选Allow ‘unsafe’ Code:否则编译器可能会报无法识别不安全代码的错误。
在这里插入图片描述

👉三、集成到ColorPicker插件源代码中完成取色笔的功能

ColorPicker插件的核心脚本就是ColorPicker,我们需要在该脚本中调用GetScreenPixel脚本的GetTexture()方法,以获取鼠标点屏幕快照,并读取得到的图像中心点的像素颜色。除此之外,还要注释掉一些代码。

1、修改ColorPicker脚本中的源代码

1.添加GetScreenColor方法,并在Update函数里调用该方法和注释掉原来截图的方法
在这里插入图片描述

        /// 获取电脑屏幕鼠标点像素的方法
        private void GetScreenColor()
        {
            var xCount = m_imageMesh.XAxisCount;//取色区域的宽
            var yCount = m_imageMesh.YAxisCount;//取色区域的高
            m_texture = GetScreenPixel.GetTexture(xCount, yCount);//获取鼠标点取色区域指定宽高的图像
            m_screenImage.sprite = Sprite.Create(m_texture, new Rect(0, 0, xCount, yCount), Vector2.zero);//创建图像的精灵图赋值给取色的image
        }

在这里插入图片描述
2.添加OnApplicationFocus(bool focus)方法

在这里插入图片描述

因为当鼠标点在Game窗口外是不响应鼠标点击函数的,所以选择屏幕外像素颜色时,我们要用到OnApplicationFocus函数,也就是运行unity时在后台中点击鼠标的话,unity程序会“失去焦点”,会调用一次OnApplicationFocus函数,传入的参数是False。此时我们就可以设置取色器上的颜色为当前颜色面板图的中心点的像素颜色了。

        /// 当程序失去焦点时,点击程序Game画面外focus为false
        public void OnApplicationFocus(bool focus)
        {
            if (!focus)
            {
                //获取图片上中心点位置像素颜色
                Color = m_screenImage.sprite.texture.GetPixel(m_imageMesh.XAxisCount / 2 + 1, m_imageMesh.YAxisCount / 2 + 1);
                SetNoniusPositionByColor();
                WorkState = E_WorkState.Normal;
            }
        }

3.注释掉脚本上OnDisable函数里的内容
因为初始是要隐藏掉取色器面板的,隐藏时会调用该函数,将按钮事件监听都移除了,就无法响应鼠标事件了,所以需要注释或删掉。
在这里插入图片描述

2、使用修改后的ColorPicker插件取色笔功能修改Image和Text的颜色

1.搭建测试场景
在这里插入图片描述
分别新建image和text用来测试取色器修改后取色笔的功能。
2.写测试脚本
因为ColorPicker脚本为我们提供了取色器面板上的颜色访问器:
在这里插入图片描述
所以在需要修改的物体和UI的颜色脚本里,声明一下ColorPicker对象,调用该对象的颜色访问器即可。
如下脚本所示:

using UnityEngine;
using UnityEngine.UI;

public class SetColorTest : MonoBehaviour
{
    public GameObject colorPanel;//取色器面板
    public Image setColorImg;//需要修改颜色的image
    public Text setColorText;//需要修改颜色的text
    public Button closeBtn;
    private Button pickBtn;
    private SpringGUI.ColorPicker picker;//声明一个取色器对象
    private bool bPickColor = false;//是否拾取颜色
    private void Start()
    {
        pickBtn = setColorImg.GetComponent<Button>();
        picker = colorPanel.transform.Find("ColorPicker").GetComponent<SpringGUI.ColorPicker>();
        colorPanel.SetActive(false);
        closeBtn.onClick.AddListener(() =>
        {
            colorPanel.SetActive(false);//关闭取色器
            bPickColor = false;
        });
        pickBtn.onClick.AddListener(() =>
        {
            colorPanel.gameObject.SetActive(true);//打开取色器
            bPickColor = true;
        });
    }
    private void Update()
    {
        if (bPickColor && picker!=null && picker.gameObject.activeSelf)
        {
            //将测试的Image和Texty颜色更新为拾色器面板选择的颜色
            setColorImg.color = picker.Color;
            setColorText.color = picker.Color;
        }
    }
}
3、测试修改后的取色笔功能
1.编辑器内测试

请添加图片描述

2.打包exe后测试

请添加图片描述
Perfect!完美😄

  • 23
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 32
    评论
您可以使用Unity鼠标输入来实现这个功能。以下是一种可能的实现方式: 1. 在Unity中创建一个空的GameObject,并将以下脚本附加到该GameObject上: ```csharp using System.Collections.Generic; using UnityEngine; public class DrawArea : MonoBehaviour { private List<Vector3> points = new List<Vector3>(); private bool isDrawing = false; private void Update() { if (Input.GetMouseButtonDown(0)) { StartDrawing(); } else if (Input.GetMouseButtonUp(0)) { StopDrawing(); } if (isDrawing) { Vector3 mousePosition = Input.mousePosition; mousePosition.z = Camera.main.nearClipPlane; Vector3 worldPosition = Camera.main.ScreenToWorldPoint(mousePosition); worldPosition.z = 0f; if (!points.Contains(worldPosition)) { points.Add(worldPosition); } } } private void OnPostRender() { if (isDrawing && points.Count > 1) { GL.PushMatrix(); GL.LoadPixelMatrix(); GL.Begin(GL.QUADS); GL.Color(Color.red); for (int i = 0; i < points.Count - 1; i++) { Vector3 p1 = Camera.main.WorldToScreenPoint(points[i]); Vector3 p2 = Camera.main.WorldToScreenPoint(points[i + 1]); GL.Vertex3(p1.x, p1.y, 0); GL.Vertex3(p2.x, p2.y, 0); GL.Vertex3(p2.x, p2.y, 1); GL.Vertex3(p1.x, p1.y, 1); } GL.End(); GL.PopMatrix(); } } private void StartDrawing() { isDrawing = true; points.Clear(); } private void StopDrawing() { isDrawing = false; FillArea(); points.Clear(); } private void FillArea() { // 在此处添加填充区域的代码,可以使用Unity提供的2D或3D绘制功能来实现 // 例如,可以使用Unity的SpriteRenderer组件来绘制一个填充区域的精灵 } } ``` 2. 在FillArea()方法中,您可以使用Unity提供的2D或3D绘制功能来实现填充区域。例如,您可以使用Unity的SpriteRenderer组件来绘制一个填充区域的精灵。 请注意,以上代码仅为示例,实际实现可能需要根据您的需求进行修改和调整。希望对您有所帮助!
评论 32
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周周的Unity小屋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值