Unity中实现图片拼接的几种方案

最近项目中遇到个问题,二维码是根据不同的用户信息动态生成的,宣传海报背景是固定的图,现在需要将其拼接在一起并分享出去,目前有两种方案分别适用于不同的情况,分别是用 UGUI 拼接好之后然后截图指定区域分享出去,其次是直接一个一个的操作像素点,这里记录了操作中需要注意的点和详细代码实现。

效果展示

具体方案

方案一

适用于分享海报需要展示出来的场景。

UGUI直接拼好内容,代码中控制内容替换,使用 Texture.ReadPixels(rect, 0, 0); 的方式直接截图,然后分享出去即可:

1private IEnumerator CaptureCoroutine()
2{  
3    yield return new WaitForEndOfFrame();  
4    // 截取当前整个屏幕(根据需要,也可设置某个RectTransform)
5    Rect rect = new Rect(0, 0, Screen.width, Screen.height);
6    Texture2D  texture = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGB24, false); 
7    texture.ReadPixels(rect, 0, 0);  
8    texture.Apply();
9}

注意,一定是等当前帧执行完之后,也就是帧末再截图,否则直接用 Texture2D.ReadPixel 抓取屏幕信息则会报错

方案二

适用于分享海报无需展示的场景。

因为这种的话分享海报无需展示,直接拼接二维码和背景图(如效果展示图中的那种)

 1/// <summary>
 2/// 融合图片和二维码,得到新Texture2D ...
 3/// 根据背景图的大小比例
 4/// </summary>
 5/// <param name="tex_base">底图</param>
 6/// <param name="tex_code">二维码</param>
 7public static Texture2D MixImagAndQRCode(Texture2D tex_base, Texture2D tex_code)
 8{
 9    Texture2D newTexture = Instantiate(tex_base) as Texture2D; ;
10    for (int i = 0; i < tex_code.width; i++)
11    {
12        for (int j = 0; j < tex_code.height; j++)
13        {
14            // //从底图图片中获取到(w,h)位置的像素
15            // Color baseColor = newTexture.GetPixel((int)w, (int)h);
16            // 从二维码图片中获取到(i,j)位置的像素
17            // Color codeColor = tex_code.GetPixel(i, j);
18            newTexture.SetPixel(newTexture.width - i - 1, newTexture.height - j - 1, tex_code.GetPixel(tex_code.width - i - 1, tex_code.height - j - 1));
19        }
20    }
21    newTexture.Apply();
22    return newTexture;
23}
24
25/// <summary>
26/// Texture转换成Texture2D...
27/// </summary>
28/// <param name="texture"></param>
29/// <returns></returns>
30Texture2D TextureToTexture2D(Texture texture)
31{
32    Texture2D texture2D = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false);
33
34    RenderTexture currentRT = RenderTexture.active;
35
36    RenderTexture renderTexture = RenderTexture.GetTemporary(texture.width, texture.height, 32);
37    Graphics.Blit(texture, renderTexture);
38
39    RenderTexture.active = renderTexture;
40    texture2D.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
41    texture2D.Apply();
42
43    RenderTexture.active = currentRT;
44    RenderTexture.ReleaseTemporary(renderTexture);
45
46    return texture2D;
47}
48
49/// <summary>
50/// 将Texture转为本地PNG ...
51/// </summary>
52/// <param name="filePath">保存路径,(分享时直接使用)</param>
53/// <param name="teture">要保存的Texture</param>
54/// <returns></returns>
55public static bool saveMainTextureToPng(string filePath, Texture teture)
56{
57    if (teture.GetType() != typeof(Texture2D))
58    {
59        return false;
60    }
61    Texture2D savedTexture = (Texture2D)teture;
62    try
63    {
64        Texture2D newTexture = new Texture2D(savedTexture.width, savedTexture.height, TextureFormat.RGBA32, false);
65        newTexture.SetPixels(0, 0, savedTexture.width, savedTexture.height, savedTexture.GetPixels());
66        newTexture.Apply();
67        byte[] bytes = newTexture.EncodeToPNG();
68        if (bytes != null && bytes.Length > 0)
69        {
70            if (File.Exists(filePath))
71            {
72                File.Delete(filePath);
73            }
74            System.IO.File.WriteAllBytes(filePath, bytes);
75        }
76    }
77    catch (IOException ex)
78    {
79        return false;
80    }
81    return true;
82}

其实也就是一个一个摆像素点而已。另外需要注意的是确保 texture 可读,目标 texture 也可读可写,通过网络下载的图片默认都是 Read/Write Enable的,因此如果有限制的话可以采取从网络下载图片,WWW就可以非常简单的完成这个功能:

1coroutine.start(function()
2    ---@type CS.UnityEngine.WWW
3    local www = CS.UnityEngine.WWW(url)
4    coroutine.yield(self.commonService:WaitUntil(function()
5        return www.isDone
6    end))
7    local tex = www.texture
8    --- TODO ...
9end)

有了上面的示例,实现一个左右拼接就很简单了,就是读写像素

 1public static Texture2D mergeTexture(Texture2D left, Texture2D right)
 2{
 3    var ret = new Texture2D(left.width + right.width, right.height);
 4
 5    for (int y = 0; y < left.height; y++)
 6    {
 7        for (int x = 0; x < left.width; x++)
 8        {
 9            ret.SetPixel(x, y, left.GetPixel(x, y));
10        }
11    }
12
13    for (int y = 0; y < right.height; y++)
14    {
15        for (int x = 0; x < right.width; x++)
16        {
17            ret.SetPixel(x + left.width, y, right.GetPixel(x, y));
18        }
19    }
20	Texture2D.Destroy(left);
21	Texture2D.Destroy(right);
22	left = null;
23	right = null;
24    ret.Apply();
25    return ret;
26}

注意点

1、用完的 texture 记得及时回收 Texture2D.Destroy(tex),避免内存泄漏;

2、Texture2D 的 Read/Write Enable 否则无法读取像素;

3、如果是需要保存到本地的话判断是否需要本地文件存储权限,并且确定是否需要刷新到相册,如非必要不要刷新到用户相册;

4、尽量按偶数像素来拼接,如果是NGUI,NGUI会自动把奇数宽高的图片补起成偶数的宽高图片,并且利用三线性来过滤图片拼接的时候可能会产生割裂的直线,这个时候需要将图片模式设置为点线性过滤模式;