Unity画板功能实现

用于签字或绘画的画板功能实现

1.开箱即用

  • Canvas下新建一个RawImage组件
  • 脚本挂载到组件上
  • 调整组件RectTransform宽高并设置脚本上的texture宽高(两者对应)

2.功能实现

很容易想到要实现类似画图的功能,需要创建一个空白贴图,并在鼠标点击处绘制像素

  1. 连续绘画:与射击游戏的子弹检测类似,我们每一帧检测鼠标位置并绘制像素,有个问题是两帧之间的像素如何绘制,直接插值计算两帧之间的像素坐标是不可行的,因为鼠标移动速度可以很快,导致画出来的线横平竖直,这就不是绘画而是拉钢筋了 : ( ,解决方案是我们给定一个参数minLength,即相邻两帧绘制的两个像素点之间的距离限制,如果超出距离限制,我们就在上一帧位置的基础上,通过计算方向向量乘上minLength的方法计算出本帧的坐标进行绘制,这样就可以使得绘制更加顺滑(当然也有缺点,那就是绘画不跟手/鼠标)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    curMousePosition = Input.mousePosition;
    if (isFirstClick)
    {
    finalPosition = curMousePosition;
    isFirstClick = false;
    }
    else
    {
    Vector2 diff = curMousePosition - previousMousePosition;
    if (diff.magnitude > minLength)
    {
    finalPosition = previousMousePosition + diff.normalized * minLength;
    }
    else
    {
    finalPosition = curMousePosition;
    }
    }
    DrawOnCanvas(finalPosition);
    previousMousePosition = finalPosition;
  2. 坐标转化:校正鼠标坐标到画布坐标系,看图就明白噜
    1
    curMousePosition = new Vector2(curMousePosition.x - (Screen.width - textureWidth) / 2, curMousePosition.y - (Screen.height - textureHeight) / 2);
    坐标转化

3.一些问题

在使用过程中发现存在一些问题:

  • 如果程序帧率过低的话会导致不跟手的现象,所以需要优化两帧之间最小值(minLength),笔触大小(penSize),帧数之间的平衡,使之达到较好的书写效果

4.完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
using System;
using System.IO;
using UnityEngine;
using UnityEngine.UI;

public class DrawingBoard : MonoBehaviour
{
public int textureWidth = 512;
public int textureHeight = 512;
public Color penColor = Color.black;
public Color eraserColor = Color.white;
public float penSize = 10f;
public float minLength = 1f;

private Texture2D canvasTexture;
private RawImage rawImage;
private bool isFirstClick = true;
private Vector2 curMousePosition;
private Vector2 previousMousePosition;
private Vector3 finalPosition;
private DrawTool curDrawTool = DrawTool.Pen;

private enum DrawTool
{
Pen = 0,
Eraser = 1
}

void Start()
{
// 获取RawImage组件
rawImage = GetComponent<RawImage>();

// 创建画布纹理
CreateCanvasTexture();
}

void Update()
{
// 检测鼠标按下
if (Input.GetMouseButton(0))
{
curMousePosition = Input.mousePosition;
// 校正鼠标坐标
curMousePosition = new Vector2(curMousePosition.x - (Screen.width - textureWidth) / 2, curMousePosition.y - (Screen.height - textureHeight) / 2);
// 点击第一帧直接绘制
if (isFirstClick)
{
finalPosition = curMousePosition;
isFirstClick = false;
}
else
{
Vector2 diff = curMousePosition - previousMousePosition;
if (diff.magnitude > minLength)
{
finalPosition = previousMousePosition + diff.normalized * minLength;
}
else
{
finalPosition = curMousePosition;
}
}
// 绘制
DrawOnCanvas(finalPosition);
// 记录上一帧鼠标坐标
previousMousePosition = finalPosition;
}
// 检测鼠标抬起
if (Input.GetMouseButtonUp(0))
{
isFirstClick = true;
}
}

private void CreateCanvasTexture()
{
// 创建画布纹理
canvasTexture = new Texture2D(textureWidth, textureHeight);
rawImage.texture = canvasTexture;

// 初始化画布为白色
ResetTexture();
}

public void ResetTexture()
{
// 初始化画布为白色
Color[] pixels = canvasTexture.GetPixels();
for (int i = 0; i < pixels.Length; i++)
{
pixels[i] = Color.white;
}
canvasTexture.SetPixels(pixels);
canvasTexture.Apply();
}

private void DrawOnCanvas(Vector2 pos)
{
// 在纹理上绘制像素
int centerX = Mathf.RoundToInt(pos.x);
int centerY = Mathf.RoundToInt(pos.y);

// 避免在边界绘制时出现数组越界
if (centerX >= 0 && centerX < canvasTexture.width && centerY >= 0 && centerY < canvasTexture.height)
{
DrawPixel(centerX, centerY, curDrawTool);
}
}

private void DrawPixel(int x, int y, DrawTool tool)
{
// 根据画笔类型切换绘制颜色
Color color = Color.white;
switch (tool)
{
case DrawTool.Pen:
color = penColor;
break;
case DrawTool.Eraser:
color = eraserColor;
break;
}

// 计算圆的半径
int radius = Mathf.RoundToInt(penSize) / 2;

// 在圆的范围内绘制像素
for (int i = -radius; i <= radius; i++)
{
for (int j = -radius; j <= radius; j++)
{
// 计算当前像素的坐标
int pixelX = x + i;
int pixelY = y + j;

// 判断当前像素是否在画布范围内
if (pixelX >= 0 && pixelX < canvasTexture.width && pixelY >= 0 && pixelY < canvasTexture.height)
{
// 判断当前像素是否在圆的范围内
if (Mathf.Pow(pixelX - x, 2) + Mathf.Pow(pixelY - y, 2) <= Mathf.Pow(radius, 2))
{
canvasTexture.SetPixel(pixelX, pixelY, color);
}
}
}
}

// 更新纹理
canvasTexture.Apply();
}

public void SaveCanvasTextureToFile()
{
// 将纹理保存为PNG图片
byte[] bytes = canvasTexture.EncodeToPNG();
string time = DateTime.Now.ToString();
time = time.Replace("/", "_");
time = time.Replace(" ", "_");
time = time.Replace(":", "_");
string filePath = Application.streamingAssetsPath + "/OCR/Images/ocr_image_" + time + ".png";
File.WriteAllBytes(filePath, bytes);
}

private Texture2D WhiteToAlpha0(Texture2D originalTexture)
{
if (originalTexture == null) return null;

Texture2D newTexture = new Texture2D(originalTexture.width, originalTexture.height);

for (int x = 0; x < originalTexture.width; x++)
{
for (int y = 0; y < originalTexture.height; y++)
{
Color pixelColor = originalTexture.GetPixel(x, y);

// 判断是否为白色像素,可以根据实际需求调整阈值
if (pixelColor.r >= 0.99f && pixelColor.g >= 0.99f && pixelColor.b >= 0.99f)
{
// 将白色像素变为透明
newTexture.SetPixel(x, y, new Color(0, 0, 0, 0));
}
else
{
// 其他像素保持原样
newTexture.SetPixel(x, y, pixelColor);
}
}
}

// 应用修改并更新纹理
newTexture.Apply();

return newTexture;
}
}

Unity画板功能实现
https://baifabaiquan.cn/2024/01/24/unity画板功能实现/
作者
白发败犬
发布于
2024年1月24日
许可协议