diff --git a/thumbnailer/CMakeLists.txt b/thumbnailer/CMakeLists.txt index ce3624e..f88375e 100644 --- a/thumbnailer/CMakeLists.txt +++ b/thumbnailer/CMakeLists.txt @@ -3,13 +3,17 @@ option(BUILD_THUMBNAILER "Build generator of thumbnails" OFF) if(BUILD_THUMBNAILER) if(WIN32) add_library(tenmonthumbnailer SHARED - winmain.cpp + Dll.cpp + loadxisf.cpp + TenmonThumbnailProvider.cpp + ../rawimage.h ../rawimage.cpp ../rawimage_sse.cpp) target_compile_definitions(tenmonthumbnailer PRIVATE NO_QT) target_include_directories(tenmonthumbnailer PRIVATE ../libXISF) - target_link_libraries(tenmonthumbnailer PRIVATE ${LCMS2_LIB} XISF) + target_link_libraries(tenmonthumbnailer PRIVATE shlwapi ${LCMS2_LIB} XISF) + target_link_options(tenmonthumbnailer PRIVATE "-static") else(WIN32) qt_add_executable(tenmonthumbnailer main.cpp diff --git a/thumbnailer/Dll.cpp b/thumbnailer/Dll.cpp new file mode 100644 index 0000000..bc9537e --- /dev/null +++ b/thumbnailer/Dll.cpp @@ -0,0 +1,240 @@ +#include +#include +#include // For IThumbnailProvider. +#include // For SHChangeNotify +#include + +extern HRESULT TenmonThumbnailer_CreateInstance(REFIID riid, void **ppv); + +#define SZ_CLSID_TENMONTHUMBHANDLER L"{0f3881d7-c9f0-45cb-aadb-40192aead2b4}" +#define SZ_TENMONTHUMBHANDLER L"Tenmon Thumbnail Handler" + +const CLSID CLSID_TenmonThumbHandler = {0x0f3881d7, 0xc9f0, 0x45cb, {0xaa, 0xdb, 0x40, 0x19, 0x2a, 0xea, 0xd2, 0xb4}}; + +typedef HRESULT (*PFNCREATEINSTANCE)(REFIID riid, void **ppvObject); +struct CLASS_OBJECT_INIT +{ + const CLSID *pClsid; + PFNCREATEINSTANCE pfnCreate; +}; + +// add classes supported by this module here +const CLASS_OBJECT_INIT c_rgClassObjectInit[] = +{ + { &CLSID_TenmonThumbHandler, TenmonThumbnailer_CreateInstance } +}; + + +long g_cRefModule = 0; + +// Handle the the DLL's module +HINSTANCE g_hInst = NULL; + +// Standard DLL functions +STDAPI_(BOOL) DllMain(HINSTANCE hInstance, DWORD dwReason, void *) +{ + if (dwReason == DLL_PROCESS_ATTACH) + { + g_hInst = hInstance; + DisableThreadLibraryCalls(hInstance); + } + return TRUE; +} + +STDAPI DllCanUnloadNow() +{ + // Only allow the DLL to be unloaded after all outstanding references have been released + return (g_cRefModule == 0) ? S_OK : S_FALSE; +} + +void DllAddRef() +{ + InterlockedIncrement(&g_cRefModule); +} + +void DllRelease() +{ + InterlockedDecrement(&g_cRefModule); +} + +class CClassFactory : public IClassFactory +{ +public: + static HRESULT CreateInstance(REFCLSID clsid, const CLASS_OBJECT_INIT *pClassObjectInits, size_t cClassObjectInits, REFIID riid, void **ppv) + { + *ppv = NULL; + HRESULT hr = CLASS_E_CLASSNOTAVAILABLE; + for (size_t i = 0; i < cClassObjectInits; i++) + { + if (clsid == *pClassObjectInits[i].pClsid) + { + IClassFactory *pClassFactory = new (std::nothrow) CClassFactory(pClassObjectInits[i].pfnCreate); + hr = pClassFactory ? S_OK : E_OUTOFMEMORY; + if (SUCCEEDED(hr)) + { + hr = pClassFactory->QueryInterface(riid, ppv); + pClassFactory->Release(); + } + break; // match found + } + } + return hr; + } + + CClassFactory(PFNCREATEINSTANCE pfnCreate) : _cRef(1), _pfnCreate(pfnCreate) + { + DllAddRef(); + } + + // IUnknown + IFACEMETHODIMP QueryInterface(REFIID riid, void ** ppv) + { + static const QITAB qit[] = + { + QITABENT(CClassFactory, IClassFactory), + { 0 } + }; + return QISearch(this, qit, riid, ppv); + } + + IFACEMETHODIMP_(ULONG) AddRef() + { + return InterlockedIncrement(&_cRef); + } + + IFACEMETHODIMP_(ULONG) Release() + { + long cRef = InterlockedDecrement(&_cRef); + if (cRef == 0) + { + delete this; + } + return cRef; + } + + // IClassFactory + IFACEMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) + { + return punkOuter ? CLASS_E_NOAGGREGATION : _pfnCreate(riid, ppv); + } + + IFACEMETHODIMP LockServer(BOOL fLock) + { + if (fLock) + { + DllAddRef(); + } + else + { + DllRelease(); + } + return S_OK; + } + +private: + ~CClassFactory() + { + DllRelease(); + } + + long _cRef; + PFNCREATEINSTANCE _pfnCreate; +}; + +STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid, void **ppv) +{ + return CClassFactory::CreateInstance(clsid, c_rgClassObjectInit, ARRAYSIZE(c_rgClassObjectInit), riid, ppv); +} + +// A struct to hold the information required for a registry entry + +struct REGISTRY_ENTRY +{ + HKEY hkeyRoot; + PCWSTR pszKeyName; + PCWSTR pszValueName; + PCWSTR pszData; +}; + +// Creates a registry key (if needed) and sets the default value of the key + +HRESULT CreateRegKeyAndSetValue(const REGISTRY_ENTRY *pRegistryEntry) +{ + HKEY hKey; + HRESULT hr = HRESULT_FROM_WIN32(RegCreateKeyExW(pRegistryEntry->hkeyRoot, pRegistryEntry->pszKeyName, + 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, NULL)); + if (SUCCEEDED(hr)) + { + hr = HRESULT_FROM_WIN32(RegSetValueExW(hKey, pRegistryEntry->pszValueName, 0, REG_SZ, + (LPBYTE) pRegistryEntry->pszData, + ((DWORD) wcslen(pRegistryEntry->pszData) + 1) * sizeof(WCHAR))); + RegCloseKey(hKey); + } + return hr; +} + +// +// Registers this COM server +// +STDAPI DllRegisterServer() +{ + HRESULT hr; + + WCHAR szModuleName[MAX_PATH]; + + if (!GetModuleFileNameW(g_hInst, szModuleName, ARRAYSIZE(szModuleName))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + else + { + // List of registry entries we want to create + const REGISTRY_ENTRY rgRegistryEntries[] = + { + // RootKey KeyName ValueName Data + {HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" SZ_CLSID_TENMONTHUMBHANDLER, NULL, SZ_TENMONTHUMBHANDLER}, + {HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" SZ_CLSID_TENMONTHUMBHANDLER L"\\InProcServer32", NULL, szModuleName}, + {HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" SZ_CLSID_TENMONTHUMBHANDLER L"\\InProcServer32", L"ThreadingModel", L"Apartment"}, + {HKEY_CURRENT_USER, L"Software\\Classes\\.xisf\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}", NULL, SZ_CLSID_TENMONTHUMBHANDLER}, + }; + + hr = S_OK; + for (int i = 0; i < ARRAYSIZE(rgRegistryEntries) && SUCCEEDED(hr); i++) + { + hr = CreateRegKeyAndSetValue(&rgRegistryEntries[i]); + } + } + if (SUCCEEDED(hr)) + { + // This tells the shell to invalidate the thumbnail cache. This is important because any .recipe files + // viewed before registering this handler would otherwise show cached blank thumbnails. + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); + } + return hr; +} + +// +// Unregisters this COM server +// +STDAPI DllUnregisterServer() +{ + HRESULT hr = S_OK; + + const PCWSTR rgpszKeys[] = + { + L"Software\\Classes\\CLSID\\" SZ_CLSID_TENMONTHUMBHANDLER, + L"Software\\Classes\\.xisf\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}" + }; + + // Delete the registry entries + for (int i = 0; i < ARRAYSIZE(rgpszKeys) && SUCCEEDED(hr); i++) + { + hr = HRESULT_FROM_WIN32(RegDeleteTreeW(HKEY_CURRENT_USER, rgpszKeys[i])); + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) + { + // If the registry entry has already been deleted, say S_OK. + hr = S_OK; + } + } + return hr; +} diff --git a/thumbnailer/TenmonThumbnailProvider.cpp b/thumbnailer/TenmonThumbnailProvider.cpp new file mode 100644 index 0000000..4bde21a --- /dev/null +++ b/thumbnailer/TenmonThumbnailProvider.cpp @@ -0,0 +1,116 @@ +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved + +#include +#include // For IThumbnailProvider. +#include +#include "libxisf.h" + +bool loadXISF(const LibXISF::ByteArray &data, HBITMAP *hbmp, UINT thumbSize); + +class TenmonThumbProvider : public IInitializeWithStream, + public IThumbnailProvider +{ +public: + TenmonThumbProvider() : _cRef(1), _pStream(NULL) + { + } + + virtual ~TenmonThumbProvider() + { + if (_pStream) + { + _pStream->Release(); + } + } + + // IUnknown + IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv) + { + static const QITAB qit[] = + { + QITABENT(TenmonThumbProvider, IInitializeWithStream), + QITABENT(TenmonThumbProvider, IThumbnailProvider), + { 0 }, + }; + return QISearch(this, qit, riid, ppv); + } + + IFACEMETHODIMP_(ULONG) AddRef() + { + return InterlockedIncrement(&_cRef); + } + + IFACEMETHODIMP_(ULONG) Release() + { + ULONG cRef = InterlockedDecrement(&_cRef); + if (!cRef) + { + delete this; + } + return cRef; + } + + // IInitializeWithStream + IFACEMETHODIMP Initialize(IStream *pStream, DWORD grfMode); + + // IThumbnailProvider + IFACEMETHODIMP GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha); + +private: + + long _cRef; + IStream *_pStream; // provided during initialization. +}; + +HRESULT TenmonThumbnailer_CreateInstance(REFIID riid, void **ppv) +{ + TenmonThumbProvider *pNew = new (std::nothrow) TenmonThumbProvider(); + HRESULT hr = pNew ? S_OK : E_OUTOFMEMORY; + if (SUCCEEDED(hr)) + { + hr = pNew->QueryInterface(riid, ppv); + pNew->Release(); + } + return hr; +} + +// IInitializeWithStream +IFACEMETHODIMP TenmonThumbProvider::Initialize(IStream *pStream, DWORD) +{ + HRESULT hr = E_UNEXPECTED; // can only be inited once + if (_pStream == NULL) + { + // take a reference to the stream if we have not been inited yet + hr = pStream->QueryInterface(&_pStream); + } + return hr; +} + +// IThumbnailProvider +IFACEMETHODIMP TenmonThumbProvider::GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha) +{ + LibXISF::ByteArray data; + ULONG readSize = 0; + ULONG read; + data.resize(1024*1024); + + while(_pStream->Read(data.data() + readSize, data.size() - readSize, &read) == S_OK) + { + readSize += read; + data.resize(data.size() + 1024*1024); + } + readSize += read; + + *pdwAlpha = WTSAT_RGB; + + data.resize(readSize); + if(loadXISF(data, phbmp, cx)) + return S_OK; + else + return E_FAIL; +} diff --git a/thumbnailer/loadxisf.cpp b/thumbnailer/loadxisf.cpp new file mode 100644 index 0000000..b6a8cd2 --- /dev/null +++ b/thumbnailer/loadxisf.cpp @@ -0,0 +1,116 @@ +#include "libxisf.h" +#include +#include "../rawimage.h" + +bool OpenGLES = false; + +bool loadXISF(const LibXISF::ByteArray &data, HBITMAP *hbmp, UINT thumbSize) +{ + OutputDebugStringA("TENMON"); + try + { + LibXISF::XISFReader xisf; + xisf.open(data); + + const LibXISF::Image &xisfImage = xisf.getImage(0); + + RawImage::DataType type; + switch(xisfImage.sampleFormat()) + { + case LibXISF::Image::UInt8: type = RawImage::UINT8; break; + case LibXISF::Image::UInt16: type = RawImage::UINT16; break; + case LibXISF::Image::UInt32: type = RawImage::UINT32; break; + case LibXISF::Image::Float32: type = RawImage::FLOAT32; break; + case LibXISF::Image::Float64: type = RawImage::FLOAT64; break; + default: break; + } + + + LibXISF::Image tmpImage = xisfImage; + tmpImage.convertPixelStorageTo(LibXISF::Image::Planar); + UINT w = tmpImage.width(); + UINT h = tmpImage.height(); + std::shared_ptr rawImage; + + if(tmpImage.colorSpace() == LibXISF::Image::ColorSpace::Gray) + { + rawImage = std::make_shared(tmpImage.width(), tmpImage.height(), 1, type); + std::memcpy(rawImage->data(), tmpImage.imageData(), tmpImage.imageDataSize() / tmpImage.channelCount()); + } + else if(tmpImage.channelCount() == 3 || tmpImage.channelCount() == 4) + { + rawImage = RawImage::fromPlanar(tmpImage.imageData(), tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type); + } + + rawImage->calcStats(); + if(rawImage->imageStats().m_median[0] < rawImage->norm() * 0.2f) + { + OutputDebugStringA("Stretch image"); + MTFParam params = rawImage->calcMTFParams(); + rawImage->applySTF(params); + } + + UINT cw = thumbSize; + UINT ch = thumbSize; + if (w > h) + ch = h * thumbSize / w; + else + cw = w * thumbSize / h; + + + rawImage->resize(cw, ch); + rawImage->convertToType(RawImage::UINT8); + + BITMAPINFO bmi = {}; + bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); + bmi.bmiHeader.biWidth = cw; + bmi.bmiHeader.biHeight = -static_cast(ch); + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + UINT lw = cw * 4; + + BYTE *pBits; + HBITMAP bmp = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, reinterpret_cast(&pBits), NULL, 0); + + const unsigned char *p = (const unsigned char*)rawImage->data(); + const unsigned short *ps = (const unsigned short*)rawImage->data(); + if(tmpImage.channelCount() == 1) + { + for(UINT y = 0; y < ch; y++) + { + for(UINT x = 0; x < cw; x++) + { + pBits[(y * lw) + x * 4 + 0] = p[y * cw + x]; + pBits[(y * lw) + x * 4 + 1] = p[y * cw + x]; + pBits[(y * lw) + x * 4 + 2] = p[y * cw + x]; + pBits[(y * lw) + x * 4 + 3] = 255; + } + } + } + else + { + for(UINT y = 0; y < ch; y++) + { + for(UINT x = 0; x < cw; x++) + { + pBits[(y * lw) + x * 4 + 0] = p[y * cw * 4 + x * 4 + 2]; + pBits[(y * lw) + x * 4 + 1] = p[y * cw * 4 + x * 4 + 1]; + pBits[(y * lw) + x * 4 + 2] = p[y * cw * 4 + x * 4 + 0]; + pBits[(y * lw) + x * 4 + 3] = 255; + } + } + } + + *hbmp = bmp; + return true; + } + catch (LibXISF::Error &err) + { + char text[1024]; + sprintf(text, "Failed to open XISF image %s", err.what()); + OutputDebugStringA(text); + return false; + } + return false; +}