大家好,我是阿赵。
这次来讲一下在unity引擎里面,图片和base64字符串的互相转换问题。
一、图片传输的多种方式
有时候我们需要把图片通过网络传输发送。
在Unity里面,有不止一种方式可以实现,比如说,把图片的bytes通过WWWForm的方式来发送,这是Unity官方文档里面的方法:
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
public class WWWFormImage : MonoBehaviour
{
public string screenShotURL= "http://www.my-server.com/cgi-bin/screenshot.pl";
// Use this for initialization
void Start()
{
StartCoroutine(UploadPNG());
}
IEnumerator UploadPNG()
{
// We should only read the screen after all rendering is complete
yield return new WaitForEndOfFrame();
// Create a texture the size of the screen, RGB24 format
int width = Screen.width;
int height = Screen.height;
var tex = new Texture2D( width, height, TextureFormat.RGB24, false );
// Read screen contents into the texture
tex.ReadPixels( new Rect(0, 0, width, height), 0, 0 );
tex.Apply();
// Encode texture into PNG
byte[] bytes = tex.EncodeToPNG();
Destroy( tex );
// Create a Web Form
WWWForm form = new WWWForm();
form.AddField("frameCount", Time.frameCount.ToString());
form.AddBinaryData("fileUpload", bytes, "screenShot.png", "image/png");
// Upload to a cgi script
using (var w = UnityWebRequest.Post(screenShotURL, form))
{
yield return w.SendWebRequest();
if (w.isNetworkError || w.isHttpError) {
print(w.error);
}
else {
print("Finished Uploading Screenshot");
}
}
}
}
但有时候传输的双方并不是都能自己决定,比如和第三方的服务商做通讯,他们只能支持明文字符串的发送。这个时候,也可以把图片转换成字符串,通过发送字符串来达到目的。
二、图片转Base64字符串
比如我现在在Unity里面有这么一张图片
值得注意的是,在Unity里面,需要读取图片的字节内容,必须是把Read/Write Enabled勾上,然后为了转换的时候是无损的,最好是把图片压缩关掉。把Power of 2关掉,以保持图片是原始分辨率。
然后上代码:
static public string TexToString(Texture2D tex)
{
byte[] bs = tex.EncodeToPNG();
string base64String = System.Convert.ToBase64String(bs);
base64String = “data:image/png;base64,” + base64String;
return base64String;
}
代码很简单,用EncodeToPNG把Texture2D转成byte[],然后再通过System.Convert.ToBase64String转成字符串而已。
需要注意的有2点:
1、为什么要加上”data:image/png;base64,”?
这是一个标准,加上这个头信息,会让接收方知道,现在收到的字符串是一张图片,png格式,并且转换成base64字符串了。如果收发双方都是你自己,你也可以不用加这个信息。
这个信息对于图片解码本身是无意义的,所以接收方在把字符串转图片的时候,还需要把这段去掉。
2、base64包含特殊字符的问题
由于base64字符串会包含一些特殊字符,比如”+”、”/”、”=”,这些字符串如果作为url的明文来发送,是会有问题的。
为了解决这个问题,在发送的时候可以:
1.使用URLEncode转换
2.做字符替换
我们这里不讨论这个特殊字符的问题。
指定图片,运行之后,刚才那张图片就变成了这堆字符串了:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAJAklEQVRoBcWWyZIUSRIFm6UHpgFBOHLj/7+KE8KNnYFmG83ULC0rj6hkm5axg/Hc3Jb33L0i+eOPf8xu3Lhh78A/Meo04zdb/xrFb9++/eZcyn9LwHnec/c81/O750X+ioDJrO7b4IxsKf5IpOZnwM8JmJxoOpfX4Tl7kg4HytxG2tqCnxCwS9FgW1vQyGgtYFmaX7Dy68APCYgWXSbjXbxNnmzALhfvkv6BBf+6gC0hIgYFerUVd9nULd0igIkpcWntxHWb4PZcbLGEiC/MJH3ez26TInjXZj6YHOcKlt2W557Qlv3C+ObNm0YELQ02QxDpr1+/iregHEnrKQ8sPVleewNn2EcXsIsTYJPYACSNzwiSxtLdKBrHEyFB0G5gX4CDrdQTkavgyPzgbt26JQ7MzMYwHovlly9fEgCmBCNCvmkWgonj5SCop2BHADXuCfAYLPXSlTqktZbtHosOjlYM1iZpeGuUCCxZZFBIHE+fgPT0OwLccLZNmQGQ3AXn07+3b98WBaaGJsFA9nAFfP78WdIC+xNxKFXkEEwMcTXUMLAKqAUZYMzueIjqAdDdNcWQpjVG9vh4Qx0jX+AsdmcJQZexJxJ264qACmyXv+Bz0BDvP4/GUoB3Kw2WM4aR2GT/6dMneOMxmjcIkACBlyCmCYAcgcErAgzVpb7MkJYUY/yvo7kEKkaRuzcAac5Y3njySe50misNPEQN7lJfBcz6Sd0BCYgujO/cuaOGfBpk1viOP/Z/Hy0Bczp0M1hSW5+0BdYbiHogAR2/1GE/bWroaBusgJ4N5GeOs+QU9UC7RMB581cBKbMs9hyVp8sNKODu3bsIwGtgNZCgVGppQkNGIsA/Vm4A9h8/flTAclGRXsCWdzxPApyUB2CyxzMGU8A8/n8f7ULCQQ/mGyPfcqkkwJejSHOkUhqZi0kGTw7JgpbrDbBhEgCb7D1Xjxmi8FbAlJEAkhVANwn5fhTg8ZPACAWQA7+o+7WdR3Ckc7pPS/SrAKKm5qcGT7cnpIa/jjYfUgcsP5hBKAG8H8++XXiToJe6Pj2R8RJWATYyKrbAA9D3hCC3K4B7QMPhDR1f0e4NcPyUT/ZSjLR/J3rSiDudtOilAZLgyxuQtDegJzKPH06yn6/Ig+8tTQEwoAOtugFvRk7EYx9vP7I+MHWSLDE9repJB+xSgGu92U5aNNBdAfMefEtegrveQMM4S1jOIOw9+PnLoEKo77KfDMOrAKlPrwybwoAZWJfgkeu9DbZIAN+7dw/PpA8fPrx9+9annyQFoMoPq6+L/prjHD3JgHtCalgFEHWGZbQATA20dsZRyBUlqXr06NHjx4/526aQhnB99+7ds2fPXr16xbIXBXXV2mpSnzcgB+lu/WFAJnWWsk+DAvCynxqY6ni8bGD/5MmT+/fvy55ugAcPHhB8+PBhsiMNoEmdJ3UJTD5ifHZFANFdDQTToIzmeWx53gxnD6cGBHhmbCGy5Nkk3g5yogKiNOnV9vRTYmrRpcZdWgOmErGz9bx7Xk59FsAuVtVCus6OW/zSil0jN5e/CaLthYsI8JojwQAj3AB4GdYSxtzDzN92cCg5TbfcyIIPrQz93/32HH+Q0ukJzezZS1ykJQDzl0hwDHzji0lwdpuYDz8JM78OluPJFwsqd8tlGHB6QtWUMZPcZRjAkdNDi6W/Snzv+WI2dQFv3rx5//69tebPPmHHLX5pxa6R9Qm1wfZsUXcH4/v9n4BfK773+GUeS87++fPn/GD54zWrwIseRjMxAnWb9AxeEdB2lYCoA7bs/Y8AtPofAb9WT58+ff36NcnOALx8+ZIgW6UFUlJzwGQ/ydAwkjbf+SU2wzIbRd2jaja8NX+M/BbR98WLFzwkvph8c1hy9rwcMinkcjCw3nLiWxkMxWIv3a1fBVQAsP7Y5+A8oS17qPhF90tHIZkQgjeSbEg5EQUQxw46jkrUoDyVdBWOnpTAi4ZVgNvVxNsTmuwZ3/8C/JxTSyHj/bktKA86yBX2/znaUcgVJU7B08QqfGQW6i4vBSiOU6wAYBePJA3wmP8d6OVEFJ0kcC3LnXgDKFcAPg1q84DmDcghSp4Ry8QcBLB20sRWLgJkxjDIabEn38HkcDPubgVQi4AuQQ1EsKnBS/Ae6KxJL+oEwZc3MDcuSi5vgI5wgly8AbHv7Enrv2vbJ0S5LD34eQ8KIMF79ixsu8s+tlcEqGl5RfSCCp7WcpI33i5UMYkExmMcvzdgMjlTngKgm4btK2LQFEB5B5qYVQAb8xWxLS2CmBroC5bWMXz6/6Ds2YU9/GDvX4g6a0VCOYcXc7TYs6L2eAKnS1D2pB5pe7q8cgOGFGMlXRQgYwXEXpGeVuQ8fh8YGupD2lHC6VsE3ZOIi3+8HHJsiJ8aJml5rgLiDT+wBVIkQjt5462UmQmSW45/uQFppVO6i+cGFLmwd5aU8tLYuYEyJMdS6mqwzBxPSPa+fh9Px69a+5BcZjJ8dfmF/bwBmjR6gh0BpDY4orPG4JZW7BNQVVLVoADPGy+QPZ4cjBJGaPYB1zBwRQAZk7pJNCpbcOo6fuaY6i+XfvlDN58+MsPLcvIuaI6Cp4bYByRzRQAhttUgjvGUISF8nCDEqWe+fvrYapsvyw47YEM8VhVAGpNSkVVAG5Qx3mLawQnvbq2JSEXq5ABkr5/d5GRJhZ03gGDGCLCDaCKNuk2wI4DsTm5qKGhfPDMUxnhARmbm+FkSy4W0cduWP9kTnNTFp2/izsbF5xIq7EYIANHpJ2+3Sq5thOTn6abEYL5kysE2CdRTcK0AtuFxStpoiOIUY5CItZWzjFNADSwDbQmskgARwdafE0B2JAR5wHmbtWAZLBR3lzMZ3FK89d8RQAFEKxNL3a3rsCXmd35bGaQpYwGWGwzvgktyu9sG5bFgg1MACQV3uyWA3XDAEpdbvNuQ4A8JsPi8jCVnJrsVswUsyyXZ5Rn/EwLostCay+vwnB1XguFAmdtIW1vwcwKqn3QNbiPEDe4S2ga3kcadAb8o4Azphk1J58md363hLvgtAXWcXAt+F/wO7+82/98kIOzXtP34+P8C3AYHctj0BVgAAAAASUVORK5CYII=
三、Base64转图片
从字符串转换回Texture2D:
static public Texture2D Base64StringToTex(string base64String)
{
base64String = base64String.Replace("data:image/png;base64,", "").Replace("data:image/jgp;base64,", "").Replace("data:image/jpg;base64,", "").Replace("data:image/jpeg;base64,", "");
byte[] bytes = System.Convert.FromBase64String(base64String);
Texture2D tex = new Texture2D(2, 2);
tex.LoadImage(bytes);
return tex;
}
代码同样很简单,一开始的时候,将base64头部信息替换掉,保证下面转换的是纯base64的字符串。
然后通过System.Convert.FromBase64String把字符串转换回byte[],最后通过LoadImage把byte[]加载成Texture2D。
四、当Base64字符串不完整的处理
上面都是常规方法,下面是关于一些原理上的东西了。
假如在某些情况下,base64的字符串的末尾出现了缺失,那么将会出现问题。在System.Convert.FromBase64String的时候,会报错:
FormatException: Invalid length for a Base-64 char array or string.
这是因为Base64字符串的长度应该是4的倍数的,但由于缺失了字符串,导致长度不对。
这个时候,就需要把base64字符串补齐:
base64String = base64String.Replace("data:image/png;base64,", "").Replace("data:image/jgp;base64,", "").Replace("data:image/jpg;base64,", "").Replace("data:image/jpeg;base64,", "");
int mod = base64String.Length % 4;
if(mod>0&&mod>2)
{
base64String += new string('=', 4 - mod);
}
else
{
if(mod>0)
{
base64String += new string('A', 4 - mod);
}
}
为什么是这样补?在base64里面,等号=代表的是空,A代表的是0。当字符串长度不是4的倍数,我们求余数,如果余数是2,我们就补2个空,也就是加2个等号。但这里有一个规则,等号最多只能是2个,如果超过2个,就会报错:
FormatException: The input is not a valid Base-64 string as it
contains a non-base 64 character, more than two padding characters, or
an illegal character among the padding characters.
所以如果缺超过2个字符的时候,就已经不是补等号可以解决了,所以我这里直接补0。补0当然也是不对的,不过不用急,由于补空已经不能解决的时候,其实补什么都是不行的,所以下面还需要后续的处理。
先来了解一下,假如一张图片EncodeToPNG转换成byte[]时,这堆byte[]有什么特点?
首先,这堆byte[]会是以这些byte开头的:
137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0
然后,到了最后,是以这些byte结尾的:
0,0,0,0,73,69,78,68,174,66,96,130
知道了这个规则之后,我们就可以做这样的处理了:
1、由于开头一般不会缺失,所以我们可以不管
2、后面这一段固定的结尾,如果缺了,Unity会解析不出来,多了无所谓。所以我们可以针对转换出来的byte[]作检查,看看是不是包含0,0,0,0,73,69,78,68,174,66,96,130的。只要包含就行,不需要一定以它结尾。后面多出来的byte不影响解析。
3、如果发现不包含完整的结尾byte,那么就检查一下是不是缺了某一段,或者是整段没有了。然后,把它们补齐就行了。
4、由于结尾是12个字节,对应的是9个字符,所以base64缺失在9个字符以内,都可以这样补齐。
于是代码会变成这样:
static public Texture2D Base64StringToTex(string base64String)
{
base64String = base64String.Replace("data:image/png;base64,", "").Replace("data:image/jgp;base64,", "").Replace("data:image/jpg;base64,", "").Replace("data:image/jpeg;base64,", "");
int mod = base64String.Length % 4;
if (mod > 0 && mod > 2)
{
base64String += new string('=', 4 - mod);
}
else
{
if (mod > 0)
{
base64String += new string('A', 4 - mod);
}
}
byte[] bytes = System.Convert.FromBase64String(base64String);
bytes = CheckFormatBytes(bytes);
Texture2D tex = new Texture2D(2, 2);
tex.LoadImage(bytes);
return tex;
}
static private byte[] endBs = new byte[]{ 73, 69, 78, 68, 174, 66, 96, 130 };
static public byte[] CheckFormatBytes(byte[] bs)
{
List<byte> origList = new List<byte>();
for(int i = 0;i<bs.Length;i++)
{
origList.Add(bs[i]);
}
int lastIndex = -1;
int checkIndex = -1;
bool isFormat = true;
int firstIndex = -1;
for(int i = 0;i<endBs.Length;i++)
{
if(i==0)
{
lastIndex = origList.LastIndexOf(endBs[i]);
if(lastIndex<0)
{
isFormat = false;
break;
}
else
{
firstIndex = lastIndex;
checkIndex = i;
}
}
else
{
int curIndex = origList.LastIndexOf(endBs[i]);
if(curIndex<0||curIndex-lastIndex!=1)
{
isFormat = false;
break;
}
else
{
checkIndex = i;
lastIndex = curIndex;
}
}
}
if(isFormat == true)
{
return bs;
}
else
{
if(checkIndex>=0)
{
origList.RemoveRange(firstIndex, origList.Count - firstIndex);
}
for (int i = 0; i < endBs.Length; i++)
{
origList.Add(endBs[i]);
}
}
return origList.ToArray();
}