c# - 应用下载的纹理并通过 EncodeToPNG 转换回 PNG 后,PNG 文件在 Unity 中增长
问题描述
只是为了测试,我通过 http 下载了一个 PNG 文件(在这种情况下,通过 API 从 JIRA 服务器)
对于 http 请求,我有一个非常“标准”的类HttpFileManager我只是为了完整性而添加:
public static class HttpFileManager
{
public void DownloadImage(string url, Action<Texture> successCallback = null, Credentials credentials = null, Action<UnityWebRequest> errorCallback = null)
{
StartCoroutine(DownloadImageProcess(url, successCallback, credentials, errorCallback));
}
private static IEnumerator DownloadImageProcess(string url, Action<Texture> successCallback, Credentials credentials, Action<UnityWebRequest> errorCallback)
{
var www = UnityWebRequestTexture.GetTexture(url);
if (credentials != null)
{
// This simply adds some headers to the request required for JIRA api
// it is not relevant for this question
AddCredentials(www, credentials);
}
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.LogErrorFormat("Download from {0} failed with {1}", url, www.error);
errorCallback?.Invoke(www);
}
else
{
Debug.LogFormat("Download from {0} complete!", url);
successCallback?.Invoke(((DownloadHandlerTexture) www.downloadHandler).texture);
}
}
public static void UploadFile(byte[] rawData, string url, Action<UnityWebRequest> successcallback, Credentials credentials, Action<UnityWebRequest> errorCallback)
private static IEnumerator UploadFileProcess(byte[] rawData, string url, Action<UnityWebRequest> successCallback, Credentials credentials, Action<UnityWebRequest> errorCallback)
{
var form = new WWWForm();
form.AddBinaryData("file",rawData,"Test.png");
var www = UnityWebRequest.Post(url, form);
www.SetRequestHeader("Accept", "application/json");
if (credentials != null)
{
// This simply adds some headers to the request required for JIRA api
// it is not relevant for this question
AddCredentials(www, credentials);
}
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.LogErrorFormat("Upload to {0} failed with code {1} {2}", url, www.responseCode, www.error);
errorCallback?.Invoke(www);
}
else
{
Debug.LogFormat("Upload to {0} complete!", url);
successCallback?.Invoke(www);
}
}
}
稍后在我的脚本中
public Texture TestTexture;
// Begin the download
public void DownloadTestImage()
{
_httpFileManager.DownloadImage(ImageGetURL, DownloadImageSuccessCallback, _credentials);
}
// After Download store the Texture
private void DownloadImageSuccessCallback(Texture newTexture)
{
TestTexture = newTexture;
}
// Start the upload
public void UploadTestImage()
{
var data = ((Texture2D) TestTexture).EncodeToPNG();
_httpFileManager.UploadFile(data, ImagePostUrl, UploadSuccessCallback, _credentials);
}
// After Uploading
private static void UploadSuccessCallback(UnityWebRequest www)
{
Debug.Log("Upload worked!");
}
在简历中,问题在于 for 和 back 转换
(DownloadHandlerTexture) www.downloadHandler).texture
和
((Texture2D) TestTexture).EncodeToPNG();
结果看起来像这样
在原始图像的顶部;在底部重新上传的一个。
正如你所看到的那样,它的增长是40kb
由59kb
因素决定的1,475
。这同样适用于较大的文件,因此 a844kb
增长到1,02Mb
.
所以我的问题是
为什么上传后EncodeToPNG()
的图片比原图大?
和
是否可以/应该在 PNG 数据上使用任何压缩以存档相同的压缩级别(如果压缩是根本问题)?
首先,我认为颜色深度可能不同,但两个图像都是 RGBA-32bit
更新
这是两张图片
原始(40kb)(取自这里)
重新上传 (59kb)
更新 2
我用 JPG 文件重复了测试,EncodeToJPG()
结果似乎更糟:
在原始图像的顶部;在底部重新上传的一个。
这一次它变得27kb
如此98kb
因素2,63
。奇怪的是,98kb
无论我将什么quality
作为EncodeToJPG()
.
解决方案
如果您精确地检查这两个 PNG 文件,您会注意到不同之处。它们都具有相同的分辨率、相同的位深度、一定数量的通道,并且都不是隔行扫描的。
然而,原始图像仅包含一个包含 41370 字节编码数据的IDAT
部分。
来自 Unity 的图像包含 8IDAT
个部分:7 x 8192 字节和一个 2860 字节,共 60204 字节。
在 PNG 规范中,有一条注释:
允许多个 IDAT 块,以便编码器可以在固定数量的内存中工作;通常,块大小将对应于编码器的缓冲区大小。
此外,IDAT
对于相同的源图像,这些部分中包含的数据不一定完全相同。这些IDAT
部分包含原始字节数据,这些数据首先经过预过滤,然后使用压缩进行编码。zlib
因此,PNG 编码器可以从 5 种可用算法中选择预过滤算法:
Type Name
0 None
1 Sub
2 Up
3 Average
4 Paeth
此外,zlib
可以为压缩窗口大小配置压缩,也可以由 PNG 编码器选择。
检查zlib
流给出以下结果:
- 两个文件都使用具有相同窗口大小 32k 的“deflate”压缩
- 然而,压缩标志是不同的——原始文件的压缩级别为 1(快速算法),而 Unity 编码的文件的压缩级别为 0(最快的算法)。
这解释了二进制数据和数据大小的差异。
似乎您无法控制 Unity 的 PNG 编码器,因此很遗憾您不能强制它选择其他zlib
算法。
我想,JPEG文件也会发生同样的情况——编码器只是选择了一种更快的算法来产生更大的文件。
如果您想在 Unity 中完全控制 PNG 编码,您需要实现自己的 PNG 编码器。例如,在 Unity 论坛上,有一个使用该zlib.net
库的 PNG 编码器示例。您可以微调编码,例如通过指定zlib
压缩算法。
推荐阅读
- javascript - 如何抓取网站投资以获取股票的每项技术分析
- sql - 增加 SQL Select Query Select 到 Text
- python - 如何在 Keras/TensorFlow 中迭代张量?
- java - 不关注JavaFX应用程序窗口时注册Key Event?
- c# - 如何使用 XUnit 对每个验证错误抛出的异常进行单元测试
- ruby-on-rails - OAuth 安装 - 如何创建跨请求持续存在的会话?
- java - BottomNavigationView 下 CoordinatorLayout 上的 Fab 按钮
- node.js - 网络错误 axios 试图从节点应用程序获取数据
- r - 如何将核矩阵放在矩阵的对角线上?
- python - 如何根据条件按组查找最大值?熊猫