void writeUncompressedData(QDataStream& stream, const ImageContainer& images, int pixelFormat) { // Mipmap offset if (images.hasMipmaps()) { writeZeroes(stream, MIPMAP_OFFSET_16BPP); } // Texture data, from smallest to largest mipmap for (int i=0; i<images.imageCount(); i++) { const QImage& img = images.getByIndex(i); // The 1x1 mipmap level is a bit special for YUV textures. Since there's only // one pixel, it can't be saved as YUV422, so save it as RGB565 instead. if (img.width() == 1 && img.height() == 1 && pixelFormat == PIXELFORMAT_YUV422) { convertAndWriteTexel(stream, img.pixel(0, 0), PIXELFORMAT_RGB565, true); continue; } const Twiddler twiddler(img.width(), img.height()); const int pixels = img.width() * img.height(); // Write all texels for this mipmap level in twiddled order for (int j=0; j<pixels; j++) { const int index = twiddler.index(j); const int x = index % img.width(); const int y = index / img.width(); convertAndWriteTexel(stream, img.pixel(x, y), pixelFormat, true); } } }
static void devectorizeARGB(const ImageContainer& srcImages, const QVector<Vec<16>>& vectors, const VectorQuantizer<16>& vq, int format, QVector<QImage>& indexedImages, QVector<quint64>& codebook) { int vindex = 0; for (int i=0; i<srcImages.imageCount(); i++) { const QSize size = srcImages.getByIndex(i).size(); if (size.width() == 1 || size.height() == 1) continue; QImage img(size.width()/2, size.height()/2, QImage::Format_Indexed8); img.setColorCount(256); for (int y=0; y<img.height(); y++) { for (int x=0; x<img.width(); x++) { const Vec<16>& vec = vectors[vindex]; int codeIndex = vq.findClosest(vec); img.setPixel(x, y, codeIndex); vindex++; } } indexedImages.push_back(img); } for (int i=0; i<vq.codeCount(); i++) { const Vec<16>& vec = vq.codeVector(i); QColor tl = QColor::fromRgbF(vec[1], vec[2], vec[3], vec[0]); QColor tr = QColor::fromRgbF(vec[5], vec[6], vec[7], vec[4]); QColor bl = QColor::fromRgbF(vec[9], vec[10], vec[11], vec[8]); QColor br = QColor::fromRgbF(vec[13], vec[14], vec[15], vec[12]); quint64 quad = packQuad(tl.rgba(), tr.rgba(), bl.rgba(), br.rgba(), format); codebook.push_back(quad); } }
// Divides the image into 2x2 pixel blocks and stores them as 16-dimensional // vectors, (A, R, G, B) * 4. static void vectorizeARGB(const ImageContainer& images, QVector<Vec<16>>& vectors) { for (int i=0; i<images.imageCount(); i++) { const QImage& img = images.getByIndex(i); // Ignore images smaller than this if (img.width() < MIN_MIPMAP_VQ || img.height() < MIN_MIPMAP_VQ) continue; for (int y=0; y<img.height(); y+=2) { for (int x=0; x<img.width(); x+=2) { Vec<16> vec; uint hash = 0; int offset = 0; for (int yy=y; yy<(y+2); yy++) { for (int xx=x; xx<(x+2); xx++) { QRgb pixel = img.pixel(xx, yy); argb2vec(pixel, vec, offset); hash = combineHash(pixel, hash); offset += 4; } } vec.setHash(hash); vectors.push_back(vec); } } } }
void convert16BPP(QDataStream& stream, const ImageContainer& images, int textureType) { const int pixelFormat = (textureType >> PIXELFORMAT_SHIFT) & PIXELFORMAT_MASK; if (textureType & FLAG_STRIDED) { writeStrideData(stream, images.getByIndex(0), pixelFormat); } else if (textureType & FLAG_COMPRESSED) { writeCompressedData(stream, images, pixelFormat); } else { writeUncompressedData(stream, images, pixelFormat); } }
// This function counts how many unique 2x2 16BPP pixel blocks there are in the image. // If there are <= maxCodes, it puts the unique blocks in 'codebook' and 'indexedImages' // will contain images that index the 'codebook' vector, resulting in quick "lossless" // compression, if possible. // It will keep counting blocks even if the block count exceeds maxCodes for the sole // purpose of reporting it back to the user. // Returns number of unique 2x2 16BPP pixel blocks in all images. static int encodeLossless(const ImageContainer& images, int pixelFormat, QVector<QImage>& indexedImages, QVector<quint64>& codebook, int maxCodes) { QHash<quint64, int> uniqueQuads; // Quad <=> index for (int i=0; i<images.imageCount(); i++) { const QImage& img = images.getByIndex(i); // Ignore images smaller than this if (img.width() < MIN_MIPMAP_VQ || img.height() < MIN_MIPMAP_VQ) continue; QImage indexedImage(img.width() / 2, img.height() / 2, QImage::Format_Indexed8); indexedImage.setColorCount(256); for (int y=0; y<img.height(); y+=2) { for (int x=0; x<img.width(); x+=2) { QRgb tl = img.pixel(x + 0, y + 0); QRgb tr = img.pixel(x + 1, y + 0); QRgb bl = img.pixel(x + 0, y + 1); QRgb br = img.pixel(x + 1, y + 1); quint64 quad = packQuad(tl, tr, bl, br, pixelFormat); if (!uniqueQuads.contains(quad)) uniqueQuads.insert(quad, uniqueQuads.size()); if (uniqueQuads.size() <= maxCodes) indexedImage.setPixel(x / 2, y / 2, uniqueQuads.value(quad)); } } // Only add the image if we haven't hit the code limit if (uniqueQuads.size() <= maxCodes) { indexedImages.push_back(indexedImage); } } if (uniqueQuads.size() <= maxCodes) { // This texture can be losslessly compressed. // Copy the unique quads over to the codebook. // indexedImages is already done. codebook.resize(uniqueQuads.size()); for (auto it = uniqueQuads.cbegin(); it != uniqueQuads.cend(); ++it) codebook[it.value()] = it.key(); } else { // This texture needs lossy compression indexedImages.clear(); } return uniqueQuads.size(); }