From 6eeb0fe907c8b452210ba56a435d93a830b1bdcf Mon Sep 17 00:00:00 2001 From: SiPlus Date: Sat, 30 May 2015 21:55:04 +0300 Subject: [PATCH] Add KTX and ETC1 texture support --- libs/etclib.c | 114 ++++++++++++ libs/etclib.h | 33 ++++ plugins/image/ktx.cpp | 417 ++++++++++++++++++++++++++++++++++++++++++ plugins/image/ktx.h | 30 +++ 4 files changed, 594 insertions(+) create mode 100644 libs/etclib.c create mode 100644 libs/etclib.h create mode 100644 plugins/image/ktx.cpp create mode 100644 plugins/image/ktx.h diff --git a/libs/etclib.c b/libs/etclib.c new file mode 100644 index 00000000..09a149e6 --- /dev/null +++ b/libs/etclib.c @@ -0,0 +1,114 @@ +// Copyright 2009 Google Inc. +// +// Based on the code from Android ETC1Util. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "etclib.h" + +static void ETC_DecodeETC1SubBlock( byte *out, qboolean outRGBA, int r, int g, int b, int tableIndex, unsigned int low, qboolean second, qboolean flipped ){ + int baseX = 0, baseY = 0; + const int modifierTable[] = { + 2, 8, -2, -8, + 5, 17, -5, -17, + 9, 29, -9, -29, + 13, 42, -13, -42, + 18, 60, -18, -60, + 24, 80, -24, -80, + 33, 106, -33, -106, + 47, 183, -47, -183 + }; + const int *table = modifierTable + tableIndex * 4; + int i; + + if ( second ) { + if ( flipped ) { + baseY = 2; + } + else { + baseX = 2; + } + } + + for ( i = 0; i < 8; i++ ) + { + int x, y, k, delta; + int qr, qg, qb; + byte *q; + + if ( flipped ) { + x = baseX + ( i >> 1 ); + y = baseY + ( i & 1 ); + } + else { + x = baseX + ( i >> 2 ); + y = baseY + ( i & 3 ); + } + k = y + ( x * 4 ); + delta = table[( ( low >> k ) & 1 ) | ( ( low >> ( k + 15 ) ) & 2 )]; + + qr = r + delta; + qg = g + delta; + qb = b + delta; + if ( outRGBA ) { + q = out + 4 * ( x + 4 * y ); + } + else { + q = out + 3 * ( x + 4 * y ); + } + *( q++ ) = ( ( qr > 0 ) ? ( ( qr < 255 ) ? qr : 255 ) : 0 ); + *( q++ ) = ( ( qg > 0 ) ? ( ( qg < 255 ) ? qg : 255 ) : 0 ); + *( q++ ) = ( ( qb > 0 ) ? ( ( qb < 255 ) ? qb : 255 ) : 0 ); + if ( outRGBA ) { + *( q++ ) = 255; + } + } +} + +void ETC_DecodeETC1Block( const byte* in, byte* out, qboolean outRGBA ){ + unsigned int high = ( in[0] << 24 ) | ( in[1] << 16 ) | ( in[2] << 8 ) | in[3]; + unsigned int low = ( in[4] << 24 ) | ( in[5] << 16 ) | ( in[6] << 8 ) | in[7]; + int r1, r2, g1, g2, b1, b2; + qboolean flipped = ( ( high & 1 ) != 0 ); + + if ( high & 2 ) { + int rBase, gBase, bBase; + const int lookup[] = { 0, 1, 2, 3, -4, -3, -2, -1 }; + + rBase = ( high >> 27 ) & 31; + r1 = ( rBase << 3 ) | ( rBase >> 2 ); + rBase = ( rBase + ( lookup[( high >> 24 ) & 7] ) ) & 31; + r2 = ( rBase << 3 ) | ( rBase >> 2 ); + + gBase = ( high >> 19 ) & 31; + g1 = ( gBase << 3 ) | ( gBase >> 2 ); + gBase = ( gBase + ( lookup[( high >> 16 ) & 7] ) ) & 31; + g2 = ( gBase << 3 ) | ( gBase >> 2 ); + + bBase = ( high >> 11 ) & 31; + b1 = ( bBase << 3 ) | ( bBase >> 2 ); + bBase = ( bBase + ( lookup[( high >> 8 ) & 7] ) ) & 31; + b2 = ( bBase << 3 ) | ( bBase >> 2 ); + } + else { + r1 = ( ( high >> 24 ) & 0xf0 ) | ( ( high >> 28 ) & 0xf ); + r2 = ( ( high >> 20 ) & 0xf0 ) | ( ( high >> 24 ) & 0xf ); + g1 = ( ( high >> 16 ) & 0xf0 ) | ( ( high >> 20 ) & 0xf ); + g2 = ( ( high >> 12 ) & 0xf0 ) | ( ( high >> 16 ) & 0xf ); + b1 = ( ( high >> 8 ) & 0xf0 ) | ( ( high >> 12 ) & 0xf ); + b2 = ( ( high >> 4 ) & 0xf0 ) | ( ( high >> 8 ) & 0xf ); + } + + ETC_DecodeETC1SubBlock( out, outRGBA, r1, g1, b1, ( high >> 5 ) & 7, low, qfalse, flipped ); + ETC_DecodeETC1SubBlock( out, outRGBA, r2, g2, b2, ( high >> 2 ) & 7, low, qtrue, flipped ); +} diff --git a/libs/etclib.h b/libs/etclib.h new file mode 100644 index 00000000..7d240741 --- /dev/null +++ b/libs/etclib.h @@ -0,0 +1,33 @@ +// Copyright 2009 Google Inc. +// +// Based on the code from Android ETC1Util. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef INCLUDED_ETCLIB_H +#define INCLUDED_ETCLIB_H + +#include "bytebool.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +void ETC_DecodeETC1Block( const byte* in, byte* out, qboolean outRGBA ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/image/ktx.cpp b/plugins/image/ktx.cpp new file mode 100644 index 00000000..a12f8150 --- /dev/null +++ b/plugins/image/ktx.cpp @@ -0,0 +1,417 @@ +/* + 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" + + +#define KTX_TYPE_UNSIGNED_BYTE 0x1401 +#define KTX_TYPE_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define KTX_TYPE_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define KTX_TYPE_UNSIGNED_SHORT_5_6_5 0x8363 + +#define KTX_FORMAT_ALPHA 0x1906 +#define KTX_FORMAT_RGB 0x1907 +#define KTX_FORMAT_RGBA 0x1908 +#define KTX_FORMAT_LUMINANCE 0x1909 +#define KTX_FORMAT_LUMINANCE_ALPHA 0x190A +#define KTX_FORMAT_BGR 0x80E0 +#define KTX_FORMAT_BGRA 0x80E1 + +#define KTX_FORMAT_ETC1_RGB8 0x8D64 + +class KTX_Decoder +{ +public: + virtual void Decode( PointerInputStream& istream, byte* out ) = 0; + virtual unsigned int GetPixelSize() = 0; +protected: + bool m_bigEndian; +}; + +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 ); +} diff --git a/plugins/image/ktx.h b/plugins/image/ktx.h new file mode 100644 index 00000000..6070077b --- /dev/null +++ b/plugins/image/ktx.h @@ -0,0 +1,30 @@ +/* + 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 + */ + +#if !defined( INCLUDED_KTX_H ) +#define INCLUDED_KTX_H + +class Image; +class ArchiveFile; + +Image* LoadKTX( ArchiveFile& file ); + +#endif -- 2.39.2