/* * 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, // kASTC_4x4_Format GR_GL_COMPRESSED_RGBA_ASTC_5x4, // kASTC_5x4_Format GR_GL_COMPRESSED_RGBA_ASTC_5x5, // kASTC_5x5_Format GR_GL_COMPRESSED_RGBA_ASTC_6x5, // kASTC_6x5_Format GR_GL_COMPRESSED_RGBA_ASTC_6x6, // kASTC_6x6_Format GR_GL_COMPRESSED_RGBA_ASTC_8x5, // kASTC_8x5_Format GR_GL_COMPRESSED_RGBA_ASTC_8x6, // kASTC_8x6_Format GR_GL_COMPRESSED_RGBA_ASTC_8x8, // kASTC_8x8_Format GR_GL_COMPRESSED_RGBA_ASTC_10x5, // kASTC_10x5_Format GR_GL_COMPRESSED_RGBA_ASTC_10x6, // kASTC_10x6_Format GR_GL_COMPRESSED_RGBA_ASTC_10x8, // kASTC_10x8_Format GR_GL_COMPRESSED_RGBA_ASTC_10x10, // kASTC_10x10_Format GR_GL_COMPRESSED_RGBA_ASTC_12x10, // kASTC_12x10_Format GR_GL_COMPRESSED_RGBA_ASTC_12x12, // 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(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[], size_t size) { return size >= KTX_FILE_IDENTIFIER_SIZE && 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, KTX_FILE_IDENTIFIER_SIZE); } 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(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 pixmap'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::WritePixmapToKTX(SkWStream* stream, const SkPixmap& pixmap) { const SkColorType ct = pixmap.colorType(); const int width = pixmap.width(); const int height = pixmap.height(); const uint8_t* src = reinterpret_cast(pixmap.addr()); if (!src) { return false; } const size_t rowBytes = pixmap.rowBytes(); const int bytesPerPixel = pixmap.info().bytesPerPixel(); // 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 kvPairs; // Next, write the header based on the pixmap'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 pixmap to KTX unsupported.\n"); // VVV fall through VVV default: case kUnknown_SkColorType: // Pixmap 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 uint32_t dataSz = bytesPerPixel * width * height; if (0 >= bytesPerPixel) { 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(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 += rowBytes; } } else { for (int i = 0; i < height; ++i) { if (!stream->write(rowPtr, bytesPerPixel * width)) { return false; } rowPtr += rowBytes; } } return true; } bool SkKTXFile::WriteBitmapToKTX(SkWStream* stream, const SkBitmap& bitmap) { SkAutoLockPixels autoLockPixels(bitmap); SkPixmap pixmap; return bitmap.peekPixels(&pixmap) && SkKTXFile::WritePixmapToKTX(stream, pixmap); }