diff --git a/Source/AssetRipper.Export.Modules.Textures/DirectBitmap.cs b/Source/AssetRipper.Export.Modules.Textures/DirectBitmap.cs index 709d7dd2a..7bba0a55d 100644 --- a/Source/AssetRipper.Export.Modules.Textures/DirectBitmap.cs +++ b/Source/AssetRipper.Export.Modules.Textures/DirectBitmap.cs @@ -11,6 +11,7 @@ public abstract class DirectBitmap public Span Bits => new Span(Data, 0, ByteSize); public abstract int PixelSize { get; } protected byte[] Data { get; } + public bool IsEmpty => Width == 0 || Height == 0 || Depth == 0; protected DirectBitmap(int width, int height, int depth) { diff --git a/Source/AssetRipper.Export.Modules.Textures/TextureConverter.cs b/Source/AssetRipper.Export.Modules.Textures/TextureConverter.cs index 8ebf37253..2850b499b 100644 --- a/Source/AssetRipper.Export.Modules.Textures/TextureConverter.cs +++ b/Source/AssetRipper.Export.Modules.Textures/TextureConverter.cs @@ -82,12 +82,18 @@ namespace AssetRipper.Export.Modules.Textures return false; } + if (!TryGetTextureFormat(texture, out TextureFormat format)) + { + bitmap = DirectBitmap.Empty; + return false; + } + if (!TryConvertToBitmap( - texture.FormatE, + format, texture.Width, texture.Height, texture.Depth, - (int)texture.DataSize, + texture.GetCompleteImageSize(), texture.Collection.Version, buffer, out bitmap)) @@ -98,6 +104,20 @@ namespace AssetRipper.Export.Modules.Textures bitmap.FlipY(); return true; + + static bool TryGetTextureFormat(ITexture2DArray texture, out TextureFormat format) + { + try + { + format = ((GraphicsFormat)texture.Format).ToTextureFormat(); + return true; + } + catch (NotSupportedException) + { + format = default; + return false; + } + } } public static bool TryConvertToBitmap(ICubemapArray texture, out DirectBitmap bitmap) @@ -112,9 +132,9 @@ namespace AssetRipper.Export.Modules.Textures if (!TryConvertToBitmap( texture.FormatE, texture.Width, - texture.Width,//Not sure if this is correct - texture.CubemapCount * 6,//Not sure if this is correct - (int)texture.DataSize, + texture.GetHeight(), + texture.GetDepth(), + texture.GetCompleteImageSize(), texture.Collection.Version, buffer, out bitmap)) @@ -122,7 +142,7 @@ namespace AssetRipper.Export.Modules.Textures return false; } - bitmap.FlipY(); + bitmap.FlipY();// Maybe not needed? return true; } diff --git a/Source/AssetRipper.GUI.Web/Pages/Assets/AssetAPI.cs b/Source/AssetRipper.GUI.Web/Pages/Assets/AssetAPI.cs index ca6a382a2..fac3e6af6 100644 --- a/Source/AssetRipper.GUI.Web/Pages/Assets/AssetAPI.cs +++ b/Source/AssetRipper.GUI.Web/Pages/Assets/AssetAPI.cs @@ -1,5 +1,4 @@ -using AsmResolver.DotNet; -using AssetRipper.Assets; +using AssetRipper.Assets; using AssetRipper.Export.Modules.Audio; using AssetRipper.Export.Modules.Models; using AssetRipper.Export.Modules.Shaders.IO; @@ -18,6 +17,7 @@ using AssetRipper.Processing.Textures; using AssetRipper.SourceGenerated.Classes.ClassID_115; using AssetRipper.SourceGenerated.Classes.ClassID_128; using AssetRipper.SourceGenerated.Classes.ClassID_156; +using AssetRipper.SourceGenerated.Classes.ClassID_189; using AssetRipper.SourceGenerated.Classes.ClassID_213; using AssetRipper.SourceGenerated.Classes.ClassID_28; using AssetRipper.SourceGenerated.Classes.ClassID_329; @@ -88,8 +88,14 @@ internal static class AssetAPI if (TryGetImageExtensionFromQuery(context, out string? extension, out ImageExportFormat format)) { + DirectBitmap bitmap = GetImageBitmap(asset); + if (bitmap.IsEmpty) + { + return context.Response.NotFound("Image data could not be decoded."); + } + MemoryStream stream = new(); - GetImageBitmap(asset).Save(stream, format); + bitmap.Save(stream, format); return Results.Bytes(stream.ToArray(), $"image/{extension}").ExecuteAsync(context); } else @@ -100,7 +106,7 @@ internal static class AssetAPI public static bool HasImageData(IUnityObjectBase asset) => asset switch { - ITexture2D texture => texture.CheckAssetIntegrity(), + IImageTexture texture => texture.CheckAssetIntegrity(), SpriteInformationObject spriteInformationObject => spriteInformationObject.Texture.CheckAssetIntegrity(), ISprite sprite => sprite.TryGetTexture()?.CheckAssetIntegrity() ?? false, ITerrainData terrainData => terrainData.Heightmap.Heights.Count > 0, @@ -111,14 +117,14 @@ internal static class AssetAPI { return asset switch { - ITexture2D texture => TextureToBitmap(texture), + IImageTexture texture => TextureToBitmap(texture), SpriteInformationObject spriteInformationObject => TextureToBitmap(spriteInformationObject.Texture), ISprite sprite => SpriteToBitmap(sprite), ITerrainData terrainData => TerrainHeatmap.GetBitmap(terrainData), _ => DirectBitmap.Empty, }; - static DirectBitmap TextureToBitmap(ITexture2D texture) + static DirectBitmap TextureToBitmap(IImageTexture texture) { return TextureConverter.TryConvertToBitmap(texture, out DirectBitmap bitmap) ? bitmap : DirectBitmap.Empty; } diff --git a/Source/AssetRipper.SourceGenerated.Extensions/CubemapArrayExtensions.cs b/Source/AssetRipper.SourceGenerated.Extensions/CubemapArrayExtensions.cs index f41c765ca..5520cfba7 100644 --- a/Source/AssetRipper.SourceGenerated.Extensions/CubemapArrayExtensions.cs +++ b/Source/AssetRipper.SourceGenerated.Extensions/CubemapArrayExtensions.cs @@ -4,6 +4,16 @@ namespace AssetRipper.SourceGenerated.Extensions; public static class CubemapArrayExtensions { + public static int GetHeight(this ICubemapArray texture) + { + return texture.Width; + } + + public static int GetDepth(this ICubemapArray texture) + { + return texture.CubemapCount * 6;// Not sure about this + } + public static byte[] GetImageData(this ICubemapArray texture) { if (texture.ImageData.Length > 0) @@ -35,4 +45,10 @@ public static class CubemapArrayExtensions return false; } } + + public static int GetCompleteImageSize(this ICubemapArray texture) + { + int depth = texture.GetDepth(); + return depth > 0 ? (int)texture.DataSize / depth : 0; + } } diff --git a/Source/AssetRipper.SourceGenerated.Extensions/GraphicsFormatExtensions.cs b/Source/AssetRipper.SourceGenerated.Extensions/GraphicsFormatExtensions.cs new file mode 100644 index 000000000..875495e3a --- /dev/null +++ b/Source/AssetRipper.SourceGenerated.Extensions/GraphicsFormatExtensions.cs @@ -0,0 +1,167 @@ +using AssetRipper.SourceGenerated.Enums; + +namespace AssetRipper.SourceGenerated.Extensions; + +public static class GraphicsFormatExtensions +{ + /// + /// Converts to . + /// + /// + /// Note: Some conversions are not entirely accurate. For example, SRGB and UNorm are treated as the same. + /// + /// The to convert. + /// The equivalent to the parameter. + /// There is no direct conversion for the format. + /// The format was an invalid value. + public static TextureFormat ToTextureFormat(this GraphicsFormat format) => format switch + { + GraphicsFormat.None => throw new NotSupportedException(), + GraphicsFormat.R8_SRGB => TextureFormat.R8, + GraphicsFormat.R8G8_SRGB => TextureFormat.RG16, + GraphicsFormat.R8G8B8_SRGB => TextureFormat.RGB24, + GraphicsFormat.R8G8B8A8_SRGB => TextureFormat.RGBA32, + GraphicsFormat.R8_UNorm => TextureFormat.R8, + GraphicsFormat.R8G8_UNorm => TextureFormat.RG16, + GraphicsFormat.R8G8B8_UNorm => TextureFormat.RGB24, + GraphicsFormat.R8G8B8A8_UNorm => TextureFormat.RGBA32, + GraphicsFormat.R8_SNorm => TextureFormat.R8_SIGNED, + GraphicsFormat.R8G8_SNorm => TextureFormat.RG16_SIGNED, + GraphicsFormat.R8G8B8_SNorm => TextureFormat.RGB24_SIGNED, + GraphicsFormat.R8G8B8A8_SNorm => TextureFormat.RGBA32_SIGNED, + GraphicsFormat.R8_UInt => TextureFormat.R8, + GraphicsFormat.R8G8_UInt => TextureFormat.RG16, + GraphicsFormat.R8G8B8_UInt => TextureFormat.RGB24, + GraphicsFormat.R8G8B8A8_UInt => TextureFormat.RGBA32, + GraphicsFormat.R8_SInt => TextureFormat.R8_SIGNED, + GraphicsFormat.R8G8_SInt => TextureFormat.RG16_SIGNED, + GraphicsFormat.R8G8B8_SInt => TextureFormat.RGB24_SIGNED, + GraphicsFormat.R8G8B8A8_SInt => TextureFormat.RGBA32_SIGNED, + GraphicsFormat.R16_UNorm => TextureFormat.R16, + GraphicsFormat.R16G16_UNorm => TextureFormat.RG32, + GraphicsFormat.R16G16B16_UNorm => TextureFormat.RGB48, + GraphicsFormat.R16G16B16A16_UNorm => TextureFormat.RGBA64, + GraphicsFormat.R16_SNorm => TextureFormat.R16_SIGNED, + GraphicsFormat.R16G16_SNorm => TextureFormat.RG32_SIGNED, + GraphicsFormat.R16G16B16_SNorm => TextureFormat.RGB48_SIGNED, + GraphicsFormat.R16G16B16A16_SNorm => TextureFormat.RGBA64_SIGNED, + GraphicsFormat.R16_UInt => TextureFormat.R16, + GraphicsFormat.R16G16_UInt => TextureFormat.RG32, + GraphicsFormat.R16G16B16_UInt => TextureFormat.RGB48, + GraphicsFormat.R16G16B16A16_UInt => TextureFormat.RGBA64, + GraphicsFormat.R16_SInt => TextureFormat.R16_SIGNED, + GraphicsFormat.R16G16_SInt => TextureFormat.RG32_SIGNED, + GraphicsFormat.R16G16B16_SInt => TextureFormat.RGB48_SIGNED, + GraphicsFormat.R16G16B16A16_SInt => TextureFormat.RGBA64_SIGNED, + GraphicsFormat.R32_UInt => throw new NotSupportedException(), + GraphicsFormat.R32G32_UInt => throw new NotSupportedException(), + GraphicsFormat.R32G32B32_UInt => throw new NotSupportedException(), + GraphicsFormat.R32G32B32A32_UInt => throw new NotSupportedException(), + GraphicsFormat.R32_SInt => throw new NotSupportedException(), + GraphicsFormat.R32G32_SInt => throw new NotSupportedException(), + GraphicsFormat.R32G32B32_SInt => throw new NotSupportedException(), + GraphicsFormat.R32G32B32A32_SInt => throw new NotSupportedException(), + GraphicsFormat.R16_SFloat => throw new NotSupportedException(), + GraphicsFormat.R16G16_SFloat => throw new NotSupportedException(), + GraphicsFormat.R16G16B16_SFloat => throw new NotSupportedException(), + GraphicsFormat.R16G16B16A16_SFloat => throw new NotSupportedException(), + GraphicsFormat.R32_SFloat => throw new NotSupportedException(), + GraphicsFormat.R32G32_SFloat => throw new NotSupportedException(), + GraphicsFormat.R32G32B32_SFloat => throw new NotSupportedException(), + GraphicsFormat.R32G32B32A32_SFloat => throw new NotSupportedException(), + GraphicsFormat.B8G8R8_SRGB => throw new NotSupportedException(), + GraphicsFormat.B8G8R8A8_SRGB => TextureFormat.BGRA32_14, + GraphicsFormat.B8G8R8_UNorm => throw new NotSupportedException(), + GraphicsFormat.B8G8R8A8_UNorm => TextureFormat.BGRA32_14, + GraphicsFormat.B8G8R8_SNorm => throw new NotSupportedException(), + GraphicsFormat.B8G8R8A8_SNorm => TextureFormat.BGRA32_14, + GraphicsFormat.B8G8R8_UInt => throw new NotSupportedException(), + GraphicsFormat.B8G8R8A8_UInt => TextureFormat.BGRA32_14, + GraphicsFormat.B8G8R8_SInt => throw new NotSupportedException(), + GraphicsFormat.B8G8R8A8_SInt => TextureFormat.BGRA32_14, + GraphicsFormat.R4G4B4A4_UNormPack16 => throw new NotSupportedException(), + GraphicsFormat.B4G4R4A4_UNormPack16 => throw new NotSupportedException(), + GraphicsFormat.R5G6B5_UNormPack16 => TextureFormat.RGB565, + GraphicsFormat.B5G6R5_UNormPack16 => throw new NotSupportedException(), + GraphicsFormat.R5G5B5A1_UNormPack16 => throw new NotSupportedException(), + GraphicsFormat.B5G5R5A1_UNormPack16 => throw new NotSupportedException(), + GraphicsFormat.A1R5G5B5_UNormPack16 => throw new NotSupportedException(), + GraphicsFormat.E5B9G9R9_UFloatPack32 => throw new NotSupportedException(), + GraphicsFormat.B10G11R11_UFloatPack32 => throw new NotSupportedException(), + GraphicsFormat.A2B10G10R10_UNormPack32 => throw new NotSupportedException(), + GraphicsFormat.A2B10G10R10_UIntPack32 => throw new NotSupportedException(), + GraphicsFormat.A2B10G10R10_SIntPack32 => throw new NotSupportedException(), + GraphicsFormat.A2R10G10B10_UNormPack32 => throw new NotSupportedException(), + GraphicsFormat.A2R10G10B10_UIntPack32 => throw new NotSupportedException(), + GraphicsFormat.A2R10G10B10_SIntPack32 => throw new NotSupportedException(), + GraphicsFormat.A2R10G10B10_XRSRGBPack32 => throw new NotSupportedException(), + GraphicsFormat.A2R10G10B10_XRUNormPack32 => throw new NotSupportedException(), + GraphicsFormat.R10G10B10_XRSRGBPack32 => throw new NotSupportedException(), + GraphicsFormat.R10G10B10_XRUNormPack32 => throw new NotSupportedException(), + GraphicsFormat.A10R10G10B10_XRSRGBPack32 => throw new NotSupportedException(), + GraphicsFormat.A10R10G10B10_XRUNormPack32 => throw new NotSupportedException(), + GraphicsFormat.D16_UNorm => throw new NotSupportedException(), + GraphicsFormat.D24_UNorm => throw new NotSupportedException(), + GraphicsFormat.D24_UNorm_S8_UInt => throw new NotSupportedException(), + GraphicsFormat.D32_SFloat => throw new NotSupportedException(), + GraphicsFormat.D32_SFloat_S8_Uint => throw new NotSupportedException(), + GraphicsFormat.S8_Uint => TextureFormat.Alpha8, + GraphicsFormat.RGB_DXT1_SRGB => TextureFormat.DXT1, + GraphicsFormat.RGB_DXT1_UNorm => TextureFormat.DXT1, + GraphicsFormat.RGBA_DXT3_SRGB => TextureFormat.DXT3, + GraphicsFormat.RGBA_DXT3_UNorm => TextureFormat.DXT3, + GraphicsFormat.RGBA_DXT5_SRGB => TextureFormat.DXT5, + GraphicsFormat.RGBA_DXT5_UNorm => TextureFormat.DXT5, + GraphicsFormat.R_BC4_UNorm => TextureFormat.BC4, + GraphicsFormat.R_BC4_SNorm => TextureFormat.BC4, + GraphicsFormat.RG_BC5_UNorm => TextureFormat.BC5, + GraphicsFormat.RG_BC5_SNorm => TextureFormat.BC5, + GraphicsFormat.RGB_BC6H_UFloat => TextureFormat.BC6H, + GraphicsFormat.RGB_BC6H_SFloat => TextureFormat.BC6H, + GraphicsFormat.RGBA_BC7_SRGB => TextureFormat.BC7, + GraphicsFormat.RGBA_BC7_UNorm => TextureFormat.BC7, + GraphicsFormat.RGB_PVRTC_2Bpp_SRGB => TextureFormat.PVRTC_RGB2, + GraphicsFormat.RGB_PVRTC_2Bpp_UNorm => TextureFormat.PVRTC_RGB2, + GraphicsFormat.RGB_PVRTC_4Bpp_SRGB => TextureFormat.PVRTC_RGB4, + GraphicsFormat.RGB_PVRTC_4Bpp_UNorm => TextureFormat.PVRTC_RGB4, + GraphicsFormat.RGBA_PVRTC_2Bpp_SRGB => TextureFormat.PVRTC_RGBA2, + GraphicsFormat.RGBA_PVRTC_2Bpp_UNorm => TextureFormat.PVRTC_RGBA2, + GraphicsFormat.RGBA_PVRTC_4Bpp_SRGB => TextureFormat.PVRTC_RGBA4, + GraphicsFormat.RGBA_PVRTC_4Bpp_UNorm => TextureFormat.PVRTC_RGBA4, + GraphicsFormat.RGB_ETC_UNorm => TextureFormat.ETC_RGB4, + GraphicsFormat.RGB_ETC2_SRGB => TextureFormat.ETC2_RGB, + GraphicsFormat.RGB_ETC2_UNorm => TextureFormat.ETC2_RGB, + GraphicsFormat.RGB_A1_ETC2_SRGB => TextureFormat.ETC2_RGBA1, + GraphicsFormat.RGB_A1_ETC2_UNorm => TextureFormat.ETC2_RGBA1, + GraphicsFormat.RGBA_ETC2_SRGB => TextureFormat.ETC2_RGBA8, + GraphicsFormat.RGBA_ETC2_UNorm => TextureFormat.ETC2_RGBA8, + GraphicsFormat.R_EAC_UNorm => TextureFormat.EAC_R, + GraphicsFormat.R_EAC_SNorm => TextureFormat.EAC_R, + GraphicsFormat.RG_EAC_UNorm => TextureFormat.EAC_RG, + GraphicsFormat.RG_EAC_SNorm => TextureFormat.EAC_RG, + GraphicsFormat.RGBA_ASTC4X4_SRGB => TextureFormat.ASTC_4x4, + GraphicsFormat.RGBA_ASTC4X4_UNorm => TextureFormat.ASTC_4x4, + GraphicsFormat.RGBA_ASTC5X5_SRGB => TextureFormat.ASTC_5x5, + GraphicsFormat.RGBA_ASTC5X5_UNorm => TextureFormat.ASTC_5x5, + GraphicsFormat.RGBA_ASTC6X6_SRGB => TextureFormat.ASTC_6x6, + GraphicsFormat.RGBA_ASTC6X6_UNorm => TextureFormat.ASTC_6x6, + GraphicsFormat.RGBA_ASTC8X8_SRGB => TextureFormat.ASTC_8x8, + GraphicsFormat.RGBA_ASTC8X8_UNorm => TextureFormat.ASTC_8x8, + GraphicsFormat.RGBA_ASTC10X10_SRGB => TextureFormat.ASTC_10x10, + GraphicsFormat.RGBA_ASTC10X10_UNorm => TextureFormat.ASTC_10x10, + GraphicsFormat.RGBA_ASTC12X12_SRGB => TextureFormat.ASTC_12x12, + GraphicsFormat.RGBA_ASTC12X12_UNorm => TextureFormat.ASTC_12x12, + GraphicsFormat.YUV2 => throw new NotSupportedException(), + GraphicsFormat.DepthAuto => throw new NotSupportedException(), + GraphicsFormat.ShadowAuto => throw new NotSupportedException(), + GraphicsFormat.VideoAuto => throw new NotSupportedException(), + GraphicsFormat.RGBA_ASTC4X4_UFloat => TextureFormat.ASTC_4x4, + GraphicsFormat.RGBA_ASTC5X5_UFloat => TextureFormat.ASTC_5x5, + GraphicsFormat.RGBA_ASTC6X6_UFloat => TextureFormat.ASTC_6x6, + GraphicsFormat.RGBA_ASTC8X8_UFloat => TextureFormat.ASTC_8x8, + GraphicsFormat.RGBA_ASTC10X10_UFloat => TextureFormat.ASTC_10x10, + GraphicsFormat.RGBA_ASTC12X12_UFloat => TextureFormat.ASTC_12x12, + GraphicsFormat.D16_UNorm_S8_UInt => throw new NotSupportedException(), + _ => throw new ArgumentOutOfRangeException(nameof(format), format, null), + }; +} diff --git a/Source/AssetRipper.SourceGenerated.Extensions/Texture2DArrayExtensions.cs b/Source/AssetRipper.SourceGenerated.Extensions/Texture2DArrayExtensions.cs index b3c873ac8..50fd85545 100644 --- a/Source/AssetRipper.SourceGenerated.Extensions/Texture2DArrayExtensions.cs +++ b/Source/AssetRipper.SourceGenerated.Extensions/Texture2DArrayExtensions.cs @@ -35,5 +35,10 @@ namespace AssetRipper.SourceGenerated.Extensions return false; } } + + public static int GetCompleteImageSize(this ITexture2DArray texture) + { + return (int)texture.DataSize / (texture.Depth > 1 ? texture.Depth : 1); + } } }