You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

167 lines
5.2 KiB

#include "engine/render/subtitle_renderer.hpp"
#include <algorithm>
#include <string>
#ifdef USE_SDL3
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_pixels.h>
#include <SDL3/SDL_rect.h>
#include <SDL3/SDL_surface.h>
#else
#include <SDL.h>
#endif
#include "DiabloUI/ui_flags.hpp"
#include "engine/rectangle.hpp"
#include "engine/render/text_render.hpp"
#include "engine/surface.hpp"
#include "utils/log.hpp"
#include "utils/sdl_compat.h"
#include "utils/sdl_wrap.h"
#include "utils/srt_parser.hpp"
namespace devilution {
void SubtitleRenderer::LoadSubtitles(const char *videoFilename)
{
Clear();
// Generate subtitle filename by replacing .smk with .srt
std::string subtitlePath(videoFilename);
std::replace(subtitlePath.begin(), subtitlePath.end(), '\\', '/');
const size_t extPos = subtitlePath.rfind('.');
subtitlePath = (extPos != std::string::npos ? subtitlePath.substr(0, extPos) : subtitlePath) + ".srt";
Log("Loading subtitles from: {}", subtitlePath);
subtitles_ = LoadSrtFile(subtitlePath);
Log("Loaded {} subtitle entries", subtitles_.size());
if (!subtitles_.empty()) {
Log("First subtitle: {}ms-{}ms: \"{}\"",
subtitles_[0].startTimeMs,
subtitles_[0].endTimeMs,
subtitles_[0].text);
}
}
void SubtitleRenderer::RenderSubtitles(SDL_Surface *videoSurface, uint32_t videoWidth, uint32_t videoHeight, uint64_t currentTimeMs)
{
if (subtitles_.empty() || videoSurface == nullptr)
return;
const std::string subtitleText = GetSubtitleAtTime(subtitles_, currentTimeMs);
if (subtitleText.empty())
return;
LogVerbose(LogCategory::Video, "Rendering subtitle at {}ms: \"{}\"", currentTimeMs, subtitleText);
if (SDLC_SURFACE_BITSPERPIXEL(videoSurface) != 8)
return;
const int videoWidthInt = static_cast<int>(videoWidth);
const int videoHeightInt = static_cast<int>(videoHeight);
// Create subtitle overlay surface if not already created
if (subtitleSurface_ == nullptr) {
constexpr int SubtitleMaxHeight = 100;
subtitleSurface_ = SDLWrap::CreateRGBSurface(
0, videoWidthInt, SubtitleMaxHeight, 8, 0, 0, 0, 0);
// Create and set up palette for subtitle surface - copy from video surface
subtitlePalette_ = SDLWrap::AllocPalette();
#ifdef USE_SDL3
const SDL_Palette *videoPalette = SDL_GetSurfacePalette(videoSurface);
#else
const SDL_Palette *videoPalette = videoSurface->format->palette;
#endif
if (videoPalette != nullptr) {
// Copy the video surface's palette so text colors map correctly
SDL_Color *colors = subtitlePalette_->colors;
constexpr int MaxColors = 256;
for (int i = 0; i < MaxColors && i < videoPalette->ncolors; i++) {
colors[i] = videoPalette->colors[i];
}
// Ensure index 0 is black/transparent for color key
colors[0].r = 0;
colors[0].g = 0;
colors[0].b = 0;
} else {
// Fallback: initialize palette manually
SDL_Color *colors = subtitlePalette_->colors;
colors[0].r = 0;
colors[0].g = 0;
colors[0].b = 0;
constexpr int MaxColors = 256;
for (int i = 1; i < MaxColors; i++) {
colors[i].r = 255;
colors[i].g = 255;
colors[i].b = 255;
}
}
#ifndef USE_SDL1
constexpr int MaxColors = 256;
for (int i = 0; i < MaxColors; i++) {
subtitlePalette_->colors[i].a = SDL_ALPHA_OPAQUE;
}
#endif
if (!SDLC_SetSurfacePalette(subtitleSurface_.get(), subtitlePalette_.get())) {
Log("Failed to set subtitle overlay palette");
}
// Set color key for transparency (index 0 = transparent)
#ifdef USE_SDL1
SDL_SetColorKey(subtitleSurface_.get(), SDL_SRCCOLORKEY, 0);
#else
if (!SDL_SetSurfaceColorKey(subtitleSurface_.get(), true, 0)) {
Log("Failed to set color key: {}", SDL_GetError());
}
#endif
}
// Clear the overlay surface (fill with transparent color)
SDL_FillSurfaceRect(subtitleSurface_.get(), nullptr, 0);
// Render text to the overlay surface
Surface overlaySurface(subtitleSurface_.get());
constexpr int SubtitleMaxHeight = 100;
constexpr int SubtitleBottomPadding = 12;
// Position text rectangle at bottom of overlay (FontSize12 has line height ~12)
// Position y so text appears near bottom of overlay
constexpr int TextLineHeight = 12;
const int textY = SubtitleMaxHeight - TextLineHeight - SubtitleBottomPadding;
constexpr int TextHorizontalPadding = 10;
Rectangle subtitleRect { { TextHorizontalPadding, textY }, { videoWidthInt - TextHorizontalPadding * 2, TextLineHeight + SubtitleBottomPadding } };
TextRenderOptions opts;
opts.flags = UiFlags::AlignCenter | UiFlags::ColorWhite | UiFlags::FontSize12;
opts.spacing = 1;
DrawString(overlaySurface, subtitleText, subtitleRect, opts);
// Blit the overlay onto the video surface at the bottom
SDL_Rect dstRect;
dstRect.x = 0;
// Position overlay at the very bottom of the video, with small padding
dstRect.y = videoHeightInt - SubtitleMaxHeight - SubtitleBottomPadding;
dstRect.w = videoWidthInt;
dstRect.h = SubtitleMaxHeight;
#ifdef USE_SDL3
if (!SDL_BlitSurface(subtitleSurface_.get(), nullptr, videoSurface, &dstRect)) {
Log("Failed to blit subtitle overlay: {}", SDL_GetError());
}
#else
if (SDL_BlitSurface(subtitleSurface_.get(), nullptr, videoSurface, &dstRect) < 0) {
Log("Failed to blit subtitle overlay: {}", SDL_GetError());
}
#endif
}
void SubtitleRenderer::Clear()
{
subtitles_.clear();
subtitleSurface_ = nullptr;
subtitlePalette_ = nullptr;
}
} // namespace devilution