Files
jaildoctors_dilemma/source/surface.cpp

557 lines
18 KiB
C++

#include "surface.h"
#include <SDL2/SDL_error.h> // for SDL_GetError
#include <stddef.h> // for size_t
#include <algorithm> // for min, copy, fill
#include <fstream> // for basic_ostream, basic_ifstream, basic_ios
#include <iostream> // for cerr, cout
#include <memory> // for shared_ptr, __shared_ptr_access, unique_ptr
#include <stdexcept> // for runtime_error
#include <vector> // for vector
#include "asset.h" // for Asset
#include "screen.h"
#include "gif.h" // for LoadGif, LoadPalette
#include <iostream>
#include <fstream>
#include <sstream>
#include <array>
#include <cstdint>
#include <stdexcept>
// Carga una paleta desde un archivo .gif
Palette loadPalette(const std::string &file_path)
{
// Abrir el archivo en modo binario
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
if (!file.is_open())
{
throw std::runtime_error("Error opening file: " + file_path);
}
// Leer el contenido del archivo en un buffer
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<Uint8> buffer(size);
if (!file.read(reinterpret_cast<char *>(buffer.data()), size))
{
throw std::runtime_error("Error reading file: " + file_path);
}
// Cargar la paleta usando los datos del buffer
std::unique_ptr<Uint32[]> pal(LoadPalette(buffer.data()));
if (pal == nullptr)
{
throw std::runtime_error("Error loading palette from file: " + file_path);
}
// Crear la paleta y copiar los datos
Palette palette;
std::copy(pal.get(), pal.get() + palette.size(), palette.begin());
// Mensaje de depuración
printWithDots("Palette : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
return palette;
}
// Carga una paleta desde un archivo .pal
Palette readPalFile(const std::string &file_path)
{
Palette palette{};
palette.fill(0); // Inicializar todo con 0 (transparente por defecto)
std::ifstream file(file_path);
if (!file.is_open())
{
throw std::runtime_error("No se pudo abrir el archivo .pal");
}
std::string line;
int line_number = 0;
int color_index = 0;
while (std::getline(file, line))
{
++line_number;
// Ignorar las tres primeras líneas del archivo
if (line_number <= 3)
{
continue;
}
// Procesar las líneas restantes con valores RGB
std::istringstream ss(line);
int r, g, b;
if (ss >> r >> g >> b)
{
// Construir el color ARGB (A = 255 por defecto)
Uint32 color = (255 << 24) | (r << 16) | (g << 8) | b;
palette[color_index++] = color;
// Limitar a un máximo de 256 colores (opcional)
if (color_index >= 256)
{
break;
}
}
}
file.close();
return palette;
}
// Constructor
Surface::Surface(int w, int h)
: surface_data_(std::make_shared<SurfaceData>(w, h)),
transparent_color_(static_cast<Uint8>(PaletteColor::TRANSPARENT)) { initializeSubPalette(sub_palette_); }
Surface::Surface(const std::string &file_path)
: transparent_color_(static_cast<Uint8>(PaletteColor::TRANSPARENT))
{
SurfaceData loadedData = loadSurface(file_path);
surface_data_ = std::make_shared<SurfaceData>(std::move(loadedData));
initializeSubPalette(sub_palette_);
}
// Carga una superficie desde un archivo
SurfaceData Surface::loadSurface(const std::string &file_path)
{
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
if (!file.is_open())
{
std::cerr << "Error opening file: " << file_path << std::endl;
throw std::runtime_error("Error opening file");
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<Uint8> buffer(size);
if (!file.read((char *)buffer.data(), size))
{
std::cerr << "Error reading file: " << file_path << std::endl;
throw std::runtime_error("Error reading file");
}
Uint16 w, h;
Uint8 *pixels = LoadGif(buffer.data(), &w, &h);
if (pixels == nullptr)
{
std::cerr << "Error loading GIF from file: " << file_path << std::endl;
throw std::runtime_error("Error loading GIF");
}
// Crear y devolver directamente el objeto SurfaceData
printWithDots("Surface : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
return SurfaceData(w, h, pixels);
}
// Carga una paleta desde un archivo
void Surface::loadPalette(const std::string &file_path)
{
palette_ = ::loadPalette(file_path);
}
// Carga una paleta desde otra paleta
void Surface::loadPalette(Palette palette)
{
palette_ = palette;
}
// Establece un color en la paleta
void Surface::setColor(int index, Uint32 color)
{
palette_.at(index) = color;
}
// Rellena la superficie con un color
void Surface::clear(Uint8 color)
{
const size_t total_pixels = surface_data_->width * surface_data_->height;
std::fill(surface_data_->data, surface_data_->data + total_pixels, color);
}
// Pone un pixel en la SurfaceData
void Surface::putPixel(int x, int y, Uint8 color)
{
if (x < 0 || y < 0 || x >= surface_data_->width || y >= surface_data_->height)
{
return; // Coordenadas fuera de rango
}
const int index = x + y * surface_data_->width;
surface_data_->data[index] = color;
}
// Obtiene el color de un pixel de la surface_data
Uint8 Surface::getPixel(int x, int y) { return surface_data_->data[x + y * surface_data_->width]; }
// Dibuja un rectangulo relleno
void Surface::fillRect(SDL_Rect *rect, Uint8 color)
{
// Limitar los valores del rectángulo al tamaño de la superficie
int x_start = std::max(0, rect->x);
int y_start = std::max(0, rect->y);
int x_end = std::min(rect->x + rect->w, static_cast<int>(surface_data_->width));
int y_end = std::min(rect->y + rect->h, static_cast<int>(surface_data_->height));
// Recorrer cada píxel dentro del rectángulo directamente
for (int y = y_start; y < y_end; ++y)
{
for (int x = x_start; x < x_end; ++x)
{
const int index = x + y * surface_data_->width;
surface_data_->data[index] = color;
}
}
}
// Dibuja el borde de un rectangulo
void Surface::drawRectBorder(SDL_Rect *rect, Uint8 color)
{
// Limitar los valores del rectángulo al tamaño de la superficie
int x_start = std::max(0, rect->x);
int y_start = std::max(0, rect->y);
int x_end = std::min(rect->x + rect->w, static_cast<int>(surface_data_->width));
int y_end = std::min(rect->y + rect->h, static_cast<int>(surface_data_->height));
// Dibujar bordes horizontales
for (int x = x_start; x < x_end; ++x)
{
// Borde superior
const int top_index = x + y_start * surface_data_->width;
surface_data_->data[top_index] = color;
// Borde inferior
const int bottom_index = x + (y_end - 1) * surface_data_->width;
surface_data_->data[bottom_index] = color;
}
// Dibujar bordes verticales
for (int y = y_start; y < y_end; ++y)
{
// Borde izquierdo
const int left_index = x_start + y * surface_data_->width;
surface_data_->data[left_index] = color;
// Borde derecho
const int right_index = (x_end - 1) + y * surface_data_->width;
surface_data_->data[right_index] = color;
}
}
// Dibuja una linea
void Surface::drawLine(int x1, int y1, int x2, int y2, Uint8 color)
{
// Calcula las diferencias
int dx = std::abs(x2 - x1);
int dy = std::abs(y2 - y1);
// Determina la dirección del incremento
int sx = (x1 < x2) ? 1 : -1;
int sy = (y1 < y2) ? 1 : -1;
int err = dx - dy;
while (true)
{
// Asegúrate de no dibujar fuera de los límites de la superficie
if (x1 >= 0 && x1 < surface_data_->width && y1 >= 0 && y1 < surface_data_->height)
{
surface_data_->data[x1 + y1 * surface_data_->width] = color;
}
// Si alcanzamos el punto final, salimos
if (x1 == x2 && y1 == y2)
break;
int e2 = 2 * err;
if (e2 > -dy)
{
err -= dy;
x1 += sx;
}
if (e2 < dx)
{
err += dx;
y1 += sy;
}
}
}
void Surface::render(int dx, int dy, int sx, int sy, int w, int h)
{
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
// Limitar la región para evitar accesos fuera de rango en origen
w = std::min(w, surface_data_->width - sx);
h = std::min(h, surface_data_->height - sy);
// Limitar la región para evitar accesos fuera de rango en destino
w = std::min(w, surface_data->width - dx);
h = std::min(h, surface_data->height - dy);
for (int iy = 0; iy < h; ++iy)
{
for (int ix = 0; ix < w; ++ix)
{
int src_x = sx + ix;
int src_y = sy + iy;
int dest_x = dx + ix;
int dest_y = dy + iy;
// Verificar que las coordenadas de destino están dentro de los límites
if (dest_x >= 0 && dest_x < surface_data->width && dest_y >= 0 && dest_y < surface_data->height)
{
Uint8 color = surface_data_->data[src_x + src_y * surface_data_->width];
if (color != transparent_color_)
{
surface_data->data[dest_x + dest_y * surface_data->width] = sub_palette_[color];
}
}
}
}
}
void Surface::render(int x, int y, SDL_Rect *srcRect, SDL_RendererFlip flip)
{
auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData();
// Determina la región de origen (clip) a renderizar
int sx = (srcRect) ? srcRect->x : 0;
int sy = (srcRect) ? srcRect->y : 0;
int w = (srcRect) ? srcRect->w : surface_data_->width;
int h = (srcRect) ? srcRect->h : surface_data_->height;
// Limitar la región para evitar accesos fuera de rango en origen
w = std::min(w, surface_data_->width - sx);
h = std::min(h, surface_data_->height - sy);
w = std::min(w, surface_data_dest->width - x);
h = std::min(h, surface_data_dest->height - y);
// Limitar la región para evitar accesos fuera de rango en destino
w = std::min(w, surface_data_dest->width - x);
h = std::min(h, surface_data_dest->height - y);
// Renderiza píxel por píxel aplicando el flip si es necesario
for (int iy = 0; iy < h; ++iy)
{
for (int ix = 0; ix < w; ++ix)
{
// Coordenadas de origen
int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix);
int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy);
// Coordenadas de destino
int dest_x = x + ix;
int dest_y = y + iy;
// Verificar que las coordenadas de destino están dentro de los límites
if (dest_x >= 0 && dest_x < surface_data_dest->width && dest_y >= 0 && dest_y < surface_data_dest->height)
{
// Copia el píxel si no es transparente
Uint8 color = surface_data_->data[src_x + src_y * surface_data_->width];
if (color != transparent_color_)
{
surface_data_dest->data[dest_x + dest_y * surface_data_dest->width] = sub_palette_[color];
}
}
}
}
}
// Copia una región de la superficie de origen a la de destino
void Surface::render(SDL_Rect *srcRect, SDL_Rect *dstRect, SDL_RendererFlip flip)
{
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
// Si srcRect es nullptr, tomar toda la superficie fuente
int sx = (srcRect) ? srcRect->x : 0;
int sy = (srcRect) ? srcRect->y : 0;
int sw = (srcRect) ? srcRect->w : surface_data_->width;
int sh = (srcRect) ? srcRect->h : surface_data_->height;
// Si dstRect es nullptr, asignar las mismas dimensiones que srcRect
int dx = (dstRect) ? dstRect->x : 0;
int dy = (dstRect) ? dstRect->y : 0;
int dw = (dstRect) ? dstRect->w : sw;
int dh = (dstRect) ? dstRect->h : sh;
// Asegurarse de que srcRect y dstRect tienen las mismas dimensiones
if (sw != dw || sh != dh)
{
dw = sw; // Respetar las dimensiones de srcRect
dh = sh;
}
// Limitar la región para evitar accesos fuera de rango en src y dst
sw = std::min(sw, surface_data_->width - sx);
sh = std::min(sh, surface_data_->height - sy);
dw = std::min(dw, surface_data->width - dx);
dh = std::min(dh, surface_data->height - dy);
int final_width = std::min(sw, dw);
int final_height = std::min(sh, dh);
// Renderiza píxel por píxel aplicando el flip si es necesario
for (int iy = 0; iy < final_height; ++iy)
{
for (int ix = 0; ix < final_width; ++ix)
{
// Coordenadas de origen
int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + final_width - 1 - ix) : (sx + ix);
int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + final_height - 1 - iy) : (sy + iy);
// Coordenadas de destino
int dest_x = dx + ix;
int dest_y = dy + iy;
// Copiar el píxel si no es transparente
Uint8 color = surface_data_->data[src_x + src_y * surface_data_->width];
if (color != transparent_color_)
{
surface_data->data[dest_x + dest_y * surface_data->width] = sub_palette_[color];
}
}
}
}
// Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro
void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 target_color, SDL_Rect *srcRect, SDL_RendererFlip flip)
{
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
// Determina la región de origen (clip) a renderizar
int sx = (srcRect) ? srcRect->x : 0;
int sy = (srcRect) ? srcRect->y : 0;
int w = (srcRect) ? srcRect->w : surface_data_->width;
int h = (srcRect) ? srcRect->h : surface_data_->height;
// Limitar la región para evitar accesos fuera de rango
w = std::min(w, surface_data_->width - sx);
h = std::min(h, surface_data_->height - sy);
// Renderiza píxel por píxel aplicando el flip si es necesario
for (int iy = 0; iy < h; ++iy)
{
for (int ix = 0; ix < w; ++ix)
{
// Coordenadas de origen
int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix);
int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy);
// Coordenadas de destino
int dest_x = x + ix;
int dest_y = y + iy;
// Verifica que las coordenadas de destino estén dentro de los límites
if (dest_x < 0 || dest_y < 0 || dest_x >= surface_data->width || dest_y >= surface_data->height)
{
continue; // Saltar píxeles fuera del rango del destino
}
// Copia el píxel si no es transparente
Uint8 color = surface_data_->data[src_x + src_y * surface_data_->width];
if (color != transparent_color_)
{
surface_data->data[dest_x + dest_y * surface_data->width] =
(color == source_color) ? target_color : color;
}
}
}
}
// Vuelca la superficie a una textura
void Surface::copyToTexture(SDL_Renderer *renderer, SDL_Texture *texture)
{
if (!renderer || !texture || !surface_data_)
{
throw std::runtime_error("Renderer or texture is null.");
}
if (surface_data_->width <= 0 || surface_data_->height <= 0 || !surface_data_->data)
{
throw std::runtime_error("Invalid surface dimensions or data.");
}
Uint32 *pixels = nullptr;
int pitch = 0;
// Bloquea la textura para modificar los píxeles directamente
if (SDL_LockTexture(texture, nullptr, (void **)&pixels, &pitch) != 0)
{
throw std::runtime_error("Failed to lock texture: " + std::string(SDL_GetError()));
}
// Convertir `pitch` de bytes a Uint32 (asegurando alineación correcta en hardware)
int row_stride = pitch / sizeof(Uint32);
for (int y = 0; y < surface_data_->height; ++y)
{
for (int x = 0; x < surface_data_->width; ++x)
{
// Calcular la posición correcta en la textura teniendo en cuenta el stride
int texture_index = y * row_stride + x;
int surface_index = y * surface_data_->width + x;
pixels[texture_index] = palette_[surface_data_->data[surface_index]];
}
}
SDL_UnlockTexture(texture); // Desbloquea la textura
// Renderiza la textura en la pantalla completa
if (SDL_RenderCopy(renderer, texture, nullptr, nullptr) != 0)
{
throw std::runtime_error("Failed to copy texture to renderer: " + std::string(SDL_GetError()));
}
}
// Realiza un efecto de fundido en la paleta principal
bool Surface::fadePalette()
{
// Verificar que el tamaño mínimo de palette_ sea adecuado
static constexpr int palette_size = 19;
if (sizeof(palette_) / sizeof(palette_[0]) < palette_size)
{
throw std::runtime_error("Palette size is insufficient for fadePalette operation.");
}
// Desplazar colores (pares e impares)
for (int i = 18; i > 1; --i)
{
palette_[i] = palette_[i - 2];
}
// Ajustar el primer color
palette_[1] = palette_[0];
// Devolver si el índice 15 coincide con el índice 0
return palette_[15] == palette_[0];
}
// Realiza un efecto de fundido en la paleta secundaria
bool Surface::fadeSubPalette()
{
// Verificar que el tamaño mínimo de sub_palette_ sea adecuado
static constexpr int sub_palette_size = 19;
if (sizeof(sub_palette_) / sizeof(sub_palette_[0]) < sub_palette_size)
{
throw std::runtime_error("Palette size is insufficient for fadePalette operation.");
}
// Desplazar colores (pares e impares)
for (int i = 18; i > 1; --i)
{
sub_palette_[i] = sub_palette_[i - 2];
}
// Ajustar el primer color
sub_palette_[1] = sub_palette_[0];
// Devolver si el índice 15 coincide con el índice 0
return sub_palette_[15] == sub_palette_[0];
}