/* Copyright (C) 2015, SiPlus, Chasseur de bots. All Rights Reserved. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "ktx.h" #include #include "bytestreamutils.h" #include "etclib.h" #include "ifilesystem.h" #include "imagelib.h" const int KTX_TYPE_UNSIGNED_BYTE = 0x1401; const int KTX_TYPE_UNSIGNED_SHORT_4_4_4_4 = 0x8033; const int KTX_TYPE_UNSIGNED_SHORT_5_5_5_1 = 0x8034; const int KTX_TYPE_UNSIGNED_SHORT_5_6_5 = 0x8363; const int KTX_FORMAT_ALPHA = 0x1906; const int KTX_FORMAT_RGB = 0x1907; const int KTX_FORMAT_RGBA = 0x1908; const int KTX_FORMAT_LUMINANCE = 0x1909; const int KTX_FORMAT_LUMINANCE_ALPHA = 0x190A; const int KTX_FORMAT_BGR = 0x80E0; const int KTX_FORMAT_BGRA = 0x80E1; const int KTX_FORMAT_ETC1_RGB8 = 0x8D64; class KTX_Decoder { public: virtual ~KTX_Decoder() = default; virtual void Decode( PointerInputStream& istream, byte* out ) = 0; virtual unsigned int GetPixelSize() = 0; }; class KTX_Decoder_A8 : public KTX_Decoder { public: virtual void Decode( PointerInputStream& istream, byte* out ){ out[0] = out[1] = out[2] = 0; out[3] = istream_read_byte( istream ); } virtual unsigned int GetPixelSize(){ return 1; } }; class KTX_Decoder_RGB8 : public KTX_Decoder { public: virtual void Decode( PointerInputStream& istream, byte* out ){ istream.read( out, 3 ); out[3] = 255; } virtual unsigned int GetPixelSize(){ return 3; } }; class KTX_Decoder_RGBA8 : public KTX_Decoder { public: virtual void Decode( PointerInputStream& istream, byte* out ){ istream.read( out, 4 ); } virtual unsigned int GetPixelSize(){ return 4; } }; class KTX_Decoder_L8 : public KTX_Decoder { public: virtual void Decode( PointerInputStream& istream, byte* out ){ byte l = istream_read_byte( istream ); out[0] = out[1] = out[2] = l; out[3] = 255; } virtual unsigned int GetPixelSize(){ return 1; } }; class KTX_Decoder_LA8 : public KTX_Decoder { public: virtual void Decode( PointerInputStream& istream, byte* out ){ byte la[2]; istream.read( la, 2 ); out[0] = out[1] = out[2] = la[0]; out[3] = la[1]; } virtual unsigned int GetPixelSize(){ return 2; } }; class KTX_Decoder_BGR8 : public KTX_Decoder { public: virtual void Decode( PointerInputStream& istream, byte* out ){ byte bgr[3]; istream.read( bgr, 3 ); out[0] = bgr[2]; out[1] = bgr[1]; out[2] = bgr[0]; out[3] = 255; } virtual unsigned int GetPixelSize(){ return 3; } }; class KTX_Decoder_BGRA8 : public KTX_Decoder { public: virtual void Decode( PointerInputStream& istream, byte* out ){ byte bgra[4]; istream.read( bgra, 4 ); out[0] = bgra[2]; out[1] = bgra[1]; out[2] = bgra[0]; out[3] = bgra[3]; } virtual unsigned int GetPixelSize(){ return 4; } }; class KTX_Decoder_RGBA4 : public KTX_Decoder { protected: bool m_bigEndian; public: KTX_Decoder_RGBA4( bool bigEndian ) : m_bigEndian( bigEndian ){} virtual void Decode( PointerInputStream& istream, byte* out ){ uint16_t rgba; if ( m_bigEndian ) { rgba = istream_read_uint16_be( istream ); } else { rgba = istream_read_uint16_le( istream ); } int r = ( rgba >> 12 ) & 0xf; int g = ( rgba >> 8 ) & 0xf; int b = ( rgba >> 4 ) & 0xf; int a = rgba & 0xf; out[0] = ( r << 4 ) | r; out[1] = ( g << 4 ) | g; out[2] = ( b << 4 ) | b; out[3] = ( a << 4 ) | a; } virtual unsigned int GetPixelSize(){ return 2; } }; class KTX_Decoder_RGBA5 : public KTX_Decoder { protected: bool m_bigEndian; public: KTX_Decoder_RGBA5( bool bigEndian ) : m_bigEndian( bigEndian ){} virtual void Decode( PointerInputStream& istream, byte* out ){ uint16_t rgba; if ( m_bigEndian ) { rgba = istream_read_uint16_be( istream ); } else { rgba = istream_read_uint16_le( istream ); } int r = ( rgba >> 11 ) & 0x1f; int g = ( rgba >> 6 ) & 0x1f; int b = ( rgba >> 1 ) & 0x1f; out[0] = ( r << 3 ) | ( r >> 2 ); out[1] = ( g << 3 ) | ( g >> 2 ); out[2] = ( b << 3 ) | ( b >> 2 ); out[3] = ( rgba & 1 ) * 255; } virtual unsigned int GetPixelSize(){ return 2; } }; class KTX_Decoder_RGB5 : public KTX_Decoder { protected: bool m_bigEndian; public: KTX_Decoder_RGB5( bool bigEndian ) : m_bigEndian( bigEndian ){} virtual void Decode( PointerInputStream& istream, byte* out ){ uint16_t rgb; if ( m_bigEndian ) { rgb = istream_read_uint16_be( istream ); } else { rgb = istream_read_uint16_le( istream ); } int r = ( rgb >> 11 ) & 0x1f; int g = ( rgb >> 5 ) & 0x3f; int b = rgb & 0x1f; out[0] = ( r << 3 ) | ( r >> 2 ); out[1] = ( g << 2 ) | ( g >> 4 ); out[2] = ( b << 3 ) | ( b >> 2 ); out[3] = 255; } virtual unsigned int GetPixelSize(){ return 2; } }; static void KTX_DecodeETC1( PointerInputStream& istream, Image& image ){ unsigned int width = image.getWidth(), height = image.getHeight(); unsigned int stride = width * 4; byte* pixbuf = image.getRGBAPixels(); byte etc[8], rgba[64]; for ( unsigned int y = 0; y < height; y += 4, pixbuf += stride * 4 ) { unsigned int blockrows = height - y; if ( blockrows > 4 ) { blockrows = 4; } byte* p = pixbuf; for ( unsigned int x = 0; x < width; x += 4, p += 16 ) { istream.read( etc, 8 ); ETC_DecodeETC1Block( etc, rgba, qtrue ); unsigned int blockrowsize = width - x; if ( blockrowsize > 4 ) { blockrowsize = 4; } blockrowsize *= 4; for ( unsigned int blockrow = 0; blockrow < blockrows; blockrow++ ) { memcpy( p + blockrow * stride, rgba + blockrow * 16, blockrowsize ); } } } } Image* LoadKTXBuff( PointerInputStream& istream ){ byte identifier[12]; istream.read( identifier, 12 ); if ( memcmp( identifier, "\xABKTX 11\xBB\r\n\x1A\n", 12 ) ) { globalErrorStream() << "LoadKTX: Image has the wrong identifier\n"; return 0; } bool bigEndian = ( istream_read_uint32_le( istream ) == 0x01020304 ); unsigned int type; if ( bigEndian ) { type = istream_read_uint32_be( istream ); } else { type = istream_read_uint32_le( istream ); } // For compressed textures, the format is in glInternalFormat. // For uncompressed textures, it's in glBaseInternalFormat. istream.seek( ( type ? 3 : 2 ) * sizeof( uint32_t ) ); unsigned int format; if ( bigEndian ) { format = istream_read_uint32_be( istream ); } else { format = istream_read_uint32_le( istream ); } if ( !type ) { istream.seek( sizeof( uint32_t ) ); } unsigned int width, height; if ( bigEndian ) { width = istream_read_uint32_be( istream ); height = istream_read_uint32_be( istream ); } else { width = istream_read_uint32_le( istream ); height = istream_read_uint32_le( istream ); } if ( !width ) { globalErrorStream() << "LoadKTX: Image has zero width\n"; return 0; } if ( !height ) { height = 1; } // Skip the key/values and load the first 2D image in the texture. // Since KTXorientation is only a hint and has no effect on the texture data and coordinates, it must be ignored. istream.seek( 4 * sizeof( uint32_t ) ); unsigned int bytesOfKeyValueData; if ( bigEndian ) { bytesOfKeyValueData = istream_read_uint32_be( istream ); } else { bytesOfKeyValueData = istream_read_uint32_le( istream ); } istream.seek( bytesOfKeyValueData + sizeof( uint32_t ) ); RGBAImage* image = new RGBAImage( width, height ); if ( type ) { KTX_Decoder* decoder = NULL; switch ( type ) { case KTX_TYPE_UNSIGNED_BYTE: switch ( format ) { case KTX_FORMAT_ALPHA: decoder = new KTX_Decoder_A8(); break; case KTX_FORMAT_RGB: decoder = new KTX_Decoder_RGB8(); break; case KTX_FORMAT_RGBA: decoder = new KTX_Decoder_RGBA8(); break; case KTX_FORMAT_LUMINANCE: decoder = new KTX_Decoder_L8(); break; case KTX_FORMAT_LUMINANCE_ALPHA: decoder = new KTX_Decoder_LA8(); break; case KTX_FORMAT_BGR: decoder = new KTX_Decoder_BGR8(); break; case KTX_FORMAT_BGRA: decoder = new KTX_Decoder_BGRA8(); break; } break; case KTX_TYPE_UNSIGNED_SHORT_4_4_4_4: if ( format == KTX_FORMAT_RGBA ) { decoder = new KTX_Decoder_RGBA4( bigEndian ); } break; case KTX_TYPE_UNSIGNED_SHORT_5_5_5_1: if ( format == KTX_FORMAT_RGBA ) { decoder = new KTX_Decoder_RGBA5( bigEndian ); } break; case KTX_TYPE_UNSIGNED_SHORT_5_6_5: if ( format == KTX_FORMAT_RGB ) { decoder = new KTX_Decoder_RGB5( bigEndian ); } break; } if ( !decoder ) { globalErrorStream() << "LoadKTX: Image has an unsupported pixel type " << type << " or format " << format << "\n"; image->release(); return 0; } unsigned int inRowLength = width * decoder->GetPixelSize(); unsigned int inPadding = ( ( inRowLength + 3 ) & ~3 ) - inRowLength; byte* out = image->getRGBAPixels(); for ( unsigned int y = 0; y < height; y++ ) { for ( unsigned int x = 0; x < width; x++, out += 4 ) { decoder->Decode( istream, out ); } if ( inPadding ) { istream.seek( inPadding ); } } delete decoder; } else { switch ( format ) { case KTX_FORMAT_ETC1_RGB8: KTX_DecodeETC1( istream, *image ); break; default: globalErrorStream() << "LoadKTX: Image has an unsupported compressed format " << format << "\n"; image->release(); return 0; } } return image; } Image* LoadKTX( ArchiveFile& file ){ ScopedArchiveBuffer buffer( file ); PointerInputStream istream( buffer.buffer ); return LoadKTXBuff( istream ); }