| /* | |
| * 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)); | |
| } |