#include "surface.h" #include // for SDL_GetError #include // for size_t #include // for min, copy, fill #include // for basic_ostream, basic_ifstream, basic_ios #include // for cerr, cout #include // for shared_ptr, __shared_ptr_access, unique_ptr #include // for runtime_error #include // for vector #include "asset.h" // for Asset #include "gif.h" // for LoadGif, LoadPalette Surface::Surface(std::shared_ptr surface_dest, int w, int h) : surface_data_dest_(surface_dest), surface_data_(std::make_shared(w, h)), original_surface_data_(surface_data_), transparent_color_(0) {} Surface::Surface(std::shared_ptr surface_dest, const std::string &file_path) : surface_data_dest_(surface_dest), transparent_color_(0) { // Carga la SurfaceData SurfaceData loadedData = loadSurface(file_path); surface_data_ = std::make_shared(std::move(loadedData)); // Inicializa el puntero original original_surface_data_ = surface_data_; } 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 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) { // 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 buffer(size); if (!file.read(reinterpret_cast(buffer.data()), size)) { throw std::runtime_error("Error reading file: " + file_path); } // Cargar la paleta usando los datos del buffer std::unique_ptr 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 printWithDots("Palette : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]"); std::copy(pal.get(), pal.get() + palette_.size(), palette_.begin()); } // 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(std::shared_ptr surface_data, 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 superficie de origen Uint8 Surface::getPixel(int x, int y) { return surface_data_->data[x + y * surface_data_->width]; } // Dibuja un rectangulo void Surface::fillRect(std::shared_ptr surface_data, SDL_Rect *rect, Uint8 color) { if (!rect) return; // Verificar si el rectángulo es válido // 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(surface_data->width)); int y_end = std::min(rect->y + rect->h, static_cast(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 una linea void Surface::drawLine(std::shared_ptr surface_data, 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; } } } // 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_data_ || !surface_data_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_data_->width - sx); h = std::min(h, surface_data_->height - sy); w = std::min(w, surface_data_dest_->width - dx); h = std::min(h, surface_data_dest_->height - dy); for (int iy = 0; iy < h; ++iy) { for (int ix = 0; ix < w; ++ix) { Uint8 color = surface_data_->data[(sx + ix) + (sy + iy) * surface_data_->width]; if (color != transparent_color_) { surface_data_dest_->data[(dx + ix) + (dy + iy) * surface_data_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 *srcRect, SDL_RendererFlip flip) { if (!surface_data_ || !surface_data_dest_) { throw std::runtime_error("Surface source or destination is null."); } // 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); 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; // 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] = 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) { if (!surface_data_ || !surface_data_dest_) { throw std::runtime_error("Surface source or destination is null."); } // 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_dest_->width - dx); dh = std::min(dh, surface_data_dest_->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_dest_->data[dest_x + dest_y * surface_data_dest_->width] = 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) { if (!surface_data_ || !surface_data_dest_) { throw std::runtime_error("Surface source or destination is null."); } // 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); 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; // 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] = (color == source_color) ? target_color : 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_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 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]; }