unity 生成标记根据背景色变色为明显的颜色

设置

两个是RawImage一个是显示相机,一个是显示标记

两个的层级关系不能错,不然相机显示的会挡住标记。

CameraDrawingAutoColor.cs

cs 复制代码
    using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;

public class CameraDrawingAutoColor : MonoBehaviour
{
    [Header("底层显示摄像头")]
    public RawImage CamRaw;
    [Header("顶层绘图面板(同尺寸全屏)")]
    public RawImage DrawRaw;

    private WebCamTexture _camTex;
    private Texture2D _drawTex;
    private int _w, _h;

    private bool _isDraw;
    private List<Vector2> _curPoints = new List<Vector2>();
    private List<Stroke> _history = new List<Stroke>();

    private readonly Color[] _candColors =
    {
        Color.black,Color.white,Color.red,Color.green,
        Color.blue,Color.yellow,Color.cyan,Color.magenta
    };

    [Serializable]
    class Stroke
    {
        public List<Vector2> pts = new List<Vector2>();
        public Color col;
    }

    void Start()
    {
        // 初始化相机
        var device = WebCamTexture.devices[0];
        _camTex = new WebCamTexture(device.name, 1280, 720);
        _camTex.Play();
        CamRaw.texture = _camTex;

        // 初始化顶层绘图纹理
        Invoke(nameof(InitDrawTex), 0.5f);
    }

    void InitDrawTex()
    {
        _w = _camTex.width;
        _h = _camTex.height;
        _drawTex = new Texture2D(_w, _h, TextureFormat.RGBA32, false);
        _drawTex.wrapMode = TextureWrapMode.Clamp;
        ClearDrawTex();
        DrawRaw.texture = _drawTex;
    }

    void Update()
    {
        if (_drawTex == null) return;

        DealMouse();
        RenderDraw();

        // 快捷键
        if (Input.GetKeyDown(KeyCode.C)) ClearAll();
        if (Input.GetKeyDown(KeyCode.S)) SaveImg();
    }

    void DealMouse()
    {
        // 按下
        if (Input.GetMouseButtonDown(0))
        {
            _isDraw = true;
            _curPoints.Clear();
            _curPoints.Add(GetMouseTexPos());
        }
        // 拖动
        if (Input.GetMouseButton(0) && _isDraw)
        {
            var p = GetMouseTexPos();
            if (Vector2.Distance(p, _curPoints.Last()) > 2f)
                _curPoints.Add(p);
        }
        // 抬起
        if (Input.GetMouseButtonUp(0) && _isDraw)
        {
            _isDraw = false;
            if (_curPoints.Count < 2)
            {
                _curPoints.Clear();
                return;
            }
            // 自动选对比色
            Color bestCol = CalcStrokeColor(_curPoints);
            _history.Add(new Stroke { pts = new List<Vector2>(_curPoints), col = bestCol });
            _curPoints.Clear();
        }
    }

    // 鼠标屏幕坐标 → 纹理像素坐标
    Vector2 GetMouseTexPos()
    {
        RectTransform rect = DrawRaw.rectTransform;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(rect, Input.mousePosition, null, out Vector2 local);

        float x = Mathf.Lerp(0, _w, (local.x / rect.rect.width) + 0.5f);
        float y = Mathf.Lerp(0, _h, (local.y / rect.rect.height) + 0.5f);

        x = Mathf.Clamp(x, 0, _w - 1);
        y = Mathf.Clamp(y, 0, _h - 1);
        return new Vector2(x, y);
    }

    // 渲染所有笔画
    void RenderDraw()
    {
        ClearDrawTex();

        // 历史正式线条
        foreach (var s in _history)
            DrawLineList(s.pts, s.col);

        // 当前临时灰色线
        if (_isDraw && _curPoints.Count > 1)
            DrawLineList(_curPoints, Color.red);

        _drawTex.Apply();
    }

    void DrawLineList(List<Vector2> pts, Color col)
    {
        for (int i = 0; i < pts.Count - 1; i++)
            DrawBresenham(pts[i], pts[i + 1], col, 3);
    }

    // 画线
    void DrawBresenham(Vector2 p1, Vector2 p2, Color col, int size)
    {
        int x0 = (int)p1.x, y0 = (int)p1.y;
        int x1 = (int)p2.x, y1 = (int)p2.y;

        int dx = Mathf.Abs(x1 - x0);
        int dy = Mathf.Abs(y1 - y0);
        int sx = x0 < x1 ? 1 : -1;
        int sy = y0 < y1 ? 1 : -1;
        int err = dx - dy;

        while (true)
        {
            DrawPoint(x0, y0, col, size);
            if (x0 == x1 && y0 == y1) break;
            int e2 = 2 * err;
            if (e2 > -dy) { err -= dy; x0 += sx; }
            if (e2 < dx) { err += dx; y0 += sy; }
        }
    }

    void DrawPoint(int x, int y, Color col, int r)
    {
        for (int dx = -r; dx <= r; dx++)
            for (int dy = -r; dy <= r; dy++)
            {
                if (dx * dx + dy * dy <= r * r)
                {
                    int px = x + dx;
                    int py = y + dy;
                    if (px >= 0 && px < _w && py >= 0 && py < _h)
                        _drawTex.SetPixel(px, py, col);
                }
            }
    }

    // 计算整条笔画最佳对比色
    Color CalcStrokeColor(List<Vector2> pts)
    {
        Dictionary<Color, int> vote = new Dictionary<Color, int>();
        foreach (var p in pts)
        {
            Color c = GetBestColor((int)p.x, (int)p.y);
            if (vote.ContainsKey(c)) vote[c]++;
            else vote[c] = 1;
        }
        return vote.OrderByDescending(k => k.Value).First().Key;
    }

    Color GetBestColor(int x, int y)
    {
        int win = 15;
        int x1 = Mathf.Max(0, x - win);
        int y1 = Mathf.Max(0, y - win);
        int x2 = Mathf.Min(_w - 1, x + win);
        int y2 = Mathf.Min(_h - 1, y + win);

        Color avg = Color.black;
        float r = 0, g = 0, b = 0;
        int cnt = 0;

        for (int yy = y1; yy <= y2; yy++)
            for (int xx = x1; xx <= x2; xx++)
            {
                Color c = _camTex.GetPixel(xx, yy);
                r += c.r; g += c.g; b += c.b; cnt++;
            }

        avg = new Color(r / cnt, g / cnt, b / cnt);
        float maxDis = -999;
        Color best = Color.red;

        foreach (var cc in _candColors)
        {
            float d = Mathf.Pow(cc.r - avg.r, 2) + Mathf.Pow(cc.g - avg.g, 2) + Mathf.Pow(cc.b - avg.b, 2);
            if (d > maxDis)
            {
                maxDis = d;
                best = cc;
            }
        }
        return best;
    }

    void ClearDrawTex()
    {
        if (_drawTex == null) return;
        Color[] clear = new Color[_w * _h];
        for (int i = 0; i < clear.Length; i++)
            clear[i] = Color.clear;
        _drawTex.SetPixels(clear);
    }

    void ClearAll()
    {
        _history.Clear();
        _curPoints.Clear();
        _isDraw = false;
        ClearDrawTex();
    }

    void SaveImg()
    {
        RenderTexture rt = new RenderTexture(_w, _h, 24);
        rt.Create();
        CamRaw.texture = _camTex;
        DrawRaw.texture = _drawTex;

        Texture2D saveTex = new Texture2D(_w, _h);
        saveTex.ReadPixels(new Rect(0, 0, _w, _h), 0, 0);
        saveTex.Apply();

        byte[] png = saveTex.EncodeToPNG();
        string path = Application.persistentDataPath + "/cam_draw.png";
        System.IO.File.WriteAllBytes(path, png);
        Debug.Log("保存成功:" + path);
    }

    void OnDestroy()
    {
        if (_camTex != null) _camTex.Stop();
    }
}
相关推荐
真鬼12313 小时前
【Unity 6】Unity6快捷下载,快速下载
unity·游戏引擎
会潜水的小火龙15 小时前
unity打包apk报错Failure to initialize问题解决方法
unity·游戏引擎
平行云17 小时前
实时云渲染平台数据通道,支持3D应用文件上传下载分享无缝交互
linux·unity·云原生·ue5·gpu算力·实时云渲染·像素流送
Sator119 小时前
unity仅用粒子系统实现拖尾
unity·游戏引擎
游乐码19 小时前
Unity基础(五)四元数相关
unity·游戏引擎
想做后端的前端19 小时前
Unity热更新 - HybridCLR & YooAsset
unity·游戏引擎
鹿野素材屋20 小时前
Unity预加载:减少游戏中首次加载资源时的卡顿
windows·游戏·unity
RPGMZ20 小时前
RPGMZ游戏引擎事件技巧大全
javascript·游戏引擎·事件·rpgmz·rpgmakermz
天若有情67320 小时前
Superpowers 游戏引擎核心应用场景与落地指南
游戏引擎·superpowers
winlife_21 小时前
嵌入式 MCP server vs 外挂桥接进程:引擎编辑器自动化的架构取舍
架构·自动化·编辑器·游戏引擎·架构设计·mcp·编辑器自动化