/// <summary> /// Decodes the specified level of the encoded texture to an array of RGBA8 pixels. /// </summary> /// <param name="level">The level of the texture to decode.</param> /// <param name="desiredStride">Desired stride (number of bytes per scanline) of the result.</param> /// <returns>An array with the RGBA8 data of the level (with no extra row padding).</returns> public byte[] DecodeLevelToRGBA8(int level, int desiredStride) { if (level < 0 || level >= LevelCount) { throw new ArgumentOutOfRangeException("level"); } if (desiredStride < 0) { throw new ArgumentOutOfRangeException("desiredStride"); } int levelWidth = WidthOfLevel(level), levelHeight = HeightOfLevel(level); if (desiredStride < levelWidth * 4) { throw new ArgumentOutOfRangeException("desiredStride", "Stride is too small to contain a row of data."); } // Decode texture as RGBA8 (GxTextureDecode format) byte[] decodedData = new byte[levelHeight * desiredStride]; GxTextureFormatCodec.GetCodec(format).DecodeTexture(decodedData, 0, levelWidth, levelHeight, desiredStride, encodedLevelData[level], 0, null, 0); return(decodedData); }
public Texture2D ReadTexture(BinaryReader reader, TEXDescriptor desc, string saveFilePath, string textureName) { // Verify if index is valid (some entries can be nulled out) // We check for 0 specifically because garbage can be store in the // first few entries of the file. // TPL can have null entries so this is actually correct to the format if (desc.isNullEntry != 0) { return(null); } // We use Try as GxTextureFormatCodec.GetCodec() can return an error if the type is invalid try { GxTextureFormatCodec codec = GxTextureFormatCodec.GetCodec((GxTextureFormat)desc.format); reader.BaseStream.Position = desc.dataPtr; byte[] texRaw = reader.GetBytes(codec.CalcTextureSize(desc.width, desc.height)); byte[] texRGBA = new byte[4 * desc.width * desc.height]; // RGBA (4 bytes) * w * h codec.DecodeTexture(texRGBA, 0, desc.width, desc.height, desc.width * 4, texRaw, 0, null, 0); // Reconstruct Texture using Unity's format Texture2D texture = new Texture2D(desc.width, desc.height); for (int y = 0; y < desc.height; y++) { for (int x = 0; x < desc.width; x++) { // Invert Y because LibGXTexture returns array upside-down? // ei 'x, (desc.width - y)' instead of 'x, y' texture.SetPixel(x, (desc.width - y), new Color32( texRGBA[(y * desc.width + x) * 4 + 0], texRGBA[(y * desc.width + x) * 4 + 1], texRGBA[(y * desc.width + x) * 4 + 2], texRGBA[(y * desc.width + x) * 4 + 3])); } } string assetPath = string.Format("{0}/tex_{1}.png", saveFilePath, textureName).PathToUnityPath(); byte[] imageBytes = texture.EncodeToPNG(); DestroyImmediate(texture); using (BinaryWriter writer = new BinaryWriter(File.Create(assetPath, imageBytes.Length))) { writer.Write(imageBytes); } AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); AssetDatabase.Refresh(ImportAssetOptions.Default); Texture2D tex = (Texture2D)AssetDatabase.LoadAssetAtPath(assetPath, typeof(Texture2D)); return(tex); } catch { Debug.LogErrorFormat("GxTextureFormatCodec.GetCodec() failed to find format [0x{0}]", desc.format.ToString("X2")); return(null); } }
private void updateTextureCount() { int.TryParse(texX.Text, out int x); int.TryParse(texY.Text, out int y); int texSize = x * y * (GxTextureFormatCodec.GetCodec(currentFormat).BitsPerPixel / 8); if (texSize != 0) { int finalCount = fileSize / texSize; texCount.Text = finalCount.ToString(); } }
/// <summary> /// Create or replace the specified texture level from the specified image data. /// New texture levels must be created in order. /// </summary> /// <param name="level">The level of the texture to create or replace.</param> /// <param name="newImageDataStride">The stride (number of bytes per row) of the new image data.</param> /// <param name="newImageData">The new image data for the level.</param> public void DefineLevelData(int level, int newImageDataStride, byte[] newImageData) { if (level > LevelCount) // We allow to either replace an existing level or to generate the next level { throw new ArgumentOutOfRangeException("level"); } if (newImageDataStride < 0) { throw new ArgumentOutOfRangeException("newImageDataStride"); } if (newImageData == null) { throw new ArgumentNullException("newImageData"); } // Check that this texture level can be defined (size is not too small) // This checks that width and height can be divided evenly by 2^level if ((width & ((1 << level) - 1)) != 0 || (height & ((1 << level) - 1)) != 0) { throw new ArgumentOutOfRangeException("level", "Level is too low for the image dimensions."); } int levelWidth = width >> level; int levelHeight = height >> level; if (newImageDataStride < levelWidth * 4) { throw new ArgumentOutOfRangeException("newImageDataStride", "Stride is too small to contain a row of data."); } // Adding a new mipmap? if (level == LevelCount) { encodedLevelData.Add(new byte[CalculateSizeOfLevel(level)]); } // Encode GxTextureFormatCodec.GetCodec(format).EncodeTexture( newImageData, 0, levelWidth, levelHeight, newImageDataStride, encodedLevelData[level], 0, null, 0); }
/// <summary> /// Calculates the size of a level of the texture. /// </summary> /// <param name="level">The level of the texture.</param> /// <param name="replicateCmprBug">true to replicate the F-Zero GX CMPR encoding bug.</param> /// <returns>The size of the encoded data in the specified level.</returns> private int CalculateSizeOfLevel(int level, bool replicateCmprBug = false) { // Here we allow also to specify the "next" level for easier implementation // of the methods that encode the new texture if (level < 0 || level > LevelCount) { throw new ArgumentOutOfRangeException("level"); } int levelWidth = width >> level, levelHeight = height >> level; // Hack: CMPR sizes are not calculated correctly, replicate the bug if (replicateCmprBug && format == GxTextureFormat.CMPR) { int w = PaddingUtils.Align(levelWidth, 4); // Align to 4 (should really be 8) int h = PaddingUtils.Align(levelHeight, 4); // Align to 4 (should really be 8) int sz = (w * h * 4) / 8; // CMPR is 4 bits per pixel int szpad = PaddingUtils.Align(sz, 32); // Align to 32 (this should normally not be needed) return(szpad); } return(GxTextureFormatCodec.GetCodec(format).CalcTextureSize(levelWidth, levelHeight)); }
private void Load(EndianBinaryReader input, GxGame game, GeneratedTextureHeader?newHeader) { if (game == GxGame.SuperMonkeyBallDX) { input.ReadInt32(); } // If there is a header, use the value from that header, otherwise use the generated header value int numTextures = (newHeader == null) ? (numTextures = input.ReadInt32()) : (numTextures = newHeader.Value.textureCount); // Load texture definition headers TextureHeader[] texHdr = new TextureHeader[numTextures]; if (newHeader == null) { for (int i = 0; i < numTextures; i++) { texHdr[i].FormatRaw = input.ReadInt32(); texHdr[i].Offset = input.ReadInt32(); texHdr[i].Width = Convert.ToInt32(input.ReadUInt16()); texHdr[i].Height = Convert.ToInt32(input.ReadUInt16()); texHdr[i].LevelCount = Convert.ToInt32(input.ReadUInt16()); UInt16 check = input.ReadUInt16(); if ((game != GxGame.SuperMonkeyBallDX && check != 0x1234) || (game == GxGame.SuperMonkeyBallDX && check != 0x3412)) { throw new InvalidTplFileException("Invalid texture header (Field @0x0E)."); } } } // Creates a new texture header from provided texture header characteristics else { // The length of the header after this operation's completion int initialOffset = newHeader.Value.textureCount * 16; // Size of a texture based on how many bytes per pixel int texSize = newHeader.Value.textureHeight * newHeader.Value.textureWidth * GxTextureFormatCodec.GetCodec(newHeader.Value.textureFormat).BitsPerPixel / 8; // Maximum possible offset for the TPL int maxOffset = initialOffset + (texSize) * numTextures; // Iteration variable for referencing the index of the texture header int iteration; for (int iterateOffset = initialOffset; iterateOffset < maxOffset; iterateOffset += texSize) { iteration = (iterateOffset - initialOffset) / texSize; texHdr[iteration].FormatRaw = (int)newHeader.Value.textureFormat; texHdr[iteration].Width = newHeader.Value.textureWidth; texHdr[iteration].Height = newHeader.Value.textureHeight; texHdr[iteration].Offset = iterateOffset; texHdr[iteration].LevelCount = newHeader.Value.textureMipmapCount; } } // Load textures data for (int i = 0; i < numTextures; i++) { TplTexture tex = new TplTexture(); if (texHdr[i].Offset != 0 && texHdr[i].Width != 0 && texHdr[i].Height != 0 && texHdr[i].LevelCount != 0) // Texture with defined levels { if (game != GxGame.SuperMonkeyBallDX && !Enum.IsDefined(typeof(GxTextureFormat), texHdr[i].FormatRaw)) { throw new InvalidTplFileException("Invalid texture header (invalid format."); } if (game == GxGame.SuperMonkeyBallDX) { input.BaseStream.Position = texHdr[i].Offset; int formatRaw = input.ReadInt32(); switch (formatRaw) { case 0x0C: texHdr[i].FormatRaw = 0x0E; break; case 0x1A: texHdr[i].FormatRaw = 0x01; break; case 0x0E: texHdr[i].FormatRaw = 0x05; break; default: int x = 5; break; } texHdr[i].Width = input.ReadInt32(); texHdr[i].Height = input.ReadInt32(); texHdr[i].LevelCount = input.ReadInt32(); // Compressed? input.ReadInt32(); // If uncompressed, data length? input.ReadInt32(); // Some other length (for compressed)? input.ReadInt32(); // Zero? input.ReadInt32(); if (!Enum.IsDefined(typeof(GxTextureFormat), texHdr[i].FormatRaw)) { throw new InvalidTplFileException("Invalid texture header (invalid format."); } } else { if (newHeader == null) { input.BaseStream.Position = texHdr[i].Offset; } else { input.BaseStream.Position = texHdr[i].Offset - (newHeader.Value.textureCount * 16); } } tex.LoadTextureData(input, game, (GxTextureFormat)texHdr[i].FormatRaw, texHdr[i].Width, texHdr[i].Height, texHdr[i].LevelCount); } else if (texHdr[i].Offset == 0 && texHdr[i].Width == 0 && texHdr[i].Height == 0 && texHdr[i].LevelCount == 0) // Texture with no defined levels { tex.DefineEmptyTexture(texHdr[i].FormatRaw); } else { throw new InvalidTplFileException("Invalid texture header (invalid combination of fields)."); } Add(tex); } }
public bool ReadTextureFromTPL(BinaryReader reader, int index, out Texture2D texture, string name, bool saveToDisk) { // If index is invalid if (index > numDescriptors) { throw new System.IndexOutOfRangeException(string.Format("Index must be less than or equal to {0}!", numDescriptors)); } else if (index < 0) { throw new System.IndexOutOfRangeException("Index must be greater than 0!"); } // Load Texture Descriptor at index TEXDescriptor desc = descriptorArray[index]; // Verify if index is valid (some entries can be nulled out) // We check for 0 specifically because garbage can be store in the // first few entries of the file. if (desc.isNullEntry != 0) { texture = null; return(false); } // We use Try as GxTextureFormatCodec.GetCodec() can return an error if the type is invalid try { GxTextureFormatCodec codec = GxTextureFormatCodec.GetCodec((GxTextureFormat)desc.format); reader.BaseStream.Position = desc.dataPtr; byte[] texRaw = reader.GetBytes(codec.CalcTextureSize(desc.width, desc.height)); byte[] texRGBA = new byte[4 * desc.width * desc.height]; // RGBA (4 bytes) * w * h codec.DecodeTexture(texRGBA, 0, desc.width, desc.height, desc.width * 4, texRaw, 0, null, 0); // Reconstruct Texture using Unity's format texture = new Texture2D(desc.width, desc.height); for (int y = 0; y < desc.height; y++) { for (int x = 0; x < desc.width; x++) { // Invert Y because LibGXTexture return array upside-down // ei 'x, (desc.width - y)' instead of 'x, y' texture.SetPixel(x, (desc.width - y), new Color32( texRGBA[(y * desc.width + x) * 4 + 0], texRGBA[(y * desc.width + x) * 4 + 1], texRGBA[(y * desc.width + x) * 4 + 2], texRGBA[(y * desc.width + x) * 4 + 3])); } } if (saveToDisk) { Debug.LogFormat("Saved {0} to path {1}", name, PersistantDataPath("")); SaveBytes(string.Format("{0}.png", name), texture.EncodeToPNG()); } return(true); } catch { Debug.LogErrorFormat("GxTextureFormatCodec.GetCodec() failed to find format [{0}]", desc.format.ToString("X")); texture = null; return(false); } }