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));
        }
Example #2
0
        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.");
            }
        }
Example #3
0
        //-----------------------------------------------------------------------------------------
        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 */ } {
Example #4
0
        //-----------------------------------------------------------------------------------------
        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);
        }
Example #5
0
        /// <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);
        }
Example #6
0
        /// <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);
        }
Example #7
0
        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;
        }
Example #8
0
 public KtxStructure(KtxHeader ktxHeader, KtxTextureData texData)
 {
     this.header      = ktxHeader;
     this.textureData = texData;
 }
Example #9
0
 //-----------------------------------------------------------------------------------------
 static private uint _CalculateFaceSize(ref KtxHeader hdr)
 {
     return(_CalculateStride(ref hdr, hdr.pixelwidth) * (uint)hdr.pixelheight);
 }