aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorGravatar krajcevski <krajcevski@google.com>2014-06-03 13:04:35 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2014-06-03 13:04:35 -0700
commit99ffe24200d8940ceba20f6fbf8c460f994d3cd1 (patch)
treeb5fb9e1104425e2d6c3d9fd4d0832fdd66d85706 /src
parent8a35140f3fbc0937b969e6f3356b83ee756ce065 (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 'src')
-rw-r--r--src/gpu/SkGr.cpp45
-rw-r--r--src/images/SkForceLinking.cpp1
-rw-r--r--src/images/SkImageDecoder.cpp2
-rw-r--r--src/images/SkImageDecoder_ktx.cpp179
-rw-r--r--src/images/SkStreamHelpers.cpp27
-rw-r--r--src/images/SkStreamHelpers.h9
6 files changed, 249 insertions, 14 deletions
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