aboutsummaryrefslogtreecommitdiffhomepage
path: root/third_party
diff options
context:
space:
mode:
authorGravatar krajcevski <krajcevski@google.com>2014-06-06 06:16:28 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2014-06-06 06:16:28 -0700
commitc250d2e4abdbe8193357696518592af8a0b4555a (patch)
treeec333c67dd8a8fa3690ac4a2bbbcc8aab4e9120f /third_party
parentb0b0feb71f0841497c041aa4aca2b10e032224e0 (diff)
Initial KTX encoder
The encoder comes with tests to check that the encoding/decoding operations between ETC encoded bitmaps and ARGB bitmaps are sane. R=bsalomon@google.com, robertphillips@google.com Author: krajcevski@google.com Review URL: https://codereview.chromium.org/312353003
Diffstat (limited to 'third_party')
-rw-r--r--third_party/ktx/ktx.cpp261
-rw-r--r--third_party/ktx/ktx.h21
2 files changed, 277 insertions, 5 deletions
diff --git a/third_party/ktx/ktx.cpp b/third_party/ktx/ktx.cpp
index 15c44fcd35..5eaadef3bd 100644
--- a/third_party/ktx/ktx.cpp
+++ b/third_party/ktx/ktx.cpp
@@ -7,11 +7,14 @@
*/
#include "ktx.h"
+#include "SkBitmap.h"
#include "SkStream.h"
#include "SkEndian.h"
#include "gl/GrGLDefines.h"
+#include "etc1.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
@@ -39,9 +42,11 @@ bool SkKTXFile::KeyValue::readKeyAndValue(const uint8_t* data) {
++value;
size_t bytesLeft = this->fDataSz - bytesRead;
- this->fKey.set(key, bytesRead);
+
+ // We ignore the null terminator when setting the string value.
+ this->fKey.set(key, bytesRead - 1);
if (bytesLeft > 0) {
- this->fValue.set(value, bytesLeft);
+ this->fValue.set(value, bytesLeft - 1);
} else {
return false;
}
@@ -49,6 +54,42 @@ bool SkKTXFile::KeyValue::readKeyAndValue(const uint8_t* data) {
return true;
}
+bool SkKTXFile::KeyValue::writeKeyAndValueForKTX(SkWStream* strm) {
+ size_t bytesWritten = 0;
+ if (!strm->write(&(this->fDataSz), 4)) {
+ return false;
+ }
+
+ bytesWritten += 4;
+
+ // Here we know that C-strings must end with a null terminating
+ // character, so when we get a c_str(), it will have as many
+ // bytes of data as size() returns plus a zero, so we just
+ // write size() + 1 bytes into the stream.
+
+ size_t keySize = this->fKey.size() + 1;
+ if (!strm->write(this->fKey.c_str(), keySize)) {
+ return false;
+ }
+
+ bytesWritten += keySize;
+
+ size_t valueSize = this->fValue.size() + 1;
+ if (!strm->write(this->fValue.c_str(), valueSize)) {
+ return false;
+ }
+
+ bytesWritten += valueSize;
+
+ size_t bytesWrittenPadFour = (bytesWritten + 3) & ~3;
+ uint8_t nullBuf[4] = { 0, 0, 0, 0 };
+
+ size_t padding = bytesWrittenPadFour - bytesWritten;
+ SkASSERT(padding < 4);
+
+ return strm->write(nullBuf, padding);
+}
+
uint32_t SkKTXFile::readInt(const uint8_t** buf, size_t* bytesLeft) const {
SkASSERT(NULL != buf && NULL != bytesLeft);
@@ -71,6 +112,17 @@ uint32_t SkKTXFile::readInt(const uint8_t** buf, size_t* bytesLeft) const {
return result;
}
+SkString SkKTXFile::getValueForKey(const SkString& key) const {
+ const KeyValue *begin = this->fKeyValuePairs.begin();
+ const KeyValue *end = this->fKeyValuePairs.end();
+ for (const KeyValue *kv = begin; kv != end; ++kv) {
+ if (kv->key() == key) {
+ return kv->value();
+ }
+ }
+ return SkString();
+}
+
bool SkKTXFile::isETC1() const {
return this->valid() && GR_GL_COMPRESSED_RGB8_ETC1 == fHeader.fGLInternalFormat;
}
@@ -240,3 +292,208 @@ bool SkKTXFile::is_ktx(SkStreamRewindable* stream) {
}
return is_ktx(buf);
}
+
+SkKTXFile::KeyValue SkKTXFile::CreateKeyValue(const char *cstrKey, const char *cstrValue) {
+ SkString key(cstrKey);
+ SkString value(cstrValue);
+
+ // Size of buffer is length of string plus the null terminators...
+ size_t size = key.size() + 1 + value.size() + 1;
+
+ SkAutoSMalloc<256> buf(size);
+ uint8_t* kvBuf = reinterpret_cast<uint8_t*>(buf.get());
+ memcpy(kvBuf, key.c_str(), key.size() + 1);
+ memcpy(kvBuf + key.size() + 1, value.c_str(), value.size() + 1);
+
+ KeyValue kv(size);
+ SkAssertResult(kv.readKeyAndValue(kvBuf));
+ return kv;
+}
+
+bool SkKTXFile::WriteETC1ToKTX(SkWStream* stream, const uint8_t *etc1Data,
+ uint32_t width, uint32_t height) {
+ // First thing's first, write out the magic identifier and endianness...
+ if (!stream->write(KTX_FILE_IDENTIFIER, KTX_FILE_IDENTIFIER_SIZE)) {
+ return false;
+ }
+
+ if (!stream->write(&kKTX_ENDIANNESS_CODE, 4)) {
+ return false;
+ }
+
+ Header hdr;
+ hdr.fGLType = 0;
+ hdr.fGLTypeSize = 1;
+ hdr.fGLFormat = 0;
+ hdr.fGLInternalFormat = GR_GL_COMPRESSED_RGB8_ETC1;
+ hdr.fGLBaseInternalFormat = GR_GL_RGB;
+ hdr.fPixelWidth = width;
+ hdr.fPixelHeight = height;
+ hdr.fNumberOfArrayElements = 0;
+ hdr.fNumberOfFaces = 1;
+ hdr.fNumberOfMipmapLevels = 1;
+
+ // !FIXME! The spec suggests that we put KTXOrientation as a
+ // key value pair in the header, but that means that we'd have to
+ // pipe through the bitmap's orientation to properly do that.
+ hdr.fBytesOfKeyValueData = 0;
+
+ // Write the header
+ if (!stream->write(&hdr, sizeof(hdr))) {
+ return false;
+ }
+
+ // Write the size of the image data
+ etc1_uint32 dataSize = etc1_get_encoded_data_size(width, height);
+ if (!stream->write(&dataSize, 4)) {
+ return false;
+ }
+
+ // Write the actual image data
+ if (!stream->write(etc1Data, dataSize)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool SkKTXFile::WriteBitmapToKTX(SkWStream* stream, const SkBitmap& bitmap) {
+ const SkBitmap::Config config = bitmap.config();
+ SkAutoLockPixels alp(bitmap);
+
+ const int width = bitmap.width();
+ const int height = bitmap.width();
+ const uint8_t* src = reinterpret_cast<uint8_t*>(bitmap.getPixels());
+ if (NULL == bitmap.getPixels()) {
+ return false;
+ }
+
+ // First thing's first, write out the magic identifier and endianness...
+ if (!stream->write(KTX_FILE_IDENTIFIER, KTX_FILE_IDENTIFIER_SIZE) ||
+ !stream->write(&kKTX_ENDIANNESS_CODE, 4)) {
+ return false;
+ }
+
+ // Collect our key/value pairs...
+ SkTArray<KeyValue> kvPairs;
+
+ // Next, write the header based on the bitmap's config.
+ Header hdr;
+ switch (config) {
+ case SkBitmap::kIndex8_Config:
+ // There is a compressed format for this, but we don't support it yet.
+ SkDebugf("Writing indexed bitmap to KTX unsupported.\n");
+ // VVV fall through VVV
+ default:
+ case SkBitmap::kNo_Config:
+ // Bitmap hasn't been configured.
+ return false;
+
+ case SkBitmap::kA8_Config:
+ hdr.fGLType = GR_GL_UNSIGNED_BYTE;
+ hdr.fGLTypeSize = 1;
+ hdr.fGLFormat = GR_GL_RED;
+ hdr.fGLInternalFormat = GR_GL_R8;
+ hdr.fGLBaseInternalFormat = GR_GL_RED;
+ break;
+
+ case SkBitmap::kRGB_565_Config:
+ hdr.fGLType = GR_GL_UNSIGNED_SHORT_5_6_5;
+ hdr.fGLTypeSize = 2;
+ hdr.fGLFormat = GR_GL_RGB;
+ hdr.fGLInternalFormat = GR_GL_RGB;
+ hdr.fGLBaseInternalFormat = GR_GL_RGB;
+ break;
+
+ case SkBitmap::kARGB_4444_Config:
+ hdr.fGLType = GR_GL_UNSIGNED_SHORT_4_4_4_4;
+ hdr.fGLTypeSize = 2;
+ hdr.fGLFormat = GR_GL_RGBA;
+ hdr.fGLInternalFormat = GR_GL_RGBA4;
+ hdr.fGLBaseInternalFormat = GR_GL_RGBA;
+ kvPairs.push_back(CreateKeyValue("KTXPremultipliedAlpha", "True"));
+ break;
+
+ case SkBitmap::kARGB_8888_Config:
+ hdr.fGLType = GR_GL_UNSIGNED_BYTE;
+ hdr.fGLTypeSize = 1;
+ hdr.fGLFormat = GR_GL_RGBA;
+ hdr.fGLInternalFormat = GR_GL_RGBA8;
+ hdr.fGLBaseInternalFormat = GR_GL_RGBA;
+ kvPairs.push_back(CreateKeyValue("KTXPremultipliedAlpha", "True"));
+ break;
+ }
+
+ // Everything else in the header is shared.
+ hdr.fPixelWidth = width;
+ hdr.fPixelHeight = height;
+ hdr.fNumberOfArrayElements = 0;
+ hdr.fNumberOfFaces = 1;
+ hdr.fNumberOfMipmapLevels = 1;
+
+ // Calculate the key value data size
+ hdr.fBytesOfKeyValueData = 0;
+ for (KeyValue *kv = kvPairs.begin(); kv != kvPairs.end(); ++kv) {
+ // Key value size is the size of the key value data,
+ // four bytes for saying how big the key value size is
+ // and then additional bytes for padding to four byte boundary
+ size_t kvsize = kv->size();
+ kvsize += 4;
+ kvsize = (kvsize + 3) & ~3;
+ hdr.fBytesOfKeyValueData += kvsize;
+ }
+
+ // Write the header
+ if (!stream->write(&hdr, sizeof(hdr))) {
+ return false;
+ }
+
+ // Write out each key value pair
+ for (KeyValue *kv = kvPairs.begin(); kv != kvPairs.end(); ++kv) {
+ if (!kv->writeKeyAndValueForKTX(stream)) {
+ return false;
+ }
+ }
+
+ // Calculate the size of the data
+ int bpp = bitmap.bytesPerPixel();
+ uint32_t dataSz = bpp * width * height;
+
+ if (0 >= bpp) {
+ return false;
+ }
+
+ // Write it into the buffer
+ if (!stream->write(&dataSz, 4)) {
+ return false;
+ }
+
+ // Write the pixel data...
+ const uint8_t* rowPtr = src;
+ if (SkBitmap::kARGB_8888_Config == config) {
+ for (int j = 0; j < height; ++j) {
+ const uint32_t* pixelsPtr = reinterpret_cast<const uint32_t*>(rowPtr);
+ for (int i = 0; i < width; ++i) {
+ uint32_t pixel = pixelsPtr[i];
+ uint8_t dstPixel[4];
+ dstPixel[0] = pixel >> SK_R32_SHIFT;
+ dstPixel[1] = pixel >> SK_G32_SHIFT;
+ dstPixel[2] = pixel >> SK_B32_SHIFT;
+ dstPixel[3] = pixel >> SK_A32_SHIFT;
+ if (!stream->write(dstPixel, 4)) {
+ return false;
+ }
+ }
+ rowPtr += bitmap.rowBytes();
+ }
+ } else {
+ for (int i = 0; i < height; ++i) {
+ if (!stream->write(rowPtr, bpp*width)) {
+ return false;
+ }
+ rowPtr += bitmap.rowBytes();
+ }
+ }
+
+ return true;
+}
diff --git a/third_party/ktx/ktx.h b/third_party/ktx/ktx.h
index 0e4ed9b990..2f445a817a 100644
--- a/third_party/ktx/ktx.h
+++ b/third_party/ktx/ktx.h
@@ -16,7 +16,9 @@
#include "SkString.h"
#include "SkRefCnt.h"
+class SkBitmap;
class SkStreamRewindable;
+class SkWStream;
// KTX Image File
// ---
@@ -49,6 +51,11 @@ public:
return this->valid() ? fPixelData[mipmap].data() : NULL;
}
+ // If the decoded KTX file has the following key, then it will
+ // return the associated value. If not found, the empty string
+ // is returned.
+ SkString getValueForKey(const SkString& key) const;
+
int numMipmaps() const { return static_cast<int>(fHeader.fNumberOfMipmapLevels); }
bool isETC1() const;
@@ -58,6 +65,9 @@ public:
static bool is_ktx(const uint8_t *data);
static bool is_ktx(SkStreamRewindable* stream);
+ static bool WriteETC1ToKTX(SkWStream* stream, const uint8_t *etc1Data,
+ uint32_t width, uint32_t height);
+ static bool WriteBitmapToKTX(SkWStream* stream, const SkBitmap& bitmap);
private:
// The blob holding the file data.
@@ -88,13 +98,18 @@ private:
public:
KeyValue(size_t size) : fDataSz(size) { }
bool readKeyAndValue(const uint8_t *data);
-
+ size_t size() const { return fDataSz; }
+ const SkString& key() const { return fKey; }
+ const SkString& value() const { return fValue; }
+ bool writeKeyAndValueForKTX(SkWStream* strm);
private:
const size_t fDataSz;
- SkString fKey;
- SkString fValue;
+ SkString fKey;
+ SkString fValue;
};
+ static KeyValue CreateKeyValue(const char *key, const char *value);
+
// 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.