#define STB_IMAGE_IMPLEMENTATION #include "texture.h" #include // Para SDL_LogError, SDL_LogCategory, Uint8, SDL_... #include // Para uint32_t #include // Para memcpy #include // Para basic_ifstream, basic_istream, basic_ios #include // Para basic_istringstream #include // Para runtime_error #include // Para basic_string, char_traits, operator+, string #include // Para vector #include "external/gif.h" // Para Gif #include "stb_image.h" // Para stbi_image_free, stbi_load, STBI_rgb_alpha #include "utils.h" // Para getFileName, Color, printWithDots // Constructor Texture::Texture(SDL_Renderer *renderer, const std::string &path) : renderer_(renderer), path_(path) { // Carga el fichero en la textura if (!path_.empty()) { // Obtiene la extensión const std::string EXTENSION = path_.substr(path_.find_last_of(".") + 1); // .png if (EXTENSION == "png") { loadFromFile(path_); } // .gif else if (EXTENSION == "gif") { // Crea la surface desde un fichero surface_ = loadSurface(path_); // Añade la propia paleta del fichero a la lista addPaletteFromGifFile(path_); // Crea la textura, establece el BlendMode y copia la surface a la textura createBlank(width_, height_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING); SDL_SetTextureBlendMode(texture_, SDL_BLENDMODE_BLEND); flipSurface(); } } } // Destructor Texture::~Texture() { unloadTexture(); unloadSurface(); palettes_.clear(); } // Carga una imagen desde un fichero bool Texture::loadFromFile(const std::string &file_path) { if (file_path.empty()) return false; int req_format = STBI_rgb_alpha; int width, height, orig_format; unsigned char *data = stbi_load(file_path.c_str(), &width, &height, &orig_format, req_format); if (!data) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Fichero no encontrado %s", getFileName(file_path).c_str()); throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path)); } else { printWithDots("Texture : ", getFileName(file_path), "[ LOADED ]"); } int pitch; SDL_PixelFormat pixel_format; // STBI_rgb_alpha (RGBA) pitch = 4 * width; pixel_format = SDL_PIXELFORMAT_RGBA32; // Limpia unloadTexture(); // La textura final SDL_Texture *new_texture = nullptr; // Carga la imagen desde una ruta específica auto loaded_surface = SDL_CreateSurfaceFrom(width, height, pixel_format, static_cast(data), pitch); if (loaded_surface == nullptr) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to load image %s", file_path.c_str()); } else { // Crea la textura desde los pixels de la surface new_texture = SDL_CreateTextureFromSurface(renderer_, loaded_surface); if (new_texture == nullptr) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create texture from %s! SDL Error: %s", file_path.c_str(), SDL_GetError()); } else { // Obtiene las dimensiones de la imagen width_ = loaded_surface->w; height_ = loaded_surface->h; } // Elimina la textura cargada SDL_DestroySurface(loaded_surface); } // Return success stbi_image_free(data); texture_ = new_texture; return texture_ != nullptr; } // Crea una textura en blanco bool Texture::createBlank(int width, int height, SDL_PixelFormat format, SDL_TextureAccess access) { // Crea una textura sin inicializar texture_ = SDL_CreateTexture(renderer_, format, access, width, height); if (!texture_) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create blank texture! SDL Error: %s", SDL_GetError()); } else { width_ = width; height_ = height; } return texture_ != nullptr; } // Libera la memoria de la textura void Texture::unloadTexture() { // Libera la textura if (texture_) { SDL_DestroyTexture(texture_); texture_ = nullptr; width_ = 0; height_ = 0; } } // Establece el color para la modulacion void Texture::setColor(Uint8 red, Uint8 green, Uint8 blue) { SDL_SetTextureColorMod(texture_, red, green, blue); } void Texture::setColor(Color color) { SDL_SetTextureColorMod(texture_, color.r, color.g, color.b); } // Establece el blending void Texture::setBlendMode(SDL_BlendMode blending) { SDL_SetTextureBlendMode(texture_, blending); } // Establece el alpha para la modulación void Texture::setAlpha(Uint8 alpha) { SDL_SetTextureAlphaMod(texture_, alpha); } // Renderiza la textura en un punto específico void Texture::render(int x, int y, SDL_FRect *clip, float zoom_w, float zoom_h, double angle, SDL_FPoint *center, SDL_FlipMode flip) { // Establece el destino de renderizado en la pantalla SDL_FRect render_quad = {static_cast(x), static_cast(y), static_cast(width_), static_cast(height_)}; // Obtiene las dimesiones del clip de renderizado if (clip != nullptr) { render_quad.w = clip->w; render_quad.h = clip->h; } // Calcula el zoom y las coordenadas if (zoom_h != 1.0f || zoom_w != 1.0f) { render_quad.x = render_quad.x + (render_quad.w / 2); render_quad.y = render_quad.y + (render_quad.h / 2); render_quad.w = render_quad.w * zoom_w; render_quad.h = render_quad.h * zoom_h; render_quad.x = render_quad.x - (render_quad.w / 2); render_quad.y = render_quad.y - (render_quad.h / 2); } // Renderiza a pantalla SDL_RenderTextureRotated(renderer_, texture_, clip, &render_quad, angle, center, flip); } // Establece la textura como objetivo de renderizado void Texture::setAsRenderTarget(SDL_Renderer *renderer) { SDL_SetRenderTarget(renderer, texture_); } // Obtiene el ancho de la imagen int Texture::getWidth() { return width_; } // Obtiene el alto de la imagen int Texture::getHeight() { return height_; } // Recarga la textura bool Texture::reLoad() { return loadFromFile(path_); } // Obtiene la textura SDL_Texture *Texture::getSDLTexture() { return texture_; } // Desencadenar la superficie actual void Texture::unloadSurface() { surface_.reset(); // Resetea el shared_ptr width_ = 0; height_ = 0; } // Crea una surface desde un fichero .gif std::shared_ptr Texture::loadSurface(const std::string &file_path) { // Libera la superficie actual unloadSurface(); // Abrir el archivo usando std::ifstream para manejo automático del recurso std::ifstream file(file_path, std::ios::binary | std::ios::ate); if (!file) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Fichero no encontrado %s", file_path.c_str()); throw std::runtime_error("Fichero no encontrado: " + file_path); } // Obtener el tamaño del archivo std::streamsize size = file.tellg(); file.seekg(0, std::ios::beg); // Leer el contenido del archivo en un buffer std::vector buffer(size); if (!file.read(reinterpret_cast(buffer.data()), size)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error al leer el fichero %s", file_path.c_str()); throw std::runtime_error("Error al leer el fichero: " + file_path); } // Crear un objeto Gif y llamar a la función loadGif GIF::Gif gif; Uint16 w = 0, h = 0; std::vector raw_pixels = gif.loadGif(buffer.data(), w, h); if (raw_pixels.empty()) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: No se pudo cargar el GIF %s", file_path.c_str()); return nullptr; } // Si el constructor de Surface espera un std::shared_ptr: 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); auto surface = std::make_shared(w, h, pixels); // Actualizar las dimensiones width_ = w; height_ = h; SDL_LogInfo(SDL_LOG_CATEGORY_TEST, "GIF %s cargado correctamente.", file_path.c_str()); return surface; } // Vuelca la surface en la textura void Texture::flipSurface() { // Limpia la textura auto temp = SDL_GetRenderTarget(renderer_); SDL_SetRenderTarget(renderer_, texture_); SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0); SDL_RenderClear(renderer_); SDL_SetRenderTarget(renderer_, temp); // Vuelca los datos Uint32 *pixels; int pitch; SDL_LockTexture(texture_, nullptr, reinterpret_cast(&pixels), &pitch); for (int i = 0; i < width_ * height_; ++i) { pixels[i] = palettes_[current_palette_][surface_->data[i]]; } SDL_UnlockTexture(texture_); } // Establece un color de la paleta void Texture::setPaletteColor(int palette, int index, Uint32 color) { palettes_.at(palette)[index] = color; } // Carga una paleta desde un fichero Palette Texture::loadPaletteFromFile(const std::string &file_path) { Palette palette; // Abrir el archivo GIF std::ifstream file(file_path, std::ios::binary | std::ios::ate); if (!file) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Fichero no encontrado %s", file_path.c_str()); throw std::runtime_error("Fichero no encontrado: " + file_path); } else { printWithDots("Palette : ", getFileName(file_path), "[ LOADED ]"); } // Obtener el tamaño del archivo y leerlo 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)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: No se pudo leer completamente el fichero %s", file_path.c_str()); throw std::runtime_error("Error al leer el fichero: " + file_path); } // Usar la nueva función loadPalette, que devuelve un vector GIF::Gif gif; std::vector pal = gif.loadPalette(buffer.data()); if (pal.empty()) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Advertencia: No se encontró paleta en el archivo %s", file_path.c_str()); return palette; // Devuelve un vector vacío si no hay paleta } // Modificar la conversión para obtener formato RGBA (0xRRGGBBAA) for (size_t i = 0; i < pal.size() && i < palette.size(); ++i) { palette[i] = (pal[i] << 8) | 0xFF; // Resultado: 0xRRGGBBAA } SDL_LogInfo(SDL_LOG_CATEGORY_TEST, "Paleta cargada correctamente desde %s", file_path.c_str()); return palette; } // Añade una paleta a la lista void Texture::addPaletteFromGifFile(const std::string &path) { palettes_.emplace_back(loadPaletteFromFile(path)); setPaletteColor(palettes_.size() - 1, 0, 0x00000000); } // Añade una paleta a la lista void Texture::addPaletteFromPalFile(const std::string &path) { palettes_.emplace_back(readPalFile(path)); setPaletteColor(palettes_.size() - 1, 0, 0x00000000); } // Cambia la paleta de la textura void Texture::setPalette(size_t palette) { if (palette < palettes_.size()) { current_palette_ = palette; flipSurface(); } } // Obtiene el renderizador SDL_Renderer *Texture::getRenderer() { return renderer_; } // Carga una paleta desde un archivo .pal Palette Texture::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 RGBA (A = 255 por defecto) Uint32 color = (r << 24) | (g << 16) | (b << 8) | 255; palette[color_index++] = color; // Limitar a un máximo de 256 colores (opcional) if (color_index >= 256) { break; } } } file.close(); return palette; }