266 lines
12 KiB
C++
266 lines
12 KiB
C++
#include "core/rendering/gif.hpp"
|
|
|
|
#include <SDL3/SDL.h> // Para SDL_LogError, SDL_LogCategory, SDL_LogInfo
|
|
|
|
#include <cstring> // Para memcpy, size_t
|
|
#include <iostream> // Para std::cout
|
|
#include <stdexcept> // Para runtime_error
|
|
#include <string> // Para char_traits, operator==, basic_string, string
|
|
|
|
namespace GIF {
|
|
namespace {
|
|
inline void readBytes(const uint8_t *&buffer, void *dst, size_t size) {
|
|
std::memcpy(dst, buffer, size);
|
|
buffer += size;
|
|
}
|
|
|
|
// Llavor del diccionari LZW: 0..N-1 com a entrades base, i salta 2 (clear_code + stop_code).
|
|
void resetDictionary(std::vector<DictionaryEntry> &dict, int code_length, int &dictionary_ind) {
|
|
dict.resize(1 << (code_length + 1));
|
|
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) {
|
|
dict[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
|
|
dict[dictionary_ind].prev = -1;
|
|
dict[dictionary_ind].len = 1;
|
|
}
|
|
dictionary_ind += 2;
|
|
}
|
|
|
|
// Llig `code_length + 1` bits LSB-first del flux d'entrada. Llança si s'acaba el buffer.
|
|
auto readNextCode(const uint8_t *&input, int &input_length, int code_length, unsigned int &mask) -> int {
|
|
int code = 0;
|
|
for (int i = 0; i < code_length + 1; i++) {
|
|
if (input_length <= 0) {
|
|
std::cout << "Unexpected end of input in decompress" << '\n';
|
|
throw std::runtime_error("Unexpected end of input in decompress");
|
|
}
|
|
const int BIT = ((*input & mask) != 0) ? 1 : 0;
|
|
mask <<= 1;
|
|
if (mask == 0x100) {
|
|
mask = 0x01;
|
|
input++;
|
|
input_length--;
|
|
}
|
|
code |= (BIT << i);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
// Afig una nova entrada al diccionari. Resol el cas especial KwKwK (code == dictionary_ind)
|
|
// començant la cadena des de `prev` en lloc de des de `code`.
|
|
void addDictionaryEntry(std::vector<DictionaryEntry> &dict, int dictionary_ind, int code, int prev) {
|
|
int ptr = (code == dictionary_ind) ? prev : code;
|
|
while (dict[ptr].prev != -1) {
|
|
ptr = dict[ptr].prev;
|
|
}
|
|
dict[dictionary_ind].byte = dict[ptr].byte;
|
|
dict[dictionary_ind].prev = prev;
|
|
dict[dictionary_ind].len = dict[prev].len + 1;
|
|
}
|
|
|
|
// Escriu la cadena de bytes associada a `code` en `out` (en ordre invers seguint .prev).
|
|
// Retorna la longitud del match per avançar el cursor de l'eixida.
|
|
auto emitMatch(const std::vector<DictionaryEntry> &dict, int code, uint8_t *out) -> int {
|
|
const int MATCH_LEN = dict[code].len;
|
|
int cur_code = code;
|
|
while (cur_code != -1) {
|
|
out[dict[cur_code].len - 1] = dict[cur_code].byte;
|
|
if (dict[cur_code].prev == cur_code) {
|
|
std::cout << "Internal error; self-reference detected." << '\n';
|
|
throw std::runtime_error("Internal error in decompress: self-reference");
|
|
}
|
|
cur_code = dict[cur_code].prev;
|
|
}
|
|
return MATCH_LEN;
|
|
}
|
|
|
|
// Descompone (uncompress) el bloque comprimido usando LZW.
|
|
void decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out) {
|
|
if (code_length < 2 || code_length > 12) {
|
|
std::cout << "Invalid LZW code length: " << code_length << '\n';
|
|
throw std::runtime_error("Invalid LZW code length");
|
|
}
|
|
|
|
int prev = -1;
|
|
std::vector<DictionaryEntry> dictionary;
|
|
int dictionary_ind = 0;
|
|
unsigned int mask = 0x01;
|
|
const int RESET_CODE_LENGTH = code_length;
|
|
const int CLEAR_CODE = 1 << code_length;
|
|
const int STOP_CODE = CLEAR_CODE + 1;
|
|
|
|
resetDictionary(dictionary, code_length, dictionary_ind);
|
|
|
|
while (input_length > 0) {
|
|
const int CODE = readNextCode(input, input_length, code_length, mask);
|
|
|
|
if (CODE == CLEAR_CODE) {
|
|
code_length = RESET_CODE_LENGTH;
|
|
resetDictionary(dictionary, code_length, dictionary_ind);
|
|
prev = -1;
|
|
continue;
|
|
}
|
|
if (CODE == STOP_CODE) { break; }
|
|
|
|
if (prev > -1 && code_length < 12) {
|
|
if (CODE > dictionary_ind) {
|
|
std::cout << "LZW error: code (" << CODE << ") exceeds dictionary_ind (" << dictionary_ind << ")" << '\n';
|
|
throw std::runtime_error("LZW error: code exceeds dictionary_ind.");
|
|
}
|
|
addDictionaryEntry(dictionary, dictionary_ind, CODE, prev);
|
|
dictionary_ind++;
|
|
|
|
if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11)) {
|
|
code_length++;
|
|
dictionary.resize(1 << (code_length + 1));
|
|
}
|
|
}
|
|
|
|
prev = CODE;
|
|
|
|
if (CODE < 0 || static_cast<size_t>(CODE) >= dictionary.size()) {
|
|
std::cout << "Invalid LZW code " << CODE << ", dictionary size " << static_cast<unsigned long>(dictionary.size()) << '\n';
|
|
throw std::runtime_error("LZW error: invalid code encountered");
|
|
}
|
|
|
|
out += emitMatch(dictionary, CODE, out);
|
|
}
|
|
}
|
|
|
|
// Lee los sub-bloques de datos y los acumula en un std::vector<uint8_t>.
|
|
auto readSubBlocks(const uint8_t *&buffer) -> std::vector<uint8_t> {
|
|
std::vector<uint8_t> data;
|
|
uint8_t block_size = *buffer;
|
|
buffer++;
|
|
while (block_size != 0) {
|
|
data.insert(data.end(), buffer, buffer + block_size);
|
|
buffer += block_size;
|
|
block_size = *buffer;
|
|
buffer++;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
// Procesa el Image Descriptor y retorna el vector de datos sin comprimir.
|
|
auto processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits) -> std::vector<uint8_t> {
|
|
ImageDescriptor image_descriptor;
|
|
readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));
|
|
|
|
uint8_t lzw_code_size;
|
|
readBytes(buffer, &lzw_code_size, sizeof(uint8_t));
|
|
|
|
std::vector<uint8_t> compressed_data = readSubBlocks(buffer);
|
|
int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height;
|
|
std::vector<uint8_t> uncompressed_data(uncompressed_data_length);
|
|
|
|
decompress(lzw_code_size, compressed_data.data(), static_cast<int>(compressed_data.size()), uncompressed_data.data());
|
|
return uncompressed_data;
|
|
}
|
|
|
|
// Procesa el stream completo del GIF y devuelve los datos sin comprimir.
|
|
auto processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h) -> std::vector<uint8_t> {
|
|
uint8_t header[6];
|
|
std::memcpy(header, buffer, 6);
|
|
buffer += 6;
|
|
|
|
std::string header_str(reinterpret_cast<char *>(header), 6);
|
|
if (header_str != "GIF87a" && header_str != "GIF89a") {
|
|
std::cout << "Formato de archivo GIF inválido: " << header_str << '\n';
|
|
throw std::runtime_error("Formato de archivo GIF inválido.");
|
|
}
|
|
|
|
ScreenDescriptor screen_descriptor;
|
|
readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));
|
|
|
|
w = screen_descriptor.width;
|
|
h = screen_descriptor.height;
|
|
|
|
int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
|
|
std::vector<RGB> global_color_table;
|
|
if ((screen_descriptor.fields & 0x80) != 0) {
|
|
const size_t GLOBAL_COLOR_TABLE_SIZE = 1U << (((screen_descriptor.fields & 0x07) + 1));
|
|
global_color_table.resize(GLOBAL_COLOR_TABLE_SIZE);
|
|
std::memcpy(global_color_table.data(), buffer, 3 * GLOBAL_COLOR_TABLE_SIZE);
|
|
buffer += 3 * GLOBAL_COLOR_TABLE_SIZE;
|
|
}
|
|
|
|
uint8_t block_type = *buffer++;
|
|
while (block_type != TRAILER) {
|
|
if (block_type == EXTENSION_INTRODUCER) {
|
|
uint8_t extension_label = *buffer++;
|
|
switch (extension_label) {
|
|
case GRAPHIC_CONTROL: {
|
|
uint8_t block_size = *buffer++;
|
|
buffer += block_size;
|
|
uint8_t sub_block_size = *buffer++;
|
|
while (sub_block_size != 0) {
|
|
buffer += sub_block_size;
|
|
sub_block_size = *buffer++;
|
|
}
|
|
break;
|
|
}
|
|
case APPLICATION_EXTENSION:
|
|
case COMMENT_EXTENSION:
|
|
case PLAINTEXT_EXTENSION: {
|
|
uint8_t block_size = *buffer++;
|
|
buffer += block_size;
|
|
uint8_t sub_block_size = *buffer++;
|
|
while (sub_block_size != 0) {
|
|
buffer += sub_block_size;
|
|
sub_block_size = *buffer++;
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
uint8_t block_size = *buffer++;
|
|
buffer += block_size;
|
|
uint8_t sub_block_size = *buffer++;
|
|
while (sub_block_size != 0) {
|
|
buffer += sub_block_size;
|
|
sub_block_size = *buffer++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} else if (block_type == IMAGE_DESCRIPTOR) {
|
|
return processImageDescriptor(buffer, global_color_table, color_resolution_bits);
|
|
} else {
|
|
std::cout << "Unrecognized block type: 0x" << std::hex << static_cast<int>(block_type) << std::dec << '\n';
|
|
return std::vector<uint8_t>{};
|
|
}
|
|
block_type = *buffer++;
|
|
}
|
|
|
|
return std::vector<uint8_t>{};
|
|
}
|
|
} // namespace
|
|
|
|
auto loadPalette(const uint8_t *buffer) -> std::vector<uint32_t> {
|
|
uint8_t header[6];
|
|
std::memcpy(header, buffer, 6);
|
|
buffer += 6;
|
|
|
|
ScreenDescriptor screen_descriptor;
|
|
std::memcpy(&screen_descriptor, buffer, sizeof(ScreenDescriptor));
|
|
buffer += sizeof(ScreenDescriptor);
|
|
|
|
std::vector<uint32_t> global_color_table;
|
|
if ((screen_descriptor.fields & 0x80) != 0) {
|
|
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
|
|
global_color_table.resize(global_color_table_size);
|
|
for (int i = 0; i < global_color_table_size; ++i) {
|
|
uint8_t r = buffer[0];
|
|
uint8_t g = buffer[1];
|
|
uint8_t b = buffer[2];
|
|
global_color_table[i] = (r << 16) | (g << 8) | b;
|
|
buffer += 3;
|
|
}
|
|
}
|
|
return global_color_table;
|
|
}
|
|
|
|
auto loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h) -> std::vector<uint8_t> {
|
|
return processGifStream(buffer, w, h);
|
|
}
|
|
|
|
} // namespace GIF
|