/// <summary> /// Create new 2d KtxStructure from existing data /// </summary> /// <param name="glDataType">GlDataType</param> /// <param name="glPixelFormat">GlPixelFormat</param> /// <param name="glInternalFormat">GlInternalFormat</param> /// <param name="width">Width</param> /// <param name="height">Height</param> /// <param name="textureDatas">Texture datas</param> /// <param name="metadata">metadata</param> /// <returns>KtxStructure</returns> public static KtxStructure Create(GlDataType glDataType, GlPixelFormat glPixelFormat, GlInternalFormat glInternalFormat, uint width, uint height, List <byte[]> textureDatas, Dictionary <string, MetadataValue> metadata) { KtxHeader header = new KtxHeader(glDataType, glPixelFormat, glInternalFormat, width, height, (uint)textureDatas.Count, metadata); KtxTextureData textureData = new KtxTextureData(textureDatas); return(new KtxStructure(header, textureData)); }
public static (bool isValid, string possibleError) CheckIfInputIsValid(MemoryStream memoryStream) { // Currently only header and metadata are validated properly, so texture data can still contain invalid values (bool isStreamValid, string possibleStreamError) = KtxValidators.GenericStreamValidation(memoryStream); if (!isStreamValid) { return(isValid : false, possibleError : possibleStreamError); } // We have to duplicate the data, since we have to both validate it and keep it for texture data validation step long memoryStreamPos = memoryStream.Position; (bool isHeaderValid, string possibleHeaderError) = KtxValidators.ValidateHeaderData(memoryStream); if (!isHeaderValid) { return(isValid : false, possibleError : possibleHeaderError); } memoryStream.Position = memoryStreamPos; KtxHeader tempHeader = new KtxHeader(memoryStream); (bool isTextureDataValid, string possibleTextureDataError) = KtxValidators.ValidateTextureData(memoryStream, tempHeader, (uint)(memoryStream.Length - memoryStream.Position)); return(isValid : isTextureDataValid, possibleError : possibleTextureDataError); }
public static KtxStructure LoadInput(MemoryStream memoryStream) { // First we read the header KtxHeader header = new KtxHeader(memoryStream); // Then texture data KtxTextureData textureData = new KtxTextureData(header, memoryStream); // And combine those to one structure return(new KtxStructure(header, textureData)); }
private static void GenericWrite(KtxHeader header, Action <uint> writeUint, Action <byte> writeByte, Action <byte[]> writeByteArray) { writeUint(Common.expectedEndianValue); writeUint(header.glTypeAsUint); writeUint(header.glTypeSizeAsUint); writeUint(header.glFormatAsUint); writeUint(header.glInternalFormatAsUint); writeUint(header.glBaseInternalFormatAsUint); writeUint(header.pixelWidth); writeUint(header.pixelHeight); writeUint(header.pixelDepth); writeUint(header.numberOfArrayElements); writeUint(header.numberOfFaces); writeUint(header.numberOfMipmapLevels); writeUint(GetTotalSizeOfMetadata(header.metadataDictionary)); foreach (var pair in header.metadataDictionary) { uint keyLenght = Common.GetLengthOfUtf8StringAsBytes(pair.Key) + 1; uint valueLength = pair.Value.GetSizeInBytes(); uint totalLength = keyLenght + valueLength; writeUint(totalLength); writeByteArray(Common.GetUtf8StringAsBytes(pair.Key)); writeByte(Common.nulByte); writeByteArray(pair.Value.GetAsBytes()); if (pair.Value.isString) { writeByte(Common.nulByte); } // Write padding if needed while (totalLength % 4 != 0) { writeByte(Common.nulByte); totalLength++; } } }
/// <summary> /// Constructor for texture data /// </summary> /// <param name="header">Header</param> /// <param name="stream">Stream for reading</param> public KtxTextureData(KtxHeader header, Stream stream) { //this.totalTextureDataLength = (uint)stream.Length; // Try to figure out texture type basic bool containsMipmaps = header.numberOfMipmapLevels > 1; if (header.numberOfArrayElements == 0 || header.numberOfArrayElements == 1) { // Is NOT texture array if (header.numberOfFaces == 0 || header.numberOfFaces == 1) { // Is NOT cube texture if (header.pixelDepth == 0 || header.pixelDepth == 1) { // Is not 3D texture if (header.pixelHeight == 0 || header.pixelHeight == 1) { // 1D texture this.textureType = containsMipmaps ? TextureTypeBasic.Basic1DWithMipmaps : TextureTypeBasic.Basic1DNoMipmaps; } else { // 2D texture this.textureType = containsMipmaps ? TextureTypeBasic.Basic2DWithMipmaps : TextureTypeBasic.Basic2DNoMipmaps; } } else { // Is 3D texture this.textureType = containsMipmaps ? TextureTypeBasic.Basic3DWithMipmaps : TextureTypeBasic.Basic3DNoMipmaps; } } else { // Is cube texture } } else { // Is Texture array } uint mipmapLevels = header.numberOfMipmapLevels; if (mipmapLevels == 0) { mipmapLevels = 1; } // Since we know how many mipmap levels there are, allocate the capacity this.textureDataOfMipmapLevel = new List <byte[]>((int)mipmapLevels); // Check if length reads should be endian swapped bool shouldSwapEndianness = (header.endiannessValue != Common.expectedEndianValue); using (BinaryReader reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true)) { for (int i = 0; i < mipmapLevels; i++) { uint amountOfDataInThisMipmapLevel = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); this.textureDataOfMipmapLevel.Add(reader.ReadBytes((int)amountOfDataInThisMipmapLevel)); // Skip possible padding bytes while (amountOfDataInThisMipmapLevel % 4 != 0) { amountOfDataInThisMipmapLevel++; // Read but ignore values reader.ReadByte(); } } } }
public static (bool isValid, string possibleError) ValidateTextureData(MemoryStream memoryStream, KtxHeader header, uint expectedTextureDataSize) { // Use the memory stream in a binary reader. try { using (BinaryReader reader = new BinaryReader(memoryStream, Encoding.UTF8, leaveOpen: true)) { // Specs say that if value of certain things is zero (0) then it should be used as one (1) uint mipmapLevels = (header.numberOfMipmapLevels == 0) ? 1 : header.numberOfMipmapLevels; uint numberOfArrayElements = (header.numberOfArrayElements == 0) ? 1 : header.numberOfArrayElements; uint pixelDepth = (header.pixelDepth == 0) ? 1 : header.pixelDepth; uint pixelHeight = (header.pixelHeight == 0) ? 1 : header.pixelHeight; uint totalLengthOfTextureDataSection = 0; // Check if length reads should be endian swapped bool shouldSwapEndianness = (header.endiannessValue != Common.expectedEndianValue); // Check each mipmap level separately for (uint u = 0; u < mipmapLevels; u++) { uint imageSize = shouldSwapEndianness ? KtxBitFiddling.SwapEndian(reader.ReadUInt32()) : reader.ReadUInt32(); totalLengthOfTextureDataSection += (imageSize + (uint)Common.sizeOfUint); if (imageSize > expectedTextureDataSize || totalLengthOfTextureDataSection > expectedTextureDataSize) { return(isValid : false, "Texture data: More data than expected!"); } // TODO: More checks! // Read but do not use data for anything reader.ReadBytes((int)imageSize); // Skip possible padding bytes while (imageSize % 4 != 0) { imageSize++; // Read but ignore values reader.ReadByte(); } } } } catch (Exception e) { return(isValid : false, e.ToString()); } return(isValid : true, possibleError : ""); }