FifeGUI 0.3.0
A C++ GUI library designed for games.
imageloader.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause
2// SPDX-FileCopyrightText: 2004 - 2008 Olof Naessén and Per Larsson
3// SPDX-FileCopyrightText: 2013 - 2026 Fifengine contributors
4
5// Corresponding header include
6#include "fifechan/backends/sdl3/imageloader.hpp"
7
8// Standard library includes
9#include <filesystem>
10#include <memory>
11#include <string>
12
13// Third-party library includes
14#include <SDL3_image/SDL_image.h>
15
16// Project headers (subdirs before local)
17#include "fifechan/backends/sdl3/image.hpp"
18#include "fifechan/exception.hpp"
19
20namespace fcn::sdl3
21{
22 ImageLoader::ImageLoader() = default;
23
24 namespace
25 {
26 std::string resolveFromExecutableDirectory(std::string const & filename)
27 {
28 std::filesystem::path const requestedPath(filename);
29 if (requestedPath.is_absolute()) {
30 return filename;
31 }
32
33 char const * basePathRaw = SDL_GetBasePath();
34 if (basePathRaw == nullptr) {
35 return filename;
36 }
37 std::filesystem::path const candidate = std::filesystem::path(basePathRaw) / requestedPath;
38 return candidate.string();
39 }
40 } // namespace
41
42 fcn::Image* ImageLoader::load(std::string const & filename, bool convertToDisplayFormat)
43 {
44 SDL_Surface* loadedSurface = loadSDLSurface(filename);
45
46 if (loadedSurface == nullptr) {
47 throwException(std::string("Unable to load image file: ") + filename);
48 }
49
50 SDL_Surface* surface = convertToStandardFormat(loadedSurface);
51 SDL_DestroySurface(loadedSurface);
52
53 if (surface == nullptr) {
54 throwException((std::string("Not enough memory to load: ") + filename));
55 }
56
57 auto image = std::make_unique<Image>(surface, true, mRenderer);
58
59 if (convertToDisplayFormat) {
60 image->convertToDisplayFormat();
61 }
62
63 return image.release();
64 }
65
66 void ImageLoader::setRenderer(SDL_Renderer* renderer)
67 {
68 mRenderer = renderer;
69 }
70
71 SDL_Surface* ImageLoader::loadSDLSurface(std::string const & filename)
72 {
73 SDL_Surface* surface = IMG_Load(filename.c_str());
74 if (surface != nullptr) {
75 return surface;
76 }
77
78 std::string const resolvedPath = resolveFromExecutableDirectory(filename);
79 if (resolvedPath == filename) {
80 return nullptr;
81 }
82
83 return IMG_Load(resolvedPath.c_str());
84 }
85
86 SDL_Texture* ImageLoader::loadSDLTexture(std::string const & filename)
87 {
88 SDL_Texture* texture = IMG_LoadTexture(mRenderer, filename.c_str());
89 if (texture != nullptr) {
90 return texture;
91 }
92
93 std::string const resolvedPath = resolveFromExecutableDirectory(filename);
94 if (resolvedPath == filename) {
95 return nullptr;
96 }
97
98 return IMG_LoadTexture(mRenderer, resolvedPath.c_str());
99 }
100
101 SDL_Surface* ImageLoader::convertToStandardFormat(SDL_Surface* surface)
102 {
103 if (surface == nullptr) {
104 return nullptr;
105 }
106
107 // Convert the original surface to the standard format
108 auto* converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA8888);
109
110 if (converted == nullptr) {
111 return nullptr;
112 }
113
114 // Check if the surface already has a color key set (e.g., from the original surface)
115 // SDL_ConvertSurface should preserve color key information
116 bool hasPink = false;
117 Uint32 colorKey = 0;
118
119 if (SDL_SurfaceHasColorKey(converted)) {
120 if (!SDL_GetSurfaceColorKey(converted, &colorKey)) {
121 // For SDL_PIXELFORMAT_RGBA8888, the color key is stored as a 32-bit value
122 // Extract RGB components (assuming the color key was set with SDL_MapSurfaceRGB)
123 Uint8 const r = (colorKey >> 16) & 0xFF; // R is typically in bits 16-23 for RGBA8888
124 Uint8 const g = (colorKey >> 8) & 0xFF; // G is typically in bits 8-15
125 Uint8 const b = colorKey & 0xFF; // B is typically in bits 0-7
126 if (r == 255 && g == 0 && b == 255) {
127 hasPink = true;
128 }
129 }
130 }
131
132 // Fallback: if no color key set, scan for magenta pixels (O(n²) - rare case)
133 if (!hasPink) {
134 for (int y = 0; y < converted->h && !hasPink; ++y) {
135 for (int x = 0; x < converted->w && !hasPink; ++x) {
136 uint8_t r{};
137 uint8_t g{};
138 uint8_t b{};
139 uint8_t a{};
140
141 SDL_ReadSurfacePixel(converted, x, y, &r, &g, &b, &a);
142
143 if (r == 255 && g == 0 && b == 255) {
144 hasPink = true;
145 }
146 }
147 }
148 }
149
150 if (hasPink) {
151 SDL_SetSurfaceColorKey(converted, true, SDL_MapSurfaceRGB(converted, 255, 0, 255));
152 SDL_SetSurfaceRLE(converted, true);
153 }
154
155 return converted;
156 }
157
158 SDL_PixelFormat const & ImageLoader::getSDLPixelFormat()
159 {
160 return mPixelFormat;
161 }
162
163 void ImageLoader::setSDLPixelFormat(SDL_PixelFormat const & format)
164 {
165 mPixelFormat = format;
166 }
167} // namespace fcn::sdl3
Abstract holder for image data.
Definition image.hpp:34
SDL_PixelFormat const & getSDLPixelFormat()
Return the current SDL pixel format used for conversions.
virtual SDL_Surface * convertToStandardFormat(SDL_Surface *surface)
Convert a surface to a standard internal format (internal).
fcn::Image * load(std::string const &filename, bool convertToDisplayFormat) override
Load an image from filename.
virtual SDL_Surface * loadSDLSurface(std::string const &filename)
Load an SDL_Surface from disk (internal).
virtual SDL_Texture * loadSDLTexture(std::string const &filename)
Load an SDL_Texture from disk (internal).
void setSDLPixelFormat(SDL_PixelFormat const &format)
Set the SDL pixel format used for conversions.
void setRenderer(SDL_Renderer *renderer)
Set the SDL renderer used when creating textures.
void throwException(std::string const &message, std::source_location location=std::source_location::current())
Throw an Exception capturing the current source location.