Files
JailDesignerandClaude Opus 4.7 c8882a3f6e refactor(log): migra els subsistemes restants i elimina iostream
Acaba la migració dels std::cout/cerr a Log::*:

- core/locale, core/ui/ui_config, core/rendering/{screen,text,gif,
  surface, sprite/animated_sprite, sdl3gpu/*}.
- game/{options, debug_config, editor/editor_config, levels/level_manager,
  map/{map,tilemap,paint_layer}, entities/entity_manager_loadlevel}.

Severitats coherents: warns per a configs absents amb fallback,
errors per a YAML malformat o E/S fallida, infos per a estats.

Resultat:
- 0 occurrences de std::cout/std::cerr a source/.
- Cap #include <iostream> orfà (eliminat de surface.cpp).
- Output funcional idèntic al històric; AEE_LOG filtra (ex.
  `AEE_LOG=warn` només mostra warnings, `AEE_LOG=*:warn,Tag:info`
  combina globals i overrides).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:34:24 +02:00

303 lines
12 KiB
C++

#include "core/rendering/gif.hpp"
#include <cstring>
#include <stdexcept>
#include <string>
#include "utils/log.hpp"
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<DictionaryEntry>& 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<uint8_t>(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<DictionaryEntry>& 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<DictionaryEntry>& 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<DictionaryEntry>& 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) {
Log::error("GIF", "internal error; self-reference detected.");
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<DictionaryEntry> 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) {
Log::error("GIF", "code = {:x}, but dictionary_ind = {}", code, dictionary_ind);
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<size_t>(code) >= dictionary.size()) {
Log::error("GIF", "invalid LZW code {}, dictionary size {}", code, dictionary.size());
throw std::runtime_error("LZW error: invalid code encountered");
}
writeDecodedString(dictionary, code, out);
}
}
auto Gif::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;
}
auto Gif::processImageDescriptor(const uint8_t*& buffer) -> std::vector<uint8_t> {
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<uint8_t> compressed_data = readSubBlocks(buffer);
const int IMG_W = image_descriptor.image_width;
const int IMG_H = image_descriptor.image_height;
std::vector<uint8_t> uncompressed_data(static_cast<std::size_t>(IMG_W) * IMG_H);
decompress(lzw_code_size, compressed_data.data(), static_cast<int>(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<uint8_t> deinterlaced(static_cast<std::size_t>(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<std::size_t>(dst_row) * IMG_W),
uncompressed_data.data() + (static_cast<std::size_t>(src_row) * IMG_W),
IMG_W);
++src_row;
}
}
return deinterlaced;
}
return uncompressed_data;
}
auto Gif::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 & 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<uint8_t> {
// 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<char*>(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<ptrdiff_t>(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 {
Log::error("GIF", "unrecognized block type {:x}", static_cast<int>(block_type));
return std::vector<uint8_t>{};
}
block_type = *buffer++;
}
return std::vector<uint8_t>{};
}
auto Gif::loadGif(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector<uint8_t> {
return processGifStream(buffer, w, h);
}
} // namespace GIF