| |
| /* |
| * 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 "SkBitmap.h" |
| #include "SkStream.h" |
| #include "SkEndian.h" |
| |
| #include "gl/GrGLDefines.h" |
| #include "GrConfig.h" |
| |
| #include "etc1.h" |
| |
| static inline uint32_t compressed_fmt_to_gl_define(SkTextureCompressor::Format fmt) { |
| static const uint32_t kGLDefineMap[SkTextureCompressor::kFormatCnt] = { |
| GR_GL_COMPRESSED_LUMINANCE_LATC1, // kLATC_Format |
| GR_GL_COMPRESSED_R11_EAC, // kR11_EAC_Format |
| GR_GL_COMPRESSED_ETC1_RGB8, // kETC1_Format |
| GR_GL_COMPRESSED_RGBA_ASTC_4x4_KHR, // kASTC_4x4_Format |
| GR_GL_COMPRESSED_RGBA_ASTC_5x4_KHR, // kASTC_5x4_Format |
| GR_GL_COMPRESSED_RGBA_ASTC_5x5_KHR, // kASTC_5x5_Format |
| GR_GL_COMPRESSED_RGBA_ASTC_6x5_KHR, // kASTC_6x5_Format |
| GR_GL_COMPRESSED_RGBA_ASTC_6x6_KHR, // kASTC_6x6_Format |
| GR_GL_COMPRESSED_RGBA_ASTC_8x5_KHR, // kASTC_8x5_Format |
| GR_GL_COMPRESSED_RGBA_ASTC_8x6_KHR, // kASTC_8x6_Format |
| GR_GL_COMPRESSED_RGBA_ASTC_8x8_KHR, // kASTC_8x8_Format |
| GR_GL_COMPRESSED_RGBA_ASTC_10x5_KHR, // kASTC_10x5_Format |
| GR_GL_COMPRESSED_RGBA_ASTC_10x6_KHR, // kASTC_10x6_Format |
| GR_GL_COMPRESSED_RGBA_ASTC_10x8_KHR, // kASTC_10x8_Format |
| GR_GL_COMPRESSED_RGBA_ASTC_10x10_KHR, // kASTC_10x10_Format |
| GR_GL_COMPRESSED_RGBA_ASTC_12x10_KHR, // kASTC_12x10_Format |
| GR_GL_COMPRESSED_RGBA_ASTC_12x12_KHR, // kASTC_12x12_Format |
| }; |
| |
| GR_STATIC_ASSERT(0 == SkTextureCompressor::kLATC_Format); |
| GR_STATIC_ASSERT(1 == SkTextureCompressor::kR11_EAC_Format); |
| GR_STATIC_ASSERT(2 == SkTextureCompressor::kETC1_Format); |
| GR_STATIC_ASSERT(3 == SkTextureCompressor::kASTC_4x4_Format); |
| GR_STATIC_ASSERT(4 == SkTextureCompressor::kASTC_5x4_Format); |
| GR_STATIC_ASSERT(5 == SkTextureCompressor::kASTC_5x5_Format); |
| GR_STATIC_ASSERT(6 == SkTextureCompressor::kASTC_6x5_Format); |
| GR_STATIC_ASSERT(7 == SkTextureCompressor::kASTC_6x6_Format); |
| GR_STATIC_ASSERT(8 == SkTextureCompressor::kASTC_8x5_Format); |
| GR_STATIC_ASSERT(9 == SkTextureCompressor::kASTC_8x6_Format); |
| GR_STATIC_ASSERT(10 == SkTextureCompressor::kASTC_8x8_Format); |
| GR_STATIC_ASSERT(11 == SkTextureCompressor::kASTC_10x5_Format); |
| GR_STATIC_ASSERT(12 == SkTextureCompressor::kASTC_10x6_Format); |
| GR_STATIC_ASSERT(13 == SkTextureCompressor::kASTC_10x8_Format); |
| GR_STATIC_ASSERT(14 == SkTextureCompressor::kASTC_10x10_Format); |
| GR_STATIC_ASSERT(15 == SkTextureCompressor::kASTC_12x10_Format); |
| GR_STATIC_ASSERT(16 == SkTextureCompressor::kASTC_12x12_Format); |
| GR_STATIC_ASSERT(SK_ARRAY_COUNT(kGLDefineMap) == SkTextureCompressor::kFormatCnt); |
| |
| return kGLDefineMap[fmt]; |
| } |
| |
| #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; |
| |
| // We ignore the null terminator when setting the string value. |
| this->fKey.set(key, bytesRead - 1); |
| if (bytesLeft > 0) { |
| this->fValue.set(value, bytesLeft - 1); |
| } else { |
| return false; |
| } |
| |
| 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(buf && 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; |
| } |
| |
| 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::isCompressedFormat(SkTextureCompressor::Format fmt) const { |
| if (!this->valid()) { |
| return false; |
| } |
| |
| // This has many aliases |
| bool isFmt = false; |
| if (fmt == SkTextureCompressor::kLATC_Format) { |
| isFmt = GR_GL_COMPRESSED_RED_RGTC1 == fHeader.fGLInternalFormat || |
| GR_GL_COMPRESSED_3DC_X == fHeader.fGLInternalFormat; |
| } |
| |
| return isFmt || compressed_fmt_to_gl_define(fmt) == 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; |
| } |
| |
| // We don't support width and/or height <= 0 |
| if (fHeader.fPixelWidth <= 0 || fHeader.fPixelHeight <= 0) { |
| 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); |
| } |
| |
| 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_ETC1_RGB8; |
| 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 SkColorType ct = bitmap.colorType(); |
| 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 (ct) { |
| case kIndex_8_SkColorType: |
| // 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 kUnknown_SkColorType: |
| // Bitmap hasn't been configured. |
| return false; |
| |
| case kAlpha_8_SkColorType: |
| 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 kRGB_565_SkColorType: |
| 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 kARGB_4444_SkColorType: |
| 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 kN32_SkColorType: |
| 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 = SkToU32(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 (kN32_SkColorType == ct) { |
| 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; |
| } |