32[[nodiscard]]
static bool hasExtension(std::string_view path, std::span<const std::string_view> extensions)
noexcept
34 std::string lower_path(path);
35 std::transform(lower_path.begin(), lower_path.end(), lower_path.begin(),
36 [](
unsigned char c) { return static_cast<char>(std::tolower(c)); });
38 for (
const auto& ext : extensions)
40 if (lower_path.size() >= ext.size() &&
41 lower_path.compare(lower_path.size() - ext.size(), ext.size(), ext) == 0) {
48OIIO::ImageCache* SourceManager::getGlobalCache()
55 spdlog::debug(
"[SourceManager::getGlobalCache]: OIIO ImageCache created: 2GB limit");
57 spdlog::critical(
"[SourceManager::getGlobalCache]: Failed to create OIIO ImageCache");
64 : m_cache(getGlobalCache())
67 spdlog::critical(
"[SourceManager::SourceManager]: Failed to get global ImageCache");
68 throw std::runtime_error(
"SourceManager: Initialization failed");
80 spdlog::warn(
"[SourceManager::loadFile]: Empty file path");
84 std::lock_guard<std::mutex> lock(m_mutex);
86 if (isLoaded_unsafe()) {
90 spdlog::info(
"[SourceManager::loadFile]: Loading: '{}'", path);
93 if (isRawFile(path)) {
94 return loadRawFile(path);
95 }
else if (isHeicFile(path)) {
96 return loadHeicFile(path);
99 return loadStandardFile(path);
102bool SourceManager::isRawFile(std::string_view path)
const
104 static const std::array<std::string_view, 25> raw_extensions = {
105 ".nef",
".cr2",
".cr3",
".arw",
".dng",
".raw",
".rw2",
106 ".orf",
".sr2",
".raf",
".mrw",
".pef",
".srw",
".x3f",
107 ".3fr",
".iiq",
".kdc",
".mef",
".nrw",
".srw",
".crw",
108 ".mos",
".erf",
".dc2",
".dcr"
114bool SourceManager::isHeicFile(std::string_view path)
const
116 static const std::array<std::string_view, 3> heic_extensions = {
117 ".heic",
".heif",
".avif"
123std::expected<void, ErrorHandling::CoreError> SourceManager::loadRawFile(std::string_view path)
125 spdlog::debug(
"[SourceManager::loadRawFile]: Processing RAW file: {}", path);
130 ImageConfig::Raw::RawSettings settings;
134 settings.set_balance_clamped(
true);
135 settings.set_use_camera_wb(
true);
139 OIIO::ImageSpec config;
140 config.attribute(
"raw:Demosaic", settings.get_demosaic_string());
141 config.attribute(
"raw:ColorSpace", settings.get_color_space_string());
142 config.attribute(
"raw:HighlightMode", settings.get_highlight_mode_value());
143 config.attribute(
"raw:balance_clamped",
static_cast<int>(settings.get_balance_clamped()));
144 config.attribute(
"raw:use_camera_matrix", settings.get_camera_matrix_value());
145 config.attribute(
"raw:use_camera_wb",
static_cast<int>(settings.get_use_camera_wb()));
146 config.attribute(
"raw:use_auto_wb",
static_cast<int>(settings.get_use_auto_wb()));
147 config.attribute(
"raw:fbdd_noiserd", settings.get_fbdd_noiserd_value());
152 auto buf_result { loadImageBuffer(path, &config) };
154 spdlog::error(
"[SourceManager::loadRawFile]: Failed to load RAW file '{}': {}", path, buf_result.error());
155 return std::unexpected(buf_result.error());
161 m_current_path = path;
162 auto result { convertToRgbaInternal(std::move(buf_result.value())) };
165 spdlog::info(
"[SourceManager::loadRawFile]: Loaded '{}': {}x{} RGBA_F32",
166 path, m_image_buf->spec().width, m_image_buf->spec().height);
172std::expected<void, ErrorHandling::CoreError> SourceManager::loadHeicFile(std::string_view path)
174 spdlog::debug(
"[SourceManager::loadHeicFile]: Processing HEIC file: {}", path);
179 ImageConfig::Heic::HeicSettings settings;
181 OIIO::ImageSpec config;
182 config.attribute(
"oiio:reorient", settings.get_reorient_value());
183 config.attribute(
"oiio:UnassociatedAlpha", settings.get_unassociated_alpha_value());
188 auto buf_result { loadImageBuffer(path, &config) };
190 spdlog::error(
"[SourceManager::loadHeicFile]: Failed to load HEIC file '{}': {}", path, buf_result.error());
191 return std::unexpected(buf_result.error());
199 if (!is_linear && !cs_name.empty())
203 spdlog::error(
"[SourceManager::loadHeicFile]: Color space conversion failed for '{}': {}", path, conversion.error());
204 return std::unexpected(conversion.error());
211 m_current_path = path;
212 auto result { convertToRgbaInternal(std::move(buf_result.value())) };
215 spdlog::info(
"[SourceManager::loadHeicFile]: Loaded '{}': {}x{} RGBA_F32",
216 path, m_image_buf->spec().width, m_image_buf->spec().height);
222std::expected<void, ErrorHandling::CoreError> SourceManager::loadStandardFile(std::string_view path)
224 spdlog::debug(
"[SourceManager::loadStandardFile]: Processing standard image: {}", path);
229 auto buf_result { loadImageBuffer(path,
nullptr) };
231 spdlog::error(
"[SourceManager::loadStandardFile]: Failed to load standard file '{}': {}", path, buf_result.error());
232 return std::unexpected(buf_result.error());
240 if (!is_linear && !cs_name.empty())
244 spdlog::error(
"[SourceManager::loadStandardFile]: Color space conversion failed for '{}': {}", path, conversion.error());
245 return std::unexpected(conversion.error());
250 spdlog::debug(
"[SourceManager::loadStandardFile]: is linear: '{}' new space: '{}' ",
257 m_current_path = path;
258 auto result { convertToRgbaInternal(std::move(buf_result.value())) };
261 spdlog::info(
"[SourceManager::loadStandardFile]: Loaded '{}': {}x{} RGBA_F32",
262 path, m_image_buf->spec().width, m_image_buf->spec().height);
268std::expected<OIIO::ImageBuf, ErrorHandling::CoreError>
269SourceManager::loadImageBuffer(std::string_view path,
const OIIO::ImageSpec* config)
274 auto in { OIIO::ImageInput::open(std::string(path), config) };
276 spdlog::error(
"[SourceManager::loadImageBuffer]: Failed to open '{}': {}", path, OIIO::geterror());
280 OIIO::ImageSpec float_spec { in->spec() };
281 float_spec.set_format(OIIO::TypeDesc::FLOAT);
283 OIIO::ImageBuf buf(float_spec);
285 if (!in->read_image(0, 0, 0, -1, OIIO::TypeDesc::FLOAT, buf.localpixels())) {
286 spdlog::error(
"[SourceManager::loadImageBuffer]: Failed to read '{}': {}", path, in->geterror());
296 if (!buf.read(0, 0,
true, OIIO::TypeDesc::FLOAT)) {
297 spdlog::error(
"[SourceManager::loadImageBuffer]: Failed to read '{}': {}", path, buf.geterror());
304std::expected<void, ErrorHandling::CoreError> SourceManager::convertToRgbaInternal(OIIO::ImageBuf&& src_buf)
313 spdlog::debug(
"[SourceManager::convertToRgbaInternal]: {}x{} ({} channels)", w, h, src_ch);
318 OIIO::ImageSpec target_spec(
static_cast<int>(w),
static_cast<int>(h), 4, OIIO::TypeDesc::FLOAT);
319 OIIO::ImageBuf rgba_buf(target_spec);
321 std::vector<int> channel_order = {0, 1, 2, 3};
322 std::vector<float> channel_values = {0.0f, 0.0f, 0.0f, 1.0f};
326 case 1: channel_order = {0, 0, 0, -1};
break;
327 case 2: channel_order = {0, 0, 0, 1};
break;
328 case 3: channel_order = {0, 1, 2, -1};
break;
329 default: channel_order = {0, 1, 2, 3};
break;
332 if (!OIIO::ImageBufAlgo::channels(rgba_buf, src_buf, 4, channel_order, channel_values)) {
333 spdlog::error(
"[SourceManager::convertToRgbaInternal]: Channel conversion failed: {}", rgba_buf.geterror());
340 m_image_buf = std::make_unique<OIIO::ImageBuf>(std::move(rgba_buf));
347 std::lock_guard<std::mutex> lock(m_mutex);
351void SourceManager::unloadInternal()
353 if (m_image_buf && m_image_buf->initialized()) {
354 spdlog::debug(
"[SourceManager::unloadInternal]: Unloading '{}'", m_current_path);
357 m_current_path.clear();
362 std::lock_guard<std::mutex> lock(m_mutex);
363 return isLoaded_unsafe();
366bool SourceManager::isLoaded_unsafe()
const
368 return m_image_buf && m_image_buf->initialized();
383 return m_image_buf ?
static_cast<Common::ImageChan>(m_image_buf->spec().nchannels) : 0;
390 std::lock_guard<std::mutex> lock(m_mutex);
392 if (!isLoaded_unsafe()) {
393 spdlog::warn(
"[SourceManager::getTile] No image loaded");
408 if (x >= img_width || y >= img_height) {
414 static_cast<int>(x),
static_cast<int>(x +
width),
415 static_cast<int>(y),
static_cast<int>(y +
height),
423 std::vector<float> data(data_size);
425 if (!m_image_buf->get_pixels(roi, OIIO::TypeDesc::FLOAT, data.data())) {
426 spdlog::warn(
"[SourceManager::getTile]: get_pixels failed: {}", m_image_buf->geterror());
431 auto region { std::make_unique<Common::ImageRegion>(
444std::expected<void, ErrorHandling::CoreError>
447 std::lock_guard<std::mutex> lock(m_mutex);
449 if (!isLoaded_unsafe()) {
450 spdlog::warn(
"[SourceManager::setTile] No image loaded");
459 spdlog::error(
"[SourceManager::setTile] Invalid format: expected RGBA_F32");
470 const int img_width { m_image_buf->spec().width };
471 const int img_height { m_image_buf->spec().height };
472 const int tile_x { tile.
m_x };
473 const int tile_y { tile.
m_y };
474 const int tile_w {
static_cast<int>(tile.
m_width) };
475 const int tile_h {
static_cast<int>(tile.
m_height) };
477 const bool is_no_intersection {
479 || tile_y >= img_height
480 || tile_x + tile_w <= 0
481 || tile_y + tile_h <= 0
483 if (is_no_intersection) {
488 const OIIO::ROI roi(tile_x, tile_x + tile_w, tile_y, tile_y + tile_h, 0, 1, 0, 4);
490 if (!m_image_buf->set_pixels(roi, OIIO::TypeDesc::FLOAT, tile.
m_data.data())) {
491 spdlog::error(
"[SourceManager::setTile] set_pixels failed: {}", m_image_buf->geterror());
500 std::lock_guard<std::mutex> lock(m_mutex);
506 const auto& spec { m_image_buf->spec() };
507 const auto* attr { spec.find_attribute(std::string(key)) };
509 return attr ? std::optional{attr->get_string()} : std::nullopt;
514 std::lock_guard lock(m_mutex);
515 return m_current_path;