Skip to content

Commit 8ce2bde

Browse files
committed
feat(codec): implement Phase 3 CMYK support, fix Straight Alpha rendering & LZW robustness (#206)
1 parent 2540210 commit 8ce2bde

9 files changed

Lines changed: 1536 additions & 66 deletions

File tree

‎CMakeLists.txt‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ set(QUICKVIEW_SOURCES
7171
QuickView/exif.cpp
7272
QuickView/RenderEngine.cpp
7373
QuickView/ImageLoader.cpp
74+
QuickView/MiniTiff.cpp
75+
QuickView/MiniTiffLzw.cpp
76+
QuickView/MiniTiffCmyk.cpp
7477
QuickView/LosslessTransform.cpp
7578
QuickView/StbLoader.cpp
7679
QuickView/TinyExrLoader.cpp
@@ -319,6 +322,10 @@ add_executable(QuickViewTests
319322
tests/HotkeyTests.cpp
320323
tests/StringUtilsTests.cpp
321324
tests/SupportedExtensionsTests.cpp
325+
tests/MiniTiffTests.cpp
326+
QuickView/MiniTiff.cpp
327+
QuickView/MiniTiffLzw.cpp
328+
QuickView/MiniTiffCmyk.cpp
322329
QuickView/ColorMath.cpp
323330
QuickView/FileNavigator.cpp
324331
QuickView/ArchiveVFS.cpp

‎QuickView/ImageLoader.cpp‎

Lines changed: 86 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ using namespace QuickView;
4444
#include "TinyExrLoader.h"
4545
#include <shobjidl.h> // [Add] for IShellItemImageFactory
4646
#include <thread>
47+
#include "MiniTiff.h"
4748

4849
extern FileNavigator& g_navigator;
4950

@@ -532,67 +533,6 @@ void CImageLoader::ReleaseJxlRunner() {
532533
namespace QuickView {
533534
namespace Codec {
534535

535-
struct DecodeContext {
536-
// Memory Allocator: Returns pointer to allocated memory.
537-
// Caller (Codec) calculates 'totalSize' based on aligned stride and height.
538-
QuickView::AllocatorCallback allocator;
539-
QuickView::FreeCallback freeFunc;
540-
541-
// Runtime Control
542-
QuickView::SimplePredicate checkCancel;
543-
std::stop_token stopToken;
544-
545-
// Parameters
546-
int targetWidth = 0; // 0 = Full
547-
int targetHeight = 0;
548-
PixelFormat format = PixelFormat::BGRA8888; // Preferred Output Format
549-
bool forcePreview = false; // Force fast preview (e.g. JXL/Embedded Thumb)
550-
float targetHdrHeadroomStops = -1.0f; // < 0 = codec default
551-
552-
// [v5.3] Telemetry Output - backward compatible
553-
std::wstring *pLoaderName = nullptr;
554-
std::wstring *pFormatDetails = nullptr;
555-
556-
// [v5.3] Unified metadata pointer (optional - if set, loaders will populate)
557-
CImageLoader::ImageMetadata *pMetadata = nullptr;
558-
559-
// [v10] Fallback control for Titan Base Layer
560-
bool forceRenderFull = false;
561-
562-
// [v10.1] Override to prevent 1x1 Fake Base generation (e.g. for specific
563-
// LODs)
564-
bool allowFakeBase = true;
565-
bool isTitanMode = false;
566-
567-
// [v6.2.5.3] Flag to preserve FP16/FP32 data (bypassing CPU SDR reduction).
568-
// True for main viewport rendering.
569-
bool preserveFloat = false;
570-
571-
// [Async Gain Map] Callback to post AuxLayer back to main engine
572-
QuickView::AuxLayerCallback onAuxLayerReady;
573-
};
574-
575-
struct DecodeResult {
576-
uint8_t *pixels = nullptr;
577-
int width = 0;
578-
int height = 0;
579-
int stride = 0; // Must be 16/64 byte aligned for SIMD/GPU
580-
PixelFormat format = PixelFormat::BGRA8888;
581-
bool success = false;
582-
583-
// [v5.3] Unified metadata (includes loader name, format details, EXIF, etc.)
584-
CImageLoader::ImageMetadata metadata;
585-
586-
// [GPU Pipeline] Multi-layer composition asset package
587-
GpuBlendOp blendOp = GpuBlendOp::None;
588-
std::unique_ptr<AuxLayer> auxLayer;
589-
GpuShaderPayload shaderPayload;
590-
591-
// [v10.5] Animation Decoder Payload
592-
std::shared_ptr<QuickView::IAnimationDecoder> animator;
593-
QuickView::AnimationFrameMeta frameMeta; // Transport for first frame meta
594-
};
595-
596536
// File I/O Helpers
597537
// ------------------------------------------------------------------------
598538

@@ -842,7 +782,8 @@ bool IsUnifiedBufferCodec(const std::wstring &fmt) {
842782
return fmt == L"JPEG" || fmt == L"WebP" || fmt == L"PNG" || fmt == L"GIF" ||
843783
fmt == L"BMP" || fmt == L"JXL" || fmt == L"AVIF" || fmt == L"QOI" ||
844784
fmt == L"TGA" || fmt == L"PNM" || fmt == L"WBMP" || fmt == L"PSD" ||
845-
fmt == L"HDR" || fmt == L"PIC" || fmt == L"PCX" || fmt == L"EXR";
785+
fmt == L"HDR" || fmt == L"PIC" || fmt == L"PCX" || fmt == L"EXR" ||
786+
fmt == L"TIFF";
846787
}
847788

848789
bool ShouldProbeAnimatedBufferCodec(const std::wstring &fmt) {
@@ -2819,6 +2760,78 @@ HRESULT CImageLoader::LoadRegionToFrame(
28192760
targetHeight);
28202761
}
28212762

2763+
// --- Strategy 1c: MiniTIFF Region Decoding ---
2764+
if (format == L"TIFF") {
2765+
int origWidth = 0, origHeight = 0;
2766+
if (SUCCEEDED(GetImageSize(filePath, (UINT*)&origWidth, (UINT*)&origHeight))) {
2767+
RegionScalePlan plan{};
2768+
if (BuildRegionScalePlan(srcRect, origWidth, origHeight, scale, targetWidth, targetHeight, &plan)) {
2769+
QuickView::MappedFile mapping(filePath);
2770+
if (mapping.IsValid()) {
2771+
outFrame->width = plan.frameW;
2772+
outFrame->height = plan.frameH;
2773+
outFrame->stride = CalculateAlignedStride(plan.frameW, 4);
2774+
outFrame->format = PixelFormat::BGRA8888;
2775+
size_t totalSize = outFrame->GetBufferSize();
2776+
if (tileManager && totalSize <= TILE_SLAB_SIZE) {
2777+
outFrame->pixels = (uint8_t *)tileManager->Allocate();
2778+
if (outFrame->pixels) {
2779+
outFrame->memoryDeleter.ctx = tileManager;
2780+
outFrame->memoryDeleter.pfn = [](uint8_t *p, void *ctx) {
2781+
static_cast<QuickView::TileMemoryManager *>(ctx)->Free(p);
2782+
};
2783+
}
2784+
}
2785+
if (!outFrame->pixels) {
2786+
outFrame->pixels = (uint8_t *)_aligned_malloc(totalSize, 64);
2787+
outFrame->memoryDeleter = QuickView::MemoryDeleter::FromAlignedFree();
2788+
}
2789+
if (!outFrame->pixels) {
2790+
return E_OUTOFMEMORY;
2791+
}
2792+
if (plan.contentH < plan.frameH || plan.contentW < plan.frameW) {
2793+
memset(outFrame->pixels, 0, totalSize);
2794+
}
2795+
2796+
QuickView::Codec::DecodeContext tiffCtx;
2797+
tiffCtx.allocator.ctx = nullptr;
2798+
tiffCtx.allocator.pfn = [](void*, size_t s) -> uint8_t* {
2799+
return static_cast<uint8_t*>(_aligned_malloc(s, 64));
2800+
};
2801+
tiffCtx.checkCancel = checkCancel;
2802+
2803+
QuickView::Codec::DecodeResult roiResult;
2804+
HRESULT hrRegion = QuickView::MiniTiff::LoadRegion(
2805+
mapping.data(), mapping.size(), tiffCtx, roiResult,
2806+
plan.cropX, plan.cropY, plan.cropW, plan.cropH
2807+
);
2808+
2809+
if (SUCCEEDED(hrRegion)) {
2810+
ImageLoaderSimd::ResizeBilinear(
2811+
roiResult.pixels, plan.cropW, plan.cropH, roiResult.stride,
2812+
outFrame->pixels, plan.contentW, plan.contentH, outFrame->stride
2813+
);
2814+
if (roiResult.pixels) {
2815+
_aligned_free(roiResult.pixels);
2816+
}
2817+
if (pLoaderName) {
2818+
*pLoaderName = L"MiniTIFF Region";
2819+
}
2820+
return S_OK;
2821+
} else {
2822+
outFrame->Release();
2823+
if (hrRegion == E_NOTIMPL) {
2824+
return LoadRegionGeneric_StrategyB(filePath, srcRect, scale, outFrame,
2825+
tileManager, arena, checkCancel,
2826+
targetWidth, targetHeight);
2827+
}
2828+
return hrRegion;
2829+
}
2830+
}
2831+
}
2832+
}
2833+
}
2834+
28222835
// --- Strategy 2: Strategy B (Load Full & Crop/Resize) ---
28232836
// For formats without native region decoding support (PNG, etc.)
28242837
if (format == L"PNG") {
@@ -9178,6 +9191,17 @@ HRESULT CImageLoader::LoadImageUnified(LPCWSTR filePath,
91789191
return hrErr;
91799192
return hr; // Returns libavif error if WIC error was generic
91809193
} else {
9194+
if (fmt == L"TIFF") {
9195+
HRESULT hr = QuickView::MiniTiff::Load(mappedData, mappedSize, ctx, result);
9196+
if (SUCCEEDED(hr)) {
9197+
return S_OK;
9198+
}
9199+
if (hr == E_OUTOFMEMORY || hr == E_ABORT) {
9200+
return hr;
9201+
}
9202+
return hr; // Returns E_NOTIMPL to fallback, other to fail
9203+
}
9204+
91819205
HRESULT hr = LoadBufferUnified(mappedData, mappedSize, fmt, ctx, result);
91829206
if (hr == E_OUTOFMEMORY || hr == E_ABORT)
91839207
return hr;

‎QuickView/ImageLoader.h‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,3 +514,54 @@ class CImageLoader {
514514
// Custom PCX Decoder
515515
HRESULT LoadPCX(LPCWSTR filePath, IWICBitmap **ppBitmap);
516516
};
517+
518+
namespace QuickView {
519+
namespace Codec {
520+
521+
struct DecodeContext {
522+
QuickView::AllocatorCallback allocator;
523+
QuickView::FreeCallback freeFunc;
524+
525+
QuickView::SimplePredicate checkCancel;
526+
std::stop_token stopToken;
527+
528+
int targetWidth = 0;
529+
int targetHeight = 0;
530+
PixelFormat format = PixelFormat::BGRA8888;
531+
bool forcePreview = false;
532+
float targetHdrHeadroomStops = -1.0f;
533+
534+
std::wstring *pLoaderName = nullptr;
535+
std::wstring *pFormatDetails = nullptr;
536+
537+
::CImageLoader::ImageMetadata *pMetadata = nullptr;
538+
539+
bool forceRenderFull = false;
540+
bool allowFakeBase = true;
541+
bool isTitanMode = false;
542+
bool preserveFloat = false;
543+
544+
QuickView::AuxLayerCallback onAuxLayerReady;
545+
};
546+
547+
struct DecodeResult {
548+
uint8_t *pixels = nullptr;
549+
int width = 0;
550+
int height = 0;
551+
int stride = 0;
552+
PixelFormat format = PixelFormat::BGRA8888;
553+
bool success = false;
554+
555+
::CImageLoader::ImageMetadata metadata;
556+
557+
GpuBlendOp blendOp = GpuBlendOp::None;
558+
std::unique_ptr<AuxLayer> auxLayer;
559+
GpuShaderPayload shaderPayload;
560+
561+
std::shared_ptr<QuickView::IAnimationDecoder> animator;
562+
QuickView::AnimationFrameMeta frameMeta;
563+
};
564+
565+
} // namespace Codec
566+
} // namespace QuickView
567+

0 commit comments

Comments
 (0)