CoriEngine
Loading...
Searching...
No Matches
Image.cpp
Go to the documentation of this file.
1#include "Image.hpp"
2#include <SDL3_image/SDL_image.h>
4
5namespace {
6 [[nodiscard]] Uint32 GetPixel32(const SDL_Surface* surface, const int32_t x, const int32_t y) {
7 const auto* pixels = static_cast<Uint32*>(surface->pixels);
8 return pixels[y * (surface->pitch / sizeof(Uint32)) + x];
9 }
10
11 void SetPixel32(const SDL_Surface* surface, const int32_t x, const int32_t y, const Uint32 pixel) {
12 auto* pixels = static_cast<Uint32*>(surface->pixels);
13 pixels[y * (surface->pitch / sizeof(Uint32)) + x] = pixel;
14 }
15}
16
17namespace Cori {
18 namespace Graphics {
19 Image::Image(const std::filesystem::path& path) {
21 const std::filesystem::path placeholder = FileSystem::PathManager::GetAliasedPath("ENGINE_DATA") / "placeholders/missing_texture32.png";
22 if (std::filesystem::exists(path)) {
23 m_Surface = IMG_Load(path.string().c_str());
24 } else {
25 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Image }, "Could not find an image at the specified path: '{}', a placeholder will be loaded instead.", path.string());
26 m_Surface = IMG_Load(placeholder.string().c_str());
27 CORI_CORE_ASSERT(m_Surface, "Failed to load a placeholder image, placeholder path: '{}'", placeholder.string());
28 }
29
30
31 if (!m_Surface) {
32 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Image }, "Failed to load image at path: '{}'. SDL_Error: '{}'", path.string(), SDL_GetError());
33 m_Surface = IMG_Load(placeholder.string().c_str());
34 CORI_CORE_ASSERT(m_Surface, "Failed to load a placeholder image, placeholder path: '{}'", placeholder.string());
35 }
36 else {
37 m_SuccessStatus = true;
38 }
39
40 if (m_SuccessStatus) {
41 if (static_cast<SDL_Surface*>(m_Surface)->format != SDL_PIXELFORMAT_RGBA32) {
42 CORI_CORE_DEBUG_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Image }, "Converting '{}' to RGBA32 (ARGB888) format. Initial format ID: '{}'. (Refer to SDL3s' SDL_PixelFormat to get the exact format from ID)", path.string(), static_cast<uint32_t>(static_cast<SDL_Surface*>(m_Surface)->format));
43
44 SDL_Surface* converted = SDL_ConvertSurface(static_cast<SDL_Surface*>(m_Surface), SDL_PIXELFORMAT_RGBA32);
45 if (!converted) {
46 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Image }, "Failed to convert image at path: '{}'. Loading placeholder. SDL_Error: '{}'", path.string(), SDL_GetError());
47 m_Surface = IMG_Load(placeholder.string().c_str());
48 CORI_CORE_ASSERT(m_Surface, "Failed to load a placeholder image, placeholder path: '{}'", placeholder.string());
49 SDL_DestroySurface(converted);
50 m_SuccessStatus = false;
51 }
52 else {
53 SDL_DestroySurface(static_cast<SDL_Surface*>(m_Surface));
54 m_Surface = static_cast<void*>(converted);
55 }
56 }
57
58 const auto pixels = static_cast<Uint8*>(static_cast<SDL_Surface*>(m_Surface)->pixels);
59 for (int32_t y = 0; y < static_cast<SDL_Surface*>(m_Surface)->h; ++y) {
60 const Uint8* row = pixels + (y * static_cast<SDL_Surface*>(m_Surface)->pitch);
61 for (int32_t x = 0; x < static_cast<SDL_Surface*>(m_Surface)->w; ++x) {
62 const Uint8 alpha = row[x * 4 + 3];
63 if (alpha < 255 && alpha != 0) {
64 m_HasSemiTransparency = true;
65 break;
66 }
67 }
68 }
69 }
70
71 if (m_SuccessStatus) {
72 CORI_CORE_INFO_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Image } ,"Loaded image from path: '{}' successfully", path.string());
73 }
74 else {
75 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Image } ,"Failed to load image at path '{}', a placeholder was loaded instead.", path.string());
76 }
77 }
78
80 SDL_DestroySurface(static_cast<SDL_Surface*>(m_Surface));
81 }
82
83 void* Image::GetPixelData() const {
84 return static_cast<SDL_Surface*>(m_Surface)->pixels;
85 }
86
87 uint32_t Image::GetWidth() const {
88 return static_cast<SDL_Surface*>(m_Surface)->w;
89 }
90
91 uint32_t Image::GetHeight() const {
92 return static_cast<SDL_Surface*>(m_Surface)->h;
93 }
94
96 return m_HasSemiTransparency;
97 }
98
99 std::expected<void, Core::CoriError<>> Image::AddPadding(const glm::u16vec2 spriteResolution) {
100 if (m_IsPadded) {
101 std::unexpected(Core::CoriError("The image is already padded, you can't add padding to the image if it is already padded."));
102 }
103
104 auto* originalSurface = static_cast<SDL_Surface*>(m_Surface);
105
106 const uint32_t cols = originalSurface->w / spriteResolution.x;
107 const uint32_t rows = originalSurface->h / spriteResolution.y;
108 constexpr int32_t padding = 1;
109
110 const uint32_t newWidth = cols * spriteResolution.x + cols * padding * 2;
111 const uint32_t newHeight = rows * spriteResolution.y + rows * padding * 2;
112
113 SDL_Surface* paddedSurface = SDL_CreateSurface(static_cast<int32_t>(newWidth), static_cast<int32_t>(newHeight), originalSurface->format);
114 if (!paddedSurface) {
115 return std::unexpected(Core::CoriError(std::format("Failed to create new padded surface. SDL_Error: {}", SDL_GetError())));
116 }
117
118 SDL_FillSurfaceRect(paddedSurface, nullptr, 0x00000000);
119
120 for (uint32_t row = 0; row < rows; ++row) {
121 for (uint32_t col = 0; col < cols; ++col) {
122 SDL_Rect srcRect = {
123 static_cast<int32_t>(col * spriteResolution.x),
124 static_cast<int32_t>(row * spriteResolution.y),
125 static_cast<int32_t>(spriteResolution.x),
126 static_cast<int32_t>(spriteResolution.y)
127 };
128
129 SDL_Rect dstRect = {
130 padding + static_cast<int32_t>(col * (spriteResolution.x + padding * 2)),
131 padding + static_cast<int32_t>(row * (spriteResolution.y + padding * 2)),
132 static_cast<int32_t>(spriteResolution.x),
133 static_cast<int32_t>(spriteResolution.y)
134 };
135
136 SDL_BlitSurface(originalSurface, &srcRect, paddedSurface, &dstRect);
137
138 if (!SDL_LockSurface(paddedSurface) || !SDL_LockSurface(originalSurface)) {
139 SDL_DestroySurface(paddedSurface);
140 return std::unexpected(Core::CoriError(std::format("Failed to lock surfaces for padding. SDL_Error: {}", SDL_GetError())));
141 }
142
143 for (int x = 0; x < srcRect.w; ++x) {
144 const Uint32 topPixel = GetPixel32(originalSurface, srcRect.x + x, srcRect.y);
145 const Uint32 bottomPixel = GetPixel32(originalSurface, srcRect.x + x, srcRect.y + srcRect.h - 1);
146 for (int32_t p = 1; p <= padding; ++p) {
147 SetPixel32(paddedSurface, dstRect.x + x, dstRect.y - p, topPixel);
148 SetPixel32(paddedSurface, dstRect.x + x, dstRect.y + srcRect.h - 1 + p, bottomPixel);
149 }
150 }
151
152 for (int y = 0; y < srcRect.h; ++y) {
153 const Uint32 leftPixel = GetPixel32(originalSurface, srcRect.x, srcRect.y + y);
154 const Uint32 rightPixel = GetPixel32(originalSurface, srcRect.x + srcRect.w - 1, srcRect.y + y);
155 for (int32_t p = 1; p <= padding; ++p) {
156 SetPixel32(paddedSurface, dstRect.x - p, dstRect.y + y, leftPixel);
157 SetPixel32(paddedSurface, dstRect.x + srcRect.w - 1 + p, dstRect.y + y, rightPixel);
158 }
159 }
160
161 const Uint32 tl = GetPixel32(originalSurface, srcRect.x, srcRect.y);
162 const Uint32 tr = GetPixel32(originalSurface, srcRect.x + srcRect.w - 1, srcRect.y);
163 const Uint32 bl = GetPixel32(originalSurface, srcRect.x, srcRect.y + srcRect.h - 1);
164 const Uint32 br = GetPixel32(originalSurface, srcRect.x + srcRect.w - 1, srcRect.y + srcRect.h - 1);
165
166 for (int32_t px = 1; px <= padding; ++px) {
167 for (int32_t py = 1; py <= padding; ++py) {
168 SetPixel32(paddedSurface, dstRect.x - px, dstRect.y - py, tl);
169 SetPixel32(paddedSurface, dstRect.x + srcRect.w - 1 + px, dstRect.y - py, tr);
170 SetPixel32(paddedSurface, dstRect.x - px, dstRect.y + srcRect.h - 1 + py, bl);
171 SetPixel32(paddedSurface, dstRect.x + srcRect.w - 1 + px, dstRect.y + srcRect.h - 1 + py, br);
172 }
173 }
174
175 SDL_UnlockSurface(originalSurface);
176 SDL_UnlockSurface(paddedSurface);
177 }
178 }
179
180 SDL_DestroySurface(originalSurface);
181 m_Surface = static_cast<void*>(paddedSurface);
182 m_IsPadded = true;
183
184 CORI_CORE_DEBUG_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Image }, "Successfully added padding to image.");
185
186 return {};
187 }
188
189 std::shared_ptr<Image> Image::Create(const std::filesystem::path& path) {
190 return std::shared_ptr<Image>(new Image(path));
191 }
192
193 // ReSharper disable once CppMemberFunctionMayBeConst
195 if (CORI_CORE_CHECK(SDL_LockSurface(static_cast<SDL_Surface*>(m_Surface)) != 0, "FlipVertically: Failed to lock surface. SDL_Error: {}", SDL_GetError())) { return; }
196
197 const int32_t height = static_cast<SDL_Surface*>(m_Surface)->h;
198 const int32_t pitch = static_cast<SDL_Surface*>(m_Surface)->pitch;
199 const auto pixels = static_cast<uint8_t*>(static_cast<SDL_Surface*>(m_Surface)->pixels);
200
201 std::vector<uint8_t> rowBuffer(pitch);
202
203 for (int32_t y = 0; y < height / 2; ++y) {
204 uint8_t* topRow = pixels + y * pitch;
205 uint8_t* bottomRow = pixels + (height - 1 - y) * pitch;
206
207 std::copy_n(topRow, pitch, rowBuffer.data());
208 std::copy_n(bottomRow, pitch, topRow);
209 std::copy_n(rowBuffer.data(), pitch, bottomRow);
210 }
211
212 SDL_UnlockSurface(static_cast<SDL_Surface*>(m_Surface));
213 }
214
215 // ReSharper disable once CppMemberFunctionMayBeConst
217 if (CORI_CORE_CHECK(SDL_LockSurface(static_cast<SDL_Surface*>(m_Surface)) != 0, "FlipHorizontally: Failed to lock surface. SDL_Error: {}", SDL_GetError())) { return ;}
218
219
220 const int32_t width = static_cast<SDL_Surface*>(m_Surface)->w;
221 const int32_t height = static_cast<SDL_Surface*>(m_Surface)->h;
222 const int32_t pitch = static_cast<SDL_Surface*>(m_Surface)->pitch;
223 constexpr int32_t bpp = 4;
224 const auto pixels = static_cast<uint8_t*>(static_cast<SDL_Surface*>(m_Surface)->pixels);
225
226 for (int32_t y = 0; y < height; ++y) {
227 uint8_t* rowStart = pixels + y * pitch;
228 for (int32_t x = 0; x < width / 2; ++x) {
229 uint8_t* leftPixel = rowStart + x * bpp;
230 uint8_t* rightPixel = rowStart + (width - 1 - x) * bpp;
231
232 std::swap_ranges(leftPixel, leftPixel + bpp, rightPixel);
233 }
234 }
235
236 SDL_UnlockSurface(static_cast<SDL_Surface*>(m_Surface));
237 }
238 }
239}
#define CORI_CORE_DEBUG_TAGGED(...)
Definition Logger.hpp:1026
#define CORI_CORE_CHECK(x,...)
Definition Logger.hpp:1042
#define CORI_CORE_ASSERT(x,...)
Definition Logger.hpp:1029
#define CORI_CORE_ERROR_TAGGED(...)
Definition Logger.hpp:1039
#define CORI_CORE_INFO_TAGGED(...)
Definition Logger.hpp:1027
#define CORI_PROFILE_FUNCTION()
Definition Profiler.hpp:9
Custom error class mainly used in std::expected.
Definition Error.hpp:27
static std::filesystem::path GetAliasedPath(const std::string &alias)
Retries the full aliased path defined in fsgame.json.
bool HasSemiTransparency() const
Checks if an image has semi transparency or no.
Definition Image.cpp:95
uint32_t GetWidth() const
Gives you the image width.
Definition Image.cpp:87
void * GetPixelData() const
Gives you the pointer to the start of the pixel data, format is RGBA8888.
Definition Image.cpp:83
void FlipVertically()
Flips a loaded image vertically.
Definition Image.cpp:194
void FlipHorizontally()
Flips a loaded image horizontally.
Definition Image.cpp:216
static std::shared_ptr< Image > Create(const std::filesystem::path &path)
Creates an Image from the picture at the specified path.
Definition Image.cpp:189
std::expected< void, Core::CoriError<> > AddPadding(const glm::u16vec2 spriteResolution)
Adds padding to the image that will be used in a sprite atlas.
Definition Image.cpp:99
uint32_t GetHeight() const
Gives you the image height.
Definition Image.cpp:91
Almost everything connected to graphics is in this namespace.
Definition Window.hpp:7
Global engine namespace.
static constexpr char Image[]
Definition Logger.hpp:68
static constexpr char Self[]
Definition Logger.hpp:47