diff options
-rw-r--r-- | gm/etc1bitmap.cpp | 45 | ||||
-rw-r--r-- | gyp/gpu.gyp | 1 | ||||
-rw-r--r-- | gyp/images.gyp | 2 | ||||
-rw-r--r-- | gyp/ktx.gyp | 25 | ||||
-rw-r--r-- | include/core/SkImageDecoder.h | 5 | ||||
-rw-r--r-- | src/gpu/SkGr.cpp | 45 | ||||
-rw-r--r-- | src/images/SkForceLinking.cpp | 1 | ||||
-rw-r--r-- | src/images/SkImageDecoder.cpp | 2 | ||||
-rw-r--r-- | src/images/SkImageDecoder_ktx.cpp | 179 | ||||
-rw-r--r-- | src/images/SkStreamHelpers.cpp | 27 | ||||
-rw-r--r-- | src/images/SkStreamHelpers.h | 9 | ||||
-rw-r--r-- | tests/ImageDecodingTest.cpp | 3 | ||||
-rw-r--r-- | third_party/ktx/ktx.cpp | 242 | ||||
-rw-r--r-- | third_party/ktx/ktx.h | 129 |
14 files changed, 693 insertions, 22 deletions
diff --git a/gm/etc1bitmap.cpp b/gm/etc1bitmap.cpp index f61b514ecb..ec71d8daa5 100644 --- a/gm/etc1bitmap.cpp +++ b/gm/etc1bitmap.cpp @@ -15,7 +15,7 @@ namespace skiagm { /** - * Test decoding an image from a PKM file and then + * Test decoding an image from a PKM or KTX file and then * from compressed ETC1 data. */ class ETC1BitmapGM : public GM { @@ -25,18 +25,22 @@ public: protected: virtual SkString onShortName() SK_OVERRIDE { - return SkString("etc1bitmap"); + SkString str = SkString("etc1bitmap_"); + str.append(this->fileExtension()); + return str; } virtual SkISize onISize() SK_OVERRIDE { - return make_isize(512, 512); + return make_isize(128, 128); } - virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE { + virtual SkString fileExtension() const = 0; + virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE { SkBitmap bm; SkString filename = SkOSPath::SkPathJoin( - INHERITED::gResourcePath.c_str(), "mandrill_512.pkm"); + INHERITED::gResourcePath.c_str(), "mandrill_128."); + filename.append(this->fileExtension()); SkAutoTUnref<SkData> fileData(SkData::NewFromFileName(filename.c_str())); if (NULL == fileData) { @@ -58,8 +62,37 @@ private: typedef GM INHERITED; }; +// This class specializes ETC1BitmapGM to load the mandrill_128.pkm file. +class ETC1Bitmap_PKM_GM : public ETC1BitmapGM { +public: + ETC1Bitmap_PKM_GM() : ETC1BitmapGM() { } + virtual ~ETC1Bitmap_PKM_GM() { } + +protected: + + virtual SkString fileExtension() const SK_OVERRIDE { return SkString("pkm"); } + +private: + typedef ETC1BitmapGM INHERITED; +}; + +// This class specializes ETC1BitmapGM to load the mandrill_128.ktx file. +class ETC1Bitmap_KTX_GM : public ETC1BitmapGM { +public: + ETC1Bitmap_KTX_GM() : ETC1BitmapGM() { } + virtual ~ETC1Bitmap_KTX_GM() { } + +protected: + + virtual SkString fileExtension() const SK_OVERRIDE { return SkString("ktx"); } + +private: + typedef ETC1BitmapGM INHERITED; +}; + } // namespace skiagm ////////////////////////////////////////////////////////////////////////////// -DEF_GM( return SkNEW(skiagm::ETC1BitmapGM); ) +DEF_GM( return SkNEW(skiagm::ETC1Bitmap_PKM_GM); ) +DEF_GM( return SkNEW(skiagm::ETC1Bitmap_KTX_GM); ) diff --git a/gyp/gpu.gyp b/gyp/gpu.gyp index fd81d60314..f61a5159c1 100644 --- a/gyp/gpu.gyp +++ b/gyp/gpu.gyp @@ -85,6 +85,7 @@ 'core.gyp:*', 'utils.gyp:*', 'etc1.gyp:libetc1', + 'ktx.gyp:libSkKTX', ], 'includes': [ 'gpu.gypi', diff --git a/gyp/images.gyp b/gyp/images.gyp index 0062f24f1d..eb9db0a7e8 100644 --- a/gyp/images.gyp +++ b/gyp/images.gyp @@ -10,6 +10,7 @@ 'core.gyp:*', 'libjpeg.gyp:*', 'etc1.gyp:libetc1', + 'ktx.gyp:libSkKTX', 'libwebp.gyp:libwebp', 'utils.gyp:utils', ], @@ -61,6 +62,7 @@ # alphabetical order. '../src/images/SkImageDecoder_wbmp.cpp', '../src/images/SkImageDecoder_pkm.cpp', + '../src/images/SkImageDecoder_ktx.cpp', '../src/images/SkImageDecoder_libbmp.cpp', '../src/images/SkImageDecoder_libgif.cpp', '../src/images/SkImageDecoder_libico.cpp', diff --git a/gyp/ktx.gyp b/gyp/ktx.gyp new file mode 100644 index 0000000000..2eaa941b0d --- /dev/null +++ b/gyp/ktx.gyp @@ -0,0 +1,25 @@ +{ + 'variables': { + 'skia_warnings_as_errors': 0, + }, + 'targets': [ + { + 'target_name': 'libSkKTX', + 'type': 'static_library', + 'include_dirs' : [ + '../third_party/ktx', + '../src/gpu' + ], + 'sources': [ + '../third_party/ktx/ktx.cpp', + ], + 'dependencies': [ + 'core.gyp:*' + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '../third_party/ktx', + ], + }, + }], +} diff --git a/include/core/SkImageDecoder.h b/include/core/SkImageDecoder.h index 881d7f64ab..950505a4b5 100644 --- a/include/core/SkImageDecoder.h +++ b/include/core/SkImageDecoder.h @@ -38,8 +38,9 @@ public: kWBMP_Format, kWEBP_Format, kPKM_Format, + kKTX_Format, - kLastKnownFormat = kWEBP_Format, + kLastKnownFormat = kKTX_Format, }; /** Return the format of image this decoder can decode. If this decoder can decode multiple @@ -527,7 +528,7 @@ DECLARE_DECODER_CREATOR(PNGImageDecoder); DECLARE_DECODER_CREATOR(WBMPImageDecoder); DECLARE_DECODER_CREATOR(WEBPImageDecoder); DECLARE_DECODER_CREATOR(PKMImageDecoder); - +DECLARE_DECODER_CREATOR(KTXImageDecoder); // Typedefs to make registering decoder and formatter callbacks easier. // These have to be defined outside SkImageDecoder. :( diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp index 596e574198..31372cd02a 100644 --- a/src/gpu/SkGr.cpp +++ b/src/gpu/SkGr.cpp @@ -16,6 +16,7 @@ #include "GrDrawTargetCaps.h" #ifndef SK_IGNORE_ETC1_SUPPORT +# include "ktx.h" # include "etc1.h" #endif @@ -135,7 +136,7 @@ static void add_genID_listener(GrResourceKey key, SkPixelRef* pixelRef) { static GrTexture *load_etc1_texture(GrContext* ctx, const GrTextureParams* params, const SkBitmap &bm, GrTextureDesc desc) { - SkData *data = bm.pixelRef()->refEncodedData(); + SkAutoTUnref<SkData> data(bm.pixelRef()->refEncodedData()); // Is this even encoded data? if (NULL == data) { @@ -144,24 +145,40 @@ static GrTexture *load_etc1_texture(GrContext* ctx, // Is this a valid PKM encoded data? const uint8_t *bytes = data->bytes(); - if (!etc1_pkm_is_valid(bytes)) { - return NULL; - } + if (etc1_pkm_is_valid(bytes)) { + uint32_t encodedWidth = etc1_pkm_get_width(bytes); + uint32_t encodedHeight = etc1_pkm_get_height(bytes); + + // Does the data match the dimensions of the bitmap? If not, + // then we don't know how to scale the image to match it... + if (encodedWidth != static_cast<uint32_t>(bm.width()) || + encodedHeight != static_cast<uint32_t>(bm.height())) { + return NULL; + } + + // Everything seems good... skip ahead to the data. + bytes += ETC_PKM_HEADER_SIZE; + desc.fConfig = kETC1_GrPixelConfig; + } else if (SkKTXFile::is_ktx(bytes)) { + SkKTXFile ktx(data); - uint32_t encodedWidth = etc1_pkm_get_width(bytes); - uint32_t encodedHeight = etc1_pkm_get_height(bytes); + // Is it actually an ETC1 texture? + if (!ktx.isETC1()) { + return NULL; + } + + // Does the data match the dimensions of the bitmap? If not, + // then we don't know how to scale the image to match it... + if (ktx.width() != bm.width() || ktx.height() != bm.height()) { + return NULL; + } - // Does the data match the dimensions of the bitmap? If not, - // then we don't know how to scale the image to match it... - if (encodedWidth != static_cast<uint32_t>(bm.width()) || - encodedHeight != static_cast<uint32_t>(bm.height())) { + bytes = ktx.pixelData(); + desc.fConfig = kETC1_GrPixelConfig; + } else { return NULL; } - // Everything seems good... skip ahead to the data. - bytes += ETC_PKM_HEADER_SIZE; - desc.fConfig = kETC1_GrPixelConfig; - // This texture is likely to be used again so leave it in the cache GrCacheID cacheID; generate_bitmap_cache_id(bm, &cacheID); diff --git a/src/images/SkForceLinking.cpp b/src/images/SkForceLinking.cpp index dfe2d8aa12..2c9a38979a 100644 --- a/src/images/SkForceLinking.cpp +++ b/src/images/SkForceLinking.cpp @@ -19,6 +19,7 @@ int SkForceLinking(bool doNotPassTrue) { CreateBMPImageDecoder(); CreateICOImageDecoder(); CreatePKMImageDecoder(); + CreateKTXImageDecoder(); CreateWBMPImageDecoder(); // Only link GIF and PNG on platforms that build them. See images.gyp #if !defined(SK_BUILD_FOR_MAC) && !defined(SK_BUILD_FOR_WIN) && !defined(SK_BUILD_FOR_NACL) \ diff --git a/src/images/SkImageDecoder.cpp b/src/images/SkImageDecoder.cpp index 47523117f5..491d9aa72f 100644 --- a/src/images/SkImageDecoder.cpp +++ b/src/images/SkImageDecoder.cpp @@ -86,6 +86,8 @@ const char* SkImageDecoder::GetFormatName(Format format) { return "ICO"; case kPKM_Format: return "PKM"; + case kKTX_Format: + return "KTX"; case kJPEG_Format: return "JPEG"; case kPNG_Format: diff --git a/src/images/SkImageDecoder_ktx.cpp b/src/images/SkImageDecoder_ktx.cpp new file mode 100644 index 0000000000..bf3445e985 --- /dev/null +++ b/src/images/SkImageDecoder_ktx.cpp @@ -0,0 +1,179 @@ +/* + * 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 "SkColorPriv.h" +#include "SkImageDecoder.h" +#include "SkScaledBitmapSampler.h" +#include "SkStream.h" +#include "SkStreamHelpers.h" +#include "SkTypes.h" + +#include "ktx.h" +#include "etc1.h" + +///////////////////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////////////////// + +// KTX Image decoder +// --- +// 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. +// +// This decoder is meant to be used with an SkDiscardablePixelRef so that GPU backends +// can sniff the data before creating a texture. If they encounter a compressed format +// that they understand, they can then upload the data directly to the GPU. Otherwise, +// they will decode the data into a format that Skia supports. + +class SkKTXImageDecoder : public SkImageDecoder { +public: + SkKTXImageDecoder() { } + + virtual Format getFormat() const SK_OVERRIDE { + return kKTX_Format; + } + +protected: + virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE; + +private: + typedef SkImageDecoder INHERITED; +}; + +bool SkKTXImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { + // TODO: Implement SkStream::copyToData() that's cheap for memory and file streams + SkAutoDataUnref data(CopyStreamToData(stream)); + if (NULL == data) { + return false; + } + + SkKTXFile ktxFile(data); + if (!ktxFile.valid()) { + return false; + } + + const unsigned short width = ktxFile.width(); + const unsigned short height = ktxFile.height(); + + // should we allow the Chooser (if present) to pick a config for us??? + if (!this->chooseFromOneChoice(SkBitmap::kARGB_8888_Config, width, height)) { + return false; + } + + // Setup the sampler... + SkScaledBitmapSampler sampler(width, height, this->getSampleSize()); + + // Set the config... + bm->setConfig(SkBitmap::kARGB_8888_Config, + sampler.scaledWidth(), sampler.scaledHeight(), + 0, + ktxFile.isRGBA8()? kUnpremul_SkAlphaType : kOpaque_SkAlphaType); + if (SkImageDecoder::kDecodeBounds_Mode == mode) { + return true; + } + + // If we've made it this far, then we know how to grok the data. + if (!this->allocPixelRef(bm, NULL)) { + return false; + } + + // Lock the pixels, since we're about to write to them... + SkAutoLockPixels alp(*bm); + + if (ktxFile.isETC1()) { + if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) { + return false; + } + + // ETC1 Data is encoded as RGB pixels, so we should extract it as such + int nPixels = width * height; + SkAutoMalloc outRGBData(nPixels * 3); + etc1_byte *outRGBDataPtr = reinterpret_cast<etc1_byte *>(outRGBData.get()); + + // Decode ETC1 + const etc1_byte *buf = reinterpret_cast<const etc1_byte *>(ktxFile.pixelData()); + if (etc1_decode_image(buf, outRGBDataPtr, width, height, 3, width*3)) { + return false; + } + + // Set each of the pixels... + const int srcRowBytes = width * 3; + const int dstHeight = sampler.scaledHeight(); + const uint8_t *srcRow = reinterpret_cast<uint8_t *>(outRGBDataPtr); + srcRow += sampler.srcY0() * srcRowBytes; + for (int y = 0; y < dstHeight; ++y) { + sampler.next(srcRow); + srcRow += sampler.srcDY() * srcRowBytes; + } + + return true; + + } else if (ktxFile.isRGB8()) { + + // Uncompressed RGB data (without alpha) + if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) { + return false; + } + + // Just need to read RGB pixels + const int srcRowBytes = width * 3; + const int dstHeight = sampler.scaledHeight(); + const uint8_t *srcRow = reinterpret_cast<const uint8_t *>(ktxFile.pixelData()); + srcRow += sampler.srcY0() * srcRowBytes; + for (int y = 0; y < dstHeight; ++y) { + sampler.next(srcRow); + srcRow += sampler.srcDY() * srcRowBytes; + } + + return true; + + } else if (ktxFile.isRGBA8()) { + + // Uncompressed RGBA data + if (!sampler.begin(bm, SkScaledBitmapSampler::kRGBA, *this)) { + return false; + } + + // Just need to read RGBA pixels + const int srcRowBytes = width * 4; + const int dstHeight = sampler.scaledHeight(); + const uint8_t *srcRow = reinterpret_cast<const uint8_t *>(ktxFile.pixelData()); + srcRow += sampler.srcY0() * srcRowBytes; + for (int y = 0; y < dstHeight; ++y) { + sampler.next(srcRow); + srcRow += sampler.srcDY() * srcRowBytes; + } + + return true; + } + + return false; +} + +///////////////////////////////////////////////////////////////////////////////////////// +DEFINE_DECODER_CREATOR(KTXImageDecoder); +///////////////////////////////////////////////////////////////////////////////////////// + +static SkImageDecoder* sk_libktx_dfactory(SkStreamRewindable* stream) { + if (SkKTXFile::is_ktx(stream)) { + return SkNEW(SkKTXImageDecoder); + } + return NULL; +} + +static SkImageDecoder_DecodeReg gReg(sk_libktx_dfactory); + +static SkImageDecoder::Format get_format_ktx(SkStreamRewindable* stream) { + if (SkKTXFile::is_ktx(stream)) { + return SkImageDecoder::kKTX_Format; + } + return SkImageDecoder::kUnknown_Format; +} + +static SkImageDecoder_FormatReg gFormatReg(get_format_ktx); diff --git a/src/images/SkStreamHelpers.cpp b/src/images/SkStreamHelpers.cpp index 3e9ee45345..c7c66b4b0f 100644 --- a/src/images/SkStreamHelpers.cpp +++ b/src/images/SkStreamHelpers.cpp @@ -5,6 +5,7 @@ * found in the LICENSE file. */ +#include "SkData.h" #include "SkStream.h" #include "SkStreamHelpers.h" #include "SkTypes.h" @@ -38,3 +39,29 @@ size_t CopyStreamToStorage(SkAutoMalloc* storage, SkStream* stream) { tempStream.copyTo(dst); return length; } + +SkData *CopyStreamToData(SkStream* stream) { + SkASSERT(stream != NULL); + + if (stream->hasLength()) { + const size_t length = stream->getLength(); + void* dst = sk_malloc_throw(length); + if (stream->read(dst, length) != length) { + return 0; + } + return SkData::NewFromMalloc(dst, length); + } + + SkDynamicMemoryWStream tempStream; + // Arbitrary buffer size. + const size_t bufferSize = 256 * 1024; // 256KB + char buffer[bufferSize]; + SkDEBUGCODE(size_t debugLength = 0;) + do { + size_t bytesRead = stream->read(buffer, bufferSize); + tempStream.write(buffer, bytesRead); + SkDEBUGCODE(debugLength += bytesRead); + SkASSERT(tempStream.bytesWritten() == debugLength); + } while (!stream->isAtEnd()); + return tempStream.copyToData(); +} diff --git a/src/images/SkStreamHelpers.h b/src/images/SkStreamHelpers.h index 7e766b7ecc..008dd8e17a 100644 --- a/src/images/SkStreamHelpers.h +++ b/src/images/SkStreamHelpers.h @@ -10,6 +10,7 @@ class SkAutoMalloc; class SkStream; +class SkData; /** * Copy the provided stream to memory allocated by storage. @@ -24,4 +25,12 @@ class SkStream; */ size_t CopyStreamToStorage(SkAutoMalloc* storage, SkStream* stream); +/** + * Copy the provided stream to an SkData variable. Used by SkImageDecoder_libktx. + * @param stream SkStream to be copied into data. + * @return SkData* The resulting SkData after the copy. This data will have a + * ref count of one upon return and belongs to the caller. Returns NULL on failure. + */ +SkData *CopyStreamToData(SkStream* stream); + #endif // SkStreamHelpers_DEFINED diff --git a/tests/ImageDecodingTest.cpp b/tests/ImageDecodingTest.cpp index b30f551e0c..16f602b605 100644 --- a/tests/ImageDecodingTest.cpp +++ b/tests/ImageDecodingTest.cpp @@ -54,6 +54,9 @@ static bool skip_image_format(SkImageDecoder::Format format) { // decoders do not, so skip them as well. case SkImageDecoder::kICO_Format: case SkImageDecoder::kBMP_Format: + // KTX is a Texture format so it's not particularly clear how to + // decode the alpha from it. + case SkImageDecoder::kKTX_Format: // The rest of these are opaque. case SkImageDecoder::kPKM_Format: case SkImageDecoder::kWBMP_Format: 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 |