首页 > 解决方案 > 应用下载的纹理并通过 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();

结果看起来像这样

在原始图像的顶部;在底部重新上传的一个。

正如你所看到的那样,它的增长是40kb59kb因素决定的1,475。这同样适用于较大的文件,因此 a844kb增长到1,02Mb.

所以我的问题是

为什么上传后EncodeToPNG()的图片比原图大?

是否可以/应该在 PNG 数据上使用任何压缩以存档相同的压缩级别(如果压缩是根本问题)?

首先,我认为颜色深度可能不同,但两个图像都是 RGBA-32bit

在此处输入图像描述


更新

这是两张图片

原始(40kb)(取自这里

在此处输入图像描述

重新上传 (59kb)

在此处输入图像描述


更新 2

我用 JPG 文件重复了测试,EncodeToJPG()结果似乎更糟:

在此处输入图像描述

在原始图像的顶部;在底部重新上传的一个。

这一次它变得27kb如此98kb因素2,63。奇怪的是,98kb无论我将什么quality作为EncodeToJPG().

标签: c#imageunity3dencoding

解决方案


如果您精确地检查这两个 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压缩算法。


推荐阅读