2020-11-17 11:16:16 +00:00
|
|
|
/*
|
|
|
|
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 <string.h>
|
|
|
|
|
|
|
|
#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:
|
2021-08-04 20:23:18 +00:00
|
|
|
virtual ~KTX_Decoder() = default;
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
virtual void Decode(PointerInputStream &istream, byte *out) = 0;
|
2020-11-17 11:16:16 +00:00
|
|
|
|
2021-08-04 20:23:18 +00:00
|
|
|
virtual unsigned int GetPixelSize() = 0;
|
2020-11-17 11:16:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class KTX_Decoder_A8 : public KTX_Decoder {
|
|
|
|
public:
|
2021-08-04 20:23:18 +00:00
|
|
|
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;
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class KTX_Decoder_RGB8 : public KTX_Decoder {
|
|
|
|
public:
|
2021-08-04 20:23:18 +00:00
|
|
|
virtual void Decode(PointerInputStream &istream, byte *out)
|
|
|
|
{
|
|
|
|
istream.read(out, 3);
|
|
|
|
out[3] = 255;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual unsigned int GetPixelSize()
|
|
|
|
{
|
|
|
|
return 3;
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class KTX_Decoder_RGBA8 : public KTX_Decoder {
|
|
|
|
public:
|
2021-08-04 20:23:18 +00:00
|
|
|
virtual void Decode(PointerInputStream &istream, byte *out)
|
|
|
|
{
|
|
|
|
istream.read(out, 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual unsigned int GetPixelSize()
|
|
|
|
{
|
|
|
|
return 4;
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class KTX_Decoder_L8 : public KTX_Decoder {
|
|
|
|
public:
|
2021-08-04 20:23:18 +00:00
|
|
|
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;
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class KTX_Decoder_LA8 : public KTX_Decoder {
|
|
|
|
public:
|
2021-08-04 20:23:18 +00:00
|
|
|
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;
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class KTX_Decoder_BGR8 : public KTX_Decoder {
|
|
|
|
public:
|
2021-08-04 20:23:18 +00:00
|
|
|
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;
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class KTX_Decoder_BGRA8 : public KTX_Decoder {
|
|
|
|
public:
|
2021-08-04 20:23:18 +00:00
|
|
|
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;
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class KTX_Decoder_RGBA4 : public KTX_Decoder {
|
|
|
|
protected:
|
2021-08-04 20:23:18 +00:00
|
|
|
bool m_bigEndian;
|
2020-11-17 11:16:16 +00:00
|
|
|
public:
|
2021-08-04 20:23:18 +00:00
|
|
|
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;
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class KTX_Decoder_RGBA5 : public KTX_Decoder {
|
|
|
|
protected:
|
2021-08-04 20:23:18 +00:00
|
|
|
bool m_bigEndian;
|
2020-11-17 11:16:16 +00:00
|
|
|
public:
|
2021-08-04 20:23:18 +00:00
|
|
|
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;
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class KTX_Decoder_RGB5 : public KTX_Decoder {
|
|
|
|
protected:
|
2021-08-04 20:23:18 +00:00
|
|
|
bool m_bigEndian;
|
2020-11-17 11:16:16 +00:00
|
|
|
public:
|
2021-08-04 20:23:18 +00:00
|
|
|
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;
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static void KTX_DecodeETC1(PointerInputStream &istream, Image &image)
|
|
|
|
{
|
2021-08-04 20:23:18 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Image *LoadKTXBuff(PointerInputStream &istream)
|
|
|
|
{
|
2021-08-04 20:23:18 +00:00
|
|
|
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;
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Image *LoadKTX(ArchiveFile &file)
|
|
|
|
{
|
2021-08-04 20:23:18 +00:00
|
|
|
ScopedArchiveBuffer buffer(file);
|
|
|
|
PointerInputStream istream(buffer.buffer);
|
|
|
|
return LoadKTXBuff(istream);
|
2020-11-17 11:16:16 +00:00
|
|
|
}
|