public void KtxHeaderGenerationValidation() { // Arrange KtxHeader header = new KtxHeader(GlDataType.Compressed, GlPixelFormat.GL_RGBA, GlInternalFormat.GL_COMPRESSED_RGBA_ASTC_10x10_KHR, 256, 256, 1, new System.Collections.Generic.Dictionary <string, MetadataValue>()); // Act MemoryStream ms1 = new MemoryStream(); header.WriteTo(ms1); MemoryStream ms2 = new MemoryStream(ms1.ToArray()); (bool valid, string possibleError) = KtxValidators.ValidateHeaderData(ms2); // Assert Assert.IsTrue(valid); Assert.IsTrue(string.IsNullOrEmpty(possibleError)); }
private KtxHeader CreateKtxHeader(int format, Size size) { var glFormat = (GlInternalFormat)format; switch (glFormat) { case GlInternalFormat.GlCompressedRgb8PunchthroughAlpha1Etc2: case GlInternalFormat.GlCompressedRgba8Etc2Eac: return(KtxHeader.InitializeCompressed(size.Width, size.Height, glFormat, GlFormat.GlRgba)); case GlInternalFormat.GlCompressedRgb8Etc2: return(KtxHeader.InitializeCompressed(size.Width, size.Height, glFormat, GlFormat.GlRgb)); default: throw new InvalidOperationException($"{glFormat} is not supported for saving."); } }
//----------------------------------------------------------------------------------------- static public void Load(string filename, ref int tex) { BinaryReader file = null; try { file = new BinaryReader(File.Open(filename, FileMode.Open)); KtxHeader hdr; KtxHeader.FromBytes(file.ReadBytes(TypeUtils.SizeOf <KtxHeader>()), 0, out hdr); if (!hdr.identifier.SequenceEqual(_identifier)) { throw new KTXException("Invalid KTX file header."); } if (hdr.endianness == 0x04030201) /* Little-Endian, No swap needed */ } {
//----------------------------------------------------------------------------------------- static private uint _CalculateStride(ref KtxHeader hdr, int width, uint pad = 4) { uint channels = 0; switch (hdr.glbaseinternalformat) { case (int)OpenTK.Graphics.OpenGL.All.Red: channels = 1; break; case (int)OpenTK.Graphics.OpenGL.All.Rg: channels = 2; break; case (int)OpenTK.Graphics.OpenGL.All.Bgr: case (int)OpenTK.Graphics.OpenGL.All.Rgb: channels = 3; break; case (int)OpenTK.Graphics.OpenGL.All.Bgra: case (int)OpenTK.Graphics.OpenGL.All.Rgba: channels = 4; break; } uint stride = hdr.gltypesize * channels * (uint)width; stride = (stride + (pad - 1)) & ~(pad - 1); return(stride); }
/// <summary> /// Encodes all cubemap faces and mipmap levels into a Ktx file. /// Order is +X, -X, +Y, -Y, +Z, -Z. Back maps to positive Z and front to negative Z. /// </summary> public KtxFile EncodeCubeMapToKtx(Image <Rgba32> right, Image <Rgba32> left, Image <Rgba32> top, Image <Rgba32> down, Image <Rgba32> back, Image <Rgba32> front) { KtxFile output; IBcBlockEncoder compressedEncoder = null; IRawEncoder uncompressedEncoder = null; if (right.Width != left.Width || right.Width != top.Width || right.Width != down.Width || right.Width != back.Width || right.Width != front.Width || right.Height != left.Height || right.Height != top.Height || right.Height != down.Height || right.Height != back.Height || right.Height != front.Height) { throw new ArgumentException("All input images of a cubemap should be the same size."); } Image <Rgba32>[] faces = new[] { right, left, top, down, back, front }; if (OutputOptions.format.IsCompressedFormat()) { compressedEncoder = GetEncoder(OutputOptions.format); if (compressedEncoder == null) { throw new NotSupportedException($"This format is not supported: {OutputOptions.format}"); } output = new KtxFile( KtxHeader.InitializeCompressed(right.Width, right.Height, compressedEncoder.GetInternalFormat(), compressedEncoder.GetBaseInternalFormat())); } else { uncompressedEncoder = GetRawEncoder(OutputOptions.format); output = new KtxFile( KtxHeader.InitializeUncompressed(right.Width, right.Height, uncompressedEncoder.GetGlType(), uncompressedEncoder.GetGlFormat(), uncompressedEncoder.GetGlTypeSize(), uncompressedEncoder.GetInternalFormat(), uncompressedEncoder.GetBaseInternalFormat())); } uint numMipMaps = (uint)OutputOptions.maxMipMapLevel; if (!OutputOptions.generateMipMaps) { numMipMaps = 1; } uint mipLength = MipMapper.CalculateMipChainLength(right.Width, right.Height, numMipMaps); for (uint i = 0; i < mipLength; i++) { output.MipMaps.Add(new KtxMipmap(0, 0, 0, (uint)faces.Length)); } for (int f = 0; f < faces.Length; f++) { var mipChain = MipMapper.GenerateMipChain(faces[f], ref numMipMaps); for (int i = 0; i < numMipMaps; i++) { byte[] encoded = null; if (OutputOptions.format.IsCompressedFormat()) { var blocks = ImageToBlocks.ImageTo4X4(mipChain[i].Frames[0], out int blocksWidth, out int blocksHeight); encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.quality, !Debugger.IsAttached && Options.multiThreaded); } else { encoded = uncompressedEncoder.Encode(mipChain[i].GetPixelSpan()); } if (f == 0) { output.MipMaps[i] = new KtxMipmap((uint)encoded.Length, (uint)mipChain[i].Width, (uint)mipChain[i].Height, (uint)faces.Length); } output.MipMaps[i].Faces[f] = new KtxMipFace(encoded, (uint)mipChain[i].Width, (uint)mipChain[i].Height); } foreach (var image in mipChain) { image.Dispose(); } } output.Header.NumberOfFaces = (uint)faces.Length; output.Header.NumberOfMipmapLevels = mipLength; return(output); }
/// <summary> /// Encodes all mipmap levels into a Ktx file. /// </summary> public KtxFile EncodeToKtx(Image <Rgba32> inputImage) { KtxFile output; IBcBlockEncoder compressedEncoder = null; IRawEncoder uncompressedEncoder = null; if (OutputOptions.format.IsCompressedFormat()) { compressedEncoder = GetEncoder(OutputOptions.format); if (compressedEncoder == null) { throw new NotSupportedException($"This format is not supported: {OutputOptions.format}"); } output = new KtxFile( KtxHeader.InitializeCompressed(inputImage.Width, inputImage.Height, compressedEncoder.GetInternalFormat(), compressedEncoder.GetBaseInternalFormat())); } else { uncompressedEncoder = GetRawEncoder(OutputOptions.format); output = new KtxFile( KtxHeader.InitializeUncompressed(inputImage.Width, inputImage.Height, uncompressedEncoder.GetGlType(), uncompressedEncoder.GetGlFormat(), uncompressedEncoder.GetGlTypeSize(), uncompressedEncoder.GetInternalFormat(), uncompressedEncoder.GetBaseInternalFormat())); } uint numMipMaps = (uint)OutputOptions.maxMipMapLevel; if (!OutputOptions.generateMipMaps) { numMipMaps = 1; } var mipChain = MipMapper.GenerateMipChain(inputImage, ref numMipMaps); for (int i = 0; i < numMipMaps; i++) { byte[] encoded = null; if (OutputOptions.format.IsCompressedFormat()) { var blocks = ImageToBlocks.ImageTo4X4(mipChain[i].Frames[0], out int blocksWidth, out int blocksHeight); encoded = compressedEncoder.Encode(blocks, blocksWidth, blocksHeight, OutputOptions.quality, !Debugger.IsAttached && Options.multiThreaded); } else { encoded = uncompressedEncoder.Encode(mipChain[i].GetPixelSpan()); } output.MipMaps.Add(new KtxMipmap((uint)encoded.Length, (uint)inputImage.Width, (uint)inputImage.Height, 1)); output.MipMaps[i].Faces[0] = new KtxMipFace(encoded, (uint)inputImage.Width, (uint)inputImage.Height); } foreach (var image in mipChain) { image.Dispose(); } output.Header.NumberOfFaces = 1; output.Header.NumberOfMipmapLevels = numMipMaps; return(output); }
public KTXDecoder(string fileName, bool readKeyValuePairs = false) { using FileStream fs = File.OpenRead(fileName); using BinaryReader br = new BinaryReader(fs); byte[] identifier = br.ReadBytes(12); if (!identifier.SequenceEqual(KtxIdentifier)) { throw new InvalidOperationException("File is not in Khronos Texture format."); } KtxHeader header = new KtxHeader { Identifier = KtxIdentifier, Endianness = br.ReadUInt32(), GlType = br.ReadUInt32(), GlTypeSize = br.ReadUInt32(), GlFormat = br.ReadUInt32(), GlInternalFormat = br.ReadUInt32(), GlBaseInternalFormat = br.ReadUInt32(), PixelWidth = Math.Max(1, br.ReadUInt32()), PixelHeight = Math.Max(1, br.ReadUInt32()), PixelDepth = Math.Max(1, br.ReadUInt32()), NumberOfArrayElements = br.ReadUInt32(), //only for array text, else 0 NumberOfFaces = br.ReadUInt32(), //only for cube map, else 1 NumberOfMipmapLevels = Math.Max(1, br.ReadUInt32()), BytesOfKeyValueData = br.ReadUInt32(), }; KtxKeyValuePair[] kvps = default; if (readKeyValuePairs) { int keyValuePairBytesRead = 0; List <KtxKeyValuePair> keyValuePairs = new List <KtxKeyValuePair>(); while (keyValuePairBytesRead < header.BytesOfKeyValueData) { int bytesRemaining = (int)(header.BytesOfKeyValueData - keyValuePairBytesRead); KtxKeyValuePair kvp = ReadNextKeyValuePair(br, out int read); keyValuePairBytesRead += read; keyValuePairs.Add(kvp); } kvps = keyValuePairs.ToArray(); } else { br.BaseStream.Seek(header.BytesOfKeyValueData, SeekOrigin.Current); // Skip over header data. } uint numberOfFaces = Math.Max(1, header.NumberOfFaces); List <KtxFace> faces = new List <KtxFace>((int)numberOfFaces); for (int i = 0; i < numberOfFaces; i++) { faces.Add(new KtxFace(header.NumberOfMipmapLevels)); } for (uint mipLevel = 0; mipLevel < header.NumberOfMipmapLevels; mipLevel++) { uint imageSize = br.ReadUInt32(); // For cubemap textures, imageSize is actually the size of an individual face. bool isCubemap = header.NumberOfFaces == 6 && header.NumberOfArrayElements == 0; for (uint face = 0; face < numberOfFaces; face++) { byte[] faceData = br.ReadBytes((int)imageSize); faces[(int)face].Mipmaps[mipLevel] = new KtxMipmap(imageSize, faceData, header.PixelWidth / (uint)(Math.Pow(2, mipLevel)), header.PixelHeight / (uint)(Math.Pow(2, mipLevel))); uint cubePadding = 0u; if (isCubemap) { cubePadding = 3 - ((imageSize + 3) % 4); } br.BaseStream.Seek(cubePadding, SeekOrigin.Current); } uint mipPaddingBytes = 3 - ((imageSize + 3) % 4); br.BaseStream.Seek(mipPaddingBytes, SeekOrigin.Current); } Header = header; KeyValuePairs = kvps; Faces = faces.ToArray(); ImageDescription data = new() { Width = (int)Header.PixelWidth, Height = (int)Header.PixelHeight, Depth = (int)Header.PixelDepth, Format = FormatExtensions.GetFormatFromOpenGLFormat(Header.GlInternalFormat), Size = (int)GetTotalSize(), IsCubeMap = Header.NumberOfFaces is 6, MipLevels = (int)Header.NumberOfMipmapLevels, Data = GetAllTextureData(), }; ImageDescription = data; }
public KtxStructure(KtxHeader ktxHeader, KtxTextureData texData) { this.header = ktxHeader; this.textureData = texData; }
//----------------------------------------------------------------------------------------- static private uint _CalculateFaceSize(ref KtxHeader hdr) { return(_CalculateStride(ref hdr, hdr.pixelwidth) * (uint)hdr.pixelheight); }