#include "core/rendering/gif.hpp" #include #include #include #include namespace GIF { namespace { // Paràmetres del Screen Descriptor (byte de flags). constexpr uint8_t SCREEN_GCT_FLAG = 0x80; constexpr uint8_t SCREEN_GCT_SIZE_MASK = 0x07; // Paràmetres del decompressor LZW. constexpr int LZW_MIN_CODE_LENGTH = 2; constexpr int LZW_MAX_CODE_LENGTH = 12; constexpr unsigned int LZW_MASK_INITIAL = 0x01; constexpr unsigned int LZW_MASK_OVERFLOW = 0x100; // Mida de l'RGB de la GCT (bytes per entrada). constexpr int GCT_BYTES_PER_ENTRY = 3; } // namespace // Copia 'size' bytes del buffer a 'dst' avançant el punter del buffer. inline void readBytes(const uint8_t*& buffer, void* dst, size_t size) { std::memcpy(dst, buffer, size); buffer += size; } // Salta un bloc d'extensió: byte de mida + contingut fix + sub-blocs // acabats amb mida 0. Totes les extensions del GIF (graphic control, // application, comment, plaintext, ...) comparteixen aquest layout. inline void skipExtensionBlock(const uint8_t*& buffer) { 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++; } } // Inicializa el diccionario LZW con los valores iniciales inline void initializeDictionary(std::vector& dictionary, int code_length, int& dictionary_ind) { int size = 1 << code_length; dictionary.resize(1 << (code_length + 1)); for (dictionary_ind = 0; dictionary_ind < size; dictionary_ind++) { dictionary[dictionary_ind].byte = static_cast(dictionary_ind); dictionary[dictionary_ind].prev = -1; dictionary[dictionary_ind].len = 1; } dictionary_ind += 2; // Reservamos espacio para clear y stop codes } // Lee los próximos bits del stream de entrada para formar un código inline auto readNextCode(const uint8_t*& input, int& input_length, unsigned int& mask, int code_length) -> int { int code = 0; for (int i = 0; i < (code_length + 1); i++) { if (input_length <= 0) { throw std::runtime_error("Unexpected end of input in decompress"); } int bit = ((*input & mask) != 0) ? 1 : 0; mask <<= 1; if (mask == LZW_MASK_OVERFLOW) { mask = LZW_MASK_INITIAL; input++; input_length--; } code |= (bit << i); } return code; } // Encuentra el primer byte de una cadena del diccionario inline auto findFirstByte(const std::vector& dictionary, int code) -> uint8_t { int ptr = code; while (dictionary[ptr].prev != -1) { ptr = dictionary[ptr].prev; } return dictionary[ptr].byte; } // Agrega una nueva entrada al diccionario inline void addDictionaryEntry(std::vector& dictionary, int& dictionary_ind, int& code_length, int prev, int code) { uint8_t first_byte; if (code == dictionary_ind) { first_byte = findFirstByte(dictionary, prev); } else { first_byte = findFirstByte(dictionary, code); } dictionary[dictionary_ind].byte = first_byte; dictionary[dictionary_ind].prev = prev; dictionary[dictionary_ind].len = dictionary[prev].len + 1; dictionary_ind++; // Només podem créixer mentre no hagem arribat al màxim del decoder // (12 bits); el `- 1` compensa el +1 que aplicarem tot seguit per // a que code_length no sobrepasse LZW_MAX_CODE_LENGTH. if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < LZW_MAX_CODE_LENGTH - 1)) { code_length++; dictionary.resize(1 << (code_length + 1)); } } // Escribe la cadena decodificada al buffer de salida inline auto writeDecodedString(const std::vector& dictionary, int code, uint8_t*& out) -> int { int cur_code = code; int match_len = dictionary[cur_code].len; while (cur_code != -1) { out[dictionary[cur_code].len - 1] = dictionary[cur_code].byte; if (dictionary[cur_code].prev == cur_code) { std::cerr << "Internal error; self-reference detected." << '\n'; throw std::runtime_error("Internal error in decompress: self-reference"); } cur_code = dictionary[cur_code].prev; } out += match_len; return match_len; } void Gif::decompress(int code_length, const uint8_t* input, int input_length, uint8_t* out) { // Verifica que el code_length tenga un rango razonable. if (code_length < LZW_MIN_CODE_LENGTH || code_length > LZW_MAX_CODE_LENGTH) { throw std::runtime_error("Invalid LZW code length"); } int prev = -1; std::vector dictionary; int dictionary_ind; unsigned int mask = LZW_MASK_INITIAL; int reset_code_length = code_length; int clear_code = 1 << code_length; int stop_code = clear_code + 1; // Inicializamos el diccionario con el tamaño correspondiente. initializeDictionary(dictionary, code_length, dictionary_ind); // Bucle principal: procesar el stream comprimido. while (input_length > 0) { int code = readNextCode(input, input_length, mask, code_length); if (code == clear_code) { // Reinicia el diccionario. code_length = reset_code_length; initializeDictionary(dictionary, code_length, dictionary_ind); prev = -1; continue; } if (code == stop_code) { break; } if (prev > -1 && code_length < LZW_MAX_CODE_LENGTH) { if (code > dictionary_ind) { std::cerr << "code = " << std::hex << code << ", but dictionary_ind = " << dictionary_ind << '\n'; throw std::runtime_error("LZW error: code exceeds dictionary_ind."); } addDictionaryEntry(dictionary, dictionary_ind, code_length, prev, code); } prev = code; // Verifica que 'code' sea un índice válido antes de usarlo. if (code < 0 || static_cast(code) >= dictionary.size()) { std::cerr << "Invalid LZW code " << code << ", dictionary size " << dictionary.size() << '\n'; throw std::runtime_error("LZW error: invalid code encountered"); } writeDecodedString(dictionary, code, out); } } auto Gif::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; } auto Gif::processImageDescriptor(const uint8_t*& buffer) -> std::vector { ImageDescriptor image_descriptor; // Lee 9 bytes para el 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); const int IMG_W = image_descriptor.image_width; const int IMG_H = image_descriptor.image_height; std::vector uncompressed_data(static_cast(IMG_W) * IMG_H); decompress(lzw_code_size, compressed_data.data(), static_cast(compressed_data.size()), uncompressed_data.data()); // Interlaced GIF (flag 0x40 al byte de fields): les files no arriben // en ordre lineal, sinó en 4 passades (8:0, 8:4, 4:2, 2:1). Reordena // al buffer lineal que espera Surface. Sense aquest pas, les fuentes // i qualsevol GIF entrellaçat es veien completament trinxats. constexpr uint8_t INTERLACE_FLAG = 0x40; if ((image_descriptor.fields & INTERLACE_FLAG) != 0) { constexpr int PASS_START[4] = {0, 4, 2, 1}; constexpr int PASS_STEP[4] = {8, 8, 4, 2}; std::vector deinterlaced(static_cast(IMG_W) * IMG_H); int src_row = 0; for (int pass = 0; pass < 4; ++pass) { for (int dst_row = PASS_START[pass]; dst_row < IMG_H; dst_row += PASS_STEP[pass]) { std::memcpy(deinterlaced.data() + (static_cast(dst_row) * IMG_W), uncompressed_data.data() + (static_cast(src_row) * IMG_W), IMG_W); ++src_row; } } return deinterlaced; } return uncompressed_data; } auto Gif::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 & SCREEN_GCT_FLAG) != 0) { int global_color_table_size = 1 << ((screen_descriptor.fields & SCREEN_GCT_SIZE_MASK) + 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 += GCT_BYTES_PER_ENTRY; } } return global_color_table; } auto Gif::processGifStream(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector { // Leer la cabecera de 6 bytes ("GIF87a" o "GIF89a") 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") { throw std::runtime_error("Formato de archivo GIF inválido."); } // Screen Descriptor (7 bytes, empaquetado sin padding). ScreenDescriptor screen_descriptor; readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor)); w = screen_descriptor.width; h = screen_descriptor.height; // Saltar la Global Color Table si existeix; els colors els extrau // loadPalette() en una passada separada sobre el mateix buffer. if ((screen_descriptor.fields & SCREEN_GCT_FLAG) != 0) { const int GCT_SIZE = 1 << ((screen_descriptor.fields & SCREEN_GCT_SIZE_MASK) + 1); buffer += static_cast(GCT_BYTES_PER_ENTRY) * GCT_SIZE; } // Recorregut de blocs fins al TRAILER (0x3B). Les extensions comparteixen // layout (mida + contingut fix + sub-blocs), per tant totes les saltem // igual — no cal distingir per extension_label. uint8_t block_type = *buffer++; while (block_type != TRAILER) { if (block_type == EXTENSION_INTRODUCER) { ++buffer; // extension_label (ignorat) skipExtensionBlock(buffer); } else if (block_type == IMAGE_DESCRIPTOR) { return processImageDescriptor(buffer); } else { std::cerr << "Unrecognized block type " << std::hex << static_cast(block_type) << '\n'; return std::vector{}; } block_type = *buffer++; } return std::vector{}; } auto Gif::loadGif(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector { return processGifStream(buffer, w, h); } } // namespace GIF