From: Rudolf Polzer Date: Mon, 1 Jun 2015 08:45:24 +0000 (+0200) Subject: Merge commit 'refs/pull/1/head' of https://github.com/xonotic/netradient X-Git-Tag: xonotic-v0.8.1~3 X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fnetradiant.git;a=commitdiff_plain;h=e38c7bff1656ae18aeead9fc15810e163886d9df;hp=e995cc897b8a0d2b2828612cb0b226b7520fea86 Merge commit 'refs/pull/1/head' of https://github.com/xonotic/netradient --- diff --git a/Makefile b/Makefile index 9eee1212..51b3fa9f 100644 --- a/Makefile +++ b/Makefile @@ -536,6 +536,7 @@ $(INSTALLDIR)/q3map2.$(EXE): \ tools/quake3/q3map2/vis.o \ tools/quake3/q3map2/writebsp.o \ libddslib.$(A) \ + libetclib.$(A) \ libfilematch.$(A) \ libl_net.$(A) \ libmathlib.$(A) \ @@ -585,6 +586,10 @@ libddslib.$(A): CPPFLAGS_EXTRA := -Ilibs libddslib.$(A): \ libs/ddslib/ddslib.o \ +libetclib.$(A): CPPFLAGS_EXTRA := -Ilibs +libetclib.$(A): \ + libs/etclib.o \ + $(INSTALLDIR)/q3data.$(EXE): LIBS_EXTRA := $(LIBS_XML) $(LIBS_GLIB) $(LIBS_ZLIB) $(INSTALLDIR)/q3data.$(EXE): CPPFLAGS_EXTRA := $(CPPFLAGS_XML) $(CPPFLAGS_GLIB) $(CPPFLAGS_ZLIB) -Itools/quake3/common -Ilibs -Iinclude $(INSTALLDIR)/q3data.$(EXE): \ @@ -609,6 +614,7 @@ $(INSTALLDIR)/q3data.$(EXE): \ tools/quake3/q3data/stripper.o \ tools/quake3/q3data/video.o \ libfilematch.$(A) \ + libetclib.$(A) \ libl_net.$(A) \ libmathlib.$(A) \ $(if $(findstring $(OS),Win32),icons/q3data.o,) \ @@ -803,9 +809,11 @@ $(INSTALLDIR)/modules/image.$(DLL): \ plugins/image/dds.o \ plugins/image/image.o \ plugins/image/jpeg.o \ + plugins/image/ktx.o \ plugins/image/pcx.o \ plugins/image/tga.o \ libddslib.$(A) \ + libetclib.$(A) \ $(INSTALLDIR)/modules/imageq2.$(DLL): CPPFLAGS_EXTRA := -Ilibs -Iinclude $(INSTALLDIR)/modules/imageq2.$(DLL): \ diff --git a/download-gamepacks.sh b/download-gamepacks.sh index 7717cd2d..ff5b9840 100755 --- a/download-gamepacks.sh +++ b/download-gamepacks.sh @@ -158,5 +158,6 @@ pack QuakePack GPL zip1 http://ingar.satgnu.net/files/gtkradiant pack TremulousPack proprietary zip1 http://ingar.satgnu.net/files/gtkradiant/gamepacks/TremulousPack.zip pack UFOAIPack proprietary svn svn://svn.icculus.org/gtkradiant-gamepacks/UFOAIPack/branches/1.5/ #pack WarsowPack GPL svn https://svn.bountysource.com/wswpack/trunk/netradiant/games/WarsowPack/ -pack WarsowPack GPL zip1 http://ingar.satgnu.net/files/gtkradiant/gamepacks/WarsowPack.zip +#pack WarsowPack GPL zip1 http://ingar.satgnu.net/files/gtkradiant/gamepacks/WarsowPack.zip +pack WarsowPack GPL git https://github.com/Warsow/NetRadiantPack.git pack XonoticPack GPL git http://git.xonotic.org/xonotic/netradiant-xonoticpack.git diff --git a/libs/bytestreamutils.h b/libs/bytestreamutils.h index 156333a1..90a9048a 100644 --- a/libs/bytestreamutils.h +++ b/libs/bytestreamutils.h @@ -82,6 +82,13 @@ inline int16_t istream_read_int16_le( InputStreamType& istream ){ return value; } +template +inline int16_t istream_read_int16_be( InputStreamType& istream ){ + int16_t value; + istream_read_big_endian( istream, value ); + return value; +} + template inline uint16_t istream_read_uint16_le( InputStreamType& istream ){ uint16_t value; @@ -89,6 +96,13 @@ inline uint16_t istream_read_uint16_le( InputStreamType& istream ){ return value; } +template +inline uint16_t istream_read_uint16_be( InputStreamType& istream ){ + uint16_t value; + istream_read_big_endian( istream, value ); + return value; +} + template inline int32_t istream_read_int32_le( InputStreamType& istream ){ int32_t value; @@ -96,6 +110,13 @@ inline int32_t istream_read_int32_le( InputStreamType& istream ){ return value; } +template +inline int32_t istream_read_int32_be( InputStreamType& istream ){ + int32_t value; + istream_read_big_endian( istream, value ); + return value; +} + template inline uint32_t istream_read_uint32_le( InputStreamType& istream ){ uint32_t value; @@ -103,6 +124,13 @@ inline uint32_t istream_read_uint32_le( InputStreamType& istream ){ return value; } +template +inline uint32_t istream_read_uint32_be( InputStreamType& istream ){ + uint32_t value; + istream_read_big_endian( istream, value ); + return value; +} + template inline float istream_read_float32_le( InputStreamType& istream ){ float value; @@ -110,6 +138,13 @@ inline float istream_read_float32_le( InputStreamType& istream ){ return value; } +template +inline float istream_read_float32_be( InputStreamType& istream ){ + float value; + istream_read_big_endian( istream, value ); + return value; +} + template inline typename InputStreamType::byte_type istream_read_byte( InputStreamType& istream ){ typename InputStreamType::byte_type b; 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/image.cpp b/plugins/image/image.cpp index fcfdd018..4f3e07a8 100644 --- a/plugins/image/image.cpp +++ b/plugins/image/image.cpp @@ -29,6 +29,7 @@ #include "bmp.h" #include "pcx.h" #include "dds.h" +#include "ktx.h" #include "modulesystem/singletonmodule.h" @@ -137,6 +138,26 @@ typedef SingletonModule ImageDDSModule; ImageDDSModule g_ImageDDSModule; +class ImageKTXAPI +{ +_QERPlugImageTable m_imagektx; +public: +typedef _QERPlugImageTable Type; +STRING_CONSTANT( Name, "ktx" ); + +ImageKTXAPI(){ + m_imagektx.loadImage = LoadKTX; +} +_QERPlugImageTable* getTable(){ + return &m_imagektx; +} +}; + +typedef SingletonModule ImageKTXModule; + +ImageKTXModule g_ImageKTXModule; + + extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules( ModuleServer& server ){ initialiseModule( server ); @@ -145,4 +166,5 @@ extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules( ModuleServer& server g_ImageBMPModule.selfRegister(); g_ImagePCXModule.selfRegister(); g_ImageDDSModule.selfRegister(); + g_ImageKTXModule.selfRegister(); } diff --git a/plugins/image/ktx.cpp b/plugins/image/ktx.cpp new file mode 100644 index 00000000..582d0b8b --- /dev/null +++ b/plugins/image/ktx.cpp @@ -0,0 +1,415 @@ +/* + 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; +}; + +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 diff --git a/tools/quake3/common/imagelib.c b/tools/quake3/common/imagelib.c index c349b498..3a590292 100644 --- a/tools/quake3/common/imagelib.c +++ b/tools/quake3/common/imagelib.c @@ -23,6 +23,7 @@ #include "inout.h" #include "cmdlib.h" +#include "etclib.h" #include "imagelib.h" #include "vfs.h" @@ -1232,3 +1233,302 @@ void Load32BitImage( const char *name, unsigned **pixels, int *width, int *heig } } } + + +/* + ============================================================================ + + KHRONOS TEXTURE + + ============================================================================ + */ + + +#define KTX_UINT32_LE( buf ) ( ( unsigned int )( (buf)[0] | ( (buf)[1] << 8 ) | ( (buf)[2] << 16 ) | ( (buf)[3] << 24 ) ) ) +#define KTX_UINT32_BE( buf ) ( ( unsigned int )( (buf)[3] | ( (buf)[2] << 8 ) | ( (buf)[1] << 16 ) | ( (buf)[0] << 24 ) ) ) + +#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 + +static void KTX_DecodeA8( const byte *in, qboolean bigEndian, byte *out ){ + out[0] = out[1] = out[2] = 0; + out[3] = in[0]; +} + +static void KTX_DecodeRGB8( const byte *in, qboolean bigEndian, byte *out ){ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = 255; +} + +static void KTX_DecodeRGBA8( const byte *in, qboolean bigEndian, byte *out ){ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = in[3]; +} + +static void KTX_DecodeL8( const byte *in, qboolean bigEndian, byte *out ){ + out[0] = out[1] = out[2] = in[0]; + out[3] = 255; +} + +static void KTX_DecodeLA8( const byte *in, qboolean bigEndian, byte *out ){ + out[0] = out[1] = out[2] = in[0]; + out[3] = in[1]; +} + +static void KTX_DecodeBGR8( const byte *in, qboolean bigEndian, byte *out ){ + out[0] = in[2]; + out[1] = in[1]; + out[2] = in[0]; + out[3] = 255; +} + +static void KTX_DecodeBGRA8( const byte *in, qboolean bigEndian, byte *out ){ + out[0] = in[2]; + out[1] = in[1]; + out[2] = in[0]; + out[3] = in[3]; +} + +static void KTX_DecodeRGBA4( const byte *in, qboolean bigEndian, byte *out ){ + unsigned short rgba; + int r, g, b, a; + + if ( bigEndian ) { + rgba = ( in[0] << 8 ) | in[1]; + } + else { + rgba = ( in[1] << 8 ) | in[0]; + } + + r = ( rgba >> 12 ) & 0xf; + g = ( rgba >> 8 ) & 0xf; + b = ( rgba >> 4 ) & 0xf; + a = rgba & 0xf; + out[0] = ( r << 4 ) | r; + out[1] = ( g << 4 ) | g; + out[2] = ( b << 4 ) | b; + out[3] = ( a << 4 ) | a; +} + +static void KTX_DecodeRGBA5( const byte *in, qboolean bigEndian, byte *out ){ + unsigned short rgba; + int r, g, b; + + if ( bigEndian ) { + rgba = ( in[0] << 8 ) | in[1]; + } + else { + rgba = ( in[1] << 8 ) | in[0]; + } + + r = ( rgba >> 11 ) & 0x1f; + g = ( rgba >> 6 ) & 0x1f; + 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; +} + +static void KTX_DecodeRGB5( const byte *in, qboolean bigEndian, byte *out ){ + unsigned short rgba; + int r, g, b; + + if ( bigEndian ) { + rgba = ( in[0] << 8 ) | in[1]; + } + else { + rgba = ( in[1] << 8 ) | in[0]; + } + + r = ( rgba >> 11 ) & 0x1f; + g = ( rgba >> 5 ) & 0x3f; + b = rgba & 0x1f; + out[0] = ( r << 3 ) | ( r >> 2 ); + out[1] = ( g << 2 ) | ( g >> 4 ); + out[2] = ( b << 3 ) | ( b >> 2 ); + out[3] = 255; +} + +typedef struct +{ + unsigned int type; + unsigned int format; + unsigned int pixelSize; + void ( *decode )( const byte *in, qboolean bigEndian, byte *out ); +} KTX_UncompressedFormat_t; + +static const KTX_UncompressedFormat_t KTX_UncompressedFormats[] = +{ + { KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_ALPHA, 1, KTX_DecodeA8 }, + { KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_RGB, 3, KTX_DecodeRGB8 }, + { KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_RGBA, 4, KTX_DecodeRGBA8 }, + { KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_LUMINANCE, 1, KTX_DecodeL8 }, + { KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_LUMINANCE_ALPHA, 2, KTX_DecodeLA8 }, + { KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_BGR, 3, KTX_DecodeBGR8 }, + { KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_BGRA, 4, KTX_DecodeBGRA8 }, + { KTX_TYPE_UNSIGNED_SHORT_4_4_4_4, KTX_FORMAT_RGBA, 2, KTX_DecodeRGBA4 }, + { KTX_TYPE_UNSIGNED_SHORT_5_5_5_1, KTX_FORMAT_RGBA, 2, KTX_DecodeRGBA5 }, + { KTX_TYPE_UNSIGNED_SHORT_5_6_5, KTX_FORMAT_RGB, 2, KTX_DecodeRGB5 }, + { 0, 0, 0, NULL } +}; + +static qboolean KTX_DecodeETC1( const byte* in, size_t inSize, unsigned int width, unsigned int height, byte* out ){ + unsigned int y, stride = width * 4; + byte rgba[64]; + + if ( inSize < ( ( ( ( width + 3 ) & ~3 ) * ( ( height + 3 ) & ~3 ) ) >> 1 ) ) { + return qfalse; + } + + for ( y = 0; y < height; y += 4, out += stride * 4 ) + { + byte *p; + unsigned int x, blockrows; + + blockrows = height - y; + if ( blockrows > 4 ) { + blockrows = 4; + } + + p = out; + for ( x = 0; x < width; x += 4, p += 16 ) + { + unsigned int blockrowsize, blockrow; + + ETC_DecodeETC1Block( in, rgba, qtrue ); + in += 8; + + blockrowsize = width - x; + if ( blockrowsize > 4 ) { + blockrowsize = 4; + } + blockrowsize *= 4; + for ( blockrow = 0; blockrow < blockrows; blockrow++ ) + { + memcpy( p + blockrow * stride, rgba + blockrow * 16, blockrowsize ); + } + } + } + + return qtrue; +} + +#define KTX_HEADER_UINT32( buf ) ( bigEndian ? KTX_UINT32_BE( buf ) : KTX_UINT32_LE( buf ) ) + +void LoadKTXBufferFirstImage( const byte *buffer, size_t bufSize, byte **pic, int *picWidth, int *picHeight ){ + unsigned int type, format, width, height, imageOffset; + byte *pixels; + + if ( bufSize < 64 ) { + Error( "LoadKTX: Image doesn't have a header" ); + } + + if ( memcmp( buffer, "\xABKTX 11\xBB\r\n\x1A\n", 12 ) ) { + Error( "LoadKTX: Image has the wrong identifier" ); + } + + qboolean bigEndian = ( buffer[4] == 4 ); + + type = KTX_HEADER_UINT32( buffer + 16 ); + if ( type ) { + format = KTX_HEADER_UINT32( buffer + 32 ); + } + else { + format = KTX_HEADER_UINT32( buffer + 28 ); + } + + width = KTX_HEADER_UINT32( buffer + 36 ); + height = KTX_HEADER_UINT32( buffer + 40 ); + if ( !width ) { + Error( "LoadKTX: Image has zero width" ); + } + if ( !height ) { + height = 1; + } + if ( picWidth ) { + *picWidth = width; + } + if ( picHeight ) { + *picHeight = height; + } + + imageOffset = 64 + KTX_HEADER_UINT32( buffer + 60 ) + 4; + if ( bufSize < imageOffset ) { + Error( "LoadKTX: No image in the file" ); + } + buffer += imageOffset; + bufSize -= imageOffset; + + pixels = safe_malloc( width * height * 4 ); + *pic = pixels; + + if ( type ) { + const KTX_UncompressedFormat_t *ktxFormat = KTX_UncompressedFormats; + unsigned int pixelSize; + unsigned int inRowLength, inPadding; + unsigned int y; + + while ( ktxFormat->type ) + { + if ( ktxFormat->type == type && ktxFormat->format == format ) { + break; + } + ktxFormat++; + } + if ( !ktxFormat->type ) { + Error( "LoadKTX: Image has an unsupported pixel type 0x%X or format 0x%X", type, format ); + } + + pixelSize = ktxFormat->pixelSize; + inRowLength = width * pixelSize; + inPadding = ( ( inRowLength + 3 ) & ~3 ) - inRowLength; + + if ( bufSize < height * ( inRowLength + inPadding ) ) { + Error( "LoadKTX: Image is truncated" ); + } + + for ( y = 0; y < height; y++ ) + { + unsigned int x; + for ( x = 0; x < width; x++, buffer += pixelSize, pixels += 4 ) + { + ktxFormat->decode( buffer, bigEndian, pixels ); + } + buffer += inPadding; + } + } + else { + qboolean decoded = qfalse; + + switch ( format ) + { + case KTX_FORMAT_ETC1_RGB8: + decoded = KTX_DecodeETC1( buffer, bufSize, width, height, pixels ); + break; + default: + Error( "LoadKTX: Image has an unsupported compressed format format 0x%X", format ); + break; + } + + if ( !decoded ) { + Error( "LoadKTX: Image is truncated" ); + } + } +} diff --git a/tools/quake3/common/imagelib.h b/tools/quake3/common/imagelib.h index 194ac5b1..8c00f7ce 100644 --- a/tools/quake3/common/imagelib.h +++ b/tools/quake3/common/imagelib.h @@ -43,3 +43,5 @@ void WriteTGAGray( const char *filename, byte *data, int width, int height ); int LoadJPGBuff( void *src_buffer, int src_size, unsigned char **pic, int *width, int *height ); void Load32BitImage( const char *name, unsigned **pixels, int *width, int *height ); + +void LoadKTXBufferFirstImage( const byte *buffer, size_t bufSize, byte **pic, int *picWidth, int *picHeight ); diff --git a/tools/quake3/q3map2/image.c b/tools/quake3/q3map2/image.c index 60062a17..c1c737f5 100644 --- a/tools/quake3/q3map2/image.c +++ b/tools/quake3/q3map2/image.c @@ -430,6 +430,16 @@ image_t *ImageLoad( const char *filename ){ } #endif } + else + { + /* attempt to load ktx */ + StripExtension( name ); + strcat( name, ".ktx" ); + size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 ); + if ( size > 0 ) { + LoadKTXBufferFirstImage( buffer, size, &image->pixels, &image->width, &image->height ); + } + } } } }