243 lines
7.1 KiB
C++
243 lines
7.1 KiB
C++
|
|
||
|
/*
|
||
|
* Copyright 2014 Google Inc.
|
||
|
*
|
||
|
* Use of this source code is governed by a BSD-style license that can be
|
||
|
* found in the LICENSE file.
|
||
|
*/
|
||
|
|
||
|
#include "ktx.h"
|
||
|
#include "SkStream.h"
|
||
|
#include "SkEndian.h"
|
||
|
|
||
|
#include "gl/GrGLDefines.h"
|
||
|
|
||
|
#define KTX_FILE_IDENTIFIER_SIZE 12
|
||
|
static const uint8_t KTX_FILE_IDENTIFIER[KTX_FILE_IDENTIFIER_SIZE] = {
|
||
|
0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
|
||
|
};
|
||
|
|
||
|
static const uint32_t kKTX_ENDIANNESS_CODE = 0x04030201;
|
||
|
|
||
|
bool SkKTXFile::KeyValue::readKeyAndValue(const uint8_t* data) {
|
||
|
const char *key = reinterpret_cast<const char *>(data);
|
||
|
const char *value = key;
|
||
|
|
||
|
size_t bytesRead = 0;
|
||
|
while (*value != '\0' && bytesRead < this->fDataSz) {
|
||
|
++bytesRead;
|
||
|
++value;
|
||
|
}
|
||
|
|
||
|
// Error of some sort..
|
||
|
if (bytesRead >= this->fDataSz) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Read the zero terminator
|
||
|
++bytesRead;
|
||
|
++value;
|
||
|
|
||
|
size_t bytesLeft = this->fDataSz - bytesRead;
|
||
|
this->fKey.set(key, bytesRead);
|
||
|
if (bytesLeft > 0) {
|
||
|
this->fValue.set(value, bytesLeft);
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
uint32_t SkKTXFile::readInt(const uint8_t** buf, size_t* bytesLeft) const {
|
||
|
SkASSERT(NULL != buf && NULL != bytesLeft);
|
||
|
|
||
|
uint32_t result;
|
||
|
|
||
|
if (*bytesLeft < 4) {
|
||
|
SkASSERT(false);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
memcpy(&result, *buf, 4);
|
||
|
*buf += 4;
|
||
|
|
||
|
if (fSwapBytes) {
|
||
|
SkEndianSwap32(result);
|
||
|
}
|
||
|
|
||
|
*bytesLeft -= 4;
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
bool SkKTXFile::isETC1() const {
|
||
|
return this->valid() && GR_GL_COMPRESSED_RGB8_ETC1 == fHeader.fGLInternalFormat;
|
||
|
}
|
||
|
|
||
|
bool SkKTXFile::isRGBA8() const {
|
||
|
return this->valid() && GR_GL_RGBA8 == fHeader.fGLInternalFormat;
|
||
|
}
|
||
|
|
||
|
bool SkKTXFile::isRGB8() const {
|
||
|
return this->valid() && GR_GL_RGB8 == fHeader.fGLInternalFormat;
|
||
|
}
|
||
|
|
||
|
bool SkKTXFile::readKTXFile(const uint8_t* data, size_t dataLen) {
|
||
|
const uint8_t *buf = data;
|
||
|
size_t bytesLeft = dataLen;
|
||
|
|
||
|
// Make sure original KTX header is there... this should have been checked
|
||
|
// already by a call to is_ktx()
|
||
|
SkASSERT(bytesLeft > KTX_FILE_IDENTIFIER_SIZE);
|
||
|
SkASSERT(0 == memcmp(KTX_FILE_IDENTIFIER, buf, KTX_FILE_IDENTIFIER_SIZE));
|
||
|
buf += KTX_FILE_IDENTIFIER_SIZE;
|
||
|
bytesLeft -= KTX_FILE_IDENTIFIER_SIZE;
|
||
|
|
||
|
// Read header, but first make sure that we have the proper space: we need
|
||
|
// two 32-bit ints: 1 for endianness, and another for the mandatory image
|
||
|
// size after the header.
|
||
|
if (bytesLeft < 8 + sizeof(Header)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
uint32_t endianness = this->readInt(&buf, &bytesLeft);
|
||
|
fSwapBytes = kKTX_ENDIANNESS_CODE != endianness;
|
||
|
|
||
|
// Read header values
|
||
|
fHeader.fGLType = this->readInt(&buf, &bytesLeft);
|
||
|
fHeader.fGLTypeSize = this->readInt(&buf, &bytesLeft);
|
||
|
fHeader.fGLFormat = this->readInt(&buf, &bytesLeft);
|
||
|
fHeader.fGLInternalFormat = this->readInt(&buf, &bytesLeft);
|
||
|
fHeader.fGLBaseInternalFormat = this->readInt(&buf, &bytesLeft);
|
||
|
fHeader.fPixelWidth = this->readInt(&buf, &bytesLeft);
|
||
|
fHeader.fPixelHeight = this->readInt(&buf, &bytesLeft);
|
||
|
fHeader.fPixelDepth = this->readInt(&buf, &bytesLeft);
|
||
|
fHeader.fNumberOfArrayElements = this->readInt(&buf, &bytesLeft);
|
||
|
fHeader.fNumberOfFaces = this->readInt(&buf, &bytesLeft);
|
||
|
fHeader.fNumberOfMipmapLevels = this->readInt(&buf, &bytesLeft);
|
||
|
fHeader.fBytesOfKeyValueData = this->readInt(&buf, &bytesLeft);
|
||
|
|
||
|
// Check for things that we understand...
|
||
|
{
|
||
|
// First, we only support compressed formats and single byte
|
||
|
// representations at the moment. If the internal format is
|
||
|
// compressed, the the GLType field in the header must be zero.
|
||
|
// In the future, we may support additional data types (such
|
||
|
// as GL_UNSIGNED_SHORT_5_6_5)
|
||
|
if (fHeader.fGLType != 0 && fHeader.fGLType != GR_GL_UNSIGNED_BYTE) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// This means that for well-formatted KTX files, the glTypeSize
|
||
|
// field must be one...
|
||
|
if (fHeader.fGLTypeSize != 1) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// We don't support 3D textures.
|
||
|
if (fHeader.fPixelDepth > 1) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// We don't support texture arrays
|
||
|
if (fHeader.fNumberOfArrayElements > 1) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// We don't support cube maps
|
||
|
if (fHeader.fNumberOfFaces > 1) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Make sure that we have enough bytes left for the key/value
|
||
|
// data according to what was said in the header.
|
||
|
if (bytesLeft < fHeader.fBytesOfKeyValueData) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Next read the key value pairs
|
||
|
size_t keyValueBytesRead = 0;
|
||
|
while (keyValueBytesRead < fHeader.fBytesOfKeyValueData) {
|
||
|
uint32_t keyValueBytes = this->readInt(&buf, &bytesLeft);
|
||
|
keyValueBytesRead += 4;
|
||
|
|
||
|
if (keyValueBytes > bytesLeft) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
KeyValue kv(keyValueBytes);
|
||
|
if (!kv.readKeyAndValue(buf)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
fKeyValuePairs.push_back(kv);
|
||
|
|
||
|
uint32_t keyValueBytesPadded = (keyValueBytes + 3) & ~3;
|
||
|
buf += keyValueBytesPadded;
|
||
|
keyValueBytesRead += keyValueBytesPadded;
|
||
|
bytesLeft -= keyValueBytesPadded;
|
||
|
}
|
||
|
|
||
|
// Read the pixel data...
|
||
|
int mipmaps = SkMax32(fHeader.fNumberOfMipmapLevels, 1);
|
||
|
SkASSERT(mipmaps == 1);
|
||
|
|
||
|
int arrayElements = SkMax32(fHeader.fNumberOfArrayElements, 1);
|
||
|
SkASSERT(arrayElements == 1);
|
||
|
|
||
|
int faces = SkMax32(fHeader.fNumberOfFaces, 1);
|
||
|
SkASSERT(faces == 1);
|
||
|
|
||
|
int depth = SkMax32(fHeader.fPixelDepth, 1);
|
||
|
SkASSERT(depth == 1);
|
||
|
|
||
|
for (int mipmap = 0; mipmap < mipmaps; ++mipmap) {
|
||
|
// Make sure that we have at least 4 more bytes for the first image size
|
||
|
if (bytesLeft < 4) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
uint32_t imgSize = this->readInt(&buf, &bytesLeft);
|
||
|
|
||
|
// Truncated file.
|
||
|
if (bytesLeft < imgSize) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// !FIXME! If support is ever added for cube maps then the padding
|
||
|
// needs to be taken into account here.
|
||
|
for (int arrayElement = 0; arrayElement < arrayElements; ++arrayElement) {
|
||
|
for (int face = 0; face < faces; ++face) {
|
||
|
for (int z = 0; z < depth; ++z) {
|
||
|
PixelData pd(buf, imgSize);
|
||
|
fPixelData.append(1, &pd);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
uint32_t imgSizePadded = (imgSize + 3) & ~3;
|
||
|
buf += imgSizePadded;
|
||
|
bytesLeft -= imgSizePadded;
|
||
|
}
|
||
|
|
||
|
return bytesLeft == 0;
|
||
|
}
|
||
|
|
||
|
bool SkKTXFile::is_ktx(const uint8_t *data) {
|
||
|
return 0 == memcmp(KTX_FILE_IDENTIFIER, data, KTX_FILE_IDENTIFIER_SIZE);
|
||
|
}
|
||
|
|
||
|
bool SkKTXFile::is_ktx(SkStreamRewindable* stream) {
|
||
|
// Read the KTX header and make sure it's valid.
|
||
|
unsigned char buf[KTX_FILE_IDENTIFIER_SIZE];
|
||
|
bool largeEnough =
|
||
|
stream->read((void*)buf, KTX_FILE_IDENTIFIER_SIZE) == KTX_FILE_IDENTIFIER_SIZE;
|
||
|
stream->rewind();
|
||
|
if (!largeEnough) {
|
||
|
return false;
|
||
|
}
|
||
|
return is_ktx(buf);
|
||
|
}
|