netradiant-custom/plugins/image/ktx.cpp
Garux df02774ff5 tweak StringOutputStream use
auto str = StringOutputStream()(bla) use form was not doing copy elision or move, but copy
2024-01-29 16:54:08 +06:00

417 lines
10 KiB
C++

/*
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 <cstring>
#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 ) const = 0;
virtual unsigned int GetPixelSize() const = 0;
};
class KTX_Decoder_A8 final : public KTX_Decoder
{
public:
void Decode( PointerInputStream& istream, byte* out ) const override {
out[0] = out[1] = out[2] = 0;
out[3] = istream_read_byte( istream );
}
unsigned int GetPixelSize() const override {
return 1;
}
};
class KTX_Decoder_RGB8 final : public KTX_Decoder
{
public:
void Decode( PointerInputStream& istream, byte* out ) const override {
istream.read( out, 3 );
out[3] = 255;
}
unsigned int GetPixelSize() const override {
return 3;
}
};
class KTX_Decoder_RGBA8 final : public KTX_Decoder
{
public:
void Decode( PointerInputStream& istream, byte* out ) const override {
istream.read( out, 4 );
}
unsigned int GetPixelSize() const override {
return 4;
}
};
class KTX_Decoder_L8 final : public KTX_Decoder
{
public:
void Decode( PointerInputStream& istream, byte* out ) const override {
byte l = istream_read_byte( istream );
out[0] = out[1] = out[2] = l;
out[3] = 255;
}
unsigned int GetPixelSize() const override {
return 1;
}
};
class KTX_Decoder_LA8 final : public KTX_Decoder
{
public:
void Decode( PointerInputStream& istream, byte* out ) const override {
byte la[2];
istream.read( la, 2 );
out[0] = out[1] = out[2] = la[0];
out[3] = la[1];
}
unsigned int GetPixelSize() const override {
return 2;
}
};
class KTX_Decoder_BGR8 final : public KTX_Decoder
{
public:
void Decode( PointerInputStream& istream, byte* out ) const override {
byte bgr[3];
istream.read( bgr, 3 );
out[0] = bgr[2];
out[1] = bgr[1];
out[2] = bgr[0];
out[3] = 255;
}
unsigned int GetPixelSize() const override {
return 3;
}
};
class KTX_Decoder_BGRA8 final : public KTX_Decoder
{
public:
void Decode( PointerInputStream& istream, byte* out ) const override {
byte bgra[4];
istream.read( bgra, 4 );
out[0] = bgra[2];
out[1] = bgra[1];
out[2] = bgra[0];
out[3] = bgra[3];
}
unsigned int GetPixelSize() const override {
return 4;
}
};
class KTX_Decoder_RGBA4 final : public KTX_Decoder
{
protected:
const bool m_bigEndian;
public:
KTX_Decoder_RGBA4( bool bigEndian ) : m_bigEndian( bigEndian ){}
void Decode( PointerInputStream& istream, byte* out ) const override {
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;
}
unsigned int GetPixelSize() const override {
return 2;
}
};
class KTX_Decoder_RGBA5 final : public KTX_Decoder
{
protected:
const bool m_bigEndian;
public:
KTX_Decoder_RGBA5( bool bigEndian ) : m_bigEndian( bigEndian ){}
void Decode( PointerInputStream& istream, byte* out ) const override {
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;
}
unsigned int GetPixelSize() const override {
return 2;
}
};
class KTX_Decoder_RGB5 final : public KTX_Decoder
{
protected:
const bool m_bigEndian;
public:
KTX_Decoder_RGB5( bool bigEndian ) : m_bigEndian( bigEndian ){}
void Decode( PointerInputStream& istream, byte* out ) const override {
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;
}
unsigned int GetPixelSize() const override {
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, true );
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;
}
const 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 );
}