From 50ad6129e91d0536a996deda173c09b6b6dd0457 Mon Sep 17 00:00:00 2001 From: justnullname <51329027+justnullname@users.noreply.github.com> Date: Fri, 20 Mar 2026 04:12:54 +0000 Subject: [PATCH 1/6] Fix file extension mismatch detection logic - Expanded `FormatExtRule` struct to support up to `alt5` alternatives to handle formats with many file extensions (e.g. PNM and TGA). - Reordered `g_formatRules` to evaluate compound and specific format strings (like "wbmp", "jpeg xl", "tinyexr", "libavif") before core formats (like "bmp", "jpeg", "exr"). This fixes false positive mismatch warnings where "wbmp" incorrectly matched the "bmp" rule via substring check. - Refactored matching loop to safely handle multiple alt conditions. - Downgraded `fmt.contains` to `fmt.find(...) != std::wstring::npos` for better compiler compatibility without altering logic. --- QuickView/main.cpp | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/QuickView/main.cpp b/QuickView/main.cpp index c90ada3..b61c78b 100644 --- a/QuickView/main.cpp +++ b/QuickView/main.cpp @@ -3831,30 +3831,42 @@ struct FormatExtRule { std::wstring_view alt1 = {}; std::wstring_view alt2 = {}; std::wstring_view alt3 = {}; + std::wstring_view alt4 = {}; + std::wstring_view alt5 = {}; }; static constexpr FormatExtRule g_formatRules[] = { + // Specific compound/derived names first to avoid prefix/substring matches + { L"wbmp", L".wbmp" }, + { L"jpeg xl", L".jxl" }, + { L"jxl", L".jxl" }, + { L"libavif", L".avif" }, + { L"tinyexr", L".exr" }, + { L"jpeg (mmf)", L".jpg", L".jpeg", L".jpe", L".jfif" }, + + // Core formats { L"jpeg", L".jpg", L".jpeg", L".jpe", L".jfif" }, { L"png", L".png" }, { L"webp", L".webp" }, - { L"avif", L".avif", L"libavif" }, + { L"avif", L".avif" }, { L"gif", L".gif" }, { L"bmp", L".bmp", L".dib" }, { L"tiff", L".tiff", L".tif" }, { L"tif", L".tiff", L".tif" }, { L"heif", L".heic", L".heif" }, { L"heic", L".heic", L".heif" }, - { L"jxl", L".jxl", L"jpeg xl" }, { L"hdr", L".hdr", L".pic" }, { L"psd", L".psd", L".psb" }, - { L"exr", L".exr", L"tinyexr" }, + { L"exr", L".exr" }, { L"qoi", L".qoi" }, - { L"tga", L".tga" }, + { L"tga", L".tga", L".icb", L".vda", L".vst" }, { L"pcx", L".pcx" }, { L"svg", L".svg" }, { L"ico", L".ico" }, - { L"wbmp", L".wbmp" }, - { L"pnm", L".pnm", L".pgm", L".ppm" } + { L"pnm", L".pnm", L".pgm", L".ppm", L".pbm" }, + { L"pgm", L".pgm", L".pnm", L".ppm", L".pbm" }, + { L"ppm", L".ppm", L".pnm", L".pgm", L".pbm" }, + { L"pbm", L".pbm", L".pnm", L".pgm", L".ppm" }, }; static std::wstring_view GetPrimaryExtensionForFormat(std::wstring_view format) { @@ -3863,7 +3875,7 @@ static std::wstring_view GetPrimaryExtensionForFormat(std::wstring_view format) std::transform(fmt.begin(), fmt.end(), fmt.begin(), ::towlower); for (const auto& rule : g_formatRules) { - if (fmt == rule.format || fmt.contains(rule.format)) return rule.primary; + if (fmt == rule.format || fmt.find(rule.format) != std::wstring::npos) return rule.primary; } return {}; } @@ -3888,11 +3900,13 @@ bool CheckExtensionMismatch(std::wstring_view path, std::wstring_view format) { std::wstring_view ext = extStr; for (const auto& rule : g_formatRules) { - if (fmt == rule.format || fmt.contains(rule.format)) { + if (fmt == rule.format || fmt.find(rule.format) != std::wstring::npos) { if (ext == rule.primary) return false; if (!rule.alt1.empty() && ext == rule.alt1) return false; if (!rule.alt2.empty() && ext == rule.alt2) return false; if (!rule.alt3.empty() && ext == rule.alt3) return false; + if (!rule.alt4.empty() && ext == rule.alt4) return false; + if (!rule.alt5.empty() && ext == rule.alt5) return false; return true; } } From 56541df5516a4c7af31f560b33a78aa2c5d3c723 Mon Sep 17 00:00:00 2001 From: justnullname <51329027+justnullname@users.noreply.github.com> Date: Fri, 20 Mar 2026 04:17:48 +0000 Subject: [PATCH 2/6] Fix file extension mismatch detection logic and metadata propagation - Expanded `FormatExtRule` struct to support up to `alt5` alternatives to handle formats with many file extensions (e.g. PNM and TGA). - Reordered `g_formatRules` to evaluate compound and specific format strings (like "wbmp", "jpeg xl", "tinyexr", "libavif") before core formats (like "bmp", "jpeg", "exr"). This fixes false positive mismatch warnings where "wbmp" incorrectly matched the "bmp" rule via substring check. - Refactored matching loop to safely handle multiple alt conditions. - Downgraded `fmt.contains` to `fmt.find(...) != std::wstring::npos` for better compiler compatibility without altering logic. - Fixed `ImageEngine` missing propagation of `formatDetails` for SVGs across FastLane and `SafeCacheFrame` cache copies, preventing missing metadata downstream. --- QuickView/ImageEngine.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/QuickView/ImageEngine.cpp b/QuickView/ImageEngine.cpp index 0f9b48b..f2db50e 100644 --- a/QuickView/ImageEngine.cpp +++ b/QuickView/ImageEngine.cpp @@ -1080,6 +1080,7 @@ void ImageEngine::FastLane::QueueWorker() { auto safeFrame = std::make_shared(); if (rawFrame.IsSvg()) { safeFrame->format = rawFrame.format; + safeFrame->formatDetails = rawFrame.formatDetails; safeFrame->width = rawFrame.width; safeFrame->height = rawFrame.height; safeFrame->svg = std::make_unique(); @@ -1441,6 +1442,7 @@ void ImageEngine::AddToCache(int index, const std::wstring& path, std::shared_pt if (frame->IsSvg()) { // SVG: Copy the SVG data struct cachedFrame->format = frame->format; + cachedFrame->formatDetails = frame->formatDetails; cachedFrame->width = frame->width; cachedFrame->height = frame->height; cachedFrame->svg = std::make_unique(); From 3d7613a1d89eb90fe519d49f609509f1e0767ad4 Mon Sep 17 00:00:00 2001 From: justnullname <51329027+justnullname@users.noreply.github.com> Date: Fri, 20 Mar 2026 06:13:39 +0000 Subject: [PATCH 3/6] Fix file extension mismatch icon not rendering and properly cache metadata for fast formats - Fix the constructor of `Toolbar` where the `FixExtension` button warning flag was hard-coded to `true` but skipped during layout, causing it to have a `0, 0, 0, 0` rect. - Added explicit call to `g_toolbar.UpdateLayout` immediately after `SetExtensionWarning` runs so the UI recalculates the bounds of the warning icon and it actually displays on screen when an extension is mismatched. - Fixed missing `formatDetails` string deep copy for SVG frames inside the `ImageEngine.cpp` FastLane processing to ensure accurate metadata is passed to the user interface. --- QuickView/Toolbar.cpp | 2 +- QuickView/main.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/QuickView/Toolbar.cpp b/QuickView/Toolbar.cpp index b9bab0e..b04bb08 100644 --- a/QuickView/Toolbar.cpp +++ b/QuickView/Toolbar.cpp @@ -54,7 +54,7 @@ Toolbar::Toolbar() { {}, false, false, - true}, // Hidden if no mismatch + false}, // Hidden if no mismatch {ToolbarButtonID::CompareToggle, ICON_COMPARE[0], {}, true, false}, diff --git a/QuickView/main.cpp b/QuickView/main.cpp index b61c78b..366dc48 100644 --- a/QuickView/main.cpp +++ b/QuickView/main.cpp @@ -8680,6 +8680,10 @@ void ProcessEngineEvents(HWND hwnd) { g_toolbar.SetExtensionWarning(false); } + // Refresh Layout to recalculate Warning Icon bounds + RECT rc; GetClientRect(hwnd, &rc); + g_toolbar.UpdateLayout((float)rc.right, (float)rc.bottom); + // [v5.3] Set EXIF Orientation based on AutoRotate config if (g_config.AutoRotate) { // Trust the metadata. From 633302a6225ba10888aa0d449f4d8794447a087a Mon Sep 17 00:00:00 2001 From: justnullname <51329027+justnullname@users.noreply.github.com> Date: Fri, 20 Mar 2026 06:36:49 +0000 Subject: [PATCH 4/6] Fix UI rendering bug for extension mismatch warning and SVG metadata - Correctly default `FixExtension` button `isWarning` flag to `false` so the UI knows it is hidden initially. - Automatically call `g_toolbar.UpdateLayout` immediately after `SetExtensionWarning` assigns warning state, fixing the `{0,0,0,0}` button rect bug where the error icon would never be drawn even when correctly detected. - Fixed `ImageLoader::LoadToFrame` missing metadata output `formatDetails` assignment for SVG files. - Extended `FormatExtRule` array and structurally reorganized format string rules to evaluate exact and compound format strings before shorter matches to prevent substring detection false positives. From 53f1828fd259718afee231cce650d13330d444b6 Mon Sep 17 00:00:00 2001 From: Vivor Loong Date: Fri, 20 Mar 2026 15:08:35 +0800 Subject: [PATCH 5/6] fix: resolve image extension mismatch detection failure across all loading channels (Scout, Heavy, RAW) --- QuickView/ImageEngine.cpp | 7 +++---- QuickView/ImageLoader.cpp | 8 ++++++-- QuickView/main.cpp | 4 ++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/QuickView/ImageEngine.cpp b/QuickView/ImageEngine.cpp index f2db50e..a7fd7fb 100644 --- a/QuickView/ImageEngine.cpp +++ b/QuickView/ImageEngine.cpp @@ -1105,14 +1105,13 @@ void ImageEngine::FastLane::QueueWorker() { e.rawFrame = safeFrame; - // [Fix] Unified Populate Metadata - // PeekHeader dimensions strictly untrusted for FastLane - // RAW/TIFF formats with IFD thumbs may return incorrect info.width/height e.metadata.Width = rawFrame.width; e.metadata.Height = rawFrame.height; + e.metadata.Format = info.format; // [Scout] Direct from PeekHeader // [v5.3] Metadata is now populated by LoadToFrame (Unified path) - // No need to call ReadMetadata separately or access global variables. + // We don't call LoadToFrame with pMetadata in FastLane currently, + // but info.format from PeekHeader is sufficient for Scout mismatch check. // [Fix] Propagate EXIF Orientation from Decoder to Metadata (Critical for AutoRotate) e.metadata.ExifOrientation = rawFrame.exifOrientation; diff --git a/QuickView/ImageLoader.cpp b/QuickView/ImageLoader.cpp index 4daa3e9..6e55010 100644 --- a/QuickView/ImageLoader.cpp +++ b/QuickView/ImageLoader.cpp @@ -5066,8 +5066,8 @@ HRESULT CImageLoader::LoadImageUnified(LPCWSTR filePath, const DecodeContext& ct // [v9.2] Use path-based detection directly (handles RAW extensions properly) std::wstring fmt = DetectFormatFromContent(filePath); - // Debug: Log detected format - + // [Fix] Ensure Format is stored in metadata for extension mismatch detection (main.cpp) + result.metadata.Format = fmt; // 3. Dispatch @@ -8790,6 +8790,10 @@ HRESULT CImageLoader::LoadToFrame(LPCWSTR filePath, QuickView::RawImageFrame* ou if (pMetadata->FileSize == 0) { PopulateFileStats(filePath, pMetadata); } + // [Fix] Final Guarantee: Format must not be empty for extension mismatch check + if (pMetadata->Format.empty()) { + pMetadata->Format = DetectFormatFromContent(filePath); + } } // [Fix] Software Downscale if Unified Codec ignored target dimensions diff --git a/QuickView/main.cpp b/QuickView/main.cpp index 366dc48..0b34f4f 100644 --- a/QuickView/main.cpp +++ b/QuickView/main.cpp @@ -3867,6 +3867,10 @@ static constexpr FormatExtRule g_formatRules[] = { { L"pgm", L".pgm", L".pnm", L".ppm", L".pbm" }, { L"ppm", L".ppm", L".pnm", L".pgm", L".pbm" }, { L"pbm", L".pbm", L".pnm", L".pgm", L".ppm" }, + + // [v10.1] New: Support all RAW formats in extension check + { L"raw", L".dng", L".arw", L".nef", L".cr2", L".cr3", L".raf" }, + { L"dds", L".dds" }, }; static std::wstring_view GetPrimaryExtensionForFormat(std::wstring_view format) { From 79fd4c5f957fab061c76251052856e9922d75b16 Mon Sep 17 00:00:00 2001 From: Vivor Loong Date: Fri, 20 Mar 2026 15:28:19 +0800 Subject: [PATCH 6/6] modernize: upgrade string/container search to C++23 contains and ranges::contains --- QuickView/HeavyLanePool.cpp | 12 ++++++------ QuickView/ImageEngine.cpp | 4 ++-- QuickView/ImageLoader.cpp | 2 +- QuickView/UIRenderer.cpp | 10 ++++++---- QuickView/UpdateManager.cpp | 8 ++++---- QuickView/main.cpp | 8 ++++---- 6 files changed, 23 insertions(+), 21 deletions(-) diff --git a/QuickView/HeavyLanePool.cpp b/QuickView/HeavyLanePool.cpp index 4befef3..bb37e79 100644 --- a/QuickView/HeavyLanePool.cpp +++ b/QuickView/HeavyLanePool.cpp @@ -13,10 +13,10 @@ using namespace QuickView; namespace { bool IsCopyOnlyLoaderName(const std::wstring& loaderName) { - return loaderName.find(L"LODCache Slice") != std::wstring::npos || - loaderName.find(L"Zero-Copy") != std::wstring::npos || - loaderName.find(L"RAM Copy") != std::wstring::npos || - loaderName.find(L"MMF Copy") != std::wstring::npos; + return loaderName.contains(L"LODCache Slice") || + loaderName.contains(L"Zero-Copy") || + loaderName.contains(L"RAM Copy") || + loaderName.contains(L"MMF Copy"); } } @@ -1276,7 +1276,7 @@ void HeavyLanePool::PerformDecode(int workerId, const JobInfo& job, std::stop_to // [v8.4 Fix] If the Base Layer is a Fake 1x1, its real MP/s is 0. // This would cause the auto-regulator to maliciously throttle the pool to < 3 threads, // crippling our N+1 Native Region Decoding. We simulate 100 MP/s to unlock full core usage! - if (loaderName.find(L"Fake Base") != std::wstring::npos) { + if (loaderName.contains(L"Fake Base")) { OutputDebugStringW(L"[HeavyPool] Base Layer is Fake. Simulating 100.0 MP/s baseline to unlock Titan tiles.\n"); RecordBaselineSample(10000000.0, 100.0, srcWidth, srcHeight, isProgressiveJPEG); } else { @@ -1287,7 +1287,7 @@ void HeavyLanePool::PerformDecode(int workerId, const JobInfo& job, std::stop_to // [JXL] Check if base layer is a true progressive DC preview. // Do not treat "Fake Base Prog" as native-region capable. if (m_titanFormat.load() == QuickView::TitanFormat::JXL) { - if (loaderName.find(L"Prog DC") != std::wstring::npos) { + if (loaderName.contains(L"Prog DC")) { m_isProgressiveJXL = true; OutputDebugStringW(L"[HeavyPool] Detected Progressive JXL. Enabling native Region Decoding!\n"); } else { diff --git a/QuickView/ImageEngine.cpp b/QuickView/ImageEngine.cpp index a7fd7fb..27d77b8 100644 --- a/QuickView/ImageEngine.cpp +++ b/QuickView/ImageEngine.cpp @@ -252,7 +252,7 @@ void ImageEngine::DispatchImageLoad(const std::wstring& path, ImageID imageId, u // [v9.0] Smart RAW Quality Check // RAW files require strict quality matching (Preview vs Full) for A/B comparison - if (info.format.find(L"RAW") != std::wstring::npos) { + if (info.format.contains(L"RAW")) { bool wantFull = m_config.ForceRawDecode; bool hasFull = (cachedFrame->quality == QuickView::DecodeQuality::Full); @@ -336,7 +336,7 @@ void ImageEngine::DispatchImageLoad(const std::wstring& path, ImageID imageId, u // 2. Recursive RAW Check // If it's a RAW file with an embedded thumb, check the preview resolution. // "RAW" detection: check if string contains RAW or format check from loader - if (info.format.find(L"RAW") != std::wstring::npos) { + if (info.format.contains(L"RAW")) { // [v9.9] If ForceRawDecode is enabled, RAW Full Decode is computationally intensive. // Always route to HeavyLane to avoid blocking FastLane (UI thread responsiveness). // [Fix] Use member config! diff --git a/QuickView/ImageLoader.cpp b/QuickView/ImageLoader.cpp index 6e55010..3aa8433 100644 --- a/QuickView/ImageLoader.cpp +++ b/QuickView/ImageLoader.cpp @@ -3568,7 +3568,7 @@ HRESULT CImageLoader::LoadToMemory(LPCWSTR filePath, IWICBitmap** ppBitmap, std: ppBitmap ); - if (SUCCEEDED(hrBitmap) && pLoaderName && pLoaderName->find(L"WIC") != std::wstring::npos && !usedNativeScaling) { + if (SUCCEEDED(hrBitmap) && pLoaderName && pLoaderName->contains(L"WIC") && !usedNativeScaling) { UINT w = 0, h = 0; (*ppBitmap)->GetSize(&w, &h); wchar_t buf[32]; swprintf_s(buf, L" [%ux%u]", w, h); diff --git a/QuickView/UIRenderer.cpp b/QuickView/UIRenderer.cpp index 17d8778..f5b946f 100644 --- a/QuickView/UIRenderer.cpp +++ b/QuickView/UIRenderer.cpp @@ -8,6 +8,8 @@ #include "HelpOverlay.h" #include "EditState.h" #include +#include +#include #pragma comment(lib, "psapi.lib") @@ -1599,7 +1601,7 @@ std::vector UIRenderer::BuildGridRows(const CImageLoader::ImageMetadata } // Extraction: Subsampling / Chroma - if (metadata.FormatDetails.find(L"4:") != std::wstring::npos) { + if (metadata.FormatDetails.contains(L"4:")) { size_t pos = metadata.FormatDetails.find(L"4:"); std::wstring chroma = metadata.FormatDetails.substr(pos, 5); rows.push_back({ L"\U0001F3A8", L"Chroma", chroma, L"", L"", TruncateMode::None, false }); @@ -2179,7 +2181,7 @@ namespace { const std::wstring tokens[] = { L"4:4:4", L"4:2:2", L"4:2:0", L"4:0:0" }; const int ranks[] = { 3, 2, 1, 0 }; for (size_t i = 0; i < 4; ++i) { - if (details.find(tokens[i]) != std::wstring::npos) { + if (details.contains(tokens[i])) { rank = ranks[i]; return tokens[i]; } @@ -2421,7 +2423,7 @@ void UIRenderer::DrawCompareInfoHUD(ID2D1DeviceContext* dc) { for (const auto& group : hudGroups) { bool hasData = false; for (const auto& l : group.labels) { - if (std::find(labels.begin(), labels.end(), l) != labels.end()) { + if (std::ranges::contains(labels, l)) { // In Lite(0) and Normal(1) mode, hide identical metrics (except File) if (hudMode < 2 && l != L"File") { @@ -2492,7 +2494,7 @@ void UIRenderer::DrawCompareInfoHUD(ID2D1DeviceContext* dc) { // Group visibility check taking identical hiding into account bool groupHasData = false; for (const auto& l : group.labels) { - if (std::find(labels.begin(), labels.end(), l) != labels.end()) { + if (std::ranges::contains(labels, l)) { if (hudMode < 2 && l != L"File") { const InfoRow* lRow = nullptr; const InfoRow* rRow = nullptr; diff --git a/QuickView/UpdateManager.cpp b/QuickView/UpdateManager.cpp index 39084f7..cc95a35 100644 --- a/QuickView/UpdateManager.cpp +++ b/QuickView/UpdateManager.cpp @@ -74,7 +74,7 @@ void UpdateManager::CheckThread(int delaySeconds) { bool downloadSuccess = false; // Determine if ZIP or EXE - bool isZip = (m_remoteInfo.downloadUrl.find(".zip") != std::string::npos); + bool isZip = (m_remoteInfo.downloadUrl.contains(".zip")); // Adjust destination based on actual type (if url says .zip but we defaulted to .exe in dest) if (isZip) { @@ -294,9 +294,9 @@ VersionInfo UpdateManager::ParseJson(const std::string& json) { if (v.is()) { picojson::object& jsonObj = v.get(); - if (jsonObj.find("version") != jsonObj.end()) info.version = jsonObj.at("version").to_str(); - if (jsonObj.find("url") != jsonObj.end()) info.downloadUrl = jsonObj.at("url").to_str(); - if (jsonObj.find("changelog") != jsonObj.end()) info.changelog = jsonObj.at("changelog").to_str(); + if (jsonObj.contains("version")) info.version = jsonObj.at("version").to_str(); + if (jsonObj.contains("url")) info.downloadUrl = jsonObj.at("url").to_str(); + if (jsonObj.contains("changelog")) info.changelog = jsonObj.at("changelog").to_str(); } return info; } diff --git a/QuickView/main.cpp b/QuickView/main.cpp index 0b34f4f..c0440b6 100644 --- a/QuickView/main.cpp +++ b/QuickView/main.cpp @@ -3879,7 +3879,7 @@ static std::wstring_view GetPrimaryExtensionForFormat(std::wstring_view format) std::transform(fmt.begin(), fmt.end(), fmt.begin(), ::towlower); for (const auto& rule : g_formatRules) { - if (fmt == rule.format || fmt.find(rule.format) != std::wstring::npos) return rule.primary; + if (fmt == rule.format || fmt.contains(rule.format)) return rule.primary; } return {}; } @@ -3904,7 +3904,7 @@ bool CheckExtensionMismatch(std::wstring_view path, std::wstring_view format) { std::wstring_view ext = extStr; for (const auto& rule : g_formatRules) { - if (fmt == rule.format || fmt.find(rule.format) != std::wstring::npos) { + if (fmt == rule.format || fmt.contains(rule.format)) { if (ext == rule.primary) return false; if (!rule.alt1.empty() && ext == rule.alt1) return false; if (!rule.alt2.empty() && ext == rule.alt2) return false; @@ -9839,7 +9839,7 @@ void OnPaint(HWND hwnd) { // [No-DC JXL Guard] For fake/tiny placeholder bases, force tile scheduling immediately. std::wstring fmtUpper = titanMeta.Format; std::transform(fmtUpper.begin(), fmtUpper.end(), fmtUpper.begin(), ::towupper); - bool isJxlLike = (fmtUpper.find(L"JXL") != std::wstring::npos || fmtUpper.find(L"JPEG XL") != std::wstring::npos); + bool isJxlLike = (fmtUpper.contains(L"JXL") || fmtUpper.contains(L"JPEG XL")); if (!isJxlLike && !g_imagePath.empty()) { std::wstring pathLower = g_imagePath; std::transform(pathLower.begin(), pathLower.end(), pathLower.begin(), ::towlower); @@ -9847,7 +9847,7 @@ void OnPaint(HWND hwnd) { } constexpr float kVirtualNoDcRatio = 0.125f; // 1:8 - bool fakeBase = (titanMeta.LoaderName.find(L"Fake Base") != std::wstring::npos); + bool fakeBase = (titanMeta.LoaderName.contains(L"Fake Base")); bool tinyPreview = (previewW <= 2.0f || previewH <= 2.0f); bool weakPreview = (previewW <= 16.0f || previewH <= 16.0f); // Expanded threshold for 4x4 or 8x8