retocat gif.cpp

This commit is contained in:
2025-03-16 12:44:38 +01:00
parent 4a07100e2a
commit b3215bf381
6 changed files with 429 additions and 359 deletions

View File

@@ -1,262 +1,317 @@
#include "gif.h"
#include <cstdio> // Para fprintf, stderr
#include <cstdlib> // Para exit, malloc, calloc, free, realloc
#include <vector> // Para vector
#include <cstdio> // for fprintf, stderr
#include <cstring> // for memcpy, size_t
#include <stdexcept> // for runtime_error
#include <string> // for allocator, char_traits, operator==, basic_string
void Gif::uncompress(int code_length, const uint8_t *input, int input_length, uint8_t *out)
namespace GIF
{
int i, bit;
int code, prev = -1;
std::vector<DictionaryEntry> dictionary;
int dictionary_ind;
unsigned int mask = 0x01;
int reset_code_length;
int clear_code;
int stop_code;
int match_len;
clear_code = 1 << (code_length);
stop_code = clear_code + 1;
reset_code_length = code_length;
dictionary.resize(1 << (code_length + 1));
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++)
// 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)
{
dictionary[dictionary_ind].byte = dictionary_ind;
dictionary[dictionary_ind].prev = -1;
dictionary[dictionary_ind].len = 1;
std::memcpy(dst, buffer, size);
buffer += size;
}
dictionary_ind += 2;
while (input_length)
void Gif::decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out)
{
code = 0x0;
for (i = 0; i < (code_length + 1); i++)
// Verifica que el code_length tenga un rango razonable.
if (code_length < 2 || code_length > 12)
{
bit = (*input & mask) ? 1 : 0;
mask <<= 1;
if (mask == 0x100)
{
mask = 0x01;
input++;
input_length--;
}
code = code | (bit << i);
throw std::runtime_error("Invalid LZW code length");
}
if (code == clear_code)
{
code_length = reset_code_length;
dictionary.resize(1 << (code_length + 1));
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;
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++)
{
dictionary[dictionary_ind].byte = dictionary_ind;
dictionary[dictionary_ind].prev = -1;
dictionary[dictionary_ind].len = 1;
}
dictionary_ind += 2;
prev = -1;
continue;
}
else if (code == stop_code)
{
break;
// 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
if ((prev > -1) && (code_length < 12))
// Bucle principal: procesar el stream comprimido.
while (input_length > 0)
{
if (code > dictionary_ind)
int code = 0;
// Lee (code_length + 1) bits para formar el código.
for (i = 0; i < (code_length + 1); i++)
{
fprintf(stderr, "code = %.02x, but dictionary_ind = %.02x\n", code, dictionary_ind);
exit(0);
}
if (code == dictionary_ind)
{
int ptr = prev;
while (dictionary[ptr].prev != -1)
if (input_length <= 0)
{
ptr = dictionary[ptr].prev;
throw std::runtime_error("Unexpected end of input in uncompress");
}
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
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::fprintf(stderr, "code = %.02x, but dictionary_ind = %d\n", code, dictionary_ind);
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::fprintf(stderr, "Invalid LZW code %d, dictionary size %zu\n", code, dictionary.size());
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::fprintf(stderr, "Internal error; self-reference detected.\n");
throw std::runtime_error("Internal error in uncompress: 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;
// Imprime para depuración
std::fprintf(stderr, "Screen Descriptor - Width: %d, Height: %d\n", w, h);
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
{
int 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));
std::fprintf(stderr, "Unrecognized block type %.02x\n", block_type);
return std::vector<uint8_t>{};
}
block_type = *buffer++;
}
prev = code;
match_len = dictionary[code].len;
while (code != -1)
{
out[dictionary[code].len - 1] = dictionary[code].byte;
if (dictionary[code].prev == code)
{
fprintf(stderr, "Internal error; self-reference.");
exit(0);
}
code = dictionary[code].prev;
}
out += match_len;
return std::vector<uint8_t>{};
}
}
int Gif::read_sub_blocks(uint8_t *buffer, uint8_t **data)
{
int data_length = 0;
int index = 0;
uint8_t block_size;
*data = nullptr;
while (true)
std::vector<uint8_t> Gif::loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h)
{
READ(&block_size, 1);
if (block_size == 0)
{
break;
}
data_length += block_size;
*data = (uint8_t *)realloc(*data, data_length);
READ(*data + index, block_size);
index += block_size;
return processGifStream(buffer, w, h);
}
return data_length;
}
uint8_t *Gif::process_image_descriptor(uint8_t *buffer, RGB *gct, int gct_size, int resolution_bits)
{
ImageDescriptor image_descriptor;
int compressed_data_length;
uint8_t *compressed_data = nullptr;
uint8_t lzw_code_size;
int uncompressed_data_length = 0;
uint8_t *uncompressed_data = nullptr;
READ(&image_descriptor, 9);
READ(&lzw_code_size, 1);
compressed_data_length = read_sub_blocks(buffer, &compressed_data);
uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height;
uncompressed_data = (uint8_t *)malloc(uncompressed_data_length);
uncompress(lzw_code_size, compressed_data, compressed_data_length, uncompressed_data);
if (compressed_data)
{
free(compressed_data);
}
return uncompressed_data;
}
uint32_t *Gif::LoadPalette(uint8_t *buffer)
{
uint8_t header[7];
ScreenDescriptor screen_descriptor;
int global_color_table_size = 0;
uint32_t *global_color_table = nullptr;
READ(header, 6);
READ(&screen_descriptor, 7);
global_color_table = (uint32_t *)calloc(1, 1024);
if (screen_descriptor.fields & 0x80)
{
global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
for (int i = 0; i < global_color_table_size; ++i)
{
global_color_table[i] = (buffer[0] << 16) + (buffer[1] << 8) + buffer[2];
buffer += 3;
}
}
return global_color_table;
}
uint8_t *Gif::process_gif_stream(uint8_t *buffer, uint16_t *w, uint16_t *h)
{
uint8_t header[7];
ScreenDescriptor screen_descriptor;
int color_resolution_bits;
int global_color_table_size = 0;
RGB *global_color_table = nullptr;
uint8_t block_type = 0x0;
READ(header, 6);
header[6] = 0x0;
READ(&screen_descriptor, 7);
*w = screen_descriptor.width;
*h = screen_descriptor.height;
color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
if (screen_descriptor.fields & 0x80)
{
global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
global_color_table = (RGB *)malloc(3 * global_color_table_size);
READ(global_color_table, 3 * global_color_table_size);
}
while (block_type != TRAILER)
{
READ(&block_type, 1);
uint8_t size;
switch (block_type)
{
case IMAGE_DESCRIPTOR:
return process_image_descriptor(buffer, global_color_table, global_color_table_size, color_resolution_bits);
case EXTENSION_INTRODUCER:
buffer++;
size = *(buffer++);
buffer += size;
do
{
size = *(buffer++);
buffer += size;
} while (size != 0);
break;
case TRAILER:
break;
default:
fprintf(stderr, "Bailing on unrecognized block type %.02x\n", block_type);
return nullptr;
}
}
return nullptr;
}
uint8_t *Gif::LoadGif(uint8_t *buffer, uint16_t *w, uint16_t *h)
{
return process_gif_stream(buffer, w, h);
}
} // namespace GIF