diff options
author | krajcevski <krajcevski@google.com> | 2014-06-03 13:04:35 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-06-03 13:04:35 -0700 |
commit | 99ffe24200d8940ceba20f6fbf8c460f994d3cd1 (patch) | |
tree | b5fb9e1104425e2d6c3d9fd4d0832fdd66d85706 /third_party | |
parent | 8a35140f3fbc0937b969e6f3356b83ee756ce065 (diff) |
Initial KTX file decoder
R=bsalomon@google.com, robertphillips@google.com, halcanary@google.com, reed@google.com
Author: krajcevski@google.com
Review URL: https://codereview.chromium.org/302333002
Diffstat (limited to 'third_party')
-rw-r--r-- | third_party/ktx/ktx.cpp | 242 | ||||
-rw-r--r-- | third_party/ktx/ktx.h | 129 |
2 files changed, 371 insertions, 0 deletions
diff --git a/third_party/ktx/ktx.cpp b/third_party/ktx/ktx.cpp new file mode 100644 index 0000000000..15c44fcd35 --- /dev/null +++ b/third_party/ktx/ktx.cpp @@ -0,0 +1,242 @@ + +/* + * 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); +} diff --git a/third_party/ktx/ktx.h b/third_party/ktx/ktx.h new file mode 100644 index 0000000000..0e4ed9b990 --- /dev/null +++ b/third_party/ktx/ktx.h @@ -0,0 +1,129 @@ + +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkKTXFile_DEFINED +#define SkKTXFile_DEFINED + +#include "SkData.h" +#include "SkTypes.h" +#include "SkTDArray.h" +#include "SkString.h" +#include "SkRefCnt.h" + +class SkStreamRewindable; + +// KTX Image File +// --- +// KTX is a general texture data storage file format ratified by the Khronos Group. As an +// overview, a KTX file contains all of the appropriate values needed to fully specify a +// texture in an OpenGL application, including the use of compressed data. +// +// A full format specification can be found here: +// http://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ + +class SkKTXFile { +public: + // The ownership of the data remains with the caller. This class is intended + // to be used as a logical wrapper around the data in order to properly + // access the pixels. + SkKTXFile(SkData* data) + : fData(data), fSwapBytes(false) + { + data->ref(); + fValid = this->readKTXFile(fData->bytes(), fData->size()); + } + + bool valid() const { return fValid; } + + int width() const { return static_cast<int>(fHeader.fPixelWidth); } + int height() const { return static_cast<int>(fHeader.fPixelHeight); } + + const uint8_t *pixelData(int mipmap = 0) const { + SkASSERT(!this->valid() || mipmap < fPixelData.count()); + return this->valid() ? fPixelData[mipmap].data() : NULL; + } + + int numMipmaps() const { return static_cast<int>(fHeader.fNumberOfMipmapLevels); } + + bool isETC1() const; + bool isRGBA8() const; + bool isRGB8() const; + + static bool is_ktx(const uint8_t *data); + static bool is_ktx(SkStreamRewindable* stream); + +private: + + // The blob holding the file data. + SkAutoTUnref<SkData> fData; + + // This header captures all of the data that describes the format + // of the image data in a KTX file. + struct Header { + uint32_t fGLType; + uint32_t fGLTypeSize; + uint32_t fGLFormat; + uint32_t fGLInternalFormat; + uint32_t fGLBaseInternalFormat; + uint32_t fPixelWidth; + uint32_t fPixelHeight; + uint32_t fPixelDepth; + uint32_t fNumberOfArrayElements; + uint32_t fNumberOfFaces; + uint32_t fNumberOfMipmapLevels; + uint32_t fBytesOfKeyValueData; + + Header() { memset(this, 0, sizeof(*this)); } + } fHeader; + + // A Key Value pair stored in the KTX file. There may be + // arbitrarily many of these. + class KeyValue { + public: + KeyValue(size_t size) : fDataSz(size) { } + bool readKeyAndValue(const uint8_t *data); + + private: + const size_t fDataSz; + SkString fKey; + SkString fValue; + }; + + // The pixel data for a single mipmap level in an image. Based on how + // the rest of the data is stored, this may be compressed, a cubemap, etc. + // The header will describe the format of this data. + class PixelData { + public: + PixelData(const uint8_t *ptr, size_t sz) : fDataSz(sz), fDataPtr(ptr) { } + const uint8_t *data() const { return fDataPtr; } + size_t dataSize() const { return fDataSz; } + private: + const size_t fDataSz; + const uint8_t *fDataPtr; + }; + + // This function is only called once from the constructor. It loads the data + // and populates the appropriate fields of this class + // (fKeyValuePairs, fPixelData, fSwapBytes) + bool readKTXFile(const uint8_t *data, size_t dataLen); + + SkTArray<KeyValue> fKeyValuePairs; + SkTDArray<PixelData> fPixelData; + bool fValid; + + // If the endianness of the platform is different than the file, + // then we need to do proper byte swapping. + bool fSwapBytes; + + // Read an integer from a buffer, advance the buffer, and swap + // bytes if fSwapBytes is set + uint32_t readInt(const uint8_t** buf, size_t* bytesLeft) const; +}; + +#endif // SkKTXFile_DEFINED |