Initial KTX file decoder
authorkrajcevski <krajcevski@google.com>
Tue, 3 Jun 2014 20:04:35 +0000 (13:04 -0700)
committerCommit bot <commit-bot@chromium.org>
Tue, 3 Jun 2014 20:04:35 +0000 (13:04 -0700)
R=bsalomon@google.com, robertphillips@google.com, halcanary@google.com, reed@google.com

Author: krajcevski@google.com

Review URL: https://codereview.chromium.org/302333002

14 files changed:
gm/etc1bitmap.cpp
gyp/gpu.gyp
gyp/images.gyp
gyp/ktx.gyp [new file with mode: 0644]
include/core/SkImageDecoder.h
src/gpu/SkGr.cpp
src/images/SkForceLinking.cpp
src/images/SkImageDecoder.cpp
src/images/SkImageDecoder_ktx.cpp [new file with mode: 0644]
src/images/SkStreamHelpers.cpp
src/images/SkStreamHelpers.h
tests/ImageDecodingTest.cpp
third_party/ktx/ktx.cpp [new file with mode: 0644]
third_party/ktx/ktx.h [new file with mode: 0644]

index f61b514..ec71d8d 100644 (file)
@@ -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); )
index fd81d60..f61a515 100644 (file)
@@ -85,6 +85,7 @@
         'core.gyp:*',
         'utils.gyp:*',
         'etc1.gyp:libetc1',
+        'ktx.gyp:libSkKTX',
       ],
       'includes': [
         'gpu.gypi',
index 0062f24..eb9db0a 100644 (file)
@@ -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 (file)
index 0000000..2eaa941
--- /dev/null
@@ -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',
+      ],
+    },
+  }],
+}
index 881d7f6..950505a 100644 (file)
@@ -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. :(
index 596e574..31372cd 100644 (file)
@@ -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);
index dfe2d8a..2c9a389 100644 (file)
@@ -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) \
index 4752311..491d9aa 100644 (file)
@@ -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 (file)
index 0000000..bf3445e
--- /dev/null
@@ -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);
index 3e9ee45..c7c66b4 100644 (file)
@@ -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();
+}
index 7e766b7..008dd8e 100644 (file)
@@ -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
index b30f551..16f602b 100644 (file)
@@ -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 (file)
index 0000000..15c44fc
--- /dev/null
@@ -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 (file)
index 0000000..0e4ed9b
--- /dev/null
@@ -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