Home | History | Annotate | Download | only in ktx
      1 
      2 /*
      3  * Copyright 2014 Google Inc.
      4  *
      5  * Use of this source code is governed by a BSD-style license that can be
      6  * found in the LICENSE file.
      7  */
      8 
      9 #include "ktx.h"
     10 #include "SkBitmap.h"
     11 #include "SkStream.h"
     12 #include "SkEndian.h"
     13 
     14 #include "gl/GrGLDefines.h"
     15 #include "GrConfig.h"
     16 
     17 #include "etc1.h"
     18 
     19 static inline uint32_t compressed_fmt_to_gl_define(SkTextureCompressor::Format fmt) {
     20     static const uint32_t kGLDefineMap[SkTextureCompressor::kFormatCnt] = {
     21         GR_GL_COMPRESSED_LUMINANCE_LATC1,      // kLATC_Format
     22         GR_GL_COMPRESSED_R11_EAC,              // kR11_EAC_Format
     23         GR_GL_COMPRESSED_ETC1_RGB8,            // kETC1_Format
     24         GR_GL_COMPRESSED_RGBA_ASTC_4x4_KHR,    // kASTC_4x4_Format
     25         GR_GL_COMPRESSED_RGBA_ASTC_5x4_KHR,    // kASTC_5x4_Format
     26         GR_GL_COMPRESSED_RGBA_ASTC_5x5_KHR,    // kASTC_5x5_Format
     27         GR_GL_COMPRESSED_RGBA_ASTC_6x5_KHR,    // kASTC_6x5_Format
     28         GR_GL_COMPRESSED_RGBA_ASTC_6x6_KHR,    // kASTC_6x6_Format
     29         GR_GL_COMPRESSED_RGBA_ASTC_8x5_KHR,    // kASTC_8x5_Format
     30         GR_GL_COMPRESSED_RGBA_ASTC_8x6_KHR,    // kASTC_8x6_Format
     31         GR_GL_COMPRESSED_RGBA_ASTC_8x8_KHR,    // kASTC_8x8_Format
     32         GR_GL_COMPRESSED_RGBA_ASTC_10x5_KHR,   // kASTC_10x5_Format
     33         GR_GL_COMPRESSED_RGBA_ASTC_10x6_KHR,   // kASTC_10x6_Format
     34         GR_GL_COMPRESSED_RGBA_ASTC_10x8_KHR,   // kASTC_10x8_Format
     35         GR_GL_COMPRESSED_RGBA_ASTC_10x10_KHR,  // kASTC_10x10_Format
     36         GR_GL_COMPRESSED_RGBA_ASTC_12x10_KHR,  // kASTC_12x10_Format
     37         GR_GL_COMPRESSED_RGBA_ASTC_12x12_KHR,  // kASTC_12x12_Format
     38     };
     39 
     40     GR_STATIC_ASSERT(0 == SkTextureCompressor::kLATC_Format);
     41     GR_STATIC_ASSERT(1 == SkTextureCompressor::kR11_EAC_Format);
     42     GR_STATIC_ASSERT(2 == SkTextureCompressor::kETC1_Format);
     43     GR_STATIC_ASSERT(3 == SkTextureCompressor::kASTC_4x4_Format);
     44     GR_STATIC_ASSERT(4 == SkTextureCompressor::kASTC_5x4_Format);
     45     GR_STATIC_ASSERT(5 == SkTextureCompressor::kASTC_5x5_Format);
     46     GR_STATIC_ASSERT(6 == SkTextureCompressor::kASTC_6x5_Format);
     47     GR_STATIC_ASSERT(7 == SkTextureCompressor::kASTC_6x6_Format);
     48     GR_STATIC_ASSERT(8 == SkTextureCompressor::kASTC_8x5_Format);
     49     GR_STATIC_ASSERT(9 == SkTextureCompressor::kASTC_8x6_Format);
     50     GR_STATIC_ASSERT(10 == SkTextureCompressor::kASTC_8x8_Format);
     51     GR_STATIC_ASSERT(11 == SkTextureCompressor::kASTC_10x5_Format);
     52     GR_STATIC_ASSERT(12 == SkTextureCompressor::kASTC_10x6_Format);
     53     GR_STATIC_ASSERT(13 == SkTextureCompressor::kASTC_10x8_Format);
     54     GR_STATIC_ASSERT(14 == SkTextureCompressor::kASTC_10x10_Format);
     55     GR_STATIC_ASSERT(15 == SkTextureCompressor::kASTC_12x10_Format);
     56     GR_STATIC_ASSERT(16 == SkTextureCompressor::kASTC_12x12_Format);
     57     GR_STATIC_ASSERT(SK_ARRAY_COUNT(kGLDefineMap) == SkTextureCompressor::kFormatCnt);
     58 
     59     return kGLDefineMap[fmt];
     60 }
     61 
     62 #define KTX_FILE_IDENTIFIER_SIZE 12
     63 static const uint8_t KTX_FILE_IDENTIFIER[KTX_FILE_IDENTIFIER_SIZE] = {
     64     0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
     65 };
     66 
     67 static const uint32_t kKTX_ENDIANNESS_CODE = 0x04030201;
     68 
     69 bool SkKTXFile::KeyValue::readKeyAndValue(const uint8_t* data) {
     70     const char *key = reinterpret_cast<const char *>(data);
     71     const char *value = key;
     72 
     73     size_t bytesRead = 0;
     74     while (*value != '\0' && bytesRead < this->fDataSz) {
     75         ++bytesRead;
     76         ++value;
     77     }
     78 
     79     // Error of some sort..
     80     if (bytesRead >= this->fDataSz) {
     81         return false;
     82     }
     83 
     84     // Read the zero terminator
     85     ++bytesRead;
     86     ++value;
     87 
     88     size_t bytesLeft = this->fDataSz - bytesRead;
     89 
     90     // We ignore the null terminator when setting the string value.
     91     this->fKey.set(key, bytesRead - 1);
     92     if (bytesLeft > 0) {
     93         this->fValue.set(value, bytesLeft - 1);
     94     } else {
     95         return false;
     96     }
     97 
     98     return true;
     99 }
    100 
    101 bool SkKTXFile::KeyValue::writeKeyAndValueForKTX(SkWStream* strm) {
    102     size_t bytesWritten = 0;
    103     if (!strm->write(&(this->fDataSz), 4)) {
    104         return false;
    105     }
    106 
    107     bytesWritten += 4;
    108 
    109     // Here we know that C-strings must end with a null terminating
    110     // character, so when we get a c_str(), it will have as many
    111     // bytes of data as size() returns plus a zero, so we just
    112     // write size() + 1 bytes into the stream.
    113 
    114     size_t keySize = this->fKey.size() + 1;
    115     if (!strm->write(this->fKey.c_str(), keySize)) {
    116         return false;
    117     }
    118 
    119     bytesWritten += keySize;
    120 
    121     size_t valueSize = this->fValue.size() + 1;
    122     if (!strm->write(this->fValue.c_str(), valueSize)) {
    123         return false;
    124     }
    125 
    126     bytesWritten += valueSize;
    127 
    128     size_t bytesWrittenPadFour = (bytesWritten + 3) & ~3;
    129     uint8_t nullBuf[4] = { 0, 0, 0, 0 };
    130 
    131     size_t padding = bytesWrittenPadFour - bytesWritten;
    132     SkASSERT(padding < 4);
    133 
    134     return strm->write(nullBuf, padding);
    135 }
    136 
    137 uint32_t SkKTXFile::readInt(const uint8_t** buf, size_t* bytesLeft) const {
    138     SkASSERT(buf && bytesLeft);
    139 
    140     uint32_t result;
    141 
    142     if (*bytesLeft < 4) {
    143         SkASSERT(false);
    144         return 0;
    145     }
    146 
    147     memcpy(&result, *buf, 4);
    148     *buf += 4;
    149 
    150     if (fSwapBytes) {
    151         SkEndianSwap32(result);
    152     }
    153 
    154     *bytesLeft -= 4;
    155 
    156     return result;
    157 }
    158 
    159 SkString SkKTXFile::getValueForKey(const SkString& key) const {
    160     const KeyValue *begin = this->fKeyValuePairs.begin();
    161     const KeyValue *end = this->fKeyValuePairs.end();
    162     for (const KeyValue *kv = begin; kv != end; ++kv) {
    163         if (kv->key() == key) {
    164             return kv->value();
    165         }
    166     }
    167     return SkString();
    168 }
    169 
    170 bool SkKTXFile::isCompressedFormat(SkTextureCompressor::Format fmt) const {
    171     if (!this->valid()) {
    172         return false;
    173     }
    174 
    175     // This has many aliases
    176     bool isFmt = false;
    177     if (fmt == SkTextureCompressor::kLATC_Format) {
    178         isFmt = GR_GL_COMPRESSED_RED_RGTC1 == fHeader.fGLInternalFormat ||
    179                 GR_GL_COMPRESSED_3DC_X == fHeader.fGLInternalFormat;
    180     }
    181 
    182     return isFmt || compressed_fmt_to_gl_define(fmt) == fHeader.fGLInternalFormat;
    183 }
    184 
    185 bool SkKTXFile::isRGBA8() const {
    186     return this->valid() && GR_GL_RGBA8 == fHeader.fGLInternalFormat;
    187 }
    188 
    189 bool SkKTXFile::isRGB8() const {
    190     return this->valid() && GR_GL_RGB8 == fHeader.fGLInternalFormat;
    191 }
    192 
    193 bool SkKTXFile::readKTXFile(const uint8_t* data, size_t dataLen) {
    194     const uint8_t *buf = data;
    195     size_t bytesLeft = dataLen;
    196 
    197     // Make sure original KTX header is there... this should have been checked
    198     // already by a call to is_ktx()
    199     SkASSERT(bytesLeft > KTX_FILE_IDENTIFIER_SIZE);
    200     SkASSERT(0 == memcmp(KTX_FILE_IDENTIFIER, buf, KTX_FILE_IDENTIFIER_SIZE));
    201     buf += KTX_FILE_IDENTIFIER_SIZE;
    202     bytesLeft -= KTX_FILE_IDENTIFIER_SIZE;
    203 
    204     // Read header, but first make sure that we have the proper space: we need
    205     // two 32-bit ints: 1 for endianness, and another for the mandatory image
    206     // size after the header.
    207     if (bytesLeft < 8 + sizeof(Header)) {
    208         return false;
    209     }
    210 
    211     uint32_t endianness = this->readInt(&buf, &bytesLeft);
    212     fSwapBytes = kKTX_ENDIANNESS_CODE != endianness;
    213 
    214     // Read header values
    215     fHeader.fGLType                = this->readInt(&buf, &bytesLeft);
    216     fHeader.fGLTypeSize            = this->readInt(&buf, &bytesLeft);
    217     fHeader.fGLFormat              = this->readInt(&buf, &bytesLeft);
    218     fHeader.fGLInternalFormat      = this->readInt(&buf, &bytesLeft);
    219     fHeader.fGLBaseInternalFormat  = this->readInt(&buf, &bytesLeft);
    220     fHeader.fPixelWidth            = this->readInt(&buf, &bytesLeft);
    221     fHeader.fPixelHeight           = this->readInt(&buf, &bytesLeft);
    222     fHeader.fPixelDepth            = this->readInt(&buf, &bytesLeft);
    223     fHeader.fNumberOfArrayElements = this->readInt(&buf, &bytesLeft);
    224     fHeader.fNumberOfFaces         = this->readInt(&buf, &bytesLeft);
    225     fHeader.fNumberOfMipmapLevels  = this->readInt(&buf, &bytesLeft);
    226     fHeader.fBytesOfKeyValueData   = this->readInt(&buf, &bytesLeft);
    227 
    228     // Check for things that we understand...
    229     {
    230         // First, we only support compressed formats and single byte
    231         // representations at the moment. If the internal format is
    232         // compressed, the the GLType field in the header must be zero.
    233         // In the future, we may support additional data types (such
    234         // as GL_UNSIGNED_SHORT_5_6_5)
    235         if (fHeader.fGLType != 0 && fHeader.fGLType != GR_GL_UNSIGNED_BYTE) {
    236             return false;
    237         }
    238 
    239         // This means that for well-formatted KTX files, the glTypeSize
    240         // field must be one...
    241         if (fHeader.fGLTypeSize != 1) {
    242             return false;
    243         }
    244 
    245         // We don't support 3D textures.
    246         if (fHeader.fPixelDepth > 1) {
    247             return false;
    248         }
    249 
    250         // We don't support texture arrays
    251         if (fHeader.fNumberOfArrayElements > 1) {
    252             return false;
    253         }
    254 
    255         // We don't support cube maps
    256         if (fHeader.fNumberOfFaces > 1) {
    257             return false;
    258         }
    259 
    260         // We don't support width and/or height <= 0
    261         if (fHeader.fPixelWidth <= 0 || fHeader.fPixelHeight <= 0) {
    262             return false;
    263         }
    264     }
    265 
    266     // Make sure that we have enough bytes left for the key/value
    267     // data according to what was said in the header.
    268     if (bytesLeft < fHeader.fBytesOfKeyValueData) {
    269         return false;
    270     }
    271 
    272     // Next read the key value pairs
    273     size_t keyValueBytesRead = 0;
    274     while (keyValueBytesRead < fHeader.fBytesOfKeyValueData) {
    275         uint32_t keyValueBytes = this->readInt(&buf, &bytesLeft);
    276         keyValueBytesRead += 4;
    277 
    278         if (keyValueBytes > bytesLeft) {
    279             return false;
    280         }
    281 
    282         KeyValue kv(keyValueBytes);
    283         if (!kv.readKeyAndValue(buf)) {
    284             return false;
    285         }
    286 
    287         fKeyValuePairs.push_back(kv);
    288 
    289         uint32_t keyValueBytesPadded = (keyValueBytes + 3) & ~3;
    290         buf += keyValueBytesPadded;
    291         keyValueBytesRead += keyValueBytesPadded;
    292         bytesLeft -= keyValueBytesPadded;
    293     }
    294 
    295     // Read the pixel data...
    296     int mipmaps = SkMax32(fHeader.fNumberOfMipmapLevels, 1);
    297     SkASSERT(mipmaps == 1);
    298 
    299     int arrayElements = SkMax32(fHeader.fNumberOfArrayElements, 1);
    300     SkASSERT(arrayElements == 1);
    301 
    302     int faces = SkMax32(fHeader.fNumberOfFaces, 1);
    303     SkASSERT(faces == 1);
    304 
    305     int depth = SkMax32(fHeader.fPixelDepth, 1);
    306     SkASSERT(depth == 1);
    307 
    308     for (int mipmap = 0; mipmap < mipmaps; ++mipmap) {
    309         // Make sure that we have at least 4 more bytes for the first image size
    310         if (bytesLeft < 4) {
    311             return false;
    312         }
    313 
    314         uint32_t imgSize = this->readInt(&buf, &bytesLeft);
    315 
    316         // Truncated file.
    317         if (bytesLeft < imgSize) {
    318             return false;
    319         }
    320 
    321         // !FIXME! If support is ever added for cube maps then the padding
    322         // needs to be taken into account here.
    323         for (int arrayElement = 0; arrayElement < arrayElements; ++arrayElement) {
    324             for (int face = 0; face < faces; ++face) {
    325                 for (int z = 0; z < depth; ++z) {
    326                     PixelData pd(buf, imgSize);
    327                     fPixelData.append(1, &pd);
    328                 }
    329             }
    330         }
    331 
    332         uint32_t imgSizePadded = (imgSize + 3) & ~3;
    333         buf += imgSizePadded;
    334         bytesLeft -= imgSizePadded;
    335     }
    336 
    337     return bytesLeft == 0;
    338 }
    339 
    340 bool SkKTXFile::is_ktx(const uint8_t *data) {
    341     return 0 == memcmp(KTX_FILE_IDENTIFIER, data, KTX_FILE_IDENTIFIER_SIZE);
    342 }
    343 
    344 bool SkKTXFile::is_ktx(SkStreamRewindable* stream) {
    345     // Read the KTX header and make sure it's valid.
    346     unsigned char buf[KTX_FILE_IDENTIFIER_SIZE];
    347     bool largeEnough =
    348         stream->read((void*)buf, KTX_FILE_IDENTIFIER_SIZE) == KTX_FILE_IDENTIFIER_SIZE;
    349     stream->rewind();
    350     if (!largeEnough) {
    351         return false;
    352     }
    353     return is_ktx(buf);
    354 }
    355 
    356 SkKTXFile::KeyValue SkKTXFile::CreateKeyValue(const char *cstrKey, const char *cstrValue) {
    357     SkString key(cstrKey);
    358     SkString value(cstrValue);
    359 
    360     // Size of buffer is length of string plus the null terminators...
    361     size_t size = key.size() + 1 + value.size() + 1;
    362 
    363     SkAutoSMalloc<256> buf(size);
    364     uint8_t* kvBuf = reinterpret_cast<uint8_t*>(buf.get());
    365     memcpy(kvBuf, key.c_str(), key.size() + 1);
    366     memcpy(kvBuf + key.size() + 1, value.c_str(), value.size() + 1);
    367 
    368     KeyValue kv(size);
    369     SkAssertResult(kv.readKeyAndValue(kvBuf));
    370     return kv;
    371 }
    372 
    373 bool SkKTXFile::WriteETC1ToKTX(SkWStream* stream, const uint8_t *etc1Data,
    374                                uint32_t width, uint32_t height) {
    375     // First thing's first, write out the magic identifier and endianness...
    376     if (!stream->write(KTX_FILE_IDENTIFIER, KTX_FILE_IDENTIFIER_SIZE)) {
    377         return false;
    378     }
    379 
    380     if (!stream->write(&kKTX_ENDIANNESS_CODE, 4)) {
    381         return false;
    382     }
    383 
    384     Header hdr;
    385     hdr.fGLType = 0;
    386     hdr.fGLTypeSize = 1;
    387     hdr.fGLFormat = 0;
    388     hdr.fGLInternalFormat = GR_GL_COMPRESSED_ETC1_RGB8;
    389     hdr.fGLBaseInternalFormat = GR_GL_RGB;
    390     hdr.fPixelWidth = width;
    391     hdr.fPixelHeight = height;
    392     hdr.fNumberOfArrayElements = 0;
    393     hdr.fNumberOfFaces = 1;
    394     hdr.fNumberOfMipmapLevels = 1;
    395 
    396     // !FIXME! The spec suggests that we put KTXOrientation as a
    397     // key value pair in the header, but that means that we'd have to
    398     // pipe through the bitmap's orientation to properly do that.
    399     hdr.fBytesOfKeyValueData = 0;
    400 
    401     // Write the header
    402     if (!stream->write(&hdr, sizeof(hdr))) {
    403         return false;
    404     }
    405 
    406     // Write the size of the image data
    407     etc1_uint32 dataSize = etc1_get_encoded_data_size(width, height);
    408     if (!stream->write(&dataSize, 4)) {
    409         return false;
    410     }
    411 
    412     // Write the actual image data
    413     if (!stream->write(etc1Data, dataSize)) {
    414         return false;
    415     }
    416 
    417     return true;
    418 }
    419 
    420 bool SkKTXFile::WriteBitmapToKTX(SkWStream* stream, const SkBitmap& bitmap) {
    421     const SkColorType ct = bitmap.colorType();
    422     SkAutoLockPixels alp(bitmap);
    423 
    424     const int width = bitmap.width();
    425     const int height = bitmap.width();
    426     const uint8_t* src = reinterpret_cast<uint8_t*>(bitmap.getPixels());
    427     if (NULL == bitmap.getPixels()) {
    428         return false;
    429     }
    430 
    431     // First thing's first, write out the magic identifier and endianness...
    432     if (!stream->write(KTX_FILE_IDENTIFIER, KTX_FILE_IDENTIFIER_SIZE) ||
    433         !stream->write(&kKTX_ENDIANNESS_CODE, 4)) {
    434         return false;
    435     }
    436 
    437     // Collect our key/value pairs...
    438     SkTArray<KeyValue> kvPairs;
    439 
    440     // Next, write the header based on the bitmap's config.
    441     Header hdr;
    442     switch (ct) {
    443         case kIndex_8_SkColorType:
    444             // There is a compressed format for this, but we don't support it yet.
    445             SkDebugf("Writing indexed bitmap to KTX unsupported.\n");
    446             // VVV fall through VVV
    447         default:
    448         case kUnknown_SkColorType:
    449             // Bitmap hasn't been configured.
    450             return false;
    451 
    452         case kAlpha_8_SkColorType:
    453             hdr.fGLType = GR_GL_UNSIGNED_BYTE;
    454             hdr.fGLTypeSize = 1;
    455             hdr.fGLFormat = GR_GL_RED;
    456             hdr.fGLInternalFormat = GR_GL_R8;
    457             hdr.fGLBaseInternalFormat = GR_GL_RED;
    458             break;
    459 
    460         case kRGB_565_SkColorType:
    461             hdr.fGLType = GR_GL_UNSIGNED_SHORT_5_6_5;
    462             hdr.fGLTypeSize = 2;
    463             hdr.fGLFormat = GR_GL_RGB;
    464             hdr.fGLInternalFormat = GR_GL_RGB;
    465             hdr.fGLBaseInternalFormat = GR_GL_RGB;
    466             break;
    467 
    468         case kARGB_4444_SkColorType:
    469             hdr.fGLType = GR_GL_UNSIGNED_SHORT_4_4_4_4;
    470             hdr.fGLTypeSize = 2;
    471             hdr.fGLFormat = GR_GL_RGBA;
    472             hdr.fGLInternalFormat = GR_GL_RGBA4;
    473             hdr.fGLBaseInternalFormat = GR_GL_RGBA;
    474             kvPairs.push_back(CreateKeyValue("KTXPremultipliedAlpha", "True"));
    475             break;
    476 
    477         case kN32_SkColorType:
    478             hdr.fGLType = GR_GL_UNSIGNED_BYTE;
    479             hdr.fGLTypeSize = 1;
    480             hdr.fGLFormat = GR_GL_RGBA;
    481             hdr.fGLInternalFormat = GR_GL_RGBA8;
    482             hdr.fGLBaseInternalFormat = GR_GL_RGBA;
    483             kvPairs.push_back(CreateKeyValue("KTXPremultipliedAlpha", "True"));
    484             break;
    485     }
    486 
    487     // Everything else in the header is shared.
    488     hdr.fPixelWidth = width;
    489     hdr.fPixelHeight = height;
    490     hdr.fNumberOfArrayElements = 0;
    491     hdr.fNumberOfFaces = 1;
    492     hdr.fNumberOfMipmapLevels = 1;
    493 
    494     // Calculate the key value data size
    495     hdr.fBytesOfKeyValueData = 0;
    496     for (KeyValue *kv = kvPairs.begin(); kv != kvPairs.end(); ++kv) {
    497         // Key value size is the size of the key value data,
    498         // four bytes for saying how big the key value size is
    499         // and then additional bytes for padding to four byte boundary
    500         size_t kvsize = kv->size();
    501         kvsize += 4;
    502         kvsize = (kvsize + 3) & ~3;
    503         hdr.fBytesOfKeyValueData = SkToU32(hdr.fBytesOfKeyValueData + kvsize);
    504     }
    505 
    506     // Write the header
    507     if (!stream->write(&hdr, sizeof(hdr))) {
    508         return false;
    509     }
    510 
    511     // Write out each key value pair
    512     for (KeyValue *kv = kvPairs.begin(); kv != kvPairs.end(); ++kv) {
    513         if (!kv->writeKeyAndValueForKTX(stream)) {
    514             return false;
    515         }
    516     }
    517 
    518     // Calculate the size of the data
    519     int bpp = bitmap.bytesPerPixel();
    520     uint32_t dataSz = bpp * width * height;
    521 
    522     if (0 >= bpp) {
    523         return false;
    524     }
    525 
    526     // Write it into the buffer
    527     if (!stream->write(&dataSz, 4)) {
    528         return false;
    529     }
    530 
    531     // Write the pixel data...
    532     const uint8_t* rowPtr = src;
    533     if (kN32_SkColorType == ct) {
    534         for (int j = 0; j < height; ++j) {
    535             const uint32_t* pixelsPtr = reinterpret_cast<const uint32_t*>(rowPtr);
    536             for (int i = 0; i < width; ++i) {
    537                 uint32_t pixel = pixelsPtr[i];
    538                 uint8_t dstPixel[4];
    539                 dstPixel[0] = pixel >> SK_R32_SHIFT;
    540                 dstPixel[1] = pixel >> SK_G32_SHIFT;
    541                 dstPixel[2] = pixel >> SK_B32_SHIFT;
    542                 dstPixel[3] = pixel >> SK_A32_SHIFT;
    543                 if (!stream->write(dstPixel, 4)) {
    544                     return false;
    545                 }
    546             }
    547             rowPtr += bitmap.rowBytes();
    548         }
    549     } else {
    550         for (int i = 0; i < height; ++i) {
    551             if (!stream->write(rowPtr, bpp*width)) {
    552                 return false;
    553             }
    554             rowPtr += bitmap.rowBytes();
    555         }
    556     }
    557 
    558     return true;
    559 }
    560