blob: 567f76add105aaa92ecc83ae721783dca5b9e6d5 [file] [edit]
/*
* Copyright 2026 Rive
*/
#include "rive/decoders/bitmap_decoder.hpp"
#include "rive/gpu_texture_format.hpp"
#include <cstdio>
#include <cstring>
#include <memory>
// Forward declarations for Ericsson ETCPACK's etcdec.cxx (built as part of
// rive_decoders when RIVE_ETC_DECODER is set). Each entrypoint writes one 4x4
// block into a planar RGBA buffer at byte offsets +0/+1/+2 (RGB) and +3 (A).
extern void decompressBlockETC2c(unsigned int block_part1,
unsigned int block_part2,
unsigned char* img,
int width,
int height,
int startx,
int starty,
int channels);
extern void decompressBlockAlphaC(unsigned char* data,
unsigned char* img,
int width,
int height,
int ix,
int iy,
int channels);
namespace
{
unsigned int readBE32(const uint8_t* p)
{
return (static_cast<unsigned int>(p[0]) << 24) |
(static_cast<unsigned int>(p[1]) << 16) |
(static_cast<unsigned int>(p[2]) << 8) |
static_cast<unsigned int>(p[3]);
}
} // namespace
// Decodes ETC2 RGBA8 (16 bytes/block: 8 bytes EAC alpha + 8 bytes ETC2 RGB).
std::unique_ptr<Bitmap> decode_etc_texture(const uint8_t* blocks,
size_t byteCount,
uint32_t width,
uint32_t height,
rive::GPUTextureFormat format)
{
if (format != rive::GPUTextureFormat::etc2 || width == 0 || height == 0)
{
return nullptr;
}
const uint32_t paddedW = (width + 3u) & ~3u;
const uint32_t paddedH = (height + 3u) & ~3u;
const uint32_t blocksX = paddedW / 4u;
const uint32_t blocksY = paddedH / 4u;
const size_t expectedBytes = static_cast<size_t>(blocksX) * blocksY * 16u;
if (byteCount != expectedBytes)
{
fprintf(stderr,
"DecodeEtcTexture - byteCount %zu != expected %zu for %ux%u\n",
byteCount,
expectedBytes,
width,
height);
return nullptr;
}
const size_t paddedPixels =
static_cast<size_t>(paddedW) * static_cast<size_t>(paddedH);
auto padded = std::make_unique<uint8_t[]>(paddedPixels * 4);
const uint8_t* src = blocks;
for (uint32_t by = 0; by < blocksY; ++by)
{
for (uint32_t bx = 0; bx < blocksX; ++bx)
{
const int startX = static_cast<int>(bx * 4u);
const int startY = static_cast<int>(by * 4u);
decompressBlockAlphaC(const_cast<uint8_t*>(src),
padded.get() + 3,
static_cast<int>(paddedW),
static_cast<int>(paddedH),
startX,
startY,
4);
const unsigned int p1 = readBE32(src + 8);
const unsigned int p2 = readBE32(src + 12);
decompressBlockETC2c(p1,
p2,
padded.get(),
static_cast<int>(paddedW),
static_cast<int>(paddedH),
startX,
startY,
4);
src += 16;
}
}
// Crop padded RGBA grid down to (width, height).
const size_t outPixels =
static_cast<size_t>(width) * static_cast<size_t>(height);
auto pixels = std::make_unique<uint8_t[]>(outPixels * 4);
for (uint32_t y = 0; y < height; ++y)
{
std::memcpy(pixels.get() + static_cast<size_t>(y) * width * 4,
padded.get() + static_cast<size_t>(y) * paddedW * 4,
static_cast<size_t>(width) * 4);
}
return std::make_unique<Bitmap>(width,
height,
outPixels * 4,
Bitmap::PixelFormat::RGBA,
std::move(pixels));
}