feat: Sistema de pre-escalado de logos con stb_image_resize2

Implementa pre-escalado de alta calidad para eliminar artefactos de
escalado dinámico de SDL y mejorar la nitidez visual de los logos.

Características:
- 4 texturas pre-escaladas (2 logos × 2 resoluciones: base + nativa)
- Detección automática de resolución nativa del monitor
- Switching dinámico entre texturas al cambiar resolución (F4)
- Renderizado 1:1 sin escalado adicional (máxima calidad)
- Algoritmo Mitchell en espacio sRGB (balance calidad/velocidad)
- Todo en RAM, sin archivos temporales

Archivos nuevos:
- source/external/stb_image_resize2.h: Biblioteca de escalado stb
- source/logo_scaler.h/cpp: Clase helper para pre-escalado

Cambios en AppLogo:
- Reemplazadas shared_ptr<Texture> por SDL_Texture* raw pointers
- initialize(): Pre-escala logos a 2 resoluciones al inicio
- updateScreenSize(): Cambia entre texturas según resolución
- render(): Simplificado, siempre usa renderWithGeometry()
- ~AppLogo(): Libera 4 texturas SDL manualmente

El sistema detecta la resolución nativa al inicio y crea versiones
optimizadas. Al presionar F4, cambia automáticamente a la textura
nativa para calidad perfecta en fullscreen.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-18 10:36:26 +02:00
parent c91cb1ca56
commit ad3f5a00e4
5 changed files with 11063 additions and 150 deletions

View File

@@ -1,77 +1,194 @@
#include "app_logo.h"
#include <SDL3/SDL_render.h> // for SDL_SCALEMODE_LINEAR, SDL_RenderGeometry
#include <SDL3/SDL_render.h> // for SDL_DestroyTexture, SDL_RenderGeometry, SDL_SetTextureAlphaMod
#include <cmath> // for powf, sinf, cosf
#include <cstdlib> // for free()
#include <iostream> // for std::cout
#include "external/sprite.h" // for Sprite
#include "external/texture.h" // for Texture
#include "logo_scaler.h" // for LogoScaler
#include "defines.h" // for APPLOGO_HEIGHT_PERCENT, getResourcesDirectory
// ============================================================================
// Destructor - Liberar las 4 texturas SDL
// ============================================================================
AppLogo::~AppLogo() {
if (logo1_base_texture_) {
SDL_DestroyTexture(logo1_base_texture_);
logo1_base_texture_ = nullptr;
}
if (logo1_native_texture_) {
SDL_DestroyTexture(logo1_native_texture_);
logo1_native_texture_ = nullptr;
}
if (logo2_base_texture_) {
SDL_DestroyTexture(logo2_base_texture_);
logo2_base_texture_ = nullptr;
}
if (logo2_native_texture_) {
SDL_DestroyTexture(logo2_native_texture_);
logo2_native_texture_ = nullptr;
}
}
// ============================================================================
// Inicialización - Pre-escalar logos a 2 resoluciones (base y nativa)
// ============================================================================
bool AppLogo::initialize(SDL_Renderer* renderer, int screen_width, int screen_height) {
renderer_ = renderer;
base_screen_width_ = screen_width;
base_screen_height_ = screen_height;
screen_width_ = screen_width;
screen_height_ = screen_height;
std::string resources_dir = getResourcesDirectory();
// ========================================================================
// Cargar LOGO1 desde data/logo/logo.png
// 1. Detectar resolución nativa del monitor
// ========================================================================
if (!LogoScaler::detectNativeResolution(native_screen_width_, native_screen_height_)) {
std::cout << "No se pudo detectar resolución nativa, usando solo base" << std::endl;
// Fallback: usar resolución base como nativa
native_screen_width_ = screen_width;
native_screen_height_ = screen_height;
}
// ========================================================================
// 2. Calcular alturas finales para ambas resoluciones
// ========================================================================
int logo_base_target_height = static_cast<int>(base_screen_height_ * APPLOGO_HEIGHT_PERCENT);
int logo_native_target_height = static_cast<int>(native_screen_height_ * APPLOGO_HEIGHT_PERCENT);
std::cout << "Pre-escalando logos:" << std::endl;
std::cout << " Base: " << base_screen_width_ << "x" << base_screen_height_
<< " (altura logo: " << logo_base_target_height << "px)" << std::endl;
std::cout << " Nativa: " << native_screen_width_ << "x" << native_screen_height_
<< " (altura logo: " << logo_native_target_height << "px)" << std::endl;
// ========================================================================
// 3. Cargar y escalar LOGO1 (data/logo/logo.png) a 2 versiones
// ========================================================================
std::string logo1_path = resources_dir + "/data/logo/logo.png";
logo1_texture_ = std::make_shared<Texture>(renderer, logo1_path);
if (logo1_texture_->getWidth() == 0 || logo1_texture_->getHeight() == 0) {
// Error al cargar textura logo1
// 3a. Versión BASE de logo1
unsigned char* logo1_base_data = LogoScaler::loadAndScale(
logo1_path,
0, // width calculado automáticamente por aspect ratio
logo_base_target_height,
logo1_base_width_,
logo1_base_height_
);
if (logo1_base_data == nullptr) {
std::cout << "Error: No se pudo escalar logo1 (base)" << std::endl;
return false;
}
// Configurar filtrado LINEAR para suavizado
logo1_texture_->setScaleMode(SDL_SCALEMODE_LINEAR);
logo1_base_texture_ = LogoScaler::createTextureFromBuffer(
renderer, logo1_base_data, logo1_base_width_, logo1_base_height_
);
free(logo1_base_data); // Liberar buffer temporal
// Crear sprite con la textura
logo1_sprite_ = std::make_unique<Sprite>(logo1_texture_);
if (logo1_base_texture_ == nullptr) {
std::cout << "Error: No se pudo crear textura logo1 (base)" << std::endl;
return false;
}
// Configurar el clip para que use toda la textura
float logo1_width = static_cast<float>(logo1_texture_->getWidth());
float logo1_height = static_cast<float>(logo1_texture_->getHeight());
logo1_sprite_->setClip({0.0f, 0.0f, logo1_width, logo1_height});
// Habilitar alpha blending
SDL_SetTextureBlendMode(logo1_base_texture_, SDL_BLENDMODE_BLEND);
// 3b. Versión NATIVA de logo1
unsigned char* logo1_native_data = LogoScaler::loadAndScale(
logo1_path,
0, // width calculado automáticamente
logo_native_target_height,
logo1_native_width_,
logo1_native_height_
);
if (logo1_native_data == nullptr) {
std::cout << "Error: No se pudo escalar logo1 (nativa)" << std::endl;
return false;
}
logo1_native_texture_ = LogoScaler::createTextureFromBuffer(
renderer, logo1_native_data, logo1_native_width_, logo1_native_height_
);
free(logo1_native_data);
if (logo1_native_texture_ == nullptr) {
std::cout << "Error: No se pudo crear textura logo1 (nativa)" << std::endl;
return false;
}
SDL_SetTextureBlendMode(logo1_native_texture_, SDL_BLENDMODE_BLEND);
// ========================================================================
// Cargar LOGO2 desde data/logo/logo2.png
// 4. Cargar y escalar LOGO2 (data/logo/logo2.png) a 2 versiones
// ========================================================================
std::string logo2_path = resources_dir + "/data/logo/logo2.png";
logo2_texture_ = std::make_shared<Texture>(renderer, logo2_path);
if (logo2_texture_->getWidth() == 0 || logo2_texture_->getHeight() == 0) {
// Error al cargar textura logo2
// 4a. Versión BASE de logo2
unsigned char* logo2_base_data = LogoScaler::loadAndScale(
logo2_path,
0,
logo_base_target_height,
logo2_base_width_,
logo2_base_height_
);
if (logo2_base_data == nullptr) {
std::cout << "Error: No se pudo escalar logo2 (base)" << std::endl;
return false;
}
// Configurar filtrado LINEAR para suavizado
logo2_texture_->setScaleMode(SDL_SCALEMODE_LINEAR);
logo2_base_texture_ = LogoScaler::createTextureFromBuffer(
renderer, logo2_base_data, logo2_base_width_, logo2_base_height_
);
free(logo2_base_data);
// Crear sprite con la textura
logo2_sprite_ = std::make_unique<Sprite>(logo2_texture_);
if (logo2_base_texture_ == nullptr) {
std::cout << "Error: No se pudo crear textura logo2 (base)" << std::endl;
return false;
}
// Configurar el clip para que use toda la textura
float logo2_width = static_cast<float>(logo2_texture_->getWidth());
float logo2_height = static_cast<float>(logo2_texture_->getHeight());
logo2_sprite_->setClip({0.0f, 0.0f, logo2_width, logo2_height});
SDL_SetTextureBlendMode(logo2_base_texture_, SDL_BLENDMODE_BLEND);
// 4b. Versión NATIVA de logo2
unsigned char* logo2_native_data = LogoScaler::loadAndScale(
logo2_path,
0,
logo_native_target_height,
logo2_native_width_,
logo2_native_height_
);
if (logo2_native_data == nullptr) {
std::cout << "Error: No se pudo escalar logo2 (nativa)" << std::endl;
return false;
}
logo2_native_texture_ = LogoScaler::createTextureFromBuffer(
renderer, logo2_native_data, logo2_native_width_, logo2_native_height_
);
free(logo2_native_data);
if (logo2_native_texture_ == nullptr) {
std::cout << "Error: No se pudo crear textura logo2 (nativa)" << std::endl;
return false;
}
SDL_SetTextureBlendMode(logo2_native_texture_, SDL_BLENDMODE_BLEND);
// ========================================================================
// Calcular tamaño base (asumimos mismo tamaño para ambos logos)
// El logo debe tener una altura de APPLOGO_HEIGHT_PERCENT (40%) de la pantalla
// 5. Inicialmente usar texturas BASE (la resolución de inicio)
// ========================================================================
float target_height = screen_height_ * APPLOGO_HEIGHT_PERCENT;
float scale = target_height / logo1_height;
logo1_current_texture_ = logo1_base_texture_;
logo1_current_width_ = logo1_base_width_;
logo1_current_height_ = logo1_base_height_;
base_width_ = logo1_width * scale;
base_height_ = target_height; // = logo1_height * scale
// Aplicar escala inicial a ambos sprites
logo1_sprite_->setSize(base_width_, base_height_);
logo2_sprite_->setSize(base_width_, base_height_);
// Posicionar ambos logos en el centro del cuadrante inferior derecho (superpuestos)
updateLogoPosition();
logo2_current_texture_ = logo2_base_texture_;
logo2_current_width_ = logo2_base_width_;
logo2_current_height_ = logo2_base_height_;
std::cout << "Logos pre-escalados exitosamente (4 texturas creadas)" << std::endl;
return true;
}
@@ -340,60 +457,14 @@ void AppLogo::update(float delta_time, AppMode current_mode) {
}
break;
}
// Aplicar alpha a ambos logos (compartido - sincronizado)
if (logo1_texture_) {
logo1_texture_->setAlpha(current_alpha_);
}
if (logo2_texture_) {
logo2_texture_->setAlpha(current_alpha_);
}
// Aplicar escala animada INDEPENDIENTE a cada logo
if (logo1_sprite_) {
float scaled_width = base_width_ * logo1_scale_;
float scaled_height = base_height_ * logo1_scale_;
logo1_sprite_->setSize(scaled_width, scaled_height);
}
if (logo2_sprite_) {
float scaled_width = base_width_ * logo2_scale_;
float scaled_height = base_height_ * logo2_scale_;
logo2_sprite_->setSize(scaled_width, scaled_height);
}
// Recentrar ambos logos (están superpuestos, misma posición)
updateLogoPosition();
}
void AppLogo::render() {
// Renderizar si NO está en estado HIDDEN (incluye FADE_IN, VISIBLE, FADE_OUT)
if (state_ != AppLogoState::HIDDEN) {
// Determinar animaciones actuales para cada logo
AppLogoAnimationType logo1_anim = (state_ == AppLogoState::FADE_IN) ? logo1_entry_animation_ : logo1_exit_animation_;
AppLogoAnimationType logo2_anim = (state_ == AppLogoState::FADE_IN) ? logo2_entry_animation_ : logo2_exit_animation_;
// ====================================================================
// Renderizar LOGO1 primero (fondo)
// ====================================================================
if (logo1_anim != AppLogoAnimationType::ZOOM_ONLY) {
// Usar renderizado con geometría para deformaciones/rotación
renderWithGeometry(1);
} else if (logo1_sprite_) {
// Usar renderizado simple con Sprite (solo ZOOM_ONLY)
logo1_sprite_->render();
}
// ====================================================================
// Renderizar LOGO2 después (encima de logo1)
// ====================================================================
if (logo2_anim != AppLogoAnimationType::ZOOM_ONLY) {
// Usar renderizado con geometría para deformaciones/rotación
renderWithGeometry(2);
} else if (logo2_sprite_) {
// Usar renderizado simple con Sprite (solo ZOOM_ONLY)
logo2_sprite_->render();
}
// Renderizar LOGO1 primero (fondo), luego LOGO2 (encima)
renderWithGeometry(1);
renderWithGeometry(2);
}
}
@@ -401,59 +472,37 @@ void AppLogo::updateScreenSize(int screen_width, int screen_height) {
screen_width_ = screen_width;
screen_height_ = screen_height;
// Recalcular tamaño base para la nueva resolución (asumimos mismo tamaño para ambos logos)
if (logo1_sprite_ && logo1_texture_) {
float logo_width = static_cast<float>(logo1_texture_->getWidth());
float logo_height = static_cast<float>(logo1_texture_->getHeight());
// ========================================================================
// Detectar si coincide con resolución nativa o base, cambiar texturas
// ========================================================================
bool is_native = (screen_width == native_screen_width_ && screen_height == native_screen_height_);
// El logo debe tener una altura de APPLOGO_HEIGHT_PERCENT (40%) de la pantalla
float target_height = screen_height_ * APPLOGO_HEIGHT_PERCENT;
float scale = target_height / logo_height;
if (is_native) {
// Cambiar a texturas nativas (F4 fullscreen)
logo1_current_texture_ = logo1_native_texture_;
logo1_current_width_ = logo1_native_width_;
logo1_current_height_ = logo1_native_height_;
// Recalcular tamaño base
base_width_ = logo_width * scale;
base_height_ = target_height; // = logo_height * scale
logo2_current_texture_ = logo2_native_texture_;
logo2_current_width_ = logo2_native_width_;
logo2_current_height_ = logo2_native_height_;
// Aplicar escala actual a AMBOS logos (respeta la animación en curso)
if (logo1_sprite_) {
float scaled_width = base_width_ * logo1_scale_;
float scaled_height = base_height_ * logo1_scale_;
logo1_sprite_->setSize(scaled_width, scaled_height);
}
std::cout << "AppLogo: Cambiado a texturas NATIVAS" << std::endl;
} else {
// Cambiar a texturas base (ventana redimensionable)
logo1_current_texture_ = logo1_base_texture_;
logo1_current_width_ = logo1_base_width_;
logo1_current_height_ = logo1_base_height_;
if (logo2_sprite_) {
float scaled_width = base_width_ * logo2_scale_;
float scaled_height = base_height_ * logo2_scale_;
logo2_sprite_->setSize(scaled_width, scaled_height);
}
logo2_current_texture_ = logo2_base_texture_;
logo2_current_width_ = logo2_base_width_;
logo2_current_height_ = logo2_base_height_;
// Reposicionar ambos logos
updateLogoPosition();
}
}
void AppLogo::updateLogoPosition() {
// Calcular padding desde bordes derecho e inferior
float padding_x = screen_width_ * APPLOGO_PADDING_PERCENT;
float padding_y = screen_height_ * APPLOGO_PADDING_PERCENT;
// Posicionar LOGO1 (anclado a esquina inferior derecha con padding)
if (logo1_sprite_) {
float logo1_width = base_width_ * logo1_scale_;
float logo1_height = base_height_ * logo1_scale_;
float pos_x = screen_width_ - logo1_width - padding_x;
float pos_y = screen_height_ - logo1_height - padding_y;
logo1_sprite_->setPos({pos_x, pos_y});
std::cout << "AppLogo: Cambiado a texturas BASE" << std::endl;
}
// Posicionar LOGO2 (anclado a esquina inferior derecha con padding, superpuesto a logo1)
if (logo2_sprite_) {
float logo2_width = base_width_ * logo2_scale_;
float logo2_height = base_height_ * logo2_scale_;
float pos_x = screen_width_ - logo2_width - padding_x;
float pos_y = screen_height_ - logo2_height - padding_y;
logo2_sprite_->setPos({pos_x, pos_y});
}
// Nota: No es necesario recalcular escalas porque las texturas están pre-escaladas
// al tamaño exacto de pantalla. Solo renderizamos al 100% (o con deformaciones de animación).
}
// ============================================================================
@@ -528,26 +577,31 @@ AppLogoAnimationType AppLogo::getRandomAnimation() {
}
// ============================================================================
// Renderizado con geometría deformada (para animación ELASTIC_STICK)
// Renderizado con geometría (para todos los logos, con deformaciones)
// ============================================================================
void AppLogo::renderWithGeometry(int logo_index) {
if (!renderer_) return;
// Seleccionar variables según el logo_index (1 = logo1, 2 = logo2)
std::shared_ptr<Texture> texture;
SDL_Texture* texture;
int base_width, base_height;
float scale, squash_y, stretch_x, rotation;
if (logo_index == 1) {
if (!logo1_texture_) return;
texture = logo1_texture_;
if (!logo1_current_texture_) return;
texture = logo1_current_texture_;
base_width = logo1_current_width_;
base_height = logo1_current_height_;
scale = logo1_scale_;
squash_y = logo1_squash_y_;
stretch_x = logo1_stretch_x_;
rotation = logo1_rotation_;
} else if (logo_index == 2) {
if (!logo2_texture_) return;
texture = logo2_texture_;
if (!logo2_current_texture_) return;
texture = logo2_current_texture_;
base_width = logo2_current_width_;
base_height = logo2_current_height_;
scale = logo2_scale_;
squash_y = logo2_squash_y_;
stretch_x = logo2_stretch_x_;
@@ -556,9 +610,13 @@ void AppLogo::renderWithGeometry(int logo_index) {
return; // Índice inválido
}
// Aplicar alpha a la textura
SDL_SetTextureAlphaMod(texture, static_cast<Uint8>(current_alpha_));
// Calcular tamaño con escala y deformaciones aplicadas
float width = base_width_ * scale * stretch_x;
float height = base_height_ * scale * squash_y;
// (base_width y base_height ya están pre-escalados al tamaño correcto de pantalla)
float width = base_width * scale * stretch_x;
float height = base_height * scale * squash_y;
// Calcular padding desde bordes derecho e inferior
float padding_x = screen_width_ * APPLOGO_PADDING_PERCENT;
@@ -631,5 +689,5 @@ void AppLogo::renderWithGeometry(int logo_index) {
int indices[6] = {0, 1, 2, 2, 3, 0};
// Renderizar con la textura del logo correspondiente
SDL_RenderGeometry(renderer_, texture->getSDLTexture(), vertices, 4, indices, 6);
SDL_RenderGeometry(renderer_, texture, vertices, 4, indices, 6);
}