Rendering Engine 0.2.9
Modular Graphics Rendering Engine | v0.2.9
image_data.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Alexander Obzherin � Licensed under the zlib License. See LICENSE.md.
2
3#include "image_data.hpp"
5#include "image_codec_png.hpp"
6#include "logger.hpp"
7#include <boost/filesystem.hpp>
8#include <stdexcept>
9
10namespace rendering_engine
11{
13 :
14 ImageData::ImageData(0U, 0U)
15{
16}
17
18ImageData::ImageData( unsigned int width, unsigned int height )
19 :
20 mWidth( width ),
21 mHeight( height )
22{
23 AllocateMemory( mWidth, mHeight );
24}
25
26ImageData::ImageData(std::string filepath)
27 :
29{
30 auto const pathToTexture = boost::filesystem::path(filepath);
31 if( boost::filesystem::exists(pathToTexture) && boost::filesystem::is_regular_file(pathToTexture) )
32 {
33 boost::filesystem::path const pathToTexture = boost::filesystem::path(filepath);
34
35 size_t const dot = pathToTexture.string().find_last_of(".");
36 std::string const fileExtension = pathToTexture.string().substr(dot + 1);
37
38 if( std::string{ "jpg" } == fileExtension )
39 {
40 LoadTextureJpegFile(pathToTexture.string().c_str());
41 }
42 else
43 {
44 if( std::string{ "png" } == fileExtension )
45 {
46 LoadTexturePngFile(pathToTexture.string().c_str());
47 }
48 else
49 {
50 throw std::runtime_error("Unsupported texture file format!");
51 }
52 }
53 }
54 else
55 {
56 throw std::runtime_error("Path to texture file is incorrect.");
57 }
58}
59
60ImageData::ImageData(std::vector<uint8_t> const& fileBytes)
61{
62 if (fileBytes.size() < 4)
63 throw std::runtime_error("Invalid image buffer");
64
65 // PNG signature
66 if (fileBytes[0] == 0x89 &&
67 fileBytes[1] == 'P' &&
68 fileBytes[2] == 'N' &&
69 fileBytes[3] == 'G')
70 {
71 std::vector<std::uint8_t> pixelsRGBA;
72
73 if (!ReadPngFromMemory(fileBytes.data(), fileBytes.size(), mWidth, mHeight, pixelsRGBA))
74 throw std::runtime_error("PNG memory decode failed");
75
76 AllocateMemory(mWidth, mHeight);
77 Fill(Color(0, 0, 0, 255));
78 LoadImageDataRGBA(pixelsRGBA);
79 return;
80 }
81
82 // JPEG signature: FF D8 FF
83 if (fileBytes[0] == 0xFF &&
84 fileBytes[1] == 0xD8 &&
85 fileBytes[2] == 0xFF)
86 {
87 std::vector<std::uint8_t> pixelsRGB;
88
89 if (!ReadJpegFromMemory(fileBytes.data(), fileBytes.size(), mWidth, mHeight, pixelsRGB))
90 throw std::runtime_error("JPEG memory decode failed");
91
92 AllocateMemory(mWidth, mHeight);
93 LoadImageDataRGB(pixelsRGB);
94
95 return;
96 }
97
98 throw std::runtime_error("Unsupported image format in memory buffer");
99}
100
101ImageData::ImageData(unsigned int width, unsigned int height, std::vector<std::uint8_t> const& pixelsRGBA)
102 :
103ImageData::ImageData(width, height)
104{
105 Fill(Color(255, 255, 255, 255));
106 LoadImageDataRGBA( pixelsRGBA );
107}
108
110{
112}
113
115 :
116 mWidth(src.mWidth),
117 mHeight(src.mHeight),
118 mData(src.mData)
119{
120}
121
123{
124 if (this == &rhs)
125 {
126 return *this;
127 }
128
129 mWidth = rhs.mWidth;
130 mHeight = rhs.mHeight;
131 mData = rhs.mData;
132 return *this;
133}
134
136{
137 std::fill(mData.begin(), mData.end(), color);
138}
139
140void ImageData::SetPixel( unsigned int x, unsigned int y, Color color )
141{
142 if (x < mWidth && y < mHeight)
143 {
144 PixelRef(x, y) = color;
145 }
146}
147
148const Color ImageData::GetPixel( unsigned int x, unsigned int y ) const
149{
150 if (x >= mWidth || y >= mHeight)
151 {
152 LOG_ERROR("Out of bounds access in GetPixel");
153 return Color{};
154 }
155
156 return PixelRef(x, y);
157}
158
159void ImageData::DrawImageOnImageAtPos(unsigned int const x, unsigned int const y, ImageData& toImage, ImageData& fromImage)
160{
161 // If fromImage can not be placed entirely, it will be cropped on right and bottom side
162
163 // Source image draw from top left pixel
164
165 // At first check if point pos is inside toImage
166 if (x >= toImage.GetWidth() || y >= toImage.GetHeight())
167 {
168 return;
169 }
170
171 if (toImage.GetWidth() == 0U || toImage.GetHeight() == 0U ||
172 fromImage.GetWidth() == 0U || fromImage.GetHeight() == 0U)
173 {
174 return;
175 }
176
177 // Crop
178 unsigned int left;
179 if ((toImage.GetWidth() - x) < fromImage.GetWidth())
180 {
181 left = toImage.GetWidth() - x;
182 }
183 else
184 {
185 left = fromImage.GetWidth();
186 }
187
188 unsigned int bottom;
189 if ((toImage.GetHeight() - y) < fromImage.GetHeight())
190 {
191 bottom = toImage.GetHeight() - y;
192 }
193 else
194 {
195 bottom = fromImage.GetHeight();
196 }
197
198 if (left == 0U || bottom == 0U)
199 {
200 return;
201 }
202
203 for (unsigned int deltaY = 0; deltaY < bottom; ++deltaY)
204 {
205 const size_t srcRowOffset =
206 static_cast<size_t>(deltaY) * static_cast<size_t>(fromImage.GetWidth());
207
208 const size_t dstRowOffset =
209 static_cast<size_t>(y + deltaY) * static_cast<size_t>(toImage.GetWidth()) +
210 static_cast<size_t>(x);
211
212 std::memcpy(
213 toImage.mData.data() + dstRowOffset,
214 fromImage.mData.data() + srcRowOffset,
215 static_cast<size_t>(left) * sizeof(Color));
216 }
217}
218
219std::vector<uint8_t> ImageData::GetImageDataRGBA() const
220{
221 if (mWidth == 0 || mHeight == 0)
222 {
223 return {};
224 }
225
226 const size_t totalBytes =
227 static_cast<size_t>(mWidth) * static_cast<size_t>(mHeight) * 4;
228
229 std::vector<uint8_t> result(totalBytes);
230 std::memcpy(result.data(), mData.data(), totalBytes);
231
232 return result;
233}
234
235std::vector<uint8_t> ImageData::GetImageDataRGB() const
236{
237 if (mWidth == 0 || mHeight == 0)
238 {
239 return {};
240 }
241
242 const size_t pixelCount =
243 static_cast<size_t>(mWidth) * static_cast<size_t>(mHeight);
244
245 std::vector<uint8_t> result(pixelCount * 3);
246
247 const Color* src = mData.data();
248 uint8_t* dst = result.data();
249
250 for (size_t i = 0; i < pixelCount; ++i)
251 {
252 dst[i * 3 + 0] = src[i].r;
253 dst[i * 3 + 1] = src[i].g;
254 dst[i * 3 + 2] = src[i].b;
255 }
256
257 return result;
258}
259
260void ImageData::WriteJpegFile(char const* filename)
261{
262 SaveTextureFileJpeg(*this, filename);
263}
264
265bool ImageData::LoadTextureJpegFile(char const* filename)
266{
268 unsigned int width = 0;
269 unsigned int height = 0;
270 std::vector<std::uint8_t> rgbImageDataVector;
271
272 bool result = ReadJpegFile(filename, width, height, rgbImageDataVector);
273 if(result && (rgbImageDataVector.size() == (3U * width * height)))
274 {
275 mWidth = width;
276 mHeight = height;
277 AllocateMemory(width, height);
278 LoadImageDataRGB(rgbImageDataVector);
279 }
280
281 return result;
282}
283
284void ImageData::WritePngFile(char const* filename)
285{
286 SaveTextureFilePng(*this, filename);
287}
288
289bool ImageData::LoadTexturePngFile(char const* filename)
290{
292 unsigned int width = 0;
293 unsigned int height = 0;
294 std::vector<std::uint8_t> rgbaImageDataVector;
295
296 bool result = ReadPngFile(filename, width, height, rgbaImageDataVector);
297
298 if( result && (rgbaImageDataVector.size() == (4U * width * height)) )
299 {
300 mWidth = width;
301 mHeight = height;
302 AllocateMemory(width, height);
303 LoadImageDataRGBA(rgbaImageDataVector);
304 }
305 return true;
306}
307
308void ImageData::AllocateMemory( unsigned int width, unsigned int height )
309{
310 mWidth = width;
311 mHeight = height;
312 mData.resize(static_cast<size_t>(mWidth) * static_cast<size_t>(mHeight));
313}
314
316{
317 mData.clear();
318
319 mWidth = 0;
320 mHeight = 0;
321}
322
323void ImageData::LoadImageDataRGBA(const std::vector<uint8_t>& pixels)
324{
325 const size_t expectedSize =
326 static_cast<size_t>(mWidth) * static_cast<size_t>(mHeight) * 4;
327
328 if (pixels.size() != expectedSize)
329 {
330 throw std::runtime_error("Array data size doesn't match image dimension.");
331 }
332
333 mData.resize(mWidth * mHeight);
334
335 std::memcpy(mData.data(), pixels.data(), expectedSize);
336}
337
338void ImageData::LoadImageDataRGB(std::vector<std::uint8_t> const& pixels)
339{
340 const size_t pixelCount =
341 static_cast<size_t>(mWidth) * static_cast<size_t>(mHeight);
342
343 const size_t expectedSize = pixelCount * 3;
344 if (pixels.size() != expectedSize)
345 {
346 throw std::runtime_error("Array data size doesn't match image dimension.");
347 }
348
349 mData.resize(pixelCount);
350
351 const uint8_t* src = pixels.data();
352 Color* dst = mData.data();
353
354 for (size_t i = 0; i < pixelCount; ++i)
355 {
356 dst[i].r = src[i * 3 + 0];
357 dst[i].g = src[i * 3 + 1];
358 dst[i].b = src[i * 3 + 2];
359 dst[i].a = 255;
360 }
361}
362
363
364} //namespace rendering_engine
Represents raw 2D image data stored in memory.
Definition: image_data.hpp:80
void CleanAllocatedMemory()
Frees allocated memory.
Definition: image_data.cpp:315
~ImageData()
Destructor that frees memory.
Definition: image_data.cpp:109
void SetPixel(unsigned int x, unsigned int y, Color color)
Sets the color of a specific pixel.
Definition: image_data.cpp:140
void AllocateMemory(unsigned int width, unsigned int height)
Allocates memory for internal pixel storage.
Definition: image_data.cpp:308
unsigned int GetWidth() const
Returns the image width.
Definition: image_data.hpp:137
std::vector< uint8_t > GetImageDataRGB() const
Gets raw image data in RGB format.
Definition: image_data.cpp:235
ImageData()
Constructs an empty image.
Definition: image_data.cpp:12
void LoadImageDataRGBA(std::vector< std::uint8_t > const &pixels)
Loads image data from a 32-bit RGBA buffer.
Definition: image_data.cpp:323
bool LoadTextureJpegFile(char const *filename)
Loads image from a JPEG file.
Definition: image_data.cpp:265
static void DrawImageOnImageAtPos(unsigned int const x, unsigned int const y, ImageData &toImage, ImageData &fromImage)
Overlays one image on top of another at a given position.
Definition: image_data.cpp:159
void Fill(Color color)
Fills the image with a solid color.
Definition: image_data.cpp:135
std::vector< uint8_t > GetImageDataRGBA() const
Gets raw image data in RGBA format.
Definition: image_data.cpp:219
ImageData & operator=(const ImageData &rhs)
Copy assignment operator.
Definition: image_data.cpp:122
unsigned int GetHeight() const
Returns the image height.
Definition: image_data.hpp:145
Color & PixelRef(unsigned int x, unsigned int y)
Definition: image_data.hpp:251
void LoadImageDataRGB(std::vector< std::uint8_t > const &pixels)
Loads image data from a 24-bit RGB buffer.
Definition: image_data.cpp:338
void WritePngFile(char const *filename)
Writes the image data to a PNG file.
Definition: image_data.cpp:284
void WriteJpegFile(char const *filename)
Writes the image data to a JPEG file.
Definition: image_data.cpp:260
const Color GetPixel(unsigned int x, unsigned int y) const
Retrieves the color of a specific pixel.
Definition: image_data.cpp:148
bool LoadTexturePngFile(char const *filename)
Loads image from a PNG file.
Definition: image_data.cpp:289
JPEG read/write backend using libjpeg.
static bool ReadJpegFromMemory(const unsigned char *memory, size_t memorySize, unsigned int &width, unsigned int &height, std::vector< std::uint8_t > &rgbImageDataVector)
Decode a JPEG image directly from a memory buffer.
static bool ReadJpegFile(char const *filename, unsigned int &width, unsigned int &height, std::vector< std::uint8_t > &rgbImageDataVector)
Reads a JPEG file into an RGB image buffer.
static void SaveTextureFileJpeg(rendering_engine::ImageData const &imageData, char const *filename)
Saves image data to a JPEG file.
PNG read/write backend using libpng.
static bool ReadPngFromMemory(const unsigned char *memory, size_t memorySize, unsigned int &width, unsigned int &height, std::vector< std::uint8_t > &rgbaImageDataVector)
Reads a PNG image from memory into RGBA image data.
static void SaveTextureFilePng(rendering_engine::ImageData const &imageData, char const *filename)
Saves image data to a PNG file.
static bool ReadPngFile(char const *filename, unsigned int &width, unsigned int &height, std::vector< std::uint8_t > &rgbaImageDataVector)
Reads a PNG file into RGBA image data.
Engine-wide logging system for runtime diagnostics and performance tracking.
#define LOG_ERROR(msg)
Definition: logger.hpp:41
Represents a color with red, green, blue, and alpha channels.
Definition: image_data.hpp:23