1268 lines
54 KiB
C#
1268 lines
54 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.ComponentModel;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using Toolbox.Library.IO;
|
|
using OpenTK.Graphics.OpenGL;
|
|
using Toolbox.Library.Rendering;
|
|
using Ryujinx.Graphics.Gal.Texture; //For ASTC
|
|
using Toolbox.Library.NodeWrappers;
|
|
|
|
namespace Toolbox.Library
|
|
{
|
|
public enum STCompressionMode
|
|
{
|
|
Slow,
|
|
Normal,
|
|
Fast
|
|
}
|
|
|
|
public enum STChannelType
|
|
{
|
|
Red = 0,
|
|
Green = 1,
|
|
Blue = 2,
|
|
Alpha = 3,
|
|
One = 4,
|
|
Zero = 5,
|
|
}
|
|
|
|
public enum PlatformSwizzle
|
|
{
|
|
None = 0,
|
|
Platform_3DS = 1,
|
|
Platform_Wii = 2,
|
|
Platform_Gamecube = 3,
|
|
Platform_WiiU = 4,
|
|
Platform_Switch = 5,
|
|
Platform_Ps4 = 6,
|
|
Platform_Ps3 = 7,
|
|
Platform_Ps2 = 8,
|
|
Platform_Ps1 = 9,
|
|
}
|
|
|
|
public enum STSurfaceType
|
|
{
|
|
Texture1D,
|
|
Texture2D,
|
|
Texture3D,
|
|
TextureCube,
|
|
Texture1D_Array,
|
|
Texture2D_Array,
|
|
Texture2D_Mulitsample,
|
|
Texture2D_Multisample_Array,
|
|
TextureCube_Array,
|
|
}
|
|
|
|
|
|
public class EditedBitmap
|
|
{
|
|
public int ArrayLevel = 0;
|
|
public Bitmap bitmap;
|
|
}
|
|
|
|
public abstract class STGenericTexture : STGenericWrapper
|
|
{
|
|
public STGenericTexture()
|
|
{
|
|
RenderableTex = new RenderableTex();
|
|
RenderableTex.GLInitialized = false;
|
|
|
|
RedChannel = STChannelType.Red;
|
|
GreenChannel = STChannelType.Green;
|
|
BlueChannel = STChannelType.Blue;
|
|
AlphaChannel = STChannelType.Alpha;
|
|
}
|
|
|
|
public virtual UserControl GetEditor() { return new UserControl(); }
|
|
|
|
public virtual void FillEditor(UserControl control) { }
|
|
|
|
//A class that configs how the image should output (on display, and on export/replace)
|
|
public ImageParameters Parameters = new ImageParameters();
|
|
|
|
public bool IsCubemap
|
|
{
|
|
get
|
|
{
|
|
return ArrayCount == 6 || ArrayCount % 6 == 0;
|
|
}
|
|
}
|
|
|
|
public STSurfaceType SurfaceType = STSurfaceType.Texture2D;
|
|
|
|
/// <summary>
|
|
/// The swizzle method to use when decoding or encoding back a texture.
|
|
/// </summary>
|
|
public PlatformSwizzle PlatformSwizzle;
|
|
|
|
public bool IsSwizzled { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Is the texture edited or not. Used for the image editor for saving changes.
|
|
/// </summary>
|
|
public bool IsEdited { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// An array of <see cref="EditedBitmap"/> from the image editor to be saved back.
|
|
/// </summary>
|
|
public EditedBitmap[] EditedImages { get; set; }
|
|
|
|
//If the texture can be edited or not. Disables some functions in image editor if false
|
|
//If true, the editors will call "SetImageData" for setting data back to the original data.
|
|
public abstract bool CanEdit { get; set; }
|
|
|
|
public STChannelType RedChannel = STChannelType.Red;
|
|
public STChannelType GreenChannel = STChannelType.Green;
|
|
public STChannelType BlueChannel = STChannelType.Blue;
|
|
public STChannelType AlphaChannel = STChannelType.Alpha;
|
|
|
|
/// <summary>
|
|
/// The total length of all the bytes given from GetImageData.
|
|
/// </summary>
|
|
public long DataSizeInBytes
|
|
{
|
|
get
|
|
{
|
|
long totalSize = 0;
|
|
|
|
if (PlatformSwizzle == PlatformSwizzle.Platform_3DS)
|
|
{
|
|
for (int arrayLevel = 0; arrayLevel < ArrayCount; arrayLevel++)
|
|
{
|
|
for (int mipLevel = 0; mipLevel < MipCount; mipLevel++)
|
|
{
|
|
uint width = (uint)Math.Max(1, Width >> mipLevel);
|
|
uint height = (uint)Math.Max(1, Height >> mipLevel);
|
|
|
|
totalSize += CTR_3DS.CalculateLength((int)width, (int)height, CTR_3DS.ConvertToPICAFormat(Format));
|
|
}
|
|
}
|
|
|
|
return totalSize;
|
|
}
|
|
|
|
if (PlatformSwizzle == PlatformSwizzle.Platform_Gamecube)
|
|
{
|
|
for (int arrayLevel = 0; arrayLevel < ArrayCount; arrayLevel++)
|
|
{
|
|
for (int mipLevel = 0; mipLevel < MipCount; mipLevel++)
|
|
{
|
|
uint width = (uint)Math.Max(1, Width >> mipLevel);
|
|
uint height = (uint)Math.Max(1, Height >> mipLevel);
|
|
|
|
totalSize += Decode_Gamecube.GetDataSize((uint)Decode_Gamecube.FromGenericFormat(Format), width, height);
|
|
}
|
|
}
|
|
|
|
return totalSize;
|
|
}
|
|
|
|
if (FormatTable.ContainsKey(Format))
|
|
{
|
|
uint bpp = GetBytesPerPixel(Format);
|
|
|
|
for (int arrayLevel = 0; arrayLevel < ArrayCount; arrayLevel++)
|
|
{
|
|
for (int mipLevel = 0; mipLevel < MipCount; mipLevel++)
|
|
{
|
|
uint width = (uint)Math.Max(1, Width >> mipLevel);
|
|
uint height = (uint)Math.Max(1, Height >> mipLevel);
|
|
|
|
uint size = width * height * bpp;
|
|
if (IsCompressed(Format))
|
|
{
|
|
size = ((width + 3) >> 2) * ((Height + 3) >> 2) * bpp;
|
|
if (size < bpp)
|
|
size = bpp;
|
|
}
|
|
|
|
totalSize += size;
|
|
}
|
|
}
|
|
}
|
|
|
|
return totalSize;
|
|
}
|
|
}
|
|
|
|
public string DataSize { get { return STMath.GetFileSize(DataSizeInBytes, 5); } }
|
|
|
|
public abstract byte[] GetImageData(int ArrayLevel = 0, int MipLevel = 0);
|
|
|
|
private byte[] paletteData = new byte[0];
|
|
|
|
public virtual byte[] GetPaletteData() { return paletteData; }
|
|
|
|
public virtual void SetPaletteData(byte[] data, PALETTE_FORMAT format)
|
|
{
|
|
paletteData = data;
|
|
PaletteFormat = format;
|
|
}
|
|
|
|
//
|
|
//Gets a list of surfaces given the start index of the array and the amount of arrays to obtain
|
|
//
|
|
public List<Surface> GetSurfaces(int ArrayIndexStart = 0, bool GetAllSurfaces = true, int GetSurfaceAmount = 1)
|
|
{
|
|
|
|
|
|
if (GetAllSurfaces)
|
|
GetSurfaceAmount = (int)ArrayCount;
|
|
|
|
var surfaces = new List<Surface>();
|
|
for (int arrayLevel = 0; arrayLevel < ArrayCount; arrayLevel++)
|
|
{
|
|
bool IsLower = arrayLevel < ArrayIndexStart;
|
|
bool IsHigher = arrayLevel >= (ArrayIndexStart + GetSurfaceAmount);
|
|
if (!IsLower && !IsHigher)
|
|
{
|
|
List<byte[]> mips = new List<byte[]>();
|
|
for (int mipLevel = 0; mipLevel < MipCount; mipLevel++)
|
|
{
|
|
mips.Add(GetImageData(arrayLevel, mipLevel));
|
|
}
|
|
|
|
surfaces.Add(new Surface() { mipmaps = mips });
|
|
}
|
|
}
|
|
|
|
return surfaces;
|
|
}
|
|
|
|
public abstract void SetImageData(Bitmap bitmap, int ArrayLevel);
|
|
|
|
/// <summary>
|
|
/// The total amount of surfaces for the texture.
|
|
/// </summary>
|
|
public uint ArrayCount
|
|
{
|
|
get { return arrayCount; }
|
|
set { arrayCount = value; }
|
|
}
|
|
private uint arrayCount = 1;
|
|
|
|
/// <summary>
|
|
/// The total amount of mipmaps for the texture.
|
|
/// </summary>
|
|
public uint MipCount
|
|
{
|
|
get { return mipCount; }
|
|
set
|
|
{
|
|
if (value == 0)
|
|
mipCount = 1;
|
|
else if (value > 17)
|
|
throw new Exception($"Invalid mip map count! Texture: {Text} Value: {value}");
|
|
else
|
|
mipCount = value;
|
|
}
|
|
}
|
|
private uint mipCount = 1;
|
|
|
|
/// <summary>
|
|
/// The width of the image in pixels.
|
|
/// </summary>
|
|
public uint Width { get; set; }
|
|
|
|
/// <summary>
|
|
/// The height of the image in pixels.
|
|
/// </summary>
|
|
public uint Height { get; set; }
|
|
|
|
/// <summary>
|
|
/// The depth of the image in pixels. Used for 3D types.
|
|
/// </summary>
|
|
public uint Depth { get; set; }
|
|
|
|
/// <summary>
|
|
/// The <see cref="TEX_FORMAT"/> Format of the image.
|
|
/// </summary>
|
|
public TEX_FORMAT Format { get; set; } = TEX_FORMAT.R8G8B8A8_UNORM;
|
|
|
|
/// <summary>
|
|
/// The <see cref="PALETTE_FORMAT"/> Format of the image.
|
|
/// </summary>
|
|
public PALETTE_FORMAT PaletteFormat { get; set; } = PALETTE_FORMAT.None;
|
|
|
|
public RenderableTex RenderableTex { get; set; }
|
|
|
|
public abstract TEX_FORMAT[] SupportedFormats { get; }
|
|
|
|
public static uint GetBytesPerPixel(TEX_FORMAT Format)
|
|
{
|
|
return FormatTable[Format].BytesPerPixel;
|
|
}
|
|
|
|
public static uint GetBlockHeight(TEX_FORMAT Format)
|
|
{
|
|
return FormatTable[Format].BlockHeight;
|
|
}
|
|
|
|
public static uint GetBlockWidth(TEX_FORMAT Format)
|
|
{
|
|
return FormatTable[Format].BlockWidth;
|
|
}
|
|
|
|
public static uint GetBlockDepth(TEX_FORMAT Format)
|
|
{
|
|
return FormatTable[Format].BlockDepth;
|
|
}
|
|
|
|
// Based on Ryujinx's image table
|
|
// https://github.com/Ryujinx/Ryujinx/blob/c86aacde76b5f8e503e2b412385c8491ecc86b3b/Ryujinx.Graphics/Graphics3d/Texture/ImageUtils.cs
|
|
// A nice way to get bpp, block data, and buffer types for formats
|
|
|
|
private static readonly Dictionary<TEX_FORMAT, FormatInfo> FormatTable =
|
|
new Dictionary<TEX_FORMAT, FormatInfo>()
|
|
{
|
|
{ TEX_FORMAT.R32G32B32A32_FLOAT, new FormatInfo(16, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R32G32B32A32_SINT, new FormatInfo(16, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R32G32B32A32_UINT, new FormatInfo(16, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R32G32B32_FLOAT, new FormatInfo(8, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R16G16B16A16_FLOAT, new FormatInfo(8, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R16G16B16A16_SINT, new FormatInfo(8, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R16G16B16A16_SNORM, new FormatInfo(8, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R32G32_FLOAT, new FormatInfo(8, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R32G32_SINT, new FormatInfo(8, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R32G32_UINT, new FormatInfo(8, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R8G8B8A8_SINT, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R8G8B8A8_SNORM, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R8G8B8A8_UINT, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R8G8B8A8_UNORM, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R8G8B8A8_UNORM_SRGB, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R32G8X24_FLOAT, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R8G8_B8G8_UNORM, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.B8G8R8X8_UNORM, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.B5G5R5A1_UNORM, new FormatInfo(2, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.B8G8R8A8_UNORM, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.B8G8R8A8_UNORM_SRGB, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R5G5B5_UNORM, new FormatInfo(2, 1, 1, 1, TargetBuffer.Color) },
|
|
|
|
|
|
{ TEX_FORMAT.R10G10B10A2_UINT, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R10G10B10A2_UNORM, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R32_SINT, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R32_UINT, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R32_FLOAT, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.B4G4R4A4_UNORM, new FormatInfo(2, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R16G16_FLOAT, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R16G16_SINT, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R16G16_SNORM, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R16G16_UINT, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R16G16_UNORM, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R8G8_SINT, new FormatInfo(2, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R8G8_SNORM, new FormatInfo(2, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R8G8_UINT, new FormatInfo(2, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R8G8_UNORM, new FormatInfo(2, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R16_SINT, new FormatInfo(2, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R16_SNORM, new FormatInfo(2, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R16_UINT, new FormatInfo(2, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R16_UNORM, new FormatInfo(2, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R8_SINT, new FormatInfo(1, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R8_SNORM, new FormatInfo(1, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R4G4_UNORM, new FormatInfo(1, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R8_UINT, new FormatInfo(1, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R8_UNORM, new FormatInfo(1, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.R11G11B10_FLOAT, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.B5G6R5_UNORM, new FormatInfo(2, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.BC1_UNORM, new FormatInfo(8, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.BC1_UNORM_SRGB, new FormatInfo(8, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.BC2_UNORM, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.BC2_UNORM_SRGB, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.BC3_UNORM, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.BC3_UNORM_SRGB, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.BC4_UNORM, new FormatInfo(8, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.BC4_SNORM, new FormatInfo(8, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.BC5_UNORM, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.BC5_SNORM, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.BC6H_SF16, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.BC6H_UF16, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.BC7_UNORM, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.BC7_UNORM_SRGB, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) },
|
|
|
|
{ TEX_FORMAT.ASTC_4x4_UNORM, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_4x4_SRGB, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_5x4_UNORM, new FormatInfo(16, 5, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_5x4_SRGB, new FormatInfo(16, 5, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_5x5_UNORM, new FormatInfo(16, 5, 5, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_5x5_SRGB, new FormatInfo(16, 5, 5, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_6x5_UNORM, new FormatInfo(16, 6, 5, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_6x5_SRGB, new FormatInfo(16, 6, 5, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_6x6_UNORM, new FormatInfo(16, 6, 6, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_6x6_SRGB, new FormatInfo(16, 6, 6, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_8x5_UNORM, new FormatInfo(16, 8, 5, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_8x5_SRGB, new FormatInfo(16, 8, 5, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_8x6_UNORM, new FormatInfo(16, 8, 6, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_8x6_SRGB, new FormatInfo(16, 8, 6, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_8x8_UNORM, new FormatInfo(16, 8, 8, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_8x8_SRGB, new FormatInfo(16, 8, 8, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_10x5_UNORM, new FormatInfo(16, 10, 5, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_10x5_SRGB, new FormatInfo(16, 10, 5, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_10x6_UNORM, new FormatInfo(16, 10, 6, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_10x6_SRGB, new FormatInfo(16, 10, 6, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_10x8_UNORM, new FormatInfo(16, 10, 8, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_10x8_SRGB, new FormatInfo(16, 10, 8, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_10x10_UNORM, new FormatInfo(16, 10, 10, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_10x10_SRGB, new FormatInfo(16, 10, 10, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_12x10_UNORM, new FormatInfo(16, 12, 10, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_12x10_SRGB, new FormatInfo(16, 12, 10, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_12x12_UNORM, new FormatInfo(16, 12, 12, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ASTC_12x12_SRGB, new FormatInfo(16, 12, 12, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ETC1_UNORM, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ETC1_SRGB, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.ETC1_A4, new FormatInfo(8, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.HIL08, new FormatInfo(16, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.L4, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.LA4, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.L8, new FormatInfo(8, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.LA8, new FormatInfo(16, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.A4, new FormatInfo(4, 1, 1, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.A8_UNORM, new FormatInfo(8, 1, 1, 1, TargetBuffer.Color) },
|
|
|
|
{ TEX_FORMAT.D16_UNORM, new FormatInfo(2, 1, 1, 1, TargetBuffer.Depth) },
|
|
{ TEX_FORMAT.D24_UNORM_S8_UINT, new FormatInfo(4, 1, 1, 1, TargetBuffer.Depth) },
|
|
{ TEX_FORMAT.D32_FLOAT, new FormatInfo(4, 1, 1, 1, TargetBuffer.Depth) },
|
|
{ TEX_FORMAT.D32_FLOAT_S8X24_UINT, new FormatInfo(8, 1, 1, 1, TargetBuffer.DepthStencil)},
|
|
|
|
{ TEX_FORMAT.I4, new FormatInfo(4, 8, 8, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.I8, new FormatInfo(8, 8, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.IA4, new FormatInfo(8, 8, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.IA8, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.RGB565, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.RGB5A3, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.RGBA32, new FormatInfo(32, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.C4, new FormatInfo(4, 8, 8, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.C8, new FormatInfo(8, 8, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.C14X2, new FormatInfo(16, 4, 4, 1, TargetBuffer.Color) },
|
|
{ TEX_FORMAT.CMPR, new FormatInfo(4, 8, 8, 1, TargetBuffer.Color) }
|
|
|
|
};
|
|
|
|
/// <summary>
|
|
/// A Surface contains mip levels of compressed/uncompressed texture data
|
|
/// </summary>
|
|
public class Surface
|
|
{
|
|
public List<byte[]> mipmaps = new List<byte[]>();
|
|
}
|
|
|
|
public void CreateGenericTexture(uint width, uint height, List<Surface> surfaces, TEX_FORMAT format)
|
|
{
|
|
Width = width;
|
|
Height = height;
|
|
Format = format;
|
|
}
|
|
private enum TargetBuffer
|
|
{
|
|
Color = 1,
|
|
Depth = 2,
|
|
Stencil = 3,
|
|
DepthStencil = 4,
|
|
}
|
|
|
|
public void DisposeRenderable()
|
|
{
|
|
if (RenderableTex != null && Runtime.UseOpenGL)
|
|
{
|
|
RenderableTex.Dispose();
|
|
RenderableTex = null;
|
|
}
|
|
}
|
|
|
|
private class FormatInfo
|
|
{
|
|
public uint BytesPerPixel { get; private set; }
|
|
public uint BlockWidth { get; private set; }
|
|
public uint BlockHeight { get; private set; }
|
|
public uint BlockDepth { get; private set; }
|
|
|
|
public TargetBuffer TargetBuffer;
|
|
|
|
public FormatInfo(uint bytesPerPixel, uint blockWidth, uint blockHeight, uint blockDepth, TargetBuffer targetBuffer)
|
|
{
|
|
BytesPerPixel = bytesPerPixel;
|
|
BlockWidth = blockWidth;
|
|
BlockHeight = blockHeight;
|
|
BlockDepth = blockDepth;
|
|
TargetBuffer = targetBuffer;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a <see cref="Bitmap"/> given an array and mip index.
|
|
/// </summary>
|
|
/// <param name="ArrayIndex">The index of the surface/array. Cubemaps will have 6</param>
|
|
/// <param name="MipLevel">The index of the mip level.</param>
|
|
/// <returns></returns>
|
|
public Bitmap GetBitmap(int ArrayLevel = 0, int MipLevel = 0)
|
|
{
|
|
uint width = Math.Max(1, Width >> MipLevel);
|
|
uint height = Math.Max(1, Height >> MipLevel);
|
|
byte[] data = GetImageData(ArrayLevel, MipLevel);
|
|
byte[] paletteData = GetPaletteData();
|
|
|
|
try
|
|
{
|
|
if (data == null)
|
|
throw new Exception("Data is null!");
|
|
|
|
Console.WriteLine("Decoding " + Format + " " + Runtime.UseDirectXTexDecoder);
|
|
|
|
if (PlatformSwizzle == PlatformSwizzle.Platform_3DS)
|
|
{
|
|
var Image = BitmapExtension.GetBitmap(ConvertBgraToRgba(CTR_3DS.DecodeBlock(data, (int)width, (int)height, Format)),
|
|
(int)width, (int)height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
Image.RotateFlip(RotateFlipType.RotateNoneFlipY); //It's upside down for some reason so flip it
|
|
return Image;
|
|
}
|
|
|
|
if (PlatformSwizzle == PlatformSwizzle.Platform_Gamecube)
|
|
{
|
|
return BitmapExtension.GetBitmap(Decode_Gamecube.DecodeData(data, paletteData, width, height, Format, PaletteFormat),
|
|
(int)width, (int)height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
}
|
|
|
|
switch (Format)
|
|
{
|
|
case TEX_FORMAT.R4G4_UNORM:
|
|
return BitmapExtension.GetBitmap(R4G4.Decompress(data, (int)width, (int)height, false),
|
|
(int)width, (int)height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
case TEX_FORMAT.BC5_SNORM:
|
|
return DDSCompressor.DecompressBC5(data, (int)width, (int)height, true);
|
|
case TEX_FORMAT.ETC1_UNORM:
|
|
return BitmapExtension.GetBitmap(ETC1.ETC1Decompress(data, (int)width, (int)height, false),
|
|
(int)width, (int)height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
case TEX_FORMAT.ETC1_A4:
|
|
return BitmapExtension.GetBitmap(ETC1.ETC1Decompress(data, (int)width, (int)height, true),
|
|
(int)width, (int)height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
case TEX_FORMAT.L8:
|
|
return BitmapExtension.GetBitmap(RGBAPixelDecoder.Decode(data, (int)width, (int)height, Format),
|
|
(int)width, (int)height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
case TEX_FORMAT.LA8:
|
|
return BitmapExtension.GetBitmap(RGBAPixelDecoder.Decode(data, (int)width, (int)height, Format),
|
|
(int)width, (int)height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
}
|
|
|
|
if (Runtime.UseDirectXTexDecoder)
|
|
{
|
|
return BitmapExtension.GetBitmap(DecodeBlock(data, width, height, Format, new byte[0], Parameters),
|
|
(int)width, (int)height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
}
|
|
else
|
|
return DecodeNotDirectXTex(data, width, height, Format);
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
/* Forms.STErrorDialog.Show($"Texture failed to load!", "Texture [GetBitmap({MipLevel},{ArrayLevel})]", DebugInfo() + " \n" + ex);
|
|
|
|
try
|
|
{
|
|
return DecodeNotDirectXTex(data, width, height, Format);
|
|
}
|
|
catch
|
|
{
|
|
Forms.STErrorDialog.Show($"Texture failed to load!", "Texture [GetBitmap({MipLevel},{ArrayLevel})]", DebugInfo() + " \n" + ex);
|
|
}*/
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private Bitmap DecodeNotDirectXTex(byte[] data, uint Width, uint Height, TEX_FORMAT Format)
|
|
{
|
|
if (Format == TEX_FORMAT.R8G8B8A8_UNORM)
|
|
return BitmapExtension.GetBitmap(ConvertBgraToRgba(data), (int)Width, (int)Height);
|
|
else if (Format == TEX_FORMAT.R8G8B8A8_UNORM_SRGB)
|
|
return BitmapExtension.GetBitmap(ConvertBgraToRgba(data), (int)Width, (int)Height);
|
|
else if (Format == TEX_FORMAT.BC1_UNORM)
|
|
return DDSCompressor.DecompressBC1(data, (int)Width, (int)Height, false);
|
|
else if (Format == TEX_FORMAT.BC1_UNORM_SRGB)
|
|
return DDSCompressor.DecompressBC1(data, (int)Width, (int)Height, true);
|
|
else if (Format == TEX_FORMAT.BC3_UNORM_SRGB)
|
|
return DDSCompressor.DecompressBC3(data, (int)Width, (int)Height, false);
|
|
else if (Format == TEX_FORMAT.BC3_UNORM)
|
|
return DDSCompressor.DecompressBC3(data, (int)Width, (int)Height, true);
|
|
else if (Format == TEX_FORMAT.BC4_UNORM)
|
|
return DDSCompressor.DecompressBC4(data, (int)Width, (int)Height, false);
|
|
else if (Format == TEX_FORMAT.BC4_SNORM)
|
|
return DDSCompressor.DecompressBC4(data, (int)Width, (int)Height, true);
|
|
else if (Format == TEX_FORMAT.BC5_UNORM)
|
|
return DDSCompressor.DecompressBC5(data, (int)Width, (int)Height, false);
|
|
else if (Format == TEX_FORMAT.BC7_UNORM)
|
|
return BitmapExtension.GetBitmap(CSharpImageLibrary.DDS.Dxt.DecompressBc7(data, (int)Width, (int)Height), (int)Width, (int)Height);
|
|
else if (Format == TEX_FORMAT.BC7_UNORM_SRGB)
|
|
return BitmapExtension.GetBitmap(CSharpImageLibrary.DDS.Dxt.DecompressBc7(data, (int)Width, (int)Height), (int)Width, (int)Height);
|
|
else
|
|
{
|
|
|
|
if (Runtime.UseOpenGL)
|
|
{
|
|
Runtime.OpenTKInitialized = true;
|
|
if (RenderableTex == null || !RenderableTex.GLInitialized)
|
|
LoadOpenGLTexture();
|
|
|
|
return RenderableTex.ToBitmap();
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static Bitmap DecodeBlockGetBitmap(byte[] data, uint Width, uint Height, TEX_FORMAT Format, byte[] paletteData, PALETTE_FORMAT PaletteFormat = PALETTE_FORMAT.None)
|
|
{
|
|
Bitmap bitmap = BitmapExtension.GetBitmap(DecodeBlock(data, Width, Height, Format, paletteData, new ImageParameters(), PaletteFormat),
|
|
(int)Width, (int)Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decodes a byte array of image data given the source image in bytes, width, height, and DXGI format.
|
|
/// </summary>
|
|
/// <param name="byte[]">The byte array of the image</param>
|
|
/// <param name="Width">The width of the image in pixels.</param>
|
|
/// <param name="Height">The height of the image in pixels.</param>
|
|
/// <param name=" DDS.DXGI_FORMAT">The image format.</param>
|
|
/// <returns>Returns a byte array of decoded data. </returns>
|
|
public static byte[] DecodeBlock(byte[] data, uint Width, uint Height, TEX_FORMAT Format, byte[] paletteData, ImageParameters parameters, PALETTE_FORMAT PaletteFormat = PALETTE_FORMAT.None, PlatformSwizzle PlatformSwizzle = PlatformSwizzle.None)
|
|
{
|
|
if (data == null) throw new Exception($"Data is null!");
|
|
if (Format <= 0) throw new Exception($"Invalid Format!");
|
|
if (data.Length <= 0) throw new Exception($"Data is empty!");
|
|
if (Width <= 0) throw new Exception($"Invalid width size {Width}!");
|
|
if (Height <= 0) throw new Exception($"Invalid height size {Height}!");
|
|
|
|
byte[] imageData = new byte[0];
|
|
bool DontSwapRG = false;
|
|
|
|
if (Format.ToString().StartsWith("B") && !IsCompressed(Format))
|
|
DontSwapRG = true;
|
|
|
|
if (PlatformSwizzle == PlatformSwizzle.Platform_3DS)
|
|
{
|
|
imageData = CTR_3DS.DecodeBlock(data, (int)Width, (int)Height, Format);
|
|
DontSwapRG = true;
|
|
}
|
|
else if (PlatformSwizzle == PlatformSwizzle.Platform_Gamecube)
|
|
imageData = Decode_Gamecube.DecodeData(data, paletteData, Width, Height, Format, PaletteFormat);
|
|
else
|
|
{
|
|
if (Format == TEX_FORMAT.R32G8X24_FLOAT)
|
|
imageData = DDSCompressor.DecodePixelBlock(data, (int)Width, (int)Height, DDS.DXGI_FORMAT.DXGI_FORMAT_R32G8X24_TYPELESS);
|
|
|
|
if (Format == TEX_FORMAT.BC5_SNORM)
|
|
imageData = DDSCompressor.DecompressBC5(data, (int)Width, (int)Height, true, true);
|
|
|
|
if (Format == TEX_FORMAT.L8)
|
|
return RGBAPixelDecoder.Decode(data, (int)Width, (int)Height, Format);
|
|
if (Format == TEX_FORMAT.LA8)
|
|
return RGBAPixelDecoder.Decode(data, (int)Width, (int)Height, Format);
|
|
|
|
if (IsCompressed(Format))
|
|
imageData = DDSCompressor.DecompressBlock(data, (int)Width, (int)Height, (DDS.DXGI_FORMAT)Format);
|
|
else
|
|
{
|
|
if (IsAtscFormat(Format))
|
|
imageData = ASTCDecoder.DecodeToRGBA8888(data, (int)GetBlockWidth(Format), (int)GetBlockHeight(Format), 1, (int)Width, (int)Height, 1);
|
|
else
|
|
imageData = DDSCompressor.DecodePixelBlock(data, (int)Width, (int)Height, (DDS.DXGI_FORMAT)Format);
|
|
|
|
// imageData = RGBAPixelDecoder.Decode(data, (int)Width, (int)Height, Format);
|
|
}
|
|
}
|
|
|
|
if (parameters.DontSwapRG || DontSwapRG)
|
|
return imageData;
|
|
else
|
|
return ConvertBgraToRgba(imageData);
|
|
}
|
|
|
|
public string DebugInfo()
|
|
{
|
|
return $"Texture Info:\n" +
|
|
$"Name: {Text}\n" +
|
|
$"Format: {Format}\n" +
|
|
$"Height: {Height}\n" +
|
|
$"Width: {Width}\n" +
|
|
$"Block Height: {GetBlockHeight(Format)}\n" +
|
|
$"Block Width: {GetBlockWidth(Format)}\n" +
|
|
$"Bytes Per Pixel: {GetBytesPerPixel(Format)}\n" +
|
|
$"Array Count: {ArrayCount}\n" +
|
|
$"Mip Map Count: {MipCount}\n" +
|
|
"";
|
|
}
|
|
|
|
public uint GenerateMipCount(int Width, int Height)
|
|
{
|
|
return GenerateMipCount((uint)Width, (uint)Height);
|
|
}
|
|
|
|
public uint GenerateMipCount(uint Width, uint Height)
|
|
{
|
|
uint MipmapNum = 0;
|
|
uint num = Math.Max(Width, Height);
|
|
|
|
int width = (int)Width;
|
|
int height = (int)Height;
|
|
|
|
while (true)
|
|
{
|
|
num >>= 1;
|
|
|
|
width = width / 2;
|
|
height = height / 2;
|
|
if (width <= 0 || height <= 0)
|
|
break;
|
|
|
|
if (num > 0)
|
|
++MipmapNum;
|
|
else
|
|
break;
|
|
}
|
|
|
|
return MipmapNum;
|
|
}
|
|
|
|
public static byte[] GenerateMipsAndCompress(Bitmap bitmap, uint MipCount, TEX_FORMAT Format, bool multiThread = false, float alphaRef = 0.5f, STCompressionMode CompressionMode = STCompressionMode.Fast)
|
|
{
|
|
byte[] DecompressedData = BitmapExtension.ImageToByte(bitmap);
|
|
DecompressedData = ConvertBgraToRgba(DecompressedData);
|
|
|
|
Bitmap Image = BitmapExtension.GetBitmap(DecompressedData, bitmap.Width, bitmap.Height);
|
|
|
|
List<byte[]> mipmaps = new List<byte[]>();
|
|
for (int mipLevel = 0; mipLevel < MipCount; mipLevel++)
|
|
{
|
|
int width = Math.Max(1, bitmap.Width >> mipLevel);
|
|
int height = Math.Max(1, bitmap.Height >> mipLevel);
|
|
|
|
Image = BitmapExtension.Resize(Image, width, height);
|
|
mipmaps.Add(STGenericTexture.CompressBlock(BitmapExtension.ImageToByte(Image),
|
|
Image.Width, Image.Height, Format, alphaRef, multiThread, CompressionMode));
|
|
}
|
|
Image.Dispose();
|
|
|
|
return Utils.CombineByteArray(mipmaps.ToArray());
|
|
}
|
|
|
|
public static byte[] CompressBlock(byte[] data, int width, int height, TEX_FORMAT format, float alphaRef, bool multiThread = false, STCompressionMode CompressionMode = STCompressionMode.Fast)
|
|
{
|
|
if (!Runtime.UseDirectXTexDecoder)
|
|
return data;
|
|
|
|
if (IsCompressed(format))
|
|
return DDSCompressor.CompressBlock(data, width, height, (DDS.DXGI_FORMAT)format, multiThread, alphaRef, CompressionMode);
|
|
else if (IsAtscFormat(format))
|
|
return null;
|
|
else
|
|
return DDSCompressor.EncodePixelBlock(data, width, height, (DDS.DXGI_FORMAT)format);
|
|
}
|
|
public void LoadDDS(string path)
|
|
{
|
|
Text = SetNameFromPath(path);
|
|
|
|
DDS dds = new DDS();
|
|
LoadDDS(path);
|
|
|
|
Width = dds.header.width;
|
|
Height = dds.header.height;
|
|
Format = dds.GetFormat();
|
|
|
|
MipCount = dds.header.mipmapCount;
|
|
}
|
|
public void LoadTGA(string path)
|
|
{
|
|
Text = SetNameFromPath(path);
|
|
Bitmap tga = Paloma.TargaImage.LoadTargaImage(path);
|
|
}
|
|
public void LoadBitmap(string path)
|
|
{
|
|
Text = SetNameFromPath(path);
|
|
|
|
}
|
|
public void LoadASTC(string path)
|
|
{
|
|
ASTC astc = new ASTC();
|
|
astc.Load(new FileStream(path, FileMode.Open));
|
|
}
|
|
|
|
public override string ExportFilter
|
|
{
|
|
get
|
|
{
|
|
if (IsAtscFormat(Format))
|
|
{
|
|
return "Supported Formats|*.astc; *.png;*.tga;*.jpg;*.tiff|" +
|
|
"ASTC |*.astc|" +
|
|
"Portable Network Graphics |*.png|" +
|
|
"Joint Photographic Experts Group |*.jpg|" +
|
|
"Bitmap Image |*.bmp|" +
|
|
"Tagged Image File Format |*.tiff|" +
|
|
"All files(*.*)|*.*";
|
|
}
|
|
else
|
|
{
|
|
return "Supported Formats|*.dds; *.png;*.tga;*.jpg;*.tiff|" +
|
|
"Microsoft DDS |*.dds|" +
|
|
"Portable Network Graphics |*.png|" +
|
|
"Joint Photographic Experts Group |*.jpg|" +
|
|
"Bitmap Image |*.bmp|" +
|
|
"Tagged Image File Format |*.tiff|" +
|
|
"All files(*.*)|*.*";
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Export(string FileName)
|
|
{
|
|
Export(FileName);
|
|
}
|
|
|
|
public void ExportArrayImage(int ArrayIndex = 0)
|
|
{
|
|
SaveFileDialog sfd = new SaveFileDialog();
|
|
sfd.FileName = Text;
|
|
sfd.DefaultExt = "dds";
|
|
sfd.Filter = ExportFilter;
|
|
|
|
if (sfd.ShowDialog() == DialogResult.OK)
|
|
{
|
|
Export(sfd.FileName, true, false, ArrayIndex, 0);
|
|
}
|
|
}
|
|
|
|
public void ExportImage()
|
|
{
|
|
SaveFileDialog sfd = new SaveFileDialog();
|
|
sfd.FileName = Text;
|
|
sfd.DefaultExt = "dds";
|
|
sfd.Filter = ExportFilter;
|
|
|
|
if (sfd.ShowDialog() == DialogResult.OK)
|
|
{
|
|
Export(sfd.FileName, false, false, 0, 0);
|
|
}
|
|
}
|
|
|
|
public void Export(string FileName, bool ExportSurfaceLevel = false,
|
|
bool ExportMipMapLevel = false, int SurfaceLevel = 0, int MipLevel = 0)
|
|
{
|
|
string ext = Path.GetExtension(FileName);
|
|
ext = ext.ToLower();
|
|
|
|
switch (ext)
|
|
{
|
|
case ".dds":
|
|
SaveDDS(FileName, ExportSurfaceLevel, ExportMipMapLevel, SurfaceLevel, MipLevel);
|
|
break;
|
|
case ".astc":
|
|
SaveASTC(FileName, ExportSurfaceLevel, ExportMipMapLevel, SurfaceLevel, MipLevel);
|
|
break;
|
|
default:
|
|
SaveBitMap(FileName, ExportSurfaceLevel, ExportMipMapLevel, SurfaceLevel, MipLevel);
|
|
break;
|
|
}
|
|
}
|
|
public void SaveASTC(string FileName, bool ExportSurfaceLevel = false,
|
|
bool ExportMipMapLevel = false, int SurfaceLevel = 0, int MipLevel = 0)
|
|
{
|
|
List<Surface> surfaces = null;
|
|
if (ExportSurfaceLevel)
|
|
surfaces = GetSurfaces(SurfaceLevel, false);
|
|
else
|
|
surfaces = GetSurfaces();
|
|
|
|
ASTC atsc = new ASTC();
|
|
atsc.Width = Width;
|
|
atsc.Height = Height;
|
|
atsc.Depth = Depth;
|
|
atsc.BlockDimX = (byte)GetBlockWidth(Format);
|
|
atsc.BlockDimY = (byte)GetBlockHeight(Format);
|
|
atsc.BlockDimZ = (byte)GetBlockDepth(Format);
|
|
atsc.DataBlock = Utils.CombineByteArray(surfaces[0].mipmaps.ToArray());
|
|
|
|
Console.WriteLine("DataBlock " + atsc.DataBlock.Length);
|
|
|
|
atsc.Save(new FileStream(FileName, FileMode.Create, FileAccess.ReadWrite));
|
|
}
|
|
public void SaveTGA(string FileName, bool ExportSurfaceLevel = false,
|
|
bool ExportMipMapLevel = false, int SurfaceLevel = 0, int MipLevel = 0)
|
|
{
|
|
|
|
}
|
|
public void SaveBitMap(string FileName, bool ExportSurfaceLevel = false,
|
|
bool ExportMipMapLevel = false, int SurfaceLevel = 0, int MipLevel = 0)
|
|
{
|
|
STProgressBar progressBar = new STProgressBar();
|
|
progressBar.Task = "Exporting Image Data...";
|
|
progressBar.Value = 0;
|
|
progressBar.StartPosition = FormStartPosition.CenterScreen;
|
|
progressBar.Show();
|
|
progressBar.Refresh();
|
|
|
|
if (ArrayCount > 1 && !ExportSurfaceLevel)
|
|
{
|
|
progressBar.Task = "Select dialog option... ";
|
|
|
|
var result = MessageBox.Show("Multiple image surfaces found! Would you like to export them all?", "Image Exporter",
|
|
MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1);
|
|
if (result == DialogResult.Yes)
|
|
{
|
|
string ext = Path.GetExtension(FileName);
|
|
|
|
int index = FileName.LastIndexOf('.');
|
|
string name = index == -1 ? FileName : FileName.Substring(0, index);
|
|
|
|
for (int i = 0; i < ArrayCount; i++)
|
|
{
|
|
progressBar.Task = $"Decoding Surface [{i}] for image {Text}... ";
|
|
progressBar.Value = (i * 100) / (int)ArrayCount;
|
|
progressBar.Refresh();
|
|
|
|
Bitmap arrayBitMap = GetBitmap(i, 0);
|
|
arrayBitMap.Save($"{name}_Slice_{i}_{ext}");
|
|
arrayBitMap.Dispose();
|
|
}
|
|
|
|
progressBar.Value = 100;
|
|
progressBar.Close();
|
|
return;
|
|
}
|
|
}
|
|
|
|
progressBar.Task = $"Decoding image {Text}... ";
|
|
progressBar.Value = 20;
|
|
progressBar.Refresh();
|
|
|
|
Bitmap bitMap = GetBitmap(SurfaceLevel, MipLevel);
|
|
if (Runtime.ImageEditor.UseComponetSelector)
|
|
bitMap = BitmapExtension.SetChannel(bitMap, RedChannel, GreenChannel, BlueChannel, AlphaChannel);
|
|
if (Runtime.ImageEditor.PreviewGammaFix)
|
|
bitMap = BitmapExtension.AdjustGamma(bitMap, 1.0f / 2.2f);
|
|
|
|
if (Parameters.FlipY)
|
|
bitMap.RotateFlip(RotateFlipType.RotateNoneFlipY);
|
|
|
|
bitMap.Save(FileName);
|
|
bitMap.Dispose();
|
|
|
|
progressBar.Value = 100;
|
|
progressBar.Close();
|
|
}
|
|
public void SaveDDS(string FileName, bool ExportSurfaceLevel = false,
|
|
bool ExportMipMapLevel = false, int SurfaceLevel = 0, int MipLevel = 0)
|
|
{
|
|
List<Surface> surfaces = null;
|
|
if (ExportSurfaceLevel)
|
|
surfaces = GetSurfaces(SurfaceLevel, false, 1);
|
|
else
|
|
surfaces = GetSurfaces();
|
|
|
|
if (Depth == 0)
|
|
Depth = 1;
|
|
|
|
DDS dds = new DDS();
|
|
dds.header = new DDS.Header();
|
|
dds.header.width = Width;
|
|
dds.header.height = Height;
|
|
dds.header.depth = Depth;
|
|
dds.header.mipmapCount = (uint)MipCount;
|
|
dds.header.pitchOrLinearSize = (uint)surfaces[0].mipmaps[0].Length;
|
|
|
|
if (surfaces.Count > 0) //Use DX10 format for array surfaces as it can do custom amounts
|
|
dds.SetFlags((DDS.DXGI_FORMAT)Format, true);
|
|
else
|
|
dds.SetFlags((DDS.DXGI_FORMAT)Format);
|
|
|
|
if (dds.IsDX10)
|
|
{
|
|
if (dds.DX10header == null)
|
|
dds.DX10header = new DDS.DX10Header();
|
|
|
|
dds.DX10header.ResourceDim = 3;
|
|
dds.DX10header.arrayFlag = (uint)surfaces.Count;
|
|
}
|
|
|
|
|
|
dds.Save(dds, FileName, surfaces);
|
|
}
|
|
public void LoadOpenGLTexture()
|
|
{
|
|
if (!Runtime.UseOpenGL)
|
|
return;
|
|
|
|
if (RenderableTex == null)
|
|
RenderableTex = new RenderableTex();
|
|
|
|
RenderableTex.GLInitialized = false;
|
|
RenderableTex.LoadOpenGLTexture(this);
|
|
}
|
|
public static bool IsAtscFormat(TEX_FORMAT Format)
|
|
{
|
|
if (Format.ToString().Contains("ASTC"))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
public static bool IsCompressed(TEX_FORMAT Format)
|
|
{
|
|
switch (Format)
|
|
{
|
|
case TEX_FORMAT.BC1_UNORM:
|
|
case TEX_FORMAT.BC1_UNORM_SRGB:
|
|
case TEX_FORMAT.BC1_TYPELESS:
|
|
case TEX_FORMAT.BC2_UNORM_SRGB:
|
|
case TEX_FORMAT.BC2_UNORM:
|
|
case TEX_FORMAT.BC2_TYPELESS:
|
|
case TEX_FORMAT.BC3_UNORM_SRGB:
|
|
case TEX_FORMAT.BC3_UNORM:
|
|
case TEX_FORMAT.BC3_TYPELESS:
|
|
case TEX_FORMAT.BC4_UNORM:
|
|
case TEX_FORMAT.BC4_TYPELESS:
|
|
case TEX_FORMAT.BC4_SNORM:
|
|
case TEX_FORMAT.BC5_UNORM:
|
|
case TEX_FORMAT.BC5_TYPELESS:
|
|
case TEX_FORMAT.BC5_SNORM:
|
|
case TEX_FORMAT.BC6H_UF16:
|
|
case TEX_FORMAT.BC6H_SF16:
|
|
case TEX_FORMAT.BC7_UNORM:
|
|
case TEX_FORMAT.BC7_UNORM_SRGB:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
public static STChannelType[] SetChannelsByFormat(TEX_FORMAT Format)
|
|
{
|
|
STChannelType[] channels = new STChannelType[4];
|
|
|
|
switch (Format)
|
|
{
|
|
case TEX_FORMAT.BC5_UNORM:
|
|
case TEX_FORMAT.BC5_SNORM:
|
|
channels[0] = STChannelType.Red;
|
|
channels[1] = STChannelType.Green;
|
|
channels[2] = STChannelType.One;
|
|
channels[3] = STChannelType.One;
|
|
break;
|
|
case TEX_FORMAT.BC4_UNORM:
|
|
case TEX_FORMAT.BC4_SNORM:
|
|
channels[0] = STChannelType.Red;
|
|
channels[1] = STChannelType.Red;
|
|
channels[2] = STChannelType.Red;
|
|
channels[3] = STChannelType.Red;
|
|
break;
|
|
default:
|
|
channels[0] = STChannelType.Red;
|
|
channels[1] = STChannelType.Green;
|
|
channels[2] = STChannelType.Blue;
|
|
channels[3] = STChannelType.Alpha;
|
|
break;
|
|
}
|
|
return channels;
|
|
}
|
|
|
|
public static int GenerateTotalMipCount(uint Width, uint Height)
|
|
{
|
|
int mipCount = 1;
|
|
|
|
int width = (int)Width;
|
|
int height = (int)Height;
|
|
|
|
while (width > 1 || height > 1)
|
|
{
|
|
++mipCount;
|
|
|
|
if (width > 1)
|
|
width /= 2;
|
|
|
|
if (height > 1)
|
|
height /= 2;
|
|
}
|
|
|
|
return mipCount;
|
|
}
|
|
|
|
public static string SetNameFromPath(string path)
|
|
{
|
|
string FileName = Path.GetFileName(path);
|
|
string extension = System.IO.Path.GetExtension(FileName);
|
|
return FileName.Substring(0, FileName.Length - extension.Length);
|
|
}
|
|
|
|
public Bitmap GetComponentBitmap(Bitmap image, bool ShowAlpha = true)
|
|
{
|
|
//Check if it's RGBA to save time editing the image
|
|
if (UseRGBA())
|
|
return image;
|
|
else if (ShowAlpha)
|
|
return BitmapExtension.SetChannel(image, RedChannel, GreenChannel, BlueChannel, AlphaChannel);
|
|
else
|
|
return BitmapExtension.SetChannel(image, RedChannel, GreenChannel, BlueChannel, STChannelType.One);
|
|
}
|
|
|
|
private bool UseRGBA()
|
|
{
|
|
if (RedChannel == STChannelType.Red &&
|
|
GreenChannel == STChannelType.Green &&
|
|
BlueChannel == STChannelType.Blue &&
|
|
AlphaChannel == STChannelType.Alpha)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
public static byte[] ConvertBgraToRgba(byte[] bytes)
|
|
{
|
|
if (bytes == null)
|
|
throw new Exception("Data block returned null. Make sure the parameters and image properties are correct!");
|
|
|
|
for (int i = 0; i < bytes.Length; i += 4)
|
|
{
|
|
var temp = bytes[i];
|
|
bytes[i] = bytes[i + 2];
|
|
bytes[i + 2] = temp;
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
|
|
private static byte[] ConvertBgraToRgba(byte[] bytes, string Format, int bpp, int width, int height, byte[] compSel)
|
|
{
|
|
if (bytes == null)
|
|
throw new Exception("Data block returned null. Make sure the parameters and image properties are correct!");
|
|
|
|
int size = width * height * 4;
|
|
byte[] NewImageData = new byte[size];
|
|
|
|
byte[] comp = new byte[6] { 0, 0xFF, 0, 0, 0, 0xFF };
|
|
|
|
for (int y = 0; y < height; y += 1)
|
|
{
|
|
for (int x = 0; x < width; x += 1)
|
|
{
|
|
var pos = (y * width + x) * bpp;
|
|
var pos_ = (y * width + x) * 4;
|
|
|
|
int pixel = 0;
|
|
for (int i = 0; i < bpp; i += 1)
|
|
pixel |= bytes[pos + i] << (8 * i);
|
|
|
|
comp = GetComponentsFromPixel(Format, pixel, comp);
|
|
|
|
NewImageData[pos_ + 3] = comp[compSel[3]];
|
|
NewImageData[pos_ + 2] = comp[compSel[2]];
|
|
NewImageData[pos_ + 1] = comp[compSel[1]];
|
|
NewImageData[pos_ + 0] = comp[compSel[0]];
|
|
}
|
|
}
|
|
return NewImageData;
|
|
}
|
|
|
|
private static byte[] GetComponentsFromPixel(string Format, int pixel, byte[] comp)
|
|
{
|
|
switch (Format)
|
|
{
|
|
case "RGBX8":
|
|
comp[2] = (byte)(pixel & 0xFF);
|
|
comp[3] = (byte)((pixel & 0xFF00) >> 8);
|
|
comp[4] = (byte)((pixel & 0xFF0000) >> 16);
|
|
comp[5] = (byte)((pixel & 0xFF000000) >> 24);
|
|
break;
|
|
case "RGBA8":
|
|
comp[2] = (byte)(pixel & 0xFF);
|
|
comp[3] = (byte)((pixel & 0xFF00) >> 8);
|
|
comp[4] = (byte)((pixel & 0xFF0000) >> 16);
|
|
comp[5] = (byte)((pixel & 0xFF000000) >> 24);
|
|
break;
|
|
case "RGBA4":
|
|
comp[2] = (byte)((pixel & 0xF) * 17);
|
|
comp[3] = (byte)(((pixel & 0xF0) >> 4) * 17);
|
|
comp[4] = (byte)(((pixel & 0xF00) >> 8) * 17);
|
|
comp[5] = (byte)(((pixel & 0xF000) >> 12) * 17);
|
|
break;
|
|
case "RGBA5":
|
|
comp[2] = (byte)(((pixel & 0xF800) >> 11) / 0x1F * 0xFF);
|
|
comp[3] = (byte)(((pixel & 0x7E0) >> 5) / 0x3F * 0xFF);
|
|
comp[4] = (byte)((pixel & 0x1F) / 0x1F * 0xFF);
|
|
break;
|
|
}
|
|
|
|
return comp;
|
|
}
|
|
|
|
public Properties GenericProperties
|
|
{
|
|
get
|
|
{
|
|
Properties prop = new Properties();
|
|
prop.Height = Height;
|
|
prop.Width = Width;
|
|
prop.Format = Format;
|
|
prop.Depth = Depth;
|
|
prop.MipCount = MipCount;
|
|
prop.ArrayCount = ArrayCount;
|
|
prop.ImageSize = (uint)GetImageData().Length;
|
|
|
|
return prop;
|
|
}
|
|
}
|
|
|
|
public class Properties
|
|
{
|
|
[Browsable(true)]
|
|
[ReadOnly(true)]
|
|
[Description("Height of the image.")]
|
|
[Category("Image Info")]
|
|
public uint Height { get; set; }
|
|
|
|
[Browsable(true)]
|
|
[ReadOnly(true)]
|
|
[Description("Width of the image.")]
|
|
[Category("Image Info")]
|
|
public uint Width { get; set; }
|
|
|
|
[Browsable(true)]
|
|
[ReadOnly(true)]
|
|
[Description("Depth of the image (3D type).")]
|
|
[Category("Image Info")]
|
|
public uint Depth { get; set; }
|
|
|
|
[Browsable(true)]
|
|
[ReadOnly(true)]
|
|
[Description("Format of the image.")]
|
|
[Category("Image Info")]
|
|
public TEX_FORMAT Format { get; set; }
|
|
|
|
[Browsable(true)]
|
|
[ReadOnly(true)]
|
|
[Description("Mip map count of the image.")]
|
|
[Category("Image Info")]
|
|
public uint MipCount { get; set; }
|
|
|
|
[Browsable(true)]
|
|
[ReadOnly(true)]
|
|
[Description("Array count of the image for multiple surfaces.")]
|
|
[Category("Image Info")]
|
|
public uint ArrayCount { get; set; }
|
|
|
|
[Browsable(true)]
|
|
[ReadOnly(true)]
|
|
[Description("The image size in bytes.")]
|
|
[Category("Image Info")]
|
|
public uint ImageSize { get; set; }
|
|
|
|
[Browsable(true)]
|
|
[ReadOnly(true)]
|
|
[Description("The swizzle value.")]
|
|
[Category("Image Info")]
|
|
public uint Swizzle { get; set; }
|
|
}
|
|
}
|
|
}
|