#include "core/rendering/gif.hpp" #include // Para SDL_LogError, SDL_LogCategory, SDL_LogInfo #include // Para memcpy, size_t #include // Para std::cout #include // Para runtime_error #include // 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 &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(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 &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 &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 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(CODE) >= dictionary.size()) { std::cout << "Invalid LZW code " << CODE << ", dictionary size " << static_cast(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. auto readSubBlocks(const uint8_t *&buffer) -> std::vector { std::vector 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 &gct, int resolution_bits) -> std::vector { ImageDescriptor image_descriptor; readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor)); uint8_t lzw_code_size; readBytes(buffer, &lzw_code_size, sizeof(uint8_t)); std::vector compressed_data = readSubBlocks(buffer); int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height; std::vector uncompressed_data(uncompressed_data_length); decompress(lzw_code_size, compressed_data.data(), static_cast(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 header[6]; std::memcpy(header, buffer, 6); buffer += 6; std::string header_str(reinterpret_cast(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 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(block_type) << std::dec << '\n'; return std::vector{}; } block_type = *buffer++; } return std::vector{}; } } // namespace auto loadPalette(const uint8_t *buffer) -> std::vector { 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 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 { return processGifStream(buffer, w, h); } } // namespace GIF