forked from jaildesigner-jailgames/jaildoctors_dilemma
270 lines
8.5 KiB
C++
270 lines
8.5 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 "gif.h" // for LoadGif, LoadPalette
|
|
|
|
Surface::Surface(std::shared_ptr<SurfaceData> surface_dest, int w, int h)
|
|
: surface_dest_(surface_dest),
|
|
surface_(std::make_shared<SurfaceData>(w, h)),
|
|
transparent_color_(0) {}
|
|
|
|
Surface::Surface(std::shared_ptr<SurfaceData> surface_dest, const std::string &file_path)
|
|
: surface_dest_(surface_dest),
|
|
surface_(std::make_shared<SurfaceData>(loadSurface(Asset::get()->get(file_path)))),
|
|
transparent_color_(0) {}
|
|
|
|
Surface::~Surface() {}
|
|
|
|
// 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
|
|
return SurfaceData(w, h, pixels);
|
|
}
|
|
|
|
// Carga una paleta desde un archivo
|
|
void Surface::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);
|
|
}
|
|
|
|
// Copiar los datos de la paleta al std::array
|
|
std::copy(pal.get(), pal.get() + palette_.size(), palette_.begin());
|
|
|
|
for (auto p : palette_)
|
|
{
|
|
std::cout << std::hex << p << " ";
|
|
}
|
|
std::cout << std::endl;
|
|
}
|
|
|
|
// Establece un color en la paleta
|
|
void Surface::setColor(int index, Uint32 color)
|
|
{
|
|
palette_.at(index) = color;
|
|
}
|
|
|
|
// Limpia la superficie de destino con un color
|
|
void Surface::clear(std::shared_ptr<SurfaceData> surface, Uint8 color)
|
|
{
|
|
const size_t total_pixels = surface->width * surface->height;
|
|
std::fill(surface->data, surface->data + total_pixels, color);
|
|
}
|
|
|
|
// Pone un pixel en la superficie de destino
|
|
void Surface::putPixel(int x, int y, Uint8 color)
|
|
{
|
|
if (color == transparent_color_)
|
|
{
|
|
return; // Color transparente, no dibujar
|
|
}
|
|
|
|
if (x < 0 || y < 0 || x >= surface_dest_->width || y >= surface_dest_->height)
|
|
{
|
|
return; // Coordenadas fuera de rango
|
|
}
|
|
|
|
const int index = x + y * surface_dest_->width;
|
|
surface_dest_->data[index] = color;
|
|
}
|
|
|
|
// Obtiene el color de un pixel de la superficie de origen
|
|
Uint8 Surface::getPixel(int x, int y)
|
|
{
|
|
return surface_->data[x + y * surface_->width];
|
|
}
|
|
|
|
// Copia una región de la superficie de origen a la de destino
|
|
void Surface::render(int dx, int dy, int sx, int sy, int w, int h)
|
|
{
|
|
if (!surface_ || !surface_dest_)
|
|
{
|
|
throw std::runtime_error("Surface source or destination is null.");
|
|
}
|
|
|
|
// Limitar la región para evitar accesos fuera de rango
|
|
w = std::min(w, surface_->width - sx);
|
|
h = std::min(h, surface_->height - sy);
|
|
w = std::min(w, surface_dest_->width - dx);
|
|
h = std::min(h, surface_dest_->height - dy);
|
|
|
|
for (int iy = 0; iy < h; ++iy)
|
|
{
|
|
for (int ix = 0; ix < w; ++ix)
|
|
{
|
|
Uint8 color = surface_->data[(sx + ix) + (sy + iy) * surface_->width];
|
|
if (color != transparent_color_) // Opcional: Ignorar píxeles transparentes
|
|
{
|
|
surface_dest_->data[(dx + ix) + (dy + iy) * surface_dest_->width] = color;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copia una región de la superficie de origen a la de destino
|
|
void Surface::render(int x, int y, SDL_Rect *clip, SDL_RendererFlip flip)
|
|
{
|
|
if (!surface_ || !surface_dest_)
|
|
{
|
|
throw std::runtime_error("Surface source or destination is null.");
|
|
}
|
|
|
|
// Determina la región de origen (clip) a renderizar
|
|
int sx = (clip) ? clip->x : 0;
|
|
int sy = (clip) ? clip->y : 0;
|
|
int w = (clip) ? clip->w : surface_->width;
|
|
int h = (clip) ? clip->h : surface_->height;
|
|
|
|
// Limitar la región para evitar accesos fuera de rango
|
|
w = std::min(w, surface_->width - sx);
|
|
h = std::min(h, surface_->height - sy);
|
|
w = std::min(w, surface_dest_->width - x);
|
|
h = std::min(h, surface_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;
|
|
|
|
// Copia el píxel si no es transparente
|
|
Uint8 color = surface_->data[src_x + src_y * surface_->width];
|
|
if (color != transparent_color_) // Opcional: Ignorar píxeles transparentes
|
|
{
|
|
surface_dest_->data[dest_x + dest_y * surface_dest_->width] = color;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Vuelca la superficie a una textura
|
|
void Surface::copyToTexture(SDL_Renderer *renderer, SDL_Texture *texture)
|
|
{
|
|
if (!renderer || !texture)
|
|
{
|
|
throw std::runtime_error("Renderer or texture is null.");
|
|
}
|
|
|
|
if (surface_->width <= 0 || surface_->height <= 0 || !surface_->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_->height; ++y)
|
|
{
|
|
for (int x = 0; x < surface_->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_->width + x;
|
|
|
|
pixels[texture_index] = palette_[surface_->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
|
|
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];
|
|
}
|