// IWYU pragma: no_include #include "core/rendering/surface.hpp" #include #include // Para min, max, copy_n, fill #include // Para abs #include // Para uint32_t #include // Para memcpy, size_t #include // Para basic_ifstream, basic_ostream, basic_ist... #include // Para cerr #include // Para shared_ptr, __shared_ptr_access, default... #include // Para basic_istringstream #include // Para runtime_error #include // Para vector #include "core/rendering/gif.hpp" // Para Gif #include "core/rendering/screen.hpp" // Para Screen #include "core/resources/resource_helper.hpp" // Para ResourceHelper // Carga una paleta desde un archivo .gif auto loadPalette(const std::string& file_path) -> Palette { // Load file using ResourceHelper (supports both filesystem and pack) auto buffer = jdd::ResourceHelper::loadFile(file_path); if (buffer.empty()) { throw std::runtime_error("Error opening file: " + file_path); } // Cargar la paleta usando los datos del buffer std::vector pal = GIF::Gif::loadPalette(buffer.data()); if (pal.empty()) { throw std::runtime_error("No palette found in GIF file: " + file_path); } // Crear la paleta y copiar los datos desde 'pal' Palette palette = {}; // Inicializa la paleta con ceros std::copy_n(pal.begin(), std::min(pal.size(), 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 auto readPalFile(const std::string& file_path) -> Palette { Palette palette{}; palette.fill(0); // Inicializar todo con 0 (transparente por defecto) // Load file using ResourceHelper (supports both filesystem and pack) auto file_data = jdd::ResourceHelper::loadFile(file_path); if (file_data.empty()) { throw std::runtime_error("No se pudo abrir el archivo .pal: " + file_path); } // Convert bytes to string for parsing std::string content(file_data.begin(), file_data.end()); std::istringstream stream(content); std::string line; int line_number = 0; int color_index = 0; while (std::getline(stream, 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; int g; int 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; } } } printWithDots("Palette : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]"); return palette; } // Constructor Surface::Surface(int w, int h) : surface_data_(std::make_shared(w, h)), transparent_color_(static_cast(PaletteColor::TRANSPARENT)) { initializeSubPalette(sub_palette_); } Surface::Surface(const std::string& file_path) : transparent_color_(static_cast(PaletteColor::TRANSPARENT)) { SurfaceData loaded_data = loadSurface(file_path); surface_data_ = std::make_shared(std::move(loaded_data)); initializeSubPalette(sub_palette_); } // Carga una superficie desde un archivo auto Surface::loadSurface(const std::string& file_path) -> SurfaceData { // Load file using ResourceHelper (supports both filesystem and pack) std::vector buffer = jdd::ResourceHelper::loadFile(file_path); if (buffer.empty()) { std::cerr << "Error opening file: " << file_path << '\n'; throw std::runtime_error("Error opening file"); } // Crear un objeto Gif y llamar a la función loadGif Uint16 w = 0; Uint16 h = 0; std::vector raw_pixels = GIF::Gif::loadGif(buffer.data(), w, h); if (raw_pixels.empty()) { std::cerr << "Error loading GIF from file: " << file_path << '\n'; throw std::runtime_error("Error loading GIF"); } // Si el constructor de Surface espera un std::shared_ptr, // reservamos un bloque dinámico y copiamos los datos del vector. size_t pixel_count = raw_pixels.size(); auto pixels = std::shared_ptr(new Uint8[pixel_count], std::default_delete()); std::memcpy(pixels.get(), raw_pixels.data(), pixel_count); // Crear y devolver directamente el objeto SurfaceData printWithDots("Surface : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]"); return {static_cast(w), static_cast(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; Uint8* data_ptr = surface_data_->data.get(); std::fill(data_ptr, data_ptr + 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.get()[INDEX] = color; } // Obtiene el color de un pixel de la surface_data auto Surface::getPixel(int x, int y) -> Uint8 { return surface_data_->data.get()[x + (y * static_cast(surface_data_->width))]; } // Dibuja un rectangulo relleno void Surface::fillRect(const SDL_FRect* rect, Uint8 color) { // Limitar los valores del rectángulo al tamaño de la superficie float x_start = std::max(0.0F, rect->x); float y_start = std::max(0.0F, rect->y); float x_end = std::min(rect->x + rect->w, surface_data_->width); float y_end = std::min(rect->y + rect->h, 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.get()[INDEX] = color; } } } // Dibuja el borde de un rectangulo void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) { // Limitar los valores del rectángulo al tamaño de la superficie float x_start = std::max(0.0F, rect->x); float y_start = std::max(0.0F, rect->y); float x_end = std::min(rect->x + rect->w, surface_data_->width); float y_end = std::min(rect->y + rect->h, 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.get()[TOP_INDEX] = color; // Borde inferior const int BOTTOM_INDEX = x + ((y_end - 1) * surface_data_->width); surface_data_->data.get()[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.get()[LEFT_INDEX] = color; // Borde derecho const int RIGHT_INDEX = (x_end - 1) + (y * surface_data_->width); surface_data_->data.get()[RIGHT_INDEX] = color; } } // Dibuja una linea void Surface::drawLine(float x1, float y1, float x2, float y2, Uint8 color) { // Calcula las diferencias float dx = std::abs(x2 - x1); float dy = std::abs(y2 - y1); // Determina la dirección del incremento float sx = (x1 < x2) ? 1 : -1; float sy = (y1 < y2) ? 1 : -1; float 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.get()[static_cast(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(float dx, float dy, float sx, float sy, float w, float 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) { // Verificar que las coordenadas de destino están dentro de los límites if (int dest_x = dx + ix; dest_x >= 0 && dest_x < surface_data->width) { if (int dest_y = dy + iy; dest_y >= 0 && dest_y < surface_data->height) { int src_x = sx + ix; int src_y = sy + iy; Uint8 color = surface_data_->data.get()[static_cast(src_x + (src_y * surface_data_->width))]; if (color != transparent_color_) { surface_data->data.get()[static_cast(dest_x + (dest_y * surface_data->width))] = sub_palette_[color]; } } } } } } void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) { auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData(); // Determina la región de origen (clip) a renderizar float sx = ((src_rect) != nullptr) ? src_rect->x : 0; float sy = ((src_rect) != nullptr) ? src_rect->y : 0; float w = ((src_rect) != nullptr) ? src_rect->w : surface_data_->width; float h = ((src_rect) != nullptr) ? src_rect->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.get()[static_cast(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]; } } } } } // Helper para calcular coordenadas con flip void Surface::calculateFlippedCoords(int ix, int iy, float sx, float sy, float w, float h, SDL_FlipMode flip, int& src_x, int& src_y) { src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix); src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy); } // Helper para copiar un pixel si no es transparente void Surface::copyPixelIfNotTransparent(Uint8* dest_data, int dest_x, int dest_y, int dest_width, int src_x, int src_y) const { if (dest_x < 0 || dest_y < 0) { return; } Uint8 color = surface_data_->data.get()[static_cast(src_x + (src_y * surface_data_->width))]; if (color != transparent_color_) { dest_data[dest_x + (dest_y * dest_width)] = sub_palette_[color]; } } // Copia una región de la superficie de origen a la de destino void Surface::render(SDL_FRect* src_rect, SDL_FRect* dst_rect, SDL_FlipMode flip) { auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData(); // Si srcRect es nullptr, tomar toda la superficie fuente float sx = ((src_rect) != nullptr) ? src_rect->x : 0; float sy = ((src_rect) != nullptr) ? src_rect->y : 0; float sw = ((src_rect) != nullptr) ? src_rect->w : surface_data_->width; float sh = ((src_rect) != nullptr) ? src_rect->h : surface_data_->height; // Si dstRect es nullptr, asignar las mismas dimensiones que srcRect float dx = ((dst_rect) != nullptr) ? dst_rect->x : 0; float dy = ((dst_rect) != nullptr) ? dst_rect->y : 0; float dw = ((dst_rect) != nullptr) ? dst_rect->w : sw; float dh = ((dst_rect) != nullptr) ? dst_rect->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) { int src_x = 0; int src_y = 0; calculateFlippedCoords(ix, iy, sx, sy, final_width, final_height, flip, src_x, src_y); int dest_x = dx + ix; int dest_y = dy + iy; // Verificar límites de destino antes de copiar if (dest_x >= 0 && dest_x < surface_data->width && dest_y >= 0 && dest_y < surface_data->height) { copyPixelIfNotTransparent(surface_data->data.get(), dest_x, dest_y, surface_data->width, src_x, src_y); } } } } // 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_FRect* src_rect, SDL_FlipMode flip) { auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData(); // Determina la región de origen (clip) a renderizar float sx = ((src_rect) != nullptr) ? src_rect->x : 0; float sy = ((src_rect) != nullptr) ? src_rect->y : 0; float w = ((src_rect) != nullptr) ? src_rect->w : surface_data_->width; float h = ((src_rect) != nullptr) ? src_rect->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.get()[static_cast(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 == nullptr) || (texture == nullptr) || !surface_data_) { throw std::runtime_error("Renderer or texture is null."); } if (surface_data_->width <= 0 || surface_data_->height <= 0 || (surface_data_->data == nullptr)) { 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, reinterpret_cast(&pixels), &pitch)) { 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.get()[surface_index]]; } } SDL_UnlockTexture(texture); // Desbloquea la textura // Renderiza la textura en la pantalla completa if (!SDL_RenderTexture(renderer, texture, nullptr, nullptr)) { throw std::runtime_error("Failed to copy texture to renderer: " + std::string(SDL_GetError())); } } // Vuelca la superficie a una textura void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect* src_rect, SDL_FRect* dest_rect) { if ((renderer == nullptr) || (texture == nullptr) || !surface_data_) { throw std::runtime_error("Renderer or texture is null."); } if (surface_data_->width <= 0 || surface_data_->height <= 0 || (surface_data_->data == nullptr)) { throw std::runtime_error("Invalid surface dimensions or data."); } Uint32* pixels = nullptr; int pitch = 0; SDL_Rect lock_rect; if (dest_rect != nullptr) { lock_rect.x = static_cast(dest_rect->x); lock_rect.y = static_cast(dest_rect->y); lock_rect.w = static_cast(dest_rect->w); lock_rect.h = static_cast(dest_rect->h); } // Usa lockRect solo si destRect no es nulo if (!SDL_LockTexture(texture, (dest_rect != nullptr) ? &lock_rect : nullptr, reinterpret_cast(&pixels), &pitch)) { throw std::runtime_error("Failed to lock texture: " + std::string(SDL_GetError())); } int row_stride = pitch / sizeof(Uint32); for (int y = 0; y < surface_data_->height; ++y) { for (int x = 0; x < surface_data_->width; ++x) { int texture_index = (y * row_stride) + x; int surface_index = (y * surface_data_->width) + x; pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]]; } } SDL_UnlockTexture(texture); // Renderiza la textura con los rectángulos especificados if (!SDL_RenderTexture(renderer, texture, src_rect, dest_rect)) { throw std::runtime_error("Failed to copy texture to renderer: " + std::string(SDL_GetError())); } } // Realiza un efecto de fundido en la paleta principal auto Surface::fadePalette() -> bool { // 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 auto Surface::fadeSubPalette(Uint32 delay) -> bool { // Variable estática para almacenar el último tick static Uint32 last_tick_ = 0; // Obtener el tiempo actual Uint32 current_tick = SDL_GetTicks(); // Verificar si ha pasado el tiempo de retardo if (current_tick - last_tick_ < delay) { return false; // No se realiza el fade } // Actualizar el último tick last_tick_ = current_tick; // 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]; }