forked from jaildesigner-jailgames/jaildoctors_dilemma
317 lines
12 KiB
C++
317 lines
12 KiB
C++
#include "gif.h"
|
|
#include <iostream> // Para std::cout
|
|
#include <cstring> // Para memcpy, size_t
|
|
#include <stdexcept> // Para runtime_error
|
|
#include <string> // Para allocator, char_traits, operator==, basic_string
|
|
|
|
namespace GIF
|
|
{
|
|
|
|
// Función inline para reemplazar el macro READ.
|
|
// Actualiza el puntero 'buffer' tras copiar 'size' bytes a 'dst'.
|
|
inline void readBytes(const uint8_t *&buffer, void *dst, size_t size)
|
|
{
|
|
std::memcpy(dst, buffer, size);
|
|
buffer += size;
|
|
}
|
|
|
|
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 < 2 || code_length > 12)
|
|
{
|
|
throw std::runtime_error("Invalid LZW code length");
|
|
}
|
|
|
|
int i, bit;
|
|
int prev = -1;
|
|
std::vector<DictionaryEntry> dictionary;
|
|
int dictionary_ind;
|
|
unsigned int mask = 0x01;
|
|
int reset_code_length = code_length;
|
|
int clear_code = 1 << code_length;
|
|
int stop_code = clear_code + 1;
|
|
int match_len = 0;
|
|
|
|
// Inicializamos el diccionario con el tamaño correspondiente.
|
|
dictionary.resize(1 << (code_length + 1));
|
|
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); 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
|
|
|
|
// Bucle principal: procesar el stream comprimido.
|
|
while (input_length > 0)
|
|
{
|
|
int code = 0;
|
|
// Lee (code_length + 1) bits para formar el código.
|
|
for (i = 0; i < (code_length + 1); i++)
|
|
{
|
|
if (input_length <= 0)
|
|
{
|
|
throw std::runtime_error("Unexpected end of input in decompress");
|
|
}
|
|
bit = ((*input & mask) != 0) ? 1 : 0;
|
|
mask <<= 1;
|
|
if (mask == 0x100)
|
|
{
|
|
mask = 0x01;
|
|
input++;
|
|
input_length--;
|
|
}
|
|
code |= (bit << i);
|
|
}
|
|
|
|
if (code == clear_code)
|
|
{
|
|
// Reinicia el diccionario.
|
|
code_length = reset_code_length;
|
|
dictionary.resize(1 << (code_length + 1));
|
|
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); 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;
|
|
prev = -1;
|
|
continue;
|
|
}
|
|
else if (code == stop_code)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (prev > -1 && code_length < 12)
|
|
{
|
|
if (code > dictionary_ind)
|
|
{
|
|
std::cerr << "code = " << std::hex << code
|
|
<< ", but dictionary_ind = " << dictionary_ind << std::endl;
|
|
throw std::runtime_error("LZW error: code exceeds dictionary_ind.");
|
|
}
|
|
|
|
int ptr;
|
|
if (code == dictionary_ind)
|
|
{
|
|
ptr = prev;
|
|
while (dictionary[ptr].prev != -1)
|
|
ptr = dictionary[ptr].prev;
|
|
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
|
|
}
|
|
else
|
|
{
|
|
ptr = code;
|
|
while (dictionary[ptr].prev != -1)
|
|
ptr = dictionary[ptr].prev;
|
|
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
|
|
}
|
|
dictionary[dictionary_ind].prev = prev;
|
|
dictionary[dictionary_ind].len = dictionary[prev].len + 1;
|
|
dictionary_ind++;
|
|
|
|
if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11))
|
|
{
|
|
code_length++;
|
|
dictionary.resize(1 << (code_length + 1));
|
|
}
|
|
}
|
|
|
|
prev = code;
|
|
|
|
// Verifica que 'code' sea un índice válido antes de usarlo.
|
|
if (code < 0 || static_cast<size_t>(code) >= dictionary.size())
|
|
{
|
|
std::cerr << "Invalid LZW code " << code
|
|
<< ", dictionary size " << dictionary.size() << std::endl;
|
|
throw std::runtime_error("LZW error: invalid code encountered");
|
|
}
|
|
|
|
int curCode = code; // Variable temporal para recorrer la cadena.
|
|
match_len = dictionary[curCode].len;
|
|
while (curCode != -1)
|
|
{
|
|
// Se asume que dictionary[curCode].len > 0.
|
|
out[dictionary[curCode].len - 1] = dictionary[curCode].byte;
|
|
if (dictionary[curCode].prev == curCode)
|
|
{
|
|
std::cerr << "Internal error; self-reference detected." << std::endl;
|
|
throw std::runtime_error("Internal error in decompress: self-reference");
|
|
}
|
|
curCode = dictionary[curCode].prev;
|
|
}
|
|
out += match_len;
|
|
}
|
|
}
|
|
|
|
std::vector<uint8_t> Gif::readSubBlocks(const uint8_t *&buffer)
|
|
{
|
|
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;
|
|
}
|
|
|
|
std::vector<uint8_t> Gif::processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits)
|
|
{
|
|
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);
|
|
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;
|
|
}
|
|
|
|
std::vector<uint32_t> Gif::loadPalette(const uint8_t *buffer)
|
|
{
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
std::vector<uint8_t> Gif::processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h)
|
|
{
|
|
// Leer la cabecera de 6 bytes ("GIF87a" o "GIF89a")
|
|
uint8_t header[6];
|
|
std::memcpy(header, buffer, 6);
|
|
buffer += 6;
|
|
|
|
// Opcional: Validar header
|
|
std::string headerStr(reinterpret_cast<char *>(header), 6);
|
|
if (headerStr != "GIF87a" && headerStr != "GIF89a")
|
|
{
|
|
throw std::runtime_error("Formato de archivo GIF inválido.");
|
|
}
|
|
|
|
// Leer el Screen Descriptor (7 bytes, empaquetado sin padding)
|
|
ScreenDescriptor screen_descriptor;
|
|
readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));
|
|
|
|
// Asigna ancho y alto
|
|
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)
|
|
{
|
|
int global_color_table_size = 1 << (((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;
|
|
}
|
|
|
|
// Supongamos que 'buffer' es el puntero actual y TRAILER es 0x3B
|
|
uint8_t block_type = *buffer++;
|
|
while (block_type != TRAILER)
|
|
{
|
|
if (block_type == EXTENSION_INTRODUCER) // 0x21
|
|
{
|
|
// Se lee la etiqueta de extensión, la cual indica el tipo de extensión.
|
|
uint8_t extension_label = *buffer++;
|
|
switch (extension_label)
|
|
{
|
|
case GRAPHIC_CONTROL: // 0xF9
|
|
{
|
|
// Procesar Graphic Control Extension:
|
|
uint8_t blockSize = *buffer++; // Normalmente, blockSize == 4
|
|
buffer += blockSize; // Saltamos los 4 bytes del bloque fijo
|
|
// Saltar los sub-bloques
|
|
uint8_t subBlockSize = *buffer++;
|
|
while (subBlockSize != 0)
|
|
{
|
|
buffer += subBlockSize;
|
|
subBlockSize = *buffer++;
|
|
}
|
|
break;
|
|
}
|
|
case APPLICATION_EXTENSION: // 0xFF
|
|
case COMMENT_EXTENSION: // 0xFE
|
|
case PLAINTEXT_EXTENSION: // 0x01
|
|
{
|
|
// Para estas extensiones, saltamos el bloque fijo y los sub-bloques.
|
|
uint8_t blockSize = *buffer++;
|
|
buffer += blockSize;
|
|
uint8_t subBlockSize = *buffer++;
|
|
while (subBlockSize != 0)
|
|
{
|
|
buffer += subBlockSize;
|
|
subBlockSize = *buffer++;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
// Si la etiqueta de extensión es desconocida, saltarla también:
|
|
uint8_t blockSize = *buffer++;
|
|
buffer += blockSize;
|
|
uint8_t subBlockSize = *buffer++;
|
|
while (subBlockSize != 0)
|
|
{
|
|
buffer += subBlockSize;
|
|
subBlockSize = *buffer++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (block_type == IMAGE_DESCRIPTOR)
|
|
{
|
|
// Procesar el Image Descriptor y retornar los datos de imagen
|
|
return processImageDescriptor(buffer, global_color_table, color_resolution_bits);
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "Unrecognized block type " << std::hex << static_cast<int>(block_type) << std::endl;
|
|
return std::vector<uint8_t>{};
|
|
}
|
|
block_type = *buffer++;
|
|
}
|
|
|
|
return std::vector<uint8_t>{};
|
|
}
|
|
|
|
std::vector<uint8_t> Gif::loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h)
|
|
{
|
|
return processGifStream(buffer, w, h);
|
|
}
|
|
|
|
} // namespace GIF
|