MayaFlux 0.1.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#define STB_IMAGE_IMPLEMENTATION
11#define STBI_NO_STDIO
12#include "stb/stb_image.h"
13
14#include <cstddef>
15#include <fstream>
16
17namespace MayaFlux::IO {
18
20 : m_is_open(false)
21{
22}
23
28
29bool ImageReader::can_read(const std::string& filepath) const
30{
31 auto ext = std::filesystem::path(filepath).extension().string();
32 if (!ext.empty() && ext[0] == '.') {
33 ext = ext.substr(1);
34 }
35
36 static const std::vector<std::string> supported = {
37 "png", "jpg", "jpeg", "bmp", "tga", "psd", "gif", "hdr", "pic", "pnm"
38 };
39
40 return std::ranges::find(supported, ext) != supported.end();
41}
42
43bool ImageReader::open(const std::string& filepath, FileReadOptions /*options*/)
44{
45 if (m_is_open) {
46 close();
47 }
48
49 if (!can_read(filepath)) {
50 m_last_error = "Unsupported image format: " + filepath;
52 return false;
53 }
54
55 m_image_data = load(filepath, 4); // Force RGBA
56
57 if (!m_image_data) {
58 m_last_error = "Failed to load image data";
59 return false;
60 }
61
62 m_filepath = filepath;
63 m_is_open = true;
64
66 "Opened image: {} ({}x{}, {} channels)",
67 filepath, m_image_data->width, m_image_data->height, m_image_data->channels);
68
69 return true;
70}
71
73{
74 if (m_is_open) {
75 m_image_data.reset();
76 m_filepath.clear();
77 m_is_open = false;
78 }
79}
80
82{
83 return m_is_open;
84}
85
86std::optional<FileMetadata> ImageReader::get_metadata() const
87{
88 if (!m_is_open || !m_image_data) {
89 return std::nullopt;
90 }
91
92 FileMetadata meta;
93 meta.format = "8-bit";
94
95 meta.attributes["width"] = m_image_data->width;
96 meta.attributes["height"] = m_image_data->height;
98
99 return meta;
100}
101
102std::vector<FileRegion> ImageReader::get_regions() const
103{
104 // Images don't typically have regions
105 return {};
106}
107
108std::vector<Kakshya::DataVariant> ImageReader::read_all()
109{
110 if (!m_is_open || !m_image_data) {
111 m_last_error = "No image open";
112 return {};
113 }
114
115 return { m_image_data->pixels };
116}
117
118std::vector<Kakshya::DataVariant> ImageReader::read_region(const FileRegion& region)
119{
120 if (!m_is_open || !m_image_data) {
121 m_last_error = "No image open";
122 return {};
123 }
124
125 if (region.start_coordinates.size() < 2 || region.end_coordinates.size() < 2) {
126 m_last_error = "Invalid region coordinates for image";
127 return {};
128 }
129
130 auto x_start = static_cast<uint32_t>(region.start_coordinates[0]);
131 auto y_start = static_cast<uint32_t>(region.start_coordinates[1]);
132 auto x_end = static_cast<uint32_t>(region.end_coordinates[0]);
133 auto y_end = static_cast<uint32_t>(region.end_coordinates[1]);
134
135 if (x_end > m_image_data->width || y_end > m_image_data->height) {
136 m_last_error = "Region out of bounds";
137 return {};
138 }
139
140 uint32_t region_width = x_end - x_start;
141 uint32_t region_height = y_end - y_start;
142 size_t region_size = static_cast<size_t>(region_width * region_height) * m_image_data->channels;
143 std::vector<uint8_t> region_data(region_size);
144
145 for (uint32_t y = 0; y < region_height; ++y) {
146 size_t src_offset = (static_cast<size_t>((y_start + y) * m_image_data->width + x_start)) * m_image_data->channels;
147 size_t dst_offset = static_cast<size_t>(y * region_width) * m_image_data->channels;
148 size_t row_size = static_cast<size_t>(region_width) * m_image_data->channels;
149 std::memcpy(
150 region_data.data() + dst_offset,
151 m_image_data->pixels.data() + src_offset,
152 row_size);
153 }
154
155 return { region_data };
156}
157
158std::shared_ptr<Kakshya::SignalSourceContainer> ImageReader::create_container()
159{
160 // Images don't use SignalSourceContainer - they go directly to GPU
161 m_last_error = "Images use direct GPU texture creation, not containers";
162 return nullptr;
163}
164
165bool ImageReader::load_into_container(std::shared_ptr<Kakshya::SignalSourceContainer> /*container*/)
166{
167 // Not applicable for images
168 m_last_error = "Images cannot be loaded into SignalSourceContainer";
169 return false;
170}
171
172std::vector<uint64_t> ImageReader::get_read_position() const
173{
174 return { 0, 0 }; // Images don't have read position
175}
176
177bool ImageReader::seek(const std::vector<uint64_t>& /*position*/)
178{
179 return true; // No-op for static images
180}
181
182std::vector<std::string> ImageReader::get_supported_extensions() const
183{
184 return { "png", "jpg", "jpeg", "bmp", "tga", "psd", "gif", "hdr", "pic", "pnm" };
185}
186
187std::type_index ImageReader::get_data_type() const
188{
189 return typeid(std::vector<uint8_t>);
190}
191
192std::type_index ImageReader::get_container_type() const
193{
194 return typeid(void); // No container for images
195}
196
198{
199 return m_last_error;
200}
201
203{
204 return false; // Images are loaded entirely into memory
205}
206
208{
209 return 0; // Not applicable
210}
211
213{
214 return 2; // width, height (channels are separate)
215}
216
217std::vector<uint64_t> ImageReader::get_dimension_sizes() const
218{
219 if (!m_is_open || !m_image_data) {
220 return {};
221 }
222 return { m_image_data->width, m_image_data->height };
223}
224
225//==============================================================================
226// Static Utility Methods
227//==============================================================================
228
229std::optional<ImageData> ImageReader::load(const std::filesystem::path& path, int desired_channels)
230{
231 if (!std::filesystem::exists(path)) {
233 "Image file not found: {}", path.string());
234 return std::nullopt;
235 }
236
237 std::ifstream file(path, std::ios::binary | std::ios::ate);
238 if (!file.is_open()) {
240 "Failed to open image file: {}", path.string());
241 return std::nullopt;
242 }
243
244 std::streamsize file_size = file.tellg();
245 file.seekg(0, std::ios::beg);
246
247 std::vector<unsigned char> file_buffer(file_size);
248 if (!file.read(reinterpret_cast<char*>(file_buffer.data()), file_size)) {
250 "Failed to read image file: {}", path.string());
251 return std::nullopt;
252 }
253 file.close();
254
255 int width {}, height {}, channels {};
256 unsigned char* pixels = stbi_load_from_memory(
257 file_buffer.data(),
258 static_cast<int>(file_buffer.size()),
259 &width, &height, &channels,
260 desired_channels);
261
262 if (!pixels) {
264 "Failed to decode image: {} - {}",
265 path.string(), stbi_failure_reason());
266 return std::nullopt;
267 }
268
270 switch (channels) {
271 case 1:
273 break;
274 case 2:
276 break;
277 case 3:
279 break;
280 case 4:
282 break;
283 default:
285 "Unsupported channel count: {}", channels);
286 stbi_image_free(pixels);
287 return std::nullopt;
288 }
289
290 ImageData result;
291
292 size_t data_size = static_cast<long>(width * height) * channels;
293 result.pixels.resize(data_size);
294 std::memcpy(result.pixels.data(), pixels, data_size);
295
296 result.width = width;
297 result.height = height;
298 result.format = format;
299
300 stbi_image_free(pixels);
301
303 "Loaded image: {} ({}x{}, {} channels)",
304 path.filename().string(), width, height, channels);
305
306 return result;
307}
308
309std::optional<ImageData> ImageReader::load(const std::string& path, int desired_channels)
310{
311 return load(std::filesystem::path(path), desired_channels);
312}
313
314std::optional<ImageData> ImageReader::load_from_memory(const void* data, size_t size)
315{
316
317 if (!data || size == 0) {
319 "Invalid memory buffer for image loading");
320 return std::nullopt;
321 }
322
323 int width {}, height {}, channels {};
324 unsigned char* pixels = stbi_load_from_memory(
325 static_cast<const unsigned char*>(data),
326 static_cast<int>(size),
327 &width, &height, &channels,
328 0);
329
330 if (!pixels) {
332 "Failed to decode image from memory: {}",
333 stbi_failure_reason());
334 return std::nullopt;
335 }
336
338 switch (channels) {
339 case 1:
341 break;
342 case 2:
344 break;
345 case 3:
347 break;
348 case 4:
350 break;
351 default:
353 "Unsupported channel count: {}", channels);
354 stbi_image_free(pixels);
355 return std::nullopt;
356 }
357
358 ImageData result;
359 size_t data_size = static_cast<long>(width * height) * channels;
360 result.pixels.resize(data_size);
361 std::memcpy(result.pixels.data(), pixels, data_size);
362
363 result.width = width;
364 result.height = height;
365 result.format = format;
366
367 stbi_image_free(pixels);
368
370 "Loaded image from memory ({}x{}, {} channels)",
371 width, height, channels);
372
373 return result;
374}
375
376std::shared_ptr<Core::VKImage> ImageReader::load_texture(const std::string& path)
377{
378 auto image_data = load(path, 4);
379 if (!image_data) {
380 return nullptr;
381 }
382
384 auto texture = mgr.create_2d(
385 image_data->width,
386 image_data->height,
387 image_data->format,
388 image_data->pixels.data());
389
390 if (texture) {
392 "Created GPU texture from image: {}", path);
393 }
394
395 return texture;
396}
397
398std::optional<ImageData> ImageReader::get_image_data() const
399{
400 return m_image_data;
401}
402
403std::shared_ptr<Buffers::TextureBuffer> ImageReader::create_texture_buffer()
404{
405 if (!m_is_open || !m_image_data) {
406 m_last_error = "No image open";
407 return nullptr;
408 }
409
410 auto tex_buffer = std::make_shared<Buffers::TextureBuffer>(
411 m_image_data->width,
412 m_image_data->height,
413 m_image_data->format,
414 m_image_data->pixels.data());
415
417 "Created TextureBuffer from image: {}x{} ({} bytes)",
418 m_image_data->width, m_image_data->height, tex_buffer->get_size_bytes());
419
420 return tex_buffer;
421}
422
423bool ImageReader::load_into_buffer(const std::shared_ptr<Buffers::VKBuffer>& buffer)
424{
425 if (!m_is_open || !m_image_data) {
426 m_last_error = "No image open";
427 return false;
428 }
429
430 if (!buffer || !buffer->is_initialized()) {
431 m_last_error = "Invalid or uninitialized buffer";
432 return false;
433 }
434
435 size_t required_size = m_image_data->pixels.size();
436 if (buffer->get_size_bytes() < required_size) {
437 m_last_error = "Buffer too small for image data";
438 return false;
439 }
440
442 m_image_data->pixels.data(),
443 required_size,
444 buffer);
445
447 "Loaded image into VKBuffer: {}x{} ({} bytes)",
448 m_image_data->width, m_image_data->height, required_size);
449
450 return true;
451}
452
453} // 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.
@ FileIO
Filesystem I/O operations.
@ IO
Networking, file handling, streaming.
@ IMAGE_COLOR
2D RGB/RGBA image
MAYAFLUX_API TextureLoom & get_texture_manager()
Get the global texture manager instance.
ImageFormat
User-friendly image format enum.
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.
Portal::Graphics::ImageFormat format
std::vector< uint8_t > pixels
Raw image data loaded from file.