MayaFlux 0.3.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
ImageReader.cpp
Go to the documentation of this file.
1#include "ImageReader.hpp"
2
4
7
9
10#ifdef MAYAFLUX_ARCH_X64
11#define STBI_SSE2
12#ifdef MAYAFLUX_COMPILER_MSVC
13#include <immintrin.h>
14#endif
15#endif
16
17#ifdef MAYAFLUX_ARCH_ARM64
18#define STBI_NEON
19#endif
20
21#define STBI_NO_FAILURE_STRINGS
22#define STB_IMAGE_IMPLEMENTATION
23#define STBI_NO_STDIO
24
25#if __has_include("stb/stb_image.h")
26#include "stb/stb_image.h"
27#elif __has_include("stb_image.h")
28#include "stb_image.h"
29#else
30#error "stb_image.h not found"
31#endif
32
33#include <cstddef>
34#include <fstream>
35
36namespace MayaFlux::IO {
37
38[[maybe_unused]] static bool g_stb_simd_logged = []() {
39#ifdef STBI_SSE2
41 "STB Image: SSE2 SIMD optimizations enabled (x64)");
42#elif defined(STBI_NEON)
44 "STB Image: NEON SIMD optimizations enabled (ARM64)");
45#else
47 "STB Image: No SIMD optimizations (scalar fallback)");
48#endif
49 return true;
50}();
51
53 : m_is_open(false)
54{
55}
56
61
62bool ImageReader::can_read(const std::string& filepath) const
63{
64 auto ext = std::filesystem::path(filepath).extension().string();
65 if (!ext.empty() && ext[0] == '.') {
66 ext = ext.substr(1);
67 }
68
69 static const std::vector<std::string> supported = {
70 "png", "jpg", "jpeg", "bmp", "tga", "psd", "gif", "hdr", "pic", "pnm"
71 };
72
73 return std::ranges::find(supported, ext) != supported.end();
74}
75
76bool ImageReader::open(const std::string& filepath, FileReadOptions /*options*/)
77{
78 if (m_is_open) {
79 close();
80 }
81
82 if (!can_read(filepath)) {
83 m_last_error = "Unsupported image format: " + filepath;
85 return false;
86 }
87
88 m_image_data = load(filepath, 4); // Force RGBA
89
90 if (!m_image_data) {
91 m_last_error = "Failed to load image data";
92 return false;
93 }
94
95 m_filepath = filepath;
96 m_is_open = true;
97
99 "Opened image: {} ({}x{}, {} channels)",
100 filepath, m_image_data->width, m_image_data->height, m_image_data->channels);
101
102 return true;
103}
104
106{
107 if (m_is_open) {
108 m_image_data.reset();
109 m_filepath.clear();
110 m_is_open = false;
111 }
112}
113
115{
116 return m_is_open;
117}
118
119std::optional<FileMetadata> ImageReader::get_metadata() const
120{
121 if (!m_is_open || !m_image_data) {
122 return std::nullopt;
123 }
124
125 FileMetadata meta;
126 meta.format = "8-bit";
127
128 meta.attributes["width"] = m_image_data->width;
129 meta.attributes["height"] = m_image_data->height;
131
132 return meta;
133}
134
135std::vector<FileRegion> ImageReader::get_regions() const
136{
137 // Images don't typically have regions
138 return {};
139}
140
141std::vector<Kakshya::DataVariant> ImageReader::read_all()
142{
143 if (!m_is_open || !m_image_data) {
144 m_last_error = "No image open";
145 return {};
146 }
147
148 return { m_image_data->pixels };
149}
150
151std::vector<Kakshya::DataVariant> ImageReader::read_region(const FileRegion& region)
152{
153 if (!m_is_open || !m_image_data) {
154 m_last_error = "No image open";
155 return {};
156 }
157
158 if (region.start_coordinates.size() < 2 || region.end_coordinates.size() < 2) {
159 m_last_error = "Invalid region coordinates for image";
160 return {};
161 }
162
163 auto x_start = static_cast<uint32_t>(region.start_coordinates[0]);
164 auto y_start = static_cast<uint32_t>(region.start_coordinates[1]);
165 auto x_end = static_cast<uint32_t>(region.end_coordinates[0]);
166 auto y_end = static_cast<uint32_t>(region.end_coordinates[1]);
167
168 if (x_end > m_image_data->width || y_end > m_image_data->height) {
169 m_last_error = "Region out of bounds";
170 return {};
171 }
172
173 uint32_t region_width = x_end - x_start;
174 uint32_t region_height = y_end - y_start;
175 size_t region_size = static_cast<size_t>(region_width * region_height) * m_image_data->channels;
176 std::vector<uint8_t> region_data(region_size);
177
178 for (uint32_t y = 0; y < region_height; ++y) {
179 size_t src_offset = (static_cast<size_t>((y_start + y) * m_image_data->width + x_start)) * m_image_data->channels;
180 size_t dst_offset = static_cast<size_t>(y * region_width) * m_image_data->channels;
181 size_t row_size = static_cast<size_t>(region_width) * m_image_data->channels;
182 std::memcpy(
183 region_data.data() + dst_offset,
184 m_image_data->pixels.data() + src_offset,
185 row_size);
186 }
187
188 return { region_data };
189}
190
191std::shared_ptr<Kakshya::SignalSourceContainer> ImageReader::create_container()
192{
193 // Images don't use SignalSourceContainer - they go directly to GPU
194 m_last_error = "Images use direct GPU texture creation, not containers";
195 return nullptr;
196}
197
198bool ImageReader::load_into_container(std::shared_ptr<Kakshya::SignalSourceContainer> /*container*/)
199{
200 // Not applicable for images
201 m_last_error = "Images cannot be loaded into SignalSourceContainer";
202 return false;
203}
204
205std::vector<uint64_t> ImageReader::get_read_position() const
206{
207 return { 0, 0 }; // Images don't have read position
208}
209
210bool ImageReader::seek(const std::vector<uint64_t>& /*position*/)
211{
212 return true; // No-op for static images
213}
214
215std::vector<std::string> ImageReader::get_supported_extensions() const
216{
217 return { "png", "jpg", "jpeg", "bmp", "tga", "psd", "gif", "hdr", "pic", "pnm" };
218}
219
220std::type_index ImageReader::get_data_type() const
221{
222 return typeid(std::vector<uint8_t>);
223}
224
225std::type_index ImageReader::get_container_type() const
226{
227 return typeid(void); // No container for images
228}
229
231{
232 return m_last_error;
233}
234
236{
237 return false; // Images are loaded entirely into memory
238}
239
241{
242 return 0; // Not applicable
243}
244
246{
247 return 2; // width, height (channels are separate)
248}
249
250std::vector<uint64_t> ImageReader::get_dimension_sizes() const
251{
252 if (!m_is_open || !m_image_data) {
253 return {};
254 }
255 return { m_image_data->width, m_image_data->height };
256}
257
258//==============================================================================
259// Static Utility Methods
260//==============================================================================
261
262std::optional<ImageData> ImageReader::load(const std::filesystem::path& path, int desired_channels)
263{
264 if (!std::filesystem::exists(path)) {
266 "Image file not found: {}", path.string());
267 return std::nullopt;
268 }
269
270 std::ifstream file(path, std::ios::binary | std::ios::ate);
271 if (!file.is_open()) {
273 "Failed to open image file: {}", path.string());
274 return std::nullopt;
275 }
276
277 std::streamsize file_size = file.tellg();
278 file.seekg(0, std::ios::beg);
279
280 std::vector<unsigned char> file_buffer(file_size);
281 if (!file.read(reinterpret_cast<char*>(file_buffer.data()), file_size)) {
283 "Failed to read image file: {}", path.string());
284 return std::nullopt;
285 }
286 file.close();
287
288 int width {}, height {}, channels {};
289
290 if (desired_channels == 0) {
291 stbi_info_from_memory(file_buffer.data(), static_cast<int>(file_buffer.size()),
292 &width, &height, &channels);
293 if (channels == 3) {
294 desired_channels = 4;
295 }
296 }
297
298 unsigned char* pixels = stbi_load_from_memory(
299 file_buffer.data(),
300 static_cast<int>(file_buffer.size()),
301 &width, &height, &channels,
302 desired_channels);
303
304 if (!pixels) {
306 "Failed to decode image: {} - {}",
307 path.string(), stbi_failure_reason());
308 return std::nullopt;
309 }
310
311 int result_channels = (desired_channels != 0) ? desired_channels : channels;
312
314 "Loaded image: {} ({}x{}, {} channels{})",
315 path.filename().string(), width, height, result_channels,
316 (channels == 3 && result_channels == 4) ? " [RGB→RGBA]" : "");
317
318 ImageData result;
319 size_t data_size = static_cast<size_t>(width) * height * result_channels;
320 result.pixels.resize(data_size);
321 std::memcpy(result.pixels.data(), pixels, data_size);
322
323 result.width = width;
324 result.height = height;
325 result.channels = result_channels;
326
327 switch (result_channels) {
328 case 1:
329 result.format = Portal::Graphics::ImageFormat::R8;
330 break;
331 case 2:
333 break;
334 case 4:
336 break;
337 default:
339 "Unsupported channel count: {}", result_channels);
340 stbi_image_free(pixels);
341 return std::nullopt;
342 }
343
344 stbi_image_free(pixels);
345 return result;
346}
347
348std::optional<ImageData> ImageReader::load(const std::string& path, int desired_channels)
349{
350 return load(std::filesystem::path(path), desired_channels);
351}
352
353std::optional<ImageData> ImageReader::load_from_memory(const void* data, size_t size)
354{
355 if (!data || size == 0) {
357 "Invalid memory buffer for image loading");
358 return std::nullopt;
359 }
360
361 int width {}, height {}, channels {};
362
363 stbi_info_from_memory(
364 static_cast<const unsigned char*>(data),
365 static_cast<int>(size),
366 &width, &height, &channels);
367
368 int load_as = (channels == 3) ? 4 : 0; // Force RGBA for RGB images
369
370 unsigned char* pixels = stbi_load_from_memory(
371 static_cast<const unsigned char*>(data),
372 static_cast<int>(size),
373 &width, &height, &channels,
374 load_as);
375
376 if (!pixels) {
378 "Failed to decode image from memory: {}",
379 stbi_failure_reason());
380 return std::nullopt;
381 }
382
383 int result_channels = (load_as != 0) ? load_as : channels;
384
386 "Loaded image from memory ({}x{}, {} channels{})",
387 width, height, result_channels,
388 (channels == 3 && result_channels == 4) ? " [RGB→RGBA]" : "");
389
390 ImageData result;
391 size_t data_size = static_cast<size_t>(width) * height * result_channels;
392 result.pixels.resize(data_size);
393 std::memcpy(result.pixels.data(), pixels, data_size);
394
395 result.width = width;
396 result.height = height;
397 result.channels = result_channels;
398
399 switch (result_channels) {
400 case 1:
401 result.format = Portal::Graphics::ImageFormat::R8;
402 break;
403 case 2:
405 break;
406 case 4:
408 break;
409 default:
411 "Unsupported channel count: {}", result_channels);
412 stbi_image_free(pixels);
413 return std::nullopt;
414 }
415
416 stbi_image_free(pixels);
417 return result;
418}
419
420std::shared_ptr<Core::VKImage> ImageReader::load_texture(const std::string& path)
421{
422 auto image_data = load(path, 4);
423 if (!image_data) {
424 return nullptr;
425 }
426
428 auto texture = mgr.create_2d(
429 image_data->width,
430 image_data->height,
431 image_data->format,
432 image_data->pixels.data());
433
434 if (texture) {
436 "Created GPU texture from image: {}", path);
437 }
438
439 return texture;
440}
441
442std::optional<ImageData> ImageReader::get_image_data() const
443{
444 return m_image_data;
445}
446
447std::shared_ptr<Buffers::TextureBuffer> ImageReader::create_texture_buffer()
448{
449 if (!m_is_open || !m_image_data) {
450 m_last_error = "No image open";
451 return nullptr;
452 }
453
454 auto tex_buffer = std::make_shared<Buffers::TextureBuffer>(
455 m_image_data->width,
456 m_image_data->height,
457 m_image_data->format,
458 m_image_data->pixels.data());
459
461 "Created TextureBuffer from image: {}x{} ({} bytes)",
462 m_image_data->width, m_image_data->height, tex_buffer->get_size_bytes());
463
464 return tex_buffer;
465}
466
467bool ImageReader::load_into_buffer(const std::shared_ptr<Buffers::VKBuffer>& buffer)
468{
469 if (!m_is_open || !m_image_data) {
470 m_last_error = "No image open";
471 return false;
472 }
473
474 if (!buffer || !buffer->is_initialized()) {
475 m_last_error = "Invalid or uninitialized buffer";
476 return false;
477 }
478
479 size_t required_size = m_image_data->pixels.size();
480 if (buffer->get_size_bytes() < required_size) {
481 m_last_error = "Buffer too small for image data";
482 return false;
483 }
484
486 m_image_data->pixels.data(),
487 required_size,
488 buffer);
489
491 "Loaded image into VKBuffer: {}x{} ({} bytes)",
492 m_image_data->width, m_image_data->height, required_size);
493
494 return true;
495}
496
497} // namespace MayaFlux::IO
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
static std::optional< ImageData > load_from_memory(const void *data, size_t size)
Load image from memory (static utility)
std::type_index get_container_type() const override
Get the container type this reader creates.
std::shared_ptr< Kakshya::SignalSourceContainer > create_container() override
Create and initialize a container from the file.
std::vector< std::string > get_supported_extensions() const override
Get supported file extensions for this reader.
std::type_index get_data_type() const override
Get the data type this reader produces.
static std::optional< ImageData > load(const std::string &path, int desired_channels=4)
Load image from file (static utility)
std::optional< ImageData > m_image_data
bool is_open() const override
Check if a file is currently open.
bool supports_streaming() const override
Check if streaming is supported for the current file.
std::shared_ptr< Buffers::TextureBuffer > create_texture_buffer()
Create a VKBuffer containing the loaded image pixel data.
bool open(const std::string &filepath, FileReadOptions options=FileReadOptions::ALL) override
Open a file for reading.
size_t get_num_dimensions() const override
Get the dimensionality of the file data.
bool load_into_container(std::shared_ptr< Kakshya::SignalSourceContainer > container) override
Load file data into an existing container.
std::vector< Kakshya::DataVariant > read_region(const FileRegion &region) override
Read a specific region of data.
std::vector< Kakshya::DataVariant > read_all() override
Read all data from the file into memory.
std::vector< uint64_t > get_dimension_sizes() const override
Get size of each dimension in the file data.
std::optional< FileMetadata > get_metadata() const override
Get metadata from the open file.
std::vector< FileRegion > get_regions() const override
Get semantic regions from the file.
bool load_into_buffer(const std::shared_ptr< Buffers::VKBuffer > &buffer)
Load image directly into an existing VKBuffer.
bool seek(const std::vector< uint64_t > &position) override
Seek to a specific position in the file.
std::vector< uint64_t > get_read_position() const override
Get current read position in primary dimension.
void close() override
Close the currently open file.
bool can_read(const std::string &filepath) const override
Check if a file can be read by this reader.
std::optional< ImageData > get_image_data() const
Get the loaded image data.
static std::shared_ptr< Core::VKImage > load_texture(const std::string &path)
Load image directly into GPU texture (static utility)
std::string get_last_error() const override
Get the last error message.
uint64_t get_preferred_chunk_size() const override
Get the preferred chunk size for streaming.
void upload_to_gpu(const void *data, size_t size, const std::shared_ptr< VKBuffer > &target, const std::shared_ptr< VKBuffer > &staging)
Upload raw data to GPU buffer (auto-detects host-visible vs device-local)
FileReadOptions
Generic options for file reading behavior.
static bool g_stb_simd_logged
@ FileIO
Filesystem I/O operations.
@ Init
Engine/subsystem initialization.
@ IO
Networking, file handling, streaming.
@ IMAGE_COLOR
2D RGB/RGBA image
MAYAFLUX_API TextureLoom & get_texture_manager()
Get the global texture manager instance.
std::unordered_map< std::string, std::any > attributes
Type-specific metadata stored as key-value pairs (e.g., sample rate, channels)
std::string format
File format identifier (e.g., "wav", "mp3", "hdf5")
Generic metadata structure for any file type.
std::vector< uint64_t > start_coordinates
N-dimensional start position (e.g., frame, x, y)
std::vector< uint64_t > end_coordinates
N-dimensional end position (inclusive)
Generic region descriptor for any file type.
std::vector< uint8_t > pixels
Raw image data loaded from file.