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
This commit is contained in:
parent
b0b0feb71f
commit
c250d2e4ab
@ -14,7 +14,8 @@
|
||||
'../third_party/ktx/ktx.cpp',
|
||||
],
|
||||
'dependencies': [
|
||||
'core.gyp:*'
|
||||
'core.gyp:*',
|
||||
'etc1.gyp:libetc1',
|
||||
],
|
||||
'direct_dependent_settings': {
|
||||
'include_dirs': [
|
||||
|
@ -104,6 +104,7 @@
|
||||
'../tests/ImageFilterTest.cpp',
|
||||
'../tests/InfRectTest.cpp',
|
||||
'../tests/JpegTest.cpp',
|
||||
'../tests/KtxTest.cpp',
|
||||
'../tests/LListTest.cpp',
|
||||
'../tests/LayerDrawLooperTest.cpp',
|
||||
'../tests/LayerRasterizerTest.cpp',
|
||||
|
@ -26,6 +26,7 @@ public:
|
||||
kPNG_Type,
|
||||
kWBMP_Type,
|
||||
kWEBP_Type,
|
||||
kKTX_Type,
|
||||
};
|
||||
static SkImageEncoder* Create(Type);
|
||||
|
||||
@ -96,6 +97,7 @@ protected:
|
||||
DECLARE_ENCODER_CREATOR(ARGBImageEncoder);
|
||||
DECLARE_ENCODER_CREATOR(JPEGImageEncoder);
|
||||
DECLARE_ENCODER_CREATOR(PNGImageEncoder);
|
||||
DECLARE_ENCODER_CREATOR(KTXImageEncoder);
|
||||
DECLARE_ENCODER_CREATOR(WEBPImageEncoder);
|
||||
|
||||
// Typedef to make registering encoder callback easier
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "SkColorPriv.h"
|
||||
#include "SkImageDecoder.h"
|
||||
#include "SkPixelRef.h"
|
||||
#include "SkScaledBitmapSampler.h"
|
||||
#include "SkStream.h"
|
||||
#include "SkStreamHelpers.h"
|
||||
@ -73,7 +74,7 @@ bool SkKTXImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
|
||||
bm->setConfig(SkBitmap::kARGB_8888_Config,
|
||||
sampler.scaledWidth(), sampler.scaledHeight(),
|
||||
0,
|
||||
ktxFile.isRGBA8()? kUnpremul_SkAlphaType : kOpaque_SkAlphaType);
|
||||
ktxFile.isRGBA8()? kPremul_SkAlphaType : kOpaque_SkAlphaType);
|
||||
if (SkImageDecoder::kDecodeBounds_Mode == mode) {
|
||||
return true;
|
||||
}
|
||||
@ -135,6 +136,15 @@ bool SkKTXImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
|
||||
|
||||
} else if (ktxFile.isRGBA8()) {
|
||||
|
||||
// If we know that the image contains premultiplied alpha, then
|
||||
// don't premultiply it upon decoding.
|
||||
bool setRequireUnpremul = false;
|
||||
const SkString premulKey("KTXPremultipliedAlpha");
|
||||
if (ktxFile.getValueForKey(premulKey) == SkString("True")) {
|
||||
this->setRequireUnpremultipliedColors(true);
|
||||
setRequireUnpremul = true;
|
||||
}
|
||||
|
||||
// Uncompressed RGBA data
|
||||
if (!sampler.begin(bm, SkScaledBitmapSampler::kRGBA, *this)) {
|
||||
return false;
|
||||
@ -150,14 +160,83 @@ bool SkKTXImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
|
||||
srcRow += sampler.srcDY() * srcRowBytes;
|
||||
}
|
||||
|
||||
// Reset this in case the decoder needs to be used again.
|
||||
if (setRequireUnpremul) {
|
||||
this->setRequireUnpremultipliedColors(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// KTX Image Encoder
|
||||
//
|
||||
// This encoder takes a best guess at how to encode the bitmap passed to it. If
|
||||
// there is an installed discardable pixel ref with existing PKM data, then we
|
||||
// will repurpose the existing ETC1 data into a KTX file. If the data contains
|
||||
// KTX data, then we simply return a copy of the same data. For all other files,
|
||||
// the underlying KTX library tries to do its best to encode the appropriate
|
||||
// data specified by the bitmap based on the config. (i.e. kAlpha8_Config will
|
||||
// be represented as a full resolution 8-bit image dump with the appropriate
|
||||
// OpenGL defines in the header).
|
||||
|
||||
class SkKTXImageEncoder : public SkImageEncoder {
|
||||
protected:
|
||||
virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
|
||||
|
||||
private:
|
||||
virtual bool encodePKM(SkWStream* stream, const SkData *data);
|
||||
typedef SkImageEncoder INHERITED;
|
||||
};
|
||||
|
||||
bool SkKTXImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap, int) {
|
||||
SkAutoDataUnref data(bitmap.pixelRef()->refEncodedData());
|
||||
|
||||
// Is this even encoded data?
|
||||
if (NULL != data) {
|
||||
const uint8_t *bytes = data->bytes();
|
||||
if (etc1_pkm_is_valid(bytes)) {
|
||||
return this->encodePKM(stream, data);
|
||||
}
|
||||
|
||||
// Is it a KTX file??
|
||||
if (SkKTXFile::is_ktx(bytes)) {
|
||||
return stream->write(bytes, data->size());
|
||||
}
|
||||
|
||||
// If it's neither a KTX nor a PKM, then we need to
|
||||
// get at the actual pixels, so fall through and decompress...
|
||||
}
|
||||
|
||||
return SkKTXFile::WriteBitmapToKTX(stream, bitmap);
|
||||
}
|
||||
|
||||
bool SkKTXImageEncoder::encodePKM(SkWStream* stream, const SkData *data) {
|
||||
const uint8_t* bytes = data->bytes();
|
||||
SkASSERT(etc1_pkm_is_valid(bytes));
|
||||
|
||||
etc1_uint32 width = etc1_pkm_get_width(bytes);
|
||||
etc1_uint32 height = etc1_pkm_get_height(bytes);
|
||||
|
||||
// ETC1 Data is stored as compressed 4x4 pixel blocks, so we must make sure
|
||||
// that our dimensions are valid.
|
||||
if (width == 0 || (width & 3) != 0 || height == 0 || (height & 3) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Advance pointer to etc1 data.
|
||||
bytes += ETC_PKM_HEADER_SIZE;
|
||||
|
||||
return SkKTXFile::WriteETC1ToKTX(stream, bytes, width, height);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
DEFINE_DECODER_CREATOR(KTXImageDecoder);
|
||||
DEFINE_ENCODER_CREATOR(KTXImageEncoder);
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static SkImageDecoder* sk_libktx_dfactory(SkStreamRewindable* stream) {
|
||||
@ -167,8 +246,6 @@ static SkImageDecoder* sk_libktx_dfactory(SkStreamRewindable* stream) {
|
||||
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;
|
||||
@ -176,4 +253,10 @@ static SkImageDecoder::Format get_format_ktx(SkStreamRewindable* stream) {
|
||||
return SkImageDecoder::kUnknown_Format;
|
||||
}
|
||||
|
||||
SkImageEncoder* sk_libktx_efactory(SkImageEncoder::Type t) {
|
||||
return (SkImageEncoder::kKTX_Type == t) ? SkNEW(SkKTXImageEncoder) : NULL;
|
||||
}
|
||||
|
||||
static SkImageDecoder_DecodeReg gReg(sk_libktx_dfactory);
|
||||
static SkImageDecoder_FormatReg gFormatReg(get_format_ktx);
|
||||
static SkImageEncoder_EncodeReg gEReg(sk_libktx_efactory);
|
||||
|
168
tests/KtxTest.cpp
Normal file
168
tests/KtxTest.cpp
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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 "SkBitmap.h"
|
||||
#include "SkData.h"
|
||||
#include "SkDecodingImageGenerator.h"
|
||||
#include "SkForceLinking.h"
|
||||
#include "SkImageDecoder.h"
|
||||
#include "SkOSFile.h"
|
||||
#include "SkRandom.h"
|
||||
#include "SkStream.h"
|
||||
#include "Test.h"
|
||||
|
||||
__SK_FORCE_IMAGE_DECODER_LINKING;
|
||||
|
||||
/**
|
||||
* First, make sure that writing an 8-bit RGBA KTX file and then
|
||||
* reading it produces the same bitmap.
|
||||
*/
|
||||
DEF_TEST(KtxReadWrite, reporter) {
|
||||
|
||||
// Random number generator with explicit seed for reproducibility
|
||||
SkRandom rand(0x1005cbad);
|
||||
|
||||
SkBitmap bm8888;
|
||||
bm8888.setConfig(SkBitmap::kARGB_8888_Config, 128, 128);
|
||||
|
||||
bool pixelsAllocated = bm8888.allocPixels();
|
||||
REPORTER_ASSERT(reporter, pixelsAllocated);
|
||||
|
||||
uint8_t *pixels = reinterpret_cast<uint8_t*>(bm8888.getPixels());
|
||||
REPORTER_ASSERT(reporter, NULL != pixels);
|
||||
|
||||
if (NULL == pixels) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t *row = pixels;
|
||||
for (int y = 0; y < bm8888.height(); ++y) {
|
||||
for (int x = 0; x < bm8888.width(); ++x) {
|
||||
uint8_t a = rand.nextRangeU(0, 255);
|
||||
uint8_t r = rand.nextRangeU(0, 255);
|
||||
uint8_t g = rand.nextRangeU(0, 255);
|
||||
uint8_t b = rand.nextRangeU(0, 255);
|
||||
|
||||
SkPMColor &pixel = *(reinterpret_cast<SkPMColor*>(row + x*sizeof(SkPMColor)));
|
||||
pixel = SkPreMultiplyARGB(a, r, g, b);
|
||||
}
|
||||
row += bm8888.rowBytes();
|
||||
}
|
||||
REPORTER_ASSERT(reporter, !(bm8888.empty()));
|
||||
|
||||
SkAutoDataUnref encodedData(SkImageEncoder::EncodeData(bm8888, SkImageEncoder::kKTX_Type, 0));
|
||||
REPORTER_ASSERT(reporter, NULL != encodedData);
|
||||
|
||||
SkAutoTUnref<SkMemoryStream> stream(SkNEW_ARGS(SkMemoryStream, (encodedData)));
|
||||
REPORTER_ASSERT(reporter, NULL != stream);
|
||||
|
||||
SkBitmap decodedBitmap;
|
||||
bool imageDecodeSuccess = SkImageDecoder::DecodeStream(stream, &decodedBitmap);
|
||||
REPORTER_ASSERT(reporter, imageDecodeSuccess);
|
||||
|
||||
REPORTER_ASSERT(reporter, decodedBitmap.config() == bm8888.config());
|
||||
REPORTER_ASSERT(reporter, decodedBitmap.alphaType() == bm8888.alphaType());
|
||||
REPORTER_ASSERT(reporter, decodedBitmap.width() == bm8888.width());
|
||||
REPORTER_ASSERT(reporter, decodedBitmap.height() == bm8888.height());
|
||||
REPORTER_ASSERT(reporter, !(decodedBitmap.empty()));
|
||||
|
||||
uint8_t *decodedPixels = reinterpret_cast<uint8_t*>(decodedBitmap.getPixels());
|
||||
REPORTER_ASSERT(reporter, NULL != decodedPixels);
|
||||
REPORTER_ASSERT(reporter, decodedBitmap.getSize() == bm8888.getSize());
|
||||
|
||||
if (NULL == decodedPixels) {
|
||||
return;
|
||||
}
|
||||
|
||||
REPORTER_ASSERT(reporter, memcmp(decodedPixels, pixels, decodedBitmap.getSize()) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Next test is to see whether or not reading an unpremultiplied KTX file accurately
|
||||
* creates a premultiplied buffer...
|
||||
*/
|
||||
DEF_TEST(KtxReadUnpremul, reporter) {
|
||||
|
||||
static const uint8_t kHalfWhiteKTX[] = {
|
||||
0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, // First twelve bytes is magic
|
||||
0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A, // KTX identifier string
|
||||
0x01, 0x02, 0x03, 0x04, // Then magic endian specifier
|
||||
0x01, 0x14, 0x00, 0x00, // uint32_t fGLType;
|
||||
0x01, 0x00, 0x00, 0x00, // uint32_t fGLTypeSize;
|
||||
0x08, 0x19, 0x00, 0x00, // uint32_t fGLFormat;
|
||||
0x58, 0x80, 0x00, 0x00, // uint32_t fGLInternalFormat;
|
||||
0x08, 0x19, 0x00, 0x00, // uint32_t fGLBaseInternalFormat;
|
||||
0x02, 0x00, 0x00, 0x00, // uint32_t fPixelWidth;
|
||||
0x02, 0x00, 0x00, 0x00, // uint32_t fPixelHeight;
|
||||
0x00, 0x00, 0x00, 0x00, // uint32_t fPixelDepth;
|
||||
0x00, 0x00, 0x00, 0x00, // uint32_t fNumberOfArrayElements;
|
||||
0x01, 0x00, 0x00, 0x00, // uint32_t fNumberOfFaces;
|
||||
0x01, 0x00, 0x00, 0x00, // uint32_t fNumberOfMipmapLevels;
|
||||
0x00, 0x00, 0x00, 0x00, // uint32_t fBytesOfKeyValueData;
|
||||
0x10, 0x00, 0x00, 0x00, // image size: 2x2 image of RGBA = 4 * 4 = 16 bytes
|
||||
0xFF, 0xFF, 0xFF, 0x80, // Pixel 1
|
||||
0xFF, 0xFF, 0xFF, 0x80, // Pixel 2
|
||||
0xFF, 0xFF, 0xFF, 0x80, // Pixel 3
|
||||
0xFF, 0xFF, 0xFF, 0x80};// Pixel 4
|
||||
|
||||
SkAutoTUnref<SkMemoryStream> stream(
|
||||
SkNEW_ARGS(SkMemoryStream, (kHalfWhiteKTX, sizeof(kHalfWhiteKTX))));
|
||||
REPORTER_ASSERT(reporter, NULL != stream);
|
||||
|
||||
SkBitmap decodedBitmap;
|
||||
bool imageDecodeSuccess = SkImageDecoder::DecodeStream(stream, &decodedBitmap);
|
||||
REPORTER_ASSERT(reporter, imageDecodeSuccess);
|
||||
|
||||
REPORTER_ASSERT(reporter, decodedBitmap.config() == SkBitmap::kARGB_8888_Config);
|
||||
REPORTER_ASSERT(reporter, decodedBitmap.alphaType() == kPremul_SkAlphaType);
|
||||
REPORTER_ASSERT(reporter, decodedBitmap.width() == 2);
|
||||
REPORTER_ASSERT(reporter, decodedBitmap.height() == 2);
|
||||
REPORTER_ASSERT(reporter, !(decodedBitmap.empty()));
|
||||
|
||||
uint8_t *decodedPixels = reinterpret_cast<uint8_t*>(decodedBitmap.getPixels());
|
||||
REPORTER_ASSERT(reporter, NULL != decodedPixels);
|
||||
|
||||
uint8_t *row = decodedPixels;
|
||||
for (int j = 0; j < decodedBitmap.height(); ++j) {
|
||||
for (int i = 0; i < decodedBitmap.width(); ++i) {
|
||||
SkPMColor pixel = *(reinterpret_cast<SkPMColor*>(row + i*sizeof(SkPMColor)));
|
||||
REPORTER_ASSERT(reporter, SkPreMultiplyARGB(0x80, 0xFF, 0xFF, 0xFF) == pixel);
|
||||
}
|
||||
row += decodedBitmap.rowBytes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finally, make sure that if we get ETC1 data from a PKM file that we can then
|
||||
* accurately write it out into a KTX file (i.e. transferring the ETC1 data from
|
||||
* the PKM to the KTX should produce an identical KTX to the one we have on file)
|
||||
*/
|
||||
DEF_TEST(KtxReexportPKM, reporter) {
|
||||
SkString resourcePath = skiatest::Test::GetResourcePath();
|
||||
SkString filename = SkOSPath::SkPathJoin(resourcePath.c_str(), "mandrill_128.pkm");
|
||||
|
||||
// Load PKM file into a bitmap
|
||||
SkBitmap etcBitmap;
|
||||
SkAutoTUnref<SkData> fileData(SkData::NewFromFileName(filename.c_str()));
|
||||
REPORTER_ASSERT(reporter, NULL != fileData);
|
||||
|
||||
bool installDiscardablePixelRefSuccess =
|
||||
SkInstallDiscardablePixelRef(
|
||||
SkDecodingImageGenerator::Create(
|
||||
fileData, SkDecodingImageGenerator::Options()), &etcBitmap);
|
||||
REPORTER_ASSERT(reporter, installDiscardablePixelRefSuccess);
|
||||
|
||||
// Write the bitmap out to a KTX file.
|
||||
SkData *ktxDataPtr = SkImageEncoder::EncodeData(etcBitmap, SkImageEncoder::kKTX_Type, 0);
|
||||
SkAutoDataUnref newKtxData(ktxDataPtr);
|
||||
REPORTER_ASSERT(reporter, NULL != ktxDataPtr);
|
||||
|
||||
// See is this data is identical to data in existing ktx file.
|
||||
SkString ktxFilename = SkOSPath::SkPathJoin(resourcePath.c_str(), "mandrill_128.ktx");
|
||||
SkAutoDataUnref oldKtxData(SkData::NewFromFileName(ktxFilename.c_str()));
|
||||
REPORTER_ASSERT(reporter, oldKtxData->equals(newKtxData));
|
||||
}
|
261
third_party/ktx/ktx.cpp
vendored
261
third_party/ktx/ktx.cpp
vendored
@ -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;
|
||||
}
|
||||
|
21
third_party/ktx/ktx.h
vendored
21
third_party/ktx/ktx.h
vendored
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user