using System; using System.IO; using UnityEditor; using UnityEngine; namespace VRMShaders { public sealed class EditorTextureSerializer : ITextureSerializer { private readonly RuntimeTextureSerializer _runtimeSerializer = new RuntimeTextureSerializer(); /// /// Texture をオリジナルのテクスチャアセット(png/jpg)ファイルのバイト列そのまま出力してよいかどうか判断する。 /// 具体的な条件は以下 /// /// * TextureAsset が存在する /// * TextureImporter の maxSize が画像の縦横サイズ以上 /// * TextureImporter の色空間設定が exportColorSpace と一致する /// * 各 Texture Type ごとの判定 /// /// Unity の Texture2D のデータは、その参照元であるテクスチャアセットファイルのデータと一致することはむしろ稀。 /// public bool CanExportAsEditorAssetFile(Texture texture, ColorSpace exportColorSpace) { // Exists as UnityEditor Texture2D Assets ? if (!EditorTextureUtility.TryGetAsEditorTexture2DAsset(texture, out var texture2D, out var textureImporter)) return false; // Maintain original width/height ? if (!IsTextureSizeMaintained(textureImporter)) return false; // Equals color space ? if (!IsFileColorSpaceSameWithExportColorSpace(textureImporter, exportColorSpace)) return false; // Each Texture Importer Type Validation switch (textureImporter.textureType) { case TextureImporterType.Default: break; case TextureImporterType.NormalMap: // A texture has "Normal map" TextureType is ALWAYS converted into normalized normal pixel by Unity. // So we must copy it. return false; default: // Not Supported TextureImporterType throw new ArgumentException($"{texture}: textureImporter.textureType {textureImporter.textureType} is not supported. Only Default or NormalMap is supported"); } return true; } public (byte[] bytes, string mime) ExportBytesWithMime(Texture2D texture, ColorSpace exportColorSpace) { if (CanExportAsEditorAssetFile(texture, exportColorSpace) && TryGetBytesWithMime(texture, out byte[] bytes, out string mime)) { return (bytes, mime); } return _runtimeSerializer.ExportBytesWithMime(texture, exportColorSpace); } /// /// 出力に使用したいテクスチャに対して、Unity のエディタアセットとしての圧縮設定を OFF にする。 /// public void ModifyTextureAssetBeforeExporting(Texture texture) { if (EditorTextureUtility.TryGetAsEditorTexture2DAsset(texture, out var texture2D, out var assetImporter)) { assetImporter.textureCompression = TextureImporterCompression.Uncompressed; assetImporter.SaveAndReimport(); } } /// /// Assetから画像のバイト列を得る /// private bool TryGetBytesWithMime(Texture2D texture, out byte[] bytes, out string mime) { var path = AssetDatabase.GetAssetOrScenePath(texture); if (string.IsNullOrEmpty(path)) { bytes = default; mime = default; return false; } var ext = Path.GetExtension(path).ToLower(); switch (ext) { case ".png": bytes = System.IO.File.ReadAllBytes(path); mime = "image/png"; return true; case ".jpg": bytes = System.IO.File.ReadAllBytes(path); mime = "image/jpeg"; return true; } // dds ? astc ? tga ? bytes = default; mime = default; return false; } /// /// Texture2D の画像サイズが、オリジナルの画像サイズを維持しているかどうか /// /// TextureImporter の MaxTextureSize 設定によっては、Texture2D の画像サイズはオリジナルも小さくなりうる。 /// private bool IsTextureSizeMaintained(TextureImporter textureImporter) { if (EditorTextureUtility.TryGetOriginalTexturePixelSize(textureImporter, out var originalSize)) { var originalMaxSize = Mathf.Max(originalSize.x, originalSize.y); if (textureImporter.maxTextureSize >= originalMaxSize) { return true; } } return false; } private bool IsFileColorSpaceSameWithExportColorSpace(TextureImporter textureImporter, ColorSpace colorSpace) { switch (colorSpace) { case ColorSpace.sRGB: return textureImporter.sRGBTexture == true; case ColorSpace.Linear: return textureImporter.sRGBTexture == false; default: throw new ArgumentOutOfRangeException(nameof(colorSpace), colorSpace, null); } } } }