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 /src | |
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 'src')
-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 |
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 |