diff --git a/DirectXTex/DirectXTex.h b/DirectXTex/DirectXTex.h index c17dd1a..5655145 100644 --- a/DirectXTex/DirectXTex.h +++ b/DirectXTex/DirectXTex.h @@ -621,6 +621,8 @@ namespace DirectX HRESULT __cdecl EvaluateImage( _In_ const Image& image, _In_ std::function pixelFunc ); + HRESULT __cdecl EvaluateImage( _In_reads_(nimages) const Image* images, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ std::function pixelFunc ); HRESULT __cdecl TransformImage( _In_ const Image& image, _In_ std::function pixelFunc) +{ + if (!images || !nimages) + return E_INVALIDARG; + + if (!IsValid(metadata.format)) + return E_INVALIDARG; + + if (IsPlanar(metadata.format) || IsPalettized(metadata.format) || IsTypeless(metadata.format)) + return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); + + if (metadata.width > UINT32_MAX + || metadata.height > UINT32_MAX) + return E_INVALIDARG; + + if (metadata.IsVolumemap() && metadata.depth > UINT32_MAX) + return E_INVALIDARG; + + ScratchImage temp; + DXGI_FORMAT format = metadata.format; + if (IsCompressed(format)) + { + HRESULT hr = Decompress(images, nimages, metadata, DXGI_FORMAT_R32G32B32A32_FLOAT, temp); + if (FAILED(hr)) + return hr; + + if (nimages != temp.GetImageCount()) + return E_UNEXPECTED; + + images = temp.GetImages(); + format = DXGI_FORMAT_R32G32B32A32_FLOAT; + } + + switch (metadata.dimension) + { + case TEX_DIMENSION_TEXTURE1D: + case TEX_DIMENSION_TEXTURE2D: + for (size_t index = 0; index < nimages; ++index) + { + const Image& img = images[index]; + if (img.format != format) + return E_FAIL; + + if ((img.width > UINT32_MAX) || (img.height > UINT32_MAX)) + return E_FAIL; + + HRESULT hr = EvaluateImage_(img, pixelFunc); + if (FAILED(hr)) + return hr; + } + break; + + case TEX_DIMENSION_TEXTURE3D: + { + size_t index = 0; + size_t d = metadata.depth; + for (size_t level = 0; level < metadata.mipLevels; ++level) + { + for (size_t slice = 0; slice < d; ++slice, ++index) + { + if (index >= nimages) + return E_FAIL; + + const Image& img = images[index]; + if (img.format != format) + return E_FAIL; + + if ((img.width > UINT32_MAX) || (img.height > UINT32_MAX)) + return E_FAIL; + + HRESULT hr = EvaluateImage_(img, pixelFunc); + if (FAILED(hr)) + return hr; + } + + if (d > 1) + d >>= 1; + } + } + break; + + default: + return E_FAIL; + } + + return S_OK; +} + //------------------------------------------------------------------------------------- // Use a user-supplied function to compute a new image from an input image diff --git a/Texconv/texconv.cpp b/Texconv/texconv.cpp index d1f5914..a471c32 100644 --- a/Texconv/texconv.cpp +++ b/Texconv/texconv.cpp @@ -86,6 +86,7 @@ enum OPTIONS OPT_WIC_QUALITY, OPT_WIC_LOSSLESS, OPT_COLORKEY, + OPT_TONEMAP, OPT_MAX }; @@ -153,6 +154,7 @@ SValue g_pOptions[] = { L"wicq", OPT_WIC_QUALITY }, { L"wiclossless", OPT_WIC_LOSSLESS }, { L"c", OPT_COLORKEY }, + { L"tonemap", OPT_TONEMAP }, { nullptr, 0 } }; @@ -659,13 +661,14 @@ namespace wprintf(L" -bcuniform Use uniform rather than perceptual weighting for BC1-3\n"); wprintf(L" -bcdither Use dithering for BC1-3\n"); wprintf(L" -bcmax Use exhaustive compression (BC7 only)\n"); - wprintf(L" -bcquick USe quick compression (BC7 only)\n"); + wprintf(L" -bcquick Use quick compression (BC7 only)\n"); wprintf(L" -wicq When writing images with WIC use quality (0.0 to 1.0)\n"); wprintf(L" -wiclossless When writing images with WIC use lossless mode\n"); wprintf( L" -aw BC7 GPU compressor weighting for alpha error metric\n" L" (defaults to 1.0)\n"); wprintf(L" -c colorkey (a.k.a. chromakey) transparency\n"); + wprintf(L" -tonemap Apply a tonemap operator based on maximum luminance\n"); wprintf(L"\n"); wprintf(L" : "); @@ -1956,6 +1959,82 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) cimage.reset(); } + // --- Tonemap (if requested) -------------------------------------------------- + if (dwOptions & DWORD64(1) << OPT_TONEMAP) + { + std::unique_ptr timage(new (std::nothrow) ScratchImage); + if (!timage) + { + wprintf(L"\nERROR: Memory allocation failed\n"); + return 1; + } + + // Compute max luminosity across all images + XMVECTOR maxLum = XMVectorZero(); + hr = EvaluateImage(image->GetImages(), image->GetImageCount(), image->GetMetadata(), + [&](const XMVECTOR* pixels, size_t width, size_t y) + { + UNREFERENCED_PARAMETER(y); + + for (size_t j = 0; j < width; ++j) + { + static const XMVECTORF32 s_luminance = { 0.3f, 0.59f, 0.11f, 0.f }; + + XMVECTOR v = *pixels++; + + v = XMVector3Dot(v, s_luminance); + + maxLum = XMVectorMax(v, maxLum); + } + }); + if (FAILED(hr)) + { + wprintf(L" FAILED [tonemap maxlum] (%x)\n", hr); + return 1; + } + + // Reinhard et al, "Photographic Tone Reproduction for Digital Images" + // http://www.cs.utah.edu/~reinhard/cdrom/ + maxLum = XMVectorMultiply(maxLum, maxLum); + + hr = TransformImage(image->GetImages(), image->GetImageCount(), image->GetMetadata(), + [&](XMVECTOR* outPixels, const XMVECTOR* inPixels, size_t width, size_t y) + { + UNREFERENCED_PARAMETER(y); + + for (size_t j = 0; j < width; ++j) + { + XMVECTOR value = inPixels[j]; + + XMVECTOR scale = XMVectorDivide(XMVectorAdd(g_XMOne, XMVectorDivide(value, maxLum)), XMVectorAdd(g_XMOne, value)); + XMVECTOR nvalue = XMVectorMultiply(value, scale); + + value = XMVectorSelect(value, nvalue, g_XMSelect1110); + + outPixels[j] = value; + } + }, *timage); + if (FAILED(hr)) + { + wprintf(L" FAILED [tonemap apply] (%x)\n", hr); + return 1; + } + + auto& tinfo = timage->GetMetadata(); + tinfo; + + assert(info.width == tinfo.width); + assert(info.height == tinfo.height); + assert(info.depth == tinfo.depth); + assert(info.arraySize == tinfo.arraySize); + assert(info.mipLevels == tinfo.mipLevels); + assert(info.miscFlags == tinfo.miscFlags); + assert(info.dimension == tinfo.dimension); + + image.swap(timage); + cimage.reset(); + } + // --- Premultiplied alpha (if requested) -------------------------------------- if ((dwOptions & (DWORD64(1) << OPT_PREMUL_ALPHA)) && HasAlpha(info.format)