diff --git a/.gitignore b/.gitignore index b8373ae1b..23a8cb2ef 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,211 @@ Thumbs.db /src/plugins/automation/generated/salamander_h.h /src/plugins/automation/generated/salamander_i.c /src/plugins/automation/generated/salamander_p.c +/src/plugins/7zip/vcxproj/7ZA/salamander/Debug_x64/plugins/7zip +/src/plugins/7zip/vcxproj/7ZA/salamander/Release_x64/plugins/7zip +/src/plugins/7zip/vcxproj/7zwrapper/salamander/Debug_x64/plugins/7zip +/src/plugins/7zip/vcxproj/7zwrapper/salamander/Release_x64/plugins/7zip +/src/plugins/7zip/vcxproj/salamander/Debug_x64/plugins/7zip +/src/plugins/7zip/vcxproj/salamander/Release_x64/plugins/7zip +/src/plugins/automation/vcxproj/salamander/Debug_x64/plugins/automation +/src/plugins/automation/vcxproj/salamander/Release_x64/plugins/automation +/src/plugins/checksum/vcxproj/salamander/Debug_x64/plugins/checksum +/src/plugins/checksum/vcxproj/salamander/Release_x64/plugins/checksum +/src/plugins/checkver/vcxproj/salamander/Debug_x64/plugins/checkver +/src/plugins/checkver/vcxproj/salamander/Release_x64/plugins/checkver +/src/plugins/dbviewer/vcxproj/salamander/Debug_x64/plugins/dbviewer +/src/plugins/dbviewer/vcxproj/salamander/Release_x64/plugins/dbviewer +/src/plugins/demomenu/vcxproj/salamander/Debug_x64/plugins/demomenu +/src/plugins/demomenu/vcxproj/salamander/Release_x64/plugins/demomenu +/src/plugins/demoplug/vcxproj/salamander/Debug_x64/plugins/demoplug +/src/plugins/demoplug/vcxproj/salamander/Release_x64/plugins/demoplug +/src/plugins/demoview/vcxproj/salamander/Debug_x64/plugins/demoview +/src/plugins/demoview/vcxproj/salamander/Release_x64/plugins/demoview +/src/plugins/diskmap/vcxproj/salamander/Debug_x64/plugins/diskmap +/src/plugins/diskmap/vcxproj/salamander/Release_x64/plugins/diskmap +/src/plugins/filecomp/vcxproj/fcremote/salamander/Debug_x64/plugins/filecomp +/src/plugins/filecomp/vcxproj/fcremote/salamander/Release_x64/plugins/filecomp +/src/plugins/filecomp/vcxproj/salamander/Debug_x64/plugins/filecomp +/src/plugins/filecomp/vcxproj/salamander/Release_x64/plugins/filecomp +/src/plugins/folders/vcxproj/salamander/Debug_x64/plugins/folders +/src/plugins/folders/vcxproj/salamander/Release_x64/plugins/folders +/src/plugins/ftp/vcxproj/salamander/Debug_x64/plugins/ftp +/src/plugins/ftp/vcxproj/salamander/Release_x64/plugins/ftp +/src/plugins/ieviewer/vcxproj/salamander/Debug_x64/plugins/ieviewer +/src/plugins/ieviewer/vcxproj/salamander/Release_x64/plugins/ieviewer +/src/plugins/mmviewer/vcxproj/salamander/Debug_x64/plugins/mmviewer +/src/plugins/mmviewer/vcxproj/salamander/Release_x64/plugins/mmviewer +/src/plugins/nethood/vcxproj/salamander/Debug_x64/plugins/nethood +/src/plugins/nethood/vcxproj/salamander/Release_x64/plugins/nethood +/src/plugins/pak/vcxproj/salamander/Debug_x64/plugins/pak +/src/plugins/pak/vcxproj/salamander/Release_x64/plugins/pak +/src/plugins/peviewer/vcxproj/salamander/Debug_x64/plugins/peviewer +/src/plugins/peviewer/vcxproj/salamander/Release_x64/plugins/peviewer +/src/plugins/pictview/vcxproj/exif/salamander/Debug_x64/plugins/pictview +/src/plugins/pictview/vcxproj/exif/salamander/Release_x64/plugins/pictview +/src/plugins/pictview/vcxproj/salamander/Debug_x64/plugins/pictview +/src/plugins/pictview/vcxproj/salamander/Release_x64/plugins/pictview +/src/plugins/portables/vcxproj/salamander/Debug_x64/plugins/portables +/src/plugins/portables/vcxproj/salamander/Release_x64/plugins/portables +/src/plugins/regedt/vcxproj/salamander/Debug_x64/plugins/regedt +/src/plugins/regedt/vcxproj/salamander/Release_x64/plugins/regedt +/src/plugins/renamer/vcxproj/salamander/Debug_x64/plugins/renamer +/src/plugins/renamer/vcxproj/salamander/Release_x64/plugins/renamer +/src/plugins/splitcbn/vcxproj/salamander/Debug_x64/plugins/splitcbn +/src/plugins/splitcbn/vcxproj/salamander/Release_x64/plugins/splitcbn +/src/plugins/tar/vcxproj/salamander/Debug_x64/plugins/tar +/src/plugins/tar/vcxproj/salamander/Release_x64/plugins/tar +/src/plugins/unarj/vcxproj/salamander/Debug_x64/plugins/unarj +/src/plugins/unarj/vcxproj/salamander/Release_x64/plugins/unarj +/src/plugins/uncab/vcxproj/salamander/Debug_x64/plugins/uncab +/src/plugins/uncab/vcxproj/salamander/Release_x64/plugins/uncab +/src/plugins/unchm/vcxproj/chmlib/salamander/Debug_x64/plugins/unchm +/src/plugins/unchm/vcxproj/chmlib/salamander/Release_x64/plugins/unchm +/src/plugins/unchm/vcxproj/salamander/Debug_x64/plugins/unchm +/src/plugins/unchm/vcxproj/salamander/Release_x64/plugins/unchm +/src/plugins/undelete/vcxproj/salamander/Debug_x64/plugins/undelete +/src/plugins/undelete/vcxproj/salamander/Release_x64/plugins/undelete +/src/plugins/unfat/vcxproj/salamander/Debug_x64/plugins/unfat +/src/plugins/unfat/vcxproj/salamander/Release_x64/plugins/unfat +/src/plugins/uniso/vcxproj/salamander/Debug_x64/plugins/uniso +/src/plugins/uniso/vcxproj/salamander/Release_x64/plugins/uniso +/src/plugins/unlha/vcxproj/salamander/Debug_x64/plugins/unlha +/src/plugins/unlha/vcxproj/salamander/Release_x64/plugins/unlha +/src/plugins/unmime/vcxproj/salamander/Debug_x64/plugins/unmime +/src/plugins/unmime/vcxproj/salamander/Release_x64/plugins/unmime +/src/plugins/unole/vcxproj/salamander/Debug_x64/plugins/unole +/src/plugins/unole/vcxproj/salamander/Release_x64/plugins/unole +/src/plugins/unrar/vcxproj/salamander/Debug_x64/plugins/unrar +/src/plugins/unrar/vcxproj/salamander/Release_x64/plugins/unrar +/src/plugins/wmobile/vcxproj/salamander/Debug_x64/plugins/wmobile +/src/plugins/wmobile/vcxproj/salamander/Release_x64/plugins/wmobile +/src/plugins/zip/vcxproj/salamander/Debug_x64/plugins/zip +/src/plugins/zip/vcxproj/salamander/Release_x64/plugins/zip +/src/plugins/zip/vcxproj/zip2sfx/salamander/Debug_x86/plugins/zip/zip2sfx/Intermediate/microsoft/STL +/src/plugins/zip/vcxproj/zip2sfx/salamander/Release_x86/plugins/zip/zip2sfx/Intermediate/microsoft/STL +/src/vcxproj/salamander/Debug_x64 +/src/vcxproj/salamander/Release_x64 +/src/vcxproj/salmon/salamander/Debug_x64 +/src/vcxproj/salmon/salamander/Release_x64 +/src/vcxproj/salopen/salamander/Debug_x64/plugins/Intermediate/salopen/Intermediate/microsoft/STL +/src/vcxproj/salopen/salamander/Release_x64/plugins/Intermediate/salopen/Intermediate/microsoft/STL +/src/vcxproj/salspawn/salamander/Debug_x64/plugins/Intermediate/salspawn/Intermediate/microsoft/STL +/src/vcxproj/salspawn/salamander/Release_x64/plugins/Intermediate/salspawn/Intermediate/microsoft/STL +/src/vcxproj/shellext/salamander/Debug_x64/plugins/Intermediate/salextx64/Intermediate/microsoft/STL +/src/vcxproj/shellext/salamander/Debug_x86/plugins/Intermediate/salextx86/Intermediate/microsoft/STL +/src/vcxproj/shellext/salamander/Release_x64/plugins/Intermediate/salextx64/Intermediate/microsoft/STL +/src/vcxproj/shellext/salamander/Release_x86/plugins/Intermediate/salextx86/Intermediate/microsoft/STL +/src/vcxproj/sqlite/salamander/Debug_x64 +/src/vcxproj/sqlite/salamander/Release_x64 + +src/plugins/7zip/vcxproj/7ZA/salamander/Release_x86/ + +src/plugins/7zip/vcxproj/7zwrapper/salamander/Release_x86/ + +src/plugins/7zip/vcxproj/salamander/Release_x86/ + +src/plugins/automation/vcxproj/salamander/Release_x86/ + +src/plugins/checksum/vcxproj/salamander/Release_x86/ + +src/plugins/checkver/vcxproj/salamander/Release_x86/ + +src/plugins/dbviewer/vcxproj/salamander/Release_x86/ + +src/plugins/demomenu/vcxproj/salamander/Release_x86/ + +src/plugins/demoplug/vcxproj/salamander/Release_x86/ + +src/plugins/demoview/vcxproj/salamander/Release_x86/ + +src/plugins/diskmap/vcxproj/salamander/Release_x86/ + +src/plugins/filecomp/vcxproj/fcremote/salamander/Release_x86/ + +src/plugins/filecomp/vcxproj/salamander/Release_x86/ + +src/plugins/folders/vcxproj/salamander/Release_x86/ + +src/plugins/ftp/vcxproj/salamander/Release_x86/ + +src/plugins/ieviewer/vcxproj/salamander/Release_x86/ + +src/plugins/mmviewer/vcxproj/salamander/Release_x86/ + +src/plugins/nethood/vcxproj/salamander/Release_x86/ + +src/plugins/pak/vcxproj/salamander/Release_x86/ + +src/plugins/peviewer/vcxproj/salamander/Release_x86/ + +src/plugins/pictview/vcxproj/exif/salamander/Release_x86/ + +src/plugins/pictview/vcxproj/salamander/Release_x86/ + +src/plugins/portables/vcxproj/salamander/Release_x86/ + +src/plugins/regedt/vcxproj/salamander/Release_x86/ + +src/plugins/renamer/vcxproj/salamander/Release_x86/ + +src/plugins/splitcbn/vcxproj/salamander/Release_x86/ + +src/plugins/tar/vcxproj/salamander/Release_x86/ + +src/plugins/unarj/vcxproj/salamander/Release_x86/ + +src/plugins/uncab/vcxproj/salamander/Release_x86/ + +src/plugins/unchm/vcxproj/chmlib/salamander/Release_x86/ + +src/plugins/unchm/vcxproj/salamander/Release_x86/ + +src/plugins/undelete/vcxproj/salamander/Release_x86/ + +src/plugins/unfat/vcxproj/salamander/Release_x86/ + +src/plugins/uniso/vcxproj/salamander/Release_x86/ + +src/plugins/unlha/vcxproj/salamander/Release_x86/ + +src/plugins/unmime/vcxproj/salamander/Release_x86/ + +src/plugins/unole/vcxproj/salamander/Release_x86/ + +src/plugins/unrar/vcxproj/salamander/Release_x86/ + +src/plugins/wmobile/vcxproj/salamander/Release_x86/ + +src/plugins/zip/vcxproj/salamander/Release_x86/ + +src/vcxproj/salmon/salamander/Release_x86/ + +src/vcxproj/salopen/salamander/Release_x86/ + +src/vcxproj/salspawn/salamander/Release_x86/ + +src/vcxproj/sqlite/salamander/Release_x86/ + +src/vcxproj/salamander/Release_x86/ + +src/plugins/webview2renderviewer/managed/obj/ + +src/plugins/webview2renderviewer/managed/bin/ + +src/plugins/textviewer/managed/bin/ + +src/plugins/textviewer/managed/obj/ + +src/plugins/samandarin/managed/bin/ + +src/plugins/samandarin/managed/obj/ + +src/plugins/jsonviewer/managed/bin/ + +src/plugins/jsonviewer/managed/obj/ + +src/plugins/csdemo/managed/obj/ + +build/ + +output/ diff --git a/doc/manual-tests/animated-gif-transparency.md b/doc/manual-tests/animated-gif-transparency.md new file mode 100644 index 000000000..61786e1b6 --- /dev/null +++ b/doc/manual-tests/animated-gif-transparency.md @@ -0,0 +1,19 @@ +# Manual Test: 8-bit Animated GIF Transparency + +This regression check ensures that semi-transparent pixels in paletted GIF animations +render without colour banding or grid artefacts. + +## Required asset +- A dithered 8-bit animated GIF with transparency (for example, a ScreenToGif capture + exported with the gifski encoder). + +## Steps +1. Open the GIF in PictView. +2. Let the animation loop at least once. +3. Inspect regions that fade in/out or show cursor trails. + +## Expected result +- The animation uses the authored colours without blue/green/purple speckles. +- No grid artefacts or haloing appears around semi-transparent content. +- Pixels with fractional alpha look smooth across frames while fully transparent + regions remain transparent. diff --git a/src/plugins/pictview/PVEXEWrapper.cpp b/src/plugins/pictview/PVEXEWrapper.cpp deleted file mode 100644 index 5eff79b00..000000000 --- a/src/plugins/pictview/PVEXEWrapper.cpp +++ /dev/null @@ -1,660 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Open Salamander Authors -// SPDX-License-Identifier: GPL-2.0-or-later - -/* This wrapper implements stubs for the functions exported by PVW32Cnv.dll - * and found in struct CPVW32DLL. These stubs call a hidden remote (32-bit) process - * SalPVEnv.exe that actually makes the calls to a real PVW32Cnv.dll. - * This remote process is called the Envelope. - * Shared memory (File mapping) is used for the inter-process communication. - * The name of the file mapping is based on the AS.exe ProcessID and a unique call ID. - * The calls are signalled by a unique Event object whose name is based on the same ID's. - * Each call uses its own shared memory and event to enable proper multi-threading. - * - * This wrapper is enabled by the PICTVIEW_DLL_IN_SEPARATE_PROCESS macro. - * It supports both 32-bit and 64-bit builds of AS. - * - * NOTE: Work in progress. - */ -#include "precomp.h" - -#ifdef PICTVIEW_DLL_IN_SEPARATE_PROCESS - -#include "PVEXEWrapper.h" -#include "PVMessage.h" -#include "PixelAccess.h" -#include "Thumbnailer.h" - -CPVWrapper PVWrapper; - -static PVCODE WINAPI PVReadImage2Stub(LPPVHandle Img, HDC PaintDC, RECT* pDRect, TProgressProc Progress, void* AppSpecific, int ImageIndex); -static PVCODE WINAPI PVCloseImageStub(LPPVHandle Img); -static PVCODE WINAPI PVDrawImageStub(LPPVHandle Img, HDC PaintDC, int X, int Y, LPRECT rect); -static const char* WINAPI PVGetErrorTextStub(DWORD ErrorCode); -static PVCODE WINAPI PVOpenImageExStub(LPPVHandle* Img, LPPVOpenImageExInfo pOpenExInfo, LPPVImageInfo pImgInfo, int Size); -static PVCODE WINAPI PVSetBkHandleStub(LPPVHandle Img, COLORREF BkColor); -static DWORD WINAPI PVGetDLLVersionStub(void); -static PVCODE WINAPI PVSetStretchParametersStub(LPPVHandle Img, DWORD Width, DWORD Height, DWORD Mode); -static PVCODE WINAPI PVLoadFromClipboardStub(LPPVHandle* Img, LPPVImageInfo pImgInfo, int Size); -static PVCODE WINAPI PVGetImageInfoStub(LPPVHandle Img, LPPVImageInfo pImgInfo, int Size, int ImageIndex); -static PVCODE WINAPI PVSetParamStub(LPPVHandle Img); -static PVCODE WINAPI PVGetHandles2Stub(LPPVHandle Img, LPPVImageHandles* pHandles); -static PVCODE WINAPI PVSaveImageStub(LPPVHandle Img, const char* OutFName, LPPVSaveImageInfo pSii, TProgressProc Progress, void* AppSpecific, int ImageIndex); -static PVCODE WINAPI PVChangeImageStub(LPPVHandle Img, DWORD Flags); -static DWORD WINAPI PVIsOutCombSupportedStub(int Fmt, int Compr, int Colors, int ColorModel); -static PVCODE WINAPI PVReadImageSequenceStub(LPPVHandle Img, LPPVImageSequence* ppSeq); -static PVCODE WINAPI PVCropImageStub(LPPVHandle Img, int Left, int Top, int Width, int Height); -static bool GetRGBAtCursorStub(LPPVHandle Img, DWORD Colors, int x, int y, RGBQUAD* pRGB, int* pIndex); -static PVCODE CalculateHistogramStub(LPPVHandle Img, const LPPVImageInfo pvii, LPDWORD luminosity, LPDWORD red, LPDWORD green, LPDWORD blue, LPDWORD rgb); -static PVCODE CreateThumbnailStub(LPPVHandle Img, LPPVSaveImageInfo pSii, int imageIndex, DWORD imgWidth, DWORD imgHeight, - int thumbWidth, int thumbHeight, CSalamanderThumbnailMakerAbstract* thumbMaker, DWORD thumbFlags, TProgressProc progressProc, void* progressProcArg); -static PVCODE SimplifyImageSequenceStub(LPPVHandle hPVImage, HDC dc, int ScreenWidth, int ScreenHeight, LPPVImageSequence& pSeq, const COLORREF& bgColor); - -struct CPVW32DLL PVEXEProcStubs = { - PVReadImage2Stub, - PVCloseImageStub, - PVDrawImageStub, - PVGetErrorTextStub, - PVOpenImageExStub, - PVSetBkHandleStub, - PVGetDLLVersionStub, - PVSetStretchParametersStub, - PVLoadFromClipboardStub, - PVGetImageInfoStub, - PVSetParamStub, - PVGetHandles2Stub, - PVSaveImageStub, - PVChangeImageStub, - PVIsOutCombSupportedStub, - PVReadImageSequenceStub, - PVCropImageStub, - GetRGBAtCursorStub, - CalculateHistogramStub, - CreateThumbnailStub, - SimplifyImageSequenceStub}; - -BOOL InitPVEXEWrapper(HWND hParentWnd, LPCTSTR pPluginFolder) -{ - PVW32DLL = PVEXEProcStubs; - - _tcscpy(PVWrapper.EnvelopePath, pPluginFolder); - _tcscat(PVWrapper.EnvelopePath, _T("\\SalPVEnv.exe")); - _sntprintf(PVWrapper.MutexName, SizeOf(PVWrapper.MutexName), _T("PVEXE_%08X"), GetCurrentProcessId()); - PVWrapper.hMutex = CreateMutex(NULL, TRUE, PVWrapper.MutexName); - _ASSERTE(GetLastError() != ERROR_ALREADY_EXISTS); - if (!PVWrapper.hMutex) - { - return FALSE; - } - - // NOTE: the name of the exe in the Command line is not important. It just must be something. - _sntprintf(PVWrapper.CommandLine, SizeOf(PVWrapper.CommandLine), _T("SalPVEnv.exe %s"), PVWrapper.MutexName); - if (!LoadEnvelope()) - { - CloseHandle(PVWrapper.hMutex); - PVWrapper.hMutex = NULL; - return FALSE; - } - - return TRUE; -} - -void ReleasePVEXEWrapper() -{ - if (PVWrapper.hMutex != NULL) - { - CloseHandle(PVWrapper.hMutex); // This will make the Envelope make suicide - PVWrapper.hMutex = NULL; - } -} - -// ========================= Stubs to PVW32Cnv.dll ========================= - -PVCODE WINAPI PVReadImage2Stub(LPPVHandle Img, HDC PaintDC, RECT* pDRect, TProgressProc Progress, void* AppSpecific, int ImageIndex) -{ - PVMessage_ReadImage msg(Img, ImageIndex, Progress != NULL); - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (msg.Exec(PaintDC, pDRect, Progress, AppSpecific)) - { - PVCODE ret = msg.GetResultCode(); - if (((PVC_OK == ret) || (PVC_CANCELED == ret)) && PaintDC) - { - PVDrawImageStub(Img, PaintDC, pDRect->left, pDRect->top, pDRect); - } - return ret; - } - - return PVC_ENVELOPE_NOT_LOADED; -} - -PVCODE WINAPI PVCloseImageStub(LPPVHandle Img) -{ - if (!Img) - { - return PVC_OK; - } - PVMessage_CloseImage msg(Img); - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (msg.Exec()) - { - return msg.GetResultCode(); - } - - return PVC_ENVELOPE_NOT_LOADED; -} - -PVCODE WINAPI PVDrawImageStub(LPPVHandle Img, HDC PaintDC, int X, int Y, LPRECT rect) -{ - PVMessage_DrawImage msg(Img, PaintDC, X, Y, rect); - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (msg.Exec(PaintDC)) - { - return msg.GetResultCode(); - } - - return PVC_ENVELOPE_NOT_LOADED; -} - -const char* WINAPI PVGetErrorTextStub(DWORD ErrorCode) -{ - // FIXME: make this thread-safe. - // FIXME: implement providing the string from Salamander, when needed - // (PVW32Cnv.dll doesn't contain most strings anymore) - // FIXME: consult the default error text with Altap - static char errorStr[512]; - - if (ErrorCode == PVC_ENVELOPE_NOT_LOADED) - return "Could not load PictView process."; - - PVMessage_GetErrorText msg(ErrorCode); - - if (!msg.IsInited()) - return "Could not load PictView process."; - - if (msg.Exec()) - { - strcpy(errorStr, msg.GetErrorText()); - return errorStr; - } - - return "Could not load PictView process."; -} - -PVCODE WINAPI PVOpenImageExStub(LPPVHandle* Img, LPPVOpenImageExInfo pOpenExInfo, LPPVImageInfo pImgInfo, int Size) -{ - PVMessage_OpenImageEx msg(pOpenExInfo); - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (msg.Exec(pImgInfo)) - { - *Img = msg.GetPVHandle(); - return msg.GetResultCode(); - } - - return PVC_ENVELOPE_NOT_LOADED; -} - -PVCODE WINAPI PVSetBkHandleStub(LPPVHandle Img, COLORREF BkColor) -{ - PVMessage_SetBkHandle msg(Img, BkColor); - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (msg.Exec()) - { - return msg.GetResultCode(); - } - - return PVC_ENVELOPE_NOT_LOADED; -} - -DWORD WINAPI PVGetDLLVersionStub(void) -{ - PVMessage_GetDLLVersion msg; - DWORD version; - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (msg.Exec(version)) - { - return version; - } - - return 0; -} - -PVCODE WINAPI PVSetStretchParametersStub(LPPVHandle Img, DWORD Width, DWORD Height, DWORD Mode) -{ - PVMessage_SetStretchParameters msg(Img, Width, Height, Mode); - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (msg.Exec()) - { - return msg.GetResultCode(); - } - - return PVC_ENVELOPE_NOT_LOADED; -} - -PVCODE WINAPI PVLoadFromClipboardStub(LPPVHandle* Img, LPPVImageInfo pImgInfo, int Size) -{ - PVMessage_LoadFromClipboard msg; - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (msg.Exec(pImgInfo)) - { - *Img = msg.GetPVHandle(); - return msg.GetResultCode(); - } - - return PVC_ENVELOPE_NOT_LOADED; -} - -PVCODE WINAPI PVGetImageInfoStub(LPPVHandle Img, LPPVImageInfo pImgInfo, int Size, int ImageIndex) -{ - PVMessage_GetImageInfo msg(Img, ImageIndex); - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (msg.Exec(pImgInfo)) - { - return msg.GetResultCode(); - } - - return PVC_ENVELOPE_NOT_LOADED; -} - -PVCODE WINAPI PVSetParamStub(LPPVHandle Img) -{ - // This is not needed in this environment - return PVC_OK; -} - -PVCODE WINAPI PVGetHandles2Stub(LPPVHandle Img, LPPVImageHandles* pHandles) -{ - // This is not needed in this environment - return PVC_ENVELOPE_NOT_LOADED; -} - -PVCODE WINAPI PVSaveImageStub(LPPVHandle Img, const char* OutFName, LPPVSaveImageInfo pSii, TProgressProc Progress, void* AppSpecific, int ImageIndex) -{ - PVMessage_SaveImage msg(Img, OutFName, pSii, ImageIndex, Progress != NULL); - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (msg.Exec(Progress, AppSpecific)) - { - PVCODE ret = msg.GetResultCode(); - - if (ret != PVC_OK) - return ret; - - // Send the output to user-defined output funcs, if specified - if (pSii->Flags & PVSF_USERDEFINED_OUTPUT) - { - PVMessage_SaveImage::LPPVMessageSaveImage pHdr = (PVMessage_SaveImage::LPPVMessageSaveImage)msg.GetData(); - HANDLE hFileMap; - LPBYTE pBuffer; - - hFileMap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, - 0, pHdr->OutFileMapSize, pHdr->FileName); - if (!hFileMap) - { - return PVC_OOM; - } - pBuffer = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, pHdr->OutFileMapSize); - if (!pBuffer) - { - CloseHandle(hFileMap); - return PVC_OOM; - } - ret = (pSii->WriteFunc(AppSpecific, pBuffer, pHdr->OutFileMapSize) == pHdr->OutFileMapSize) ? PVC_OK : PVC_WRITING_ERROR; - UnmapViewOfFile(pBuffer); - CloseHandle(hFileMap); - PVMessage_CloseHandle(pHdr->InternalFileMapHandle).Exec(); - } - return ret; - } - - return PVC_ENVELOPE_NOT_LOADED; -} - -PVCODE WINAPI PVChangeImageStub(LPPVHandle Img, DWORD Flags) -{ - PVMessage_ChangeImage msg(Img, Flags); - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (msg.Exec()) - { - return msg.GetResultCode(); - } - - return PVC_ENVELOPE_NOT_LOADED; -} - -DWORD WINAPI PVIsOutCombSupportedStub(int Fmt, int Compr, int Colors, int ColorModel) -{ - PVMessage_IsOutCombSupported msg(Fmt, Compr, Colors, ColorModel); - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (msg.Exec()) - { - return msg.GetResultCode(); - } - - return PVC_ENVELOPE_NOT_LOADED; -} - -PVCODE WINAPI PVReadImageSequenceStub(LPPVHandle Img, LPPVImageSequence* ppSeq) -{ - PVMessage_ReadImageSequence msg(Img); - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (msg.Exec()) - { - return msg.GetResultCode(); - } - - return PVC_ENVELOPE_NOT_LOADED; -} - -PVCODE WINAPI PVCropImageStub(LPPVHandle Img, int Left, int Top, int Width, int Height) -{ - PVMessage_CropImage msg(Img, Left, Top, Width, Height); - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (msg.Exec()) - { - return msg.GetResultCode(); - } - - return PVC_ENVELOPE_NOT_LOADED; -} - -bool GetRGBAtCursorStub(LPPVHandle Img, DWORD Colors, int x, int y, RGBQUAD* pRGB, int* pIndex) -{ - PVMessage_GetRGBAtCursor msg(Img, Colors, x, y); - - if (!msg.IsInited()) - return false; - - if (msg.Exec()) - { - *pRGB = *msg.GetRGB(); - *pIndex = msg.GetIndex(); - return msg.GetResultCode() == PVC_OK; - } - - return false; -} - -PVCODE CalculateHistogramStub(LPPVHandle Img, const LPPVImageInfo pvii, LPDWORD luminosity, LPDWORD red, LPDWORD green, LPDWORD blue, LPDWORD rgb) -{ - PVMessage_CalculateHistogram msg(Img, pvii); - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (msg.Exec()) - { - msg.GetResults(luminosity, red, green, blue, rgb); - return msg.GetResultCode(); - } - - return PVC_ENVELOPE_NOT_LOADED; -} - -PVCODE CreateThumbnailStub(LPPVHandle Img, LPPVSaveImageInfo pSii, int imageIndex, DWORD imgWidth, DWORD imgHeight, - int thumbWidth, int thumbHeight, CSalamanderThumbnailMakerAbstract* thumbMaker, DWORD thumbFlags, - TProgressProc progressProc, void* progressProcArg) -{ - CSalamanderThumbnailMaker::CalculateThumbnailSize(imgWidth, imgHeight, thumbWidth, thumbHeight, thumbWidth, thumbHeight); - PVMessage_CreateThumbnail msg(Img, pSii, imageIndex, imgWidth, imgHeight, thumbWidth, thumbHeight); - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (thumbMaker->SetParameters(thumbWidth, thumbHeight, thumbFlags)) - { - if (msg.Exec(progressProc, progressProcArg)) - { - if ((msg.GetResultCode() == PVC_OK) || (msg.GetResultCode() == PVC_CANCELED)) - { - thumbMaker->ProcessBuffer(msg.GetPixelData(), thumbHeight); - } - } - return msg.GetResultCode(); - } - - return PVC_ENVELOPE_NOT_LOADED; -} - -PVCODE SimplifyImageSequenceStub(LPPVHandle Img, HDC dc, int ScreenWidth, int ScreenHeight, LPPVImageSequence& pSeq, const COLORREF& bgColor) -{ - PVMessage_SimplifyImageSequence msg(Img, ScreenWidth, ScreenHeight, bgColor); - - if (!msg.IsInited()) - return PVC_ENVELOPE_NOT_LOADED; - - if (msg.Exec()) - { - if (msg.GetResultCode() == PVC_OK) - { - pSeq = msg.GetImageSequence(); - } - return msg.GetResultCode(); - } - - return PVC_ENVELOPE_NOT_LOADED; -} - -// ========================= LoadEnvelope ========================= -bool LoadEnvelope() -{ - STARTUPINFO si; - - _ASSERTE(PVWrapper.hMutex != NULL); - if (!PVWrapper.hMutex) - { - return false; - } - - // FIXME: The rest of this func may need synchronization via a critical section - if (PVWrapper.pi.hProcess) - { - // FIXME: check that the envelope process is still alive - return true; - } - memset(&si, 0, sizeof(si)); - memset(&PVWrapper.pi, 0, sizeof(PVWrapper.pi)); - si.cb = sizeof(STARTUPINFO); - if (!CreateProcess(PVWrapper.EnvelopePath, PVWrapper.CommandLine, NULL, NULL, - FALSE, 0, NULL, NULL, &si, &PVWrapper.pi)) - { - return false; - } - - WaitForInputIdle(PVWrapper.pi.hProcess, 5000); - - // Feed the localized texts from our language dll to the envelope - PVMessage_InitTexts msg; - - if (!msg.IsInited() || !msg.Exec()) - { - return false; - } - - return true; -} - -PVMessage_InitTexts::PVMessage_InitTexts() -{ - char *buf = NULL, *ptr = buf; - size_t alloced = 0; - char str[5000]; - - for (int i = 0; i <= 999; i++) - { - int size = LoadString(HLanguage, IDS_DLL + i, str, 5000); - if (size == 0) - str[0] = 0; // String not found in resources - size_t len = size + 1; - if (ptr - buf + len > alloced) - { - alloced += 8192; - char* newbuf = (char*)realloc(buf, alloced); - if (!newbuf) - { - break; // What's wrong? - } - ptr = newbuf + (ptr - buf); - buf = newbuf; - } - strcpy(ptr, str); - ptr += len; - } - if (!Init(PVMSG_InitTexts, ptr - buf + sizeof(PVMessageInitTexts) - sizeof(PVMessageHeader), NULL)) - { - free(buf); - return; - } - - LPPVMessageInitTexts pHdr = (LPPVMessageInitTexts)pData; - pHdr->TextsCount = 999; - memcpy(pHdr->Texts, buf, ptr - buf); - free(buf); -} - -// The following is needed to keep the linker happy in debug builds -bool PVMessage_InitTexts::HandleRequest() -{ - return false; -} - -bool PVMessage_GetErrorText::HandleRequest() -{ - return false; -} - -bool PVMessage_OpenImageEx::HandleRequest() -{ - return false; -} - -bool PVMessage_GetImageInfo::HandleRequest() -{ - return false; -} - -bool PVMessage_ReadImage::HandleRequest() -{ - return false; -} - -bool PVMessage_CloseImage::HandleRequest() -{ - return false; -} - -bool PVMessage_DrawImage::HandleRequest() -{ - return false; -} - -bool PVMessage_SaveImage::HandleRequest() -{ - return false; -} - -bool PVMessage_LoadFromClipboard::HandleRequest() -{ - return false; -} - -bool PVMessage_ReadImageSequence::HandleRequest() -{ - return false; -} - -bool PVMessage_SetBkHandle::HandleRequest() -{ - return false; -} - -bool PVMessage_GetDLLVersion::HandleRequest() -{ - return false; -} - -bool PVMessage_SetStretchParameters::HandleRequest() -{ - return false; -} - -bool PVMessage_ChangeImage::HandleRequest() -{ - return false; -} - -bool PVMessage_IsOutCombSupported::HandleRequest() -{ - return false; -} - -bool PVMessage_CropImage::HandleRequest() -{ - return false; -} - -bool PVMessage_GetRGBAtCursor::HandleRequest() -{ - return false; -} - -bool PVMessage_CalculateHistogram::HandleRequest() -{ - return false; -} - -bool PVMessage_CreateThumbnail::HandleRequest() -{ - return false; -} - -bool PVMessage_SimplifyImageSequence::HandleRequest() -{ - return false; -} - -bool PVMessage_CloseHandle::HandleRequest() -{ - return false; -} - -#endif diff --git a/src/plugins/pictview/PVEXEWrapper.h b/src/plugins/pictview/PVEXEWrapper.h deleted file mode 100644 index 1529c9ad2..000000000 --- a/src/plugins/pictview/PVEXEWrapper.h +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Open Salamander Authors -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#ifdef PICTVIEW_DLL_IN_SEPARATE_PROCESS - -#include "pictview.h" - -BOOL InitPVEXEWrapper(HWND hParentWnd, LPCTSTR pPluginFolder); -void ReleasePVEXEWrapper(); - -#endif diff --git a/src/plugins/pictview/PVEnvelope.cpp b/src/plugins/pictview/PVEnvelope.cpp deleted file mode 100644 index 620d1e3f0..000000000 --- a/src/plugins/pictview/PVEnvelope.cpp +++ /dev/null @@ -1,180 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Open Salamander Authors -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include - -#include "PVMessage.h" -#include "PixelAccess.h" -#include "Thumbnailer.h" - -#define SizeOf(x) (sizeof(x) / sizeof(x[0])) - -extern "C" WINUSERAPI PVCODE WINAPI PVSetParam(const char*(WINAPI* GetExtTextProc)(int msgID)); - -extern const char* Strings[450]; -extern char* StringsBuf; -extern CSalamanderThumbnailMaker Thumbnailer; - -TCHAR SalamanderMutex[64]; -DWORD MainThreadID; - -CPVWrapper PVWrapper; - -DWORD WINAPI CheckThreadProc(LPVOID) -{ - for (;;) - { - HANDLE hMutex = OpenMutex(SYNCHRONIZE, FALSE, SalamanderMutex); - if (!hMutex) - { - // Hmmm, Salamander died - break; - } - CloseHandle(hMutex); - Sleep(1000); - } - - PostThreadMessage(MainThreadID, WM_QUIT, 0, 0); - - return TRUE; -} - -// This callback provides strings coming from the Salamander process to PVW32Cnv.dll -// Salamander provides translated messages, this allows using single PVW32Cnv.dll -const char* WINAPI GetExtTextProc(int msgID) -{ - if ((msgID >= 0) && (msgID <= SizeOf(Strings))) - { - const char* ret = Strings[msgID]; - return ret ? ret : ""; - } - return ""; -} - -// **************************************************************************** -// EnableExceptionsOn64 -// - -// We want to learn about SEH exceptions even on x64 Windows 7 SP1 and later -// http://blog.paulbetts.org/index.php/2010/07/20/the-case-of-the-disappearing-onload-exception-user-mode-callback-exceptions-in-x64/ -// http://connect.microsoft.com/VisualStudio/feedback/details/550944/hardware-exceptions-on-x64-machines-are-silently-caught-in-wndproc-messages -// http://support.microsoft.com/kb/976038 -void EnableExceptionsOn64() -{ - typedef BOOL(WINAPI * FSetProcessUserModeExceptionPolicy)(DWORD dwFlags); - typedef BOOL(WINAPI * FGetProcessUserModeExceptionPolicy)(LPDWORD dwFlags); - typedef BOOL(WINAPI * FIsWow64Process)(HANDLE, PBOOL); -#define PROCESS_CALLBACK_FILTER_ENABLED 0x1 - - HINSTANCE hDLL = LoadLibrary("KERNEL32.DLL"); - if (hDLL != NULL) - { - FIsWow64Process isWow64 = (FIsWow64Process)GetProcAddress(hDLL, "IsWow64Process"); // Min: XP SP2 - FSetProcessUserModeExceptionPolicy set = (FSetProcessUserModeExceptionPolicy)GetProcAddress(hDLL, "SetProcessUserModeExceptionPolicy"); // Min: Vista with hotfix - FGetProcessUserModeExceptionPolicy get = (FGetProcessUserModeExceptionPolicy)GetProcAddress(hDLL, "GetProcessUserModeExceptionPolicy"); // Min: Vista with hotfix - if (isWow64 != NULL && set != NULL && get != NULL) - { - BOOL bIsWow64; - if (isWow64(GetCurrentProcess(), &bIsWow64) && bIsWow64) - { - DWORD dwFlags; - if (get(&dwFlags)) - set(dwFlags & ~PROCESS_CALLBACK_FILTER_ENABLED); - } - } - FreeLibrary(hDLL); - } -} - -#ifdef _CONSOLE -int _tmain(int argc, TCHAR* argv[]) -#else -int WINAPI _tWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPTSTR lpCmdLine, int /*nCmdShow*/) -#endif -{ - DWORD CheckThreadID; - HANDLE hCheckThread; - HANDLE hEvent; - TCHAR eventName[32]; - MSG msg; - - EnableExceptionsOn64(); - - // I do not want any critical errors such as "no disk in drive A:" - SetErrorMode(SetErrorMode(0) | SEM_FAILCRITICALERRORS); - -#ifdef _CONSOLE - if (argc != 2) - { -#else - if (*lpCmdLine == 0) - { -#endif - return -1; - } - /* printf("Cmdline arg #: %d\n", argc); - for (int i = 0; i < argc; i++) - printf("Cmdline arg: %s\n", argv[i]);*/ - -#ifdef _CONSOLE - _tcsncpy_s(SalamanderMutex, argv[1], _TRUNCATE); -#else - _tcsncpy_s(SalamanderMutex, lpCmdLine, _TRUNCATE); -#endif - - // PVSetParam unlocks PVW32Cnv.dll for Salamander and install custom text provider - memset(Strings, 0, sizeof(Strings)); - PVSetParam(GetExtTextProc); - - MainThreadID = GetCurrentThreadId(); - - // Make thread to check whether Salamander is still alive - hCheckThread = CreateThread(NULL, 0, CheckThreadProc, NULL, 0, &CheckThreadID); - - // Main message loop - while (GetMessage(&msg, NULL, 0, 0) > 0) - { - switch (msg.message) - { - case WM_QUIT: - return 0; - - case WM_USER: -#ifdef _CONSOLE - printf("Received WM_USER\n"); -#endif - - _sntprintf(eventName, SizeOf(eventName), _T("%s_ev%x"), SalamanderMutex, msg.lParam); - hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, eventName); - _ASSERTE(hEvent); - PVMessage::HandleMessage(msg.wParam, msg.lParam, hEvent); - - SetEvent(hEvent); - CloseHandle(hEvent); - break; - - case WM_USER + 1: - // Handle messages that don't need to send response, e.g. PVMSG_CloseHandle -#ifdef _CONSOLE - printf("Received WM_USER+1\n"); -#endif - PVMessage::HandlePostedMessage(msg.wParam, msg.lParam); - break; - - default: - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - - // This should be done when the application quits - if (StringsBuf) - { - free(StringsBuf); - } - Thumbnailer.Clear(-1); - return 0; -} diff --git a/src/plugins/pictview/PVMessage.cpp b/src/plugins/pictview/PVMessage.cpp deleted file mode 100644 index e7e980f14..000000000 --- a/src/plugins/pictview/PVMessage.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Open Salamander Authors -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "precomp.h" - -#include "PVMessage.h" - -PVMessage::PVMessage() : pData(NULL), hFileMap(NULL), pWImg(NULL) -{ -} - -PVMessage::~PVMessage() -{ - // NOTE: ownership of pData not taken when hFileMap is NULL - if (hFileMap) - { - if (pData) - UnmapViewOfFile(pData); - CloseHandle(hFileMap); - } -} - -LPBYTE PVMessage::GetData() -{ - return (LPBYTE)pData; -} - -PVCODE PVMessage::GetResultCode() -{ - if (pData) - { - return ((LPPVMessageHeader)pData)->ResultCode; - } - return PVC_ENVELOPE_NOT_LOADED; -} diff --git a/src/plugins/pictview/PVMessage.h b/src/plugins/pictview/PVMessage.h deleted file mode 100644 index 262a8bc9b..000000000 --- a/src/plugins/pictview/PVMessage.h +++ /dev/null @@ -1,532 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Open Salamander Authors -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "lib/pvw32dll.h" - -#define PVC_ENVELOPE_NOT_LOADED ((PVCODE) - 123) - -struct CPVWrapper -{ - TCHAR EnvelopePath[_MAX_PATH]; - TCHAR CommandLine[128]; - PROCESS_INFORMATION pi; // Remote PV Envelope process - TCHAR MutexName[32]; // Name of the mutex hMutex - HANDLE hMutex; // Used to signal to the Envelope we are still alive to prevent zombies when AS dies - DWORD MessageID; // Each message sent to the Envelope has its unique ID -}; - -extern CPVWrapper PVWrapper; - -// PVMessage sends a single message to the PV Envelope process and waits for the answer. -// It is for one time use, instead of using single instance for the entire lifetime of AS. -// of AS because most PV functions are thread-safe. -// FIXME: ensure each PVMessage request or LPPVHandle indeed creates extra thread in the remote process. -class PVMessage -{ -protected: - enum ePVMSG - { - PVMSG_GetDLLVersion, - PVMSG_CloseImage, - PVMSG_DrawImage, - PVMSG_GetErrorText, - PVMSG_SetBkHandle, - PVMSG_SetStretchParameters, - PVMSG_ChangeImage, - PVMSG_IsOutCombSupported, - PVMSG_CropImage, - PVMSG_OpenImageEx, - PVMSG_GetImageInfo, - PVMSG_ReadImage, - PVMSG_ReadImageWithoutProgress, - PVMSG_SaveImage, - PVMSG_SaveImageWithoutProgress, - PVMSG_LoadFromClipboard, - PVMSG_ReadImageSequence, - PVMSG_GetRGBAtCursor, - PVMSG_CalculateHistogram, - PVMSG_CreateThumbnail, - PVMSG_SimplifyImageSequence, - // Internal messages for communication SPL -> Envelope, not stubbing PVW32Cnv.dll - PVMSG_InitTexts, - PVMSG_CloseHandle - }; - - typedef struct PVMessageHeader - { // Header at the beginning of pData - DWORD cbSize; // Including this header - DWORD Type; - DWORD PVHandle; - PVCODE ResultCode; - } PVMessageHeader, *LPPVMessageHeader; - -public: - PVMessage(ePVMSG type, size_t dataSize, LPPVHandle pvHandle = NULL); - PVMessage(); - PVMessage(LPPVMessageHeader pHdr); - - virtual ~PVMessage(); - - static bool HandleMessage(DWORD dataSize, DWORD id, HANDLE hEvent); - static bool HandlePostedMessage(DWORD message, DWORD data); - - bool IsInited(); - bool Exec(); - virtual bool HandleRequest() { return false; } - LPBYTE GetData(); // Returns pointer to data shared with the Envelope process - PVCODE GetResultCode(); - LPPVHandle GetPVHandle(); - class PVWrapperImageHandle* GetWImg() { return pWImg; } - -protected: - HANDLE hFileMap; - struct PVMessageHeader* pData; // Obtained via MapViewOfFile(hFileMap) - DWORD iID; - class PVWrapperImageHandle* pWImg; - - typedef struct PVImageInfoStruct - { - DWORD cbSize; - DWORD Width, Height; - DWORD BytesPerLine; - DWORD FileSize; - DWORD Colors; - DWORD Format; - DWORD Flags; - DWORD ColorModel; - DWORD NumOfImages; - DWORD CurrentImage; - char Info1[PV_MAX_INFO_LEN], Info2[PV_MAX_INFO_LEN], Info3[PV_MAX_INFO_LEN]; - DWORD StretchedWidth; - DWORD StretchedHeight; - DWORD StretchMode; - DWORD HorDPI; - DWORD VerDPI; - bool bFSI; // True when FSI is filled and valid - PVFormatSpecificInfo FSI; - DWORD Compression; - DWORD TotalBitDepth; - DWORD CommentSize; - char Comment[256]; - } PVImageInfoStruct, *LPPVImageInfoStruct; - - bool Init(ePVMSG type, size_t dataSize, LPPVHandle pvHandle); - - bool MarshalImageInfo(LPPVImageInfo pInInfo, LPPVImageInfoStruct pOutInfo); - bool UnmarshalImageInfo(LPPVImageInfoStruct pInInfo, LPPVImageInfo pOutInfo); - - static PVMessage* GetPVMessage(LPPVMessageHeader pHdr, HANDLE hEvent); -}; - -// Internal message used to feed translated strings from Salamander.exe to Enveloper -class PVMessage_InitTexts : public PVMessage -{ -public: - PVMessage_InitTexts(); - PVMessage_InitTexts(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool HandleRequest(); - -private: - typedef struct PVMessageInitTexts : PVMessageHeader - { - int TextsCount; - char Texts[1]; - } PVMessageInitTexts, *LPPVMessageInitTexts; -}; - -class PVMessage_GetErrorText : public PVMessage -{ -public: - PVMessage_GetErrorText(DWORD ErrorCode); - PVMessage_GetErrorText(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool HandleRequest(); - const char* GetErrorText(); - -private: - typedef struct PVMessageGetErrorText : PVMessageHeader - { - DWORD ErrorCode; - char ErrorText[512]; - } PVMessageGetErrorText, *LPPVMessageGetErrorText; -}; - -class PVMessage_OpenImageEx : public PVMessage -{ -public: - PVMessage_OpenImageEx(LPPVOpenImageExInfo pOpenExInfo); - PVMessage_OpenImageEx(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool IsInited(); - bool Exec(LPPVImageInfo pImgInfo); - bool HandleRequest(); - -private: - typedef struct PVMessageOpenImageEx : PVMessageHeader - { - // PVOpenImageExInfo - DWORD Flags; - char FileName[260]; // Contains FileMap name when Flags contains PVOF_ATTACH_TO_HANDLE - DWORD DataSize; // Contains size of FileMap data when Flags contains PVOF_ATTACH_TO_HANDLE - // PVImageInfo - PVImageInfoStruct ImageInfo; - } PVMessageOpenImageEx, *LPPVMessageOpenImageEx; -}; - -class PVMessage_GetImageInfo : public PVMessage -{ -public: - PVMessage_GetImageInfo(LPPVHandle pvHandle, int ImageIndex); - PVMessage_GetImageInfo(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool Exec(LPPVImageInfo pImgInfo); - bool HandleRequest(); - -private: - typedef struct PVMessageGetImageInfo : PVMessageHeader - { - int ImageIndex; - PVImageInfoStruct ImageInfo; - } PVMessageGetImageInfo, *LPPVMessageGetImageInfo; -}; - -class PVMessageWithProgress : public PVMessage -{ -public: - PVMessageWithProgress(ePVMSG type, size_t dataSize, LPPVHandle pvHandle = NULL); - PVMessageWithProgress(LPPVMessageHeader pHdr, HANDLE Event) : PVMessage(pHdr), hEvent(Event) {}; - bool Exec(TProgressProc Progress, void* AppSpecific); - BOOL HandleProgress(int done); - -protected: - typedef enum PVState_ReadImage - { - PVState_Started, - PVState_Progressing, - PVState_Cancelled, - PVState_Finished - } PVState_ReadImage; - typedef struct PVMessageWithProgressHeader : PVMessageHeader - { - LONG State; // typecast to PVState_ReadImage; LONG used for InterlockedExchange - int ProgressValue; - } PVMessageWithProgressHeader, *LPPVMessageWithProgressHeader; - HANDLE hEvent; -}; - -class PVMessage_ReadImage : public PVMessageWithProgress -{ -public: - PVMessage_ReadImage(LPPVHandle pvHandle, int ImageIndex, bool bProgress); - PVMessage_ReadImage(LPPVMessageHeader pHdr, HANDLE hEvent) : PVMessageWithProgress(pHdr, hEvent) {}; - bool Exec(HDC PaintDC, RECT* pDRect, TProgressProc Progress, void* AppSpecific); - bool HandleRequest(); - -private: - typedef struct PVMessageReadImage : PVMessageWithProgressHeader - { - int ImageIndex; - } PVMessageReadImage, *LPPVMessageReadImage; -}; - -class PVMessage_CloseImage : public PVMessage -{ -public: - PVMessage_CloseImage(LPPVHandle pvHandle); - PVMessage_CloseImage(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - ~PVMessage_CloseImage(); - bool HandleRequest(); -}; - -class PVMessage_DrawImage : public PVMessage -{ -public: - PVMessage_DrawImage(LPPVHandle pvHandle, HDC PaintDC, int X, int Y, LPRECT pDrawRect); - PVMessage_DrawImage(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool Exec(HDC PaintDC); - bool HandleRequest(); - -private: - typedef struct PVMessageDrawImage : PVMessageHeader - { - DWORD BitsPerPixel; - DWORD X, Y; - RECT DrawRect; - char SharedMemoryName[16]; - DWORD InternalFileMapHandle; // Envelope's handle to the shared memory object - } PVMessageDrawImage, *LPPVMessageDrawImage; -}; - -class PVMessage_SaveImage : public PVMessageWithProgress -{ -public: - PVMessage_SaveImage(LPPVHandle pvHandle, const char* FileName, LPPVSaveImageInfo pSii, int ImageIndex, bool bProgress); - PVMessage_SaveImage(LPPVMessageHeader pHdr, HANDLE hEvent) : PVMessageWithProgress(pHdr, hEvent) {}; - bool HandleRequest(); - -private: - typedef struct PVMessageSaveImage : PVMessageWithProgressHeader - { - DWORD cbSize; - DWORD Format; - DWORD Compression; - DWORD Colors; - DWORD ColorModel; - DWORD Flags; - DWORD Width; /* No scaling applied if Width or Height is zero */ - DWORD Height; - DWORD HorDPI; - DWORD VerDPI; - DWORD CommentSize; - char Comment[256]; - char FormatSpecificInfo[256]; - DWORD CropLeft; /* Cropping applied before scaling */ - DWORD CropTop; - DWORD CropWidth; /* Crop size */ - DWORD CropHeight; /* No cropping applied if CropWidth or CropHeight is zero */ - struct - { - BYTE Flags; /* PVTF_XXX flags */ - union - { - BYTE Index; /* Used only when Flags is PVTF_INDEX */ - struct - { - BYTE Red, Green, Blue; /* Used only when Flags is PVTF_RGB */ - } RGB; - } Value; - } Transp; - int ImageIndex; - // When Flags contains PVSF_USERDEFINED_OUTPUT, FileName contains shared memory name, - // its size is in OutFileMapSize and InternalFileMapHandle contains Envelope's HANDLE to it. - char FileName[260]; - DWORD OutFileMapSize; - DWORD InternalFileMapHandle; - } PVMessageSaveImage, *LPPVMessageSaveImage; - friend PVCODE WINAPI PVSaveImageStub(LPPVHandle, const char*, LPPVSaveImageInfo, TProgressProc, void*, int); -}; - -class PVMessage_LoadFromClipboard : public PVMessage -{ -public: - PVMessage_LoadFromClipboard(); - PVMessage_LoadFromClipboard(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool Exec(LPPVImageInfo pImgInfo); - bool HandleRequest(); - -private: - typedef struct PVMessageLoadFromClipboard : PVMessageHeader - { - PVImageInfoStruct ImageInfo; - } PVMessageLoadFromClipboard, *LPPVMessageLoadFromClipboard; -}; - -class PVMessage_ReadImageSequence : public PVMessage -{ -public: - PVMessage_ReadImageSequence(LPPVHandle pvHandle); - PVMessage_ReadImageSequence(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool Exec(); - bool HandleRequest(); - -private: - typedef struct PVMessageReadImageSequence : PVMessageHeader - { - DWORD NumberOfFrames; - } PVMessageReadImageSequence, *LPPVMessageReadImageSequence; -}; - -class PVMessage_SetBkHandle : public PVMessage -{ -public: - PVMessage_SetBkHandle(LPPVHandle pvHandle, COLORREF bkColor); - PVMessage_SetBkHandle(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool HandleRequest(); - -private: - typedef struct PVMessageSetBkHandle : PVMessageHeader - { - COLORREF BackgroundColor; - } PVMessageSetBkHandle, *LPPVMessageSetBkHandle; -}; - -class PVMessage_GetDLLVersion : public PVMessage -{ -public: - PVMessage_GetDLLVersion(); - PVMessage_GetDLLVersion(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool Exec(DWORD& Version); - bool HandleRequest(); - -private: - typedef struct PVMessageGetDLLVersion : PVMessageHeader - { - DWORD DLLVersion; - } PVMessageGetDLLVersion, *LPPVMessageGetDLLVersion; -}; - -class PVMessage_SetStretchParameters : public PVMessage -{ -public: - PVMessage_SetStretchParameters(LPPVHandle pvHandle, DWORD Width, DWORD Height, DWORD Mode); - PVMessage_SetStretchParameters(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool HandleRequest(); - -private: - typedef struct PVMessageSetStretchParameters : PVMessageHeader - { - DWORD Width; - DWORD Height; - DWORD Mode; - } PVMessageSetStretchParameters, *LPPVMessageSetStretchParameters; -}; - -class PVMessage_ChangeImage : public PVMessage -{ -public: - PVMessage_ChangeImage(LPPVHandle pvHandle, DWORD Flags); - PVMessage_ChangeImage(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool HandleRequest(); - -private: - typedef struct PVMessageChangeImage : PVMessageHeader - { - DWORD Flags; - } PVMessageChangeImage, *LPPVMessageChangeImage; -}; - -class PVMessage_IsOutCombSupported : public PVMessage -{ -public: - PVMessage_IsOutCombSupported(int Fmt, int Compr, int Colors, int ColorModel); - PVMessage_IsOutCombSupported(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool HandleRequest(); - -private: - typedef struct PVMessageIsOutCombSupported : PVMessageHeader - { - int Fmt; - int Compr; - int Colors; - int ColorModel; - } PVMessageIsOutCombSupported, *LPPVMessageIsOutCombSupported; -}; - -class PVMessage_CropImage : public PVMessage -{ -public: - PVMessage_CropImage(LPPVHandle pvHandle, int Left, int Top, int Width, int Height); - PVMessage_CropImage(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool HandleRequest(); - -private: - typedef struct PVMessageCropImage : PVMessageHeader - { - int Left; - int Top; - int Width; - int Height; - } PVMessageCropImage, *LPPVMessageCropImage; -}; - -class PVMessage_GetRGBAtCursor : public PVMessage -{ -public: - PVMessage_GetRGBAtCursor(LPPVHandle pvHandle, DWORD Colors, int x, int y); - PVMessage_GetRGBAtCursor(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool HandleRequest(); - - RGBQUAD* GetRGB(); - int GetIndex(); - -private: - typedef struct PVMessageGetRGBAtCursor : PVMessageHeader - { - DWORD Colors; // Helper to speed things a little up - int X; - int Y; - RGBQUAD RGB; - int Index; - } PVMessageGetRGBAtCursor, *LPPVMessageGetRGBAtCursor; -}; - -class PVMessage_CalculateHistogram : public PVMessage -{ -public: - PVMessage_CalculateHistogram(LPPVHandle pvHandle, const PVImageInfo* pvii); - PVMessage_CalculateHistogram(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool HandleRequest(); - - void GetResults(LPDWORD luminosity, LPDWORD red, LPDWORD green, LPDWORD blue, LPDWORD rgb); - -private: - typedef struct PVMessageCalculateHistogram : PVMessageHeader - { - DWORD Colors; // Helper to speed things a little up - DWORD Width; - DWORD Height; - DWORD BytesPerLine; - DWORD luminosity[256]; - DWORD red[256]; - DWORD green[256]; - DWORD blue[256]; - DWORD rgb[256]; - } PVMessageCalculateHistogram, *LPPVMessageCalculateHistogram; -}; - -class PVMessage_CreateThumbnail : public PVMessageWithProgress -{ -public: - PVMessage_CreateThumbnail(LPPVHandle pvHandle, LPPVSaveImageInfo pSii, int ImageIndex, DWORD imgWidth, DWORD imgHeight, DWORD thumbWidth, DWORD thumbHeight); - PVMessage_CreateThumbnail(LPPVMessageHeader pHdr, HANDLE hEvent) : PVMessageWithProgress(pHdr, hEvent) {}; - bool HandleRequest(); - LPBYTE GetPixelData(); - -private: - typedef struct PVMessageCreateThumbnail : PVMessageWithProgressHeader - { - DWORD ThumbWidth; // Target thumbnail width - DWORD ThumbHeight; // Target thumbnail height - DWORD ImageWidth; // Original image width - DWORD ImageHeight; // Original image height - DWORD Flags; - int ImageIndex; - BYTE Buffer[1]; - } PVMessageCreateThumbnail, *LPPVMessageCreateThumbnail; -}; - -class PVMessage_SimplifyImageSequence : public PVMessage -{ -public: - PVMessage_SimplifyImageSequence(LPPVHandle pvHandle, DWORD ScreenWidth, DWORD ScreenHeight, const COLORREF& bgColor); - PVMessage_SimplifyImageSequence(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool Exec(); - bool HandleRequest(); - LPPVImageSequence GetImageSequence(); - -private: - typedef struct PVMessageSimplifyImageSequence : PVMessageHeader - { - DWORD ScreenWidth; - DWORD ScreenHeight; - DWORD BitsPerPixel; - COLORREF BackgroundColor; - char SharedMemoryName[16]; - DWORD InternalFileMapHandle; // Envelope's handle to the shared memory object - DWORD Delay[1]; // Size of the array is NumberOfFrames - } PVMessageSimplifyImageSequence, *LPPVMessageSimplifyImageSequence; -}; - -// Internal message, used to return Win32 HANDLE to Envelope to be closed as no longer used -class PVMessage_CloseHandle : public PVMessage -{ -public: - PVMessage_CloseHandle(DWORD Handle); - PVMessage_CloseHandle(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; - bool Exec(); - bool HandleRequest(); - -private: - DWORD HandleToBeClosed; -}; - -bool LoadEnvelope(); diff --git a/src/plugins/pictview/PVMessageEnvelope.cpp b/src/plugins/pictview/PVMessageEnvelope.cpp deleted file mode 100644 index 292d8c77e..000000000 --- a/src/plugins/pictview/PVMessageEnvelope.cpp +++ /dev/null @@ -1,971 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Open Salamander Authors -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "precomp.h" - -#include "PVMessage.h" -#include "PixelAccess.h" -#include "Thumbnailer.h" - -class PVWrapperImageHandle -{ -public: - PVWrapperImageHandle(); - PVWrapperImageHandle(LPBYTE aBuffer, DWORD aSize); - ~PVWrapperImageHandle(); - void FreeSharedMemory(); - void FreeBuffer(); - - LPPVHandle PVHandle; - LPPVImageSequence PVImageSequence; - // The following 4 items are used when transfering file via shared memory - HANDLE hFileMap; - LPBYTE pBuffer; // Buffer with file content - LPBYTE pCur; // Current position inside pBuffer - DWORD Size; // Size of pBuffer -}; - -struct PVThumbnailerInfo -{ - CSalamanderThumbnailMaker* pThumbnailer; - DWORD BytesPerLine; - PVMessageWithProgress* pMsg; -}; - -typedef PVWrapperImageHandle* LPPVWrapperImageHandle; - -extern TCHAR SalamanderMutex[64]; -extern DWORD MainThreadID; - -const char* Strings[450]; -char* StringsBuf; -CSalamanderThumbnailMaker Thumbnailer; - -static bool CreateSharedMemoryForBuffer(LPBYTE pInBuffer, DWORD dataSize, char* fileMapName, DWORD& outFileMap); - -PVMessage::PVMessage(LPPVMessageHeader pHdr) : pData(pHdr), hFileMap(NULL), pWImg((LPPVWrapperImageHandle)(pHdr->PVHandle)) -{ -} - -bool PVMessage::MarshalImageInfo(LPPVImageInfo pInInfo, LPPVImageInfoStruct pOutInfo) -{ - pOutInfo->Width = pInInfo->Width; - pOutInfo->Height = pInInfo->Height; - pOutInfo->BytesPerLine = pInInfo->BytesPerLine; - pOutInfo->FileSize = pInInfo->FileSize; - pOutInfo->Colors = pInInfo->Colors; - pOutInfo->Format = pInInfo->Format; - pOutInfo->Flags = pInInfo->Flags; - pOutInfo->ColorModel = pInInfo->ColorModel; - pOutInfo->NumOfImages = pInInfo->NumOfImages; - pOutInfo->CurrentImage = pInInfo->CurrentImage; - // Quick fix to avoid asserts because of unterminated (although correctly clipped) strings in strcpy below translated to strcpy_s - pInInfo->Info1[sizeof(pInInfo->Info1) - 1] = 0; - pInInfo->Info2[sizeof(pInInfo->Info2) - 1] = 0; - pInInfo->Info3[sizeof(pInInfo->Info3) - 1] = 0; - strcpy(pOutInfo->Info1, pInInfo->Info1); - strcpy(pOutInfo->Info2, pInInfo->Info2); - strcpy(pOutInfo->Info3, pInInfo->Info3); - pOutInfo->StretchedWidth = pInInfo->StretchedWidth; - pOutInfo->StretchedHeight = pInInfo->StretchedHeight; - pOutInfo->StretchMode = pInInfo->StretchMode; - pOutInfo->HorDPI = pInInfo->HorDPI; - pOutInfo->VerDPI = pInInfo->VerDPI; - if (pInInfo->FSI) - { - pOutInfo->bFSI = true; - memcpy(&pOutInfo->FSI, pInInfo->FSI, sizeof(PVFormatSpecificInfo)); - } - else - { - pOutInfo->bFSI = false; - } - pOutInfo->Compression = pInInfo->Compression; - pOutInfo->TotalBitDepth = pInInfo->TotalBitDepth; - pOutInfo->CommentSize = min(sizeof(pOutInfo->Comment), pInInfo->CommentSize); - memcpy(pOutInfo->Comment, pInInfo->Comment, pOutInfo->CommentSize); - return true; -} - -PVMessage* PVMessage::GetPVMessage(LPPVMessageHeader pHdr, HANDLE hEvent) -{ - switch (pHdr->Type) - { - case PVMSG_GetDLLVersion: - return new PVMessage_GetDLLVersion(pHdr); - case PVMSG_CloseImage: - return new PVMessage_CloseImage(pHdr); - case PVMSG_DrawImage: - return new PVMessage_DrawImage(pHdr); - case PVMSG_GetErrorText: - return new PVMessage_GetErrorText(pHdr); - case PVMSG_SetBkHandle: - return new PVMessage_SetBkHandle(pHdr); - case PVMSG_SetStretchParameters: - return new PVMessage_SetStretchParameters(pHdr); - case PVMSG_ChangeImage: - return new PVMessage_ChangeImage(pHdr); - case PVMSG_IsOutCombSupported: - return new PVMessage_IsOutCombSupported(pHdr); - case PVMSG_CropImage: - return new PVMessage_CropImage(pHdr); - case PVMSG_OpenImageEx: - return new PVMessage_OpenImageEx(pHdr); - case PVMSG_GetImageInfo: - return new PVMessage_GetImageInfo(pHdr); - case PVMSG_ReadImage: - case PVMSG_ReadImageWithoutProgress: - return new PVMessage_ReadImage(pHdr, hEvent); - case PVMSG_SaveImage: - case PVMSG_SaveImageWithoutProgress: - return new PVMessage_SaveImage(pHdr, hEvent); - case PVMSG_LoadFromClipboard: - return new PVMessage_LoadFromClipboard(pHdr); - case PVMSG_ReadImageSequence: - return new PVMessage_ReadImageSequence(pHdr); - case PVMSG_GetRGBAtCursor: - return new PVMessage_GetRGBAtCursor(pHdr); - case PVMSG_CalculateHistogram: - return new PVMessage_CalculateHistogram(pHdr); - case PVMSG_CreateThumbnail: - return new PVMessage_CreateThumbnail(pHdr, hEvent); - case PVMSG_SimplifyImageSequence: - return new PVMessage_SimplifyImageSequence(pHdr); - case PVMSG_InitTexts: - return new PVMessage_InitTexts(pHdr); - default: - // Unsupported/unknown message - _ASSERTE(pHdr->Type == PVMSG_InitTexts); - return NULL; - } -} - -bool PVMessage::HandleMessage(DWORD dataSize, DWORD id, HANDLE hEvent) -{ - TCHAR fileMapName[48]; - LPPVMessageHeader pHdr; - HANDLE hFileMap; - - _sntprintf(fileMapName, SizeOf(fileMapName), _T("%s_%d"), SalamanderMutex, id); - hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, - 0, dataSize, fileMapName); - if (!hFileMap) - { - return false; - } - pHdr = (LPPVMessageHeader)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, dataSize); - if (!pHdr) - { - CloseHandle(hFileMap); - return false; - } - _ASSERTE(pHdr->cbSize == dataSize); - PVMessage* pMessage = GetPVMessage(pHdr, hEvent); - if (pMessage) - { - pMessage->HandleRequest(); - delete pMessage; - } - - UnmapViewOfFile(pHdr); - CloseHandle(hFileMap); - - return true; -} - -bool PVMessage::HandlePostedMessage(DWORD message, DWORD data) -{ - switch (message) - { - case PVMSG_CloseHandle: - CloseHandle((HANDLE)data); - break; - default: - return false; - } - return true; -} - -BOOL PVMessageWithProgress::HandleProgress(int done) -{ - LPPVMessageWithProgressHeader pHdr = (LPPVMessageWithProgressHeader)pData; - pHdr->ProgressValue = done; - PulseEvent(hEvent); - return pHdr->State == PVState_Cancelled; -} - -bool PVMessage_InitTexts::HandleRequest() -{ - LPPVMessageInitTexts pHdr = (LPPVMessageInitTexts)pData; - - if (!pHdr) - { - return false; - } - // This message should be sent just once.... - _ASSERTE(!StringsBuf); - if (StringsBuf) - { - return false; - } - // Duplioate the entire buffer - int size = pHdr->cbSize - sizeof(PVMessage) - sizeof(pHdr->TextsCount); - StringsBuf = (char*)malloc(size); - if (!StringsBuf) - { - return false; - } - memcpy(StringsBuf, pHdr->Texts, size); - // Init strings pointers to point inside the buffer - const char* str = StringsBuf; - for (int i = 0; i < min((int)(SizeOf(Strings)), pHdr->TextsCount); i++) - { - Strings[i] = str; - str += strlen(str) + 1; - } - pHdr->ResultCode = PVC_OK; - return true; -} - -bool PVMessage_GetErrorText::HandleRequest() -{ - LPPVMessageGetErrorText pHdr = (LPPVMessageGetErrorText)pData; - - if (!pHdr) - { - return false; - } - pHdr->ResultCode = PVC_OK; - strncpy_s(pHdr->ErrorText, PVGetErrorText(pHdr->ErrorCode), _TRUNCATE); - return true; -} - -DWORD WINAPI MySeekFunc(void* AppSpecific, LONG NewPos, int Origin) -{ - LPPVWrapperImageHandle pReadInfo = (LPPVWrapperImageHandle)AppSpecific; - - switch (Origin) - { - case FILE_BEGIN: - pReadInfo->pCur = pReadInfo->pBuffer + min((DWORD)NewPos, pReadInfo->Size); - break; - case FILE_CURRENT: - pReadInfo->pCur += min((DWORD)NewPos, pReadInfo->Size - (pReadInfo->pCur - pReadInfo->pBuffer)); - break; - case FILE_END: - pReadInfo->pCur = pReadInfo->pBuffer + pReadInfo->Size - min((DWORD)NewPos, pReadInfo->Size); - break; - } - return pReadInfo->pCur - pReadInfo->pBuffer; -} - -DWORD WINAPI MyMessageSeekFunc(void* AppSpecific, LONG NewPos, int Origin) -{ - return MySeekFunc(((PVMessage*)AppSpecific)->GetWImg(), NewPos, Origin); -} - -DWORD WINAPI MyDummySeekFunc(void* AppSpecific, LONG NewPos, int Origin) -{ - return 0; -} - -DWORD WINAPI MyReadFunc(void* AppSpecific, void* pData, DWORD Size) -{ - LPPVWrapperImageHandle pReadInfo = (LPPVWrapperImageHandle)AppSpecific; - - DWORD ret = min(Size, pReadInfo->Size - (pReadInfo->pCur - pReadInfo->pBuffer)); - memcpy(pData, pReadInfo->pCur, ret); - pReadInfo->pCur += ret; - return ret; -} - -DWORD WINAPI MyMessageReadFunc(void* AppSpecific, void* pData, DWORD Size) -{ - return MyReadFunc(((PVMessage*)AppSpecific)->GetWImg(), pData, Size); -} - -DWORD WINAPI MyWriteFunc(void* AppSpecific, void* pData, DWORD Size) -{ - LPPVWrapperImageHandle pWriteInfo = (LPPVWrapperImageHandle)AppSpecific; - - DWORD ret = min(Size, pWriteInfo->Size - (pWriteInfo->pCur - pWriteInfo->pBuffer)); - memcpy(pWriteInfo->pCur, pData, ret); - pWriteInfo->pCur += ret; - return ret; -} - -DWORD WINAPI MyAcumulateWriteFunc(void* AppSpecific, void* pData, DWORD Size) -{ - PVMessage_SaveImage* pSIMessage = (PVMessage_SaveImage*)AppSpecific; - LPPVWrapperImageHandle pWriteInfo = pSIMessage->GetWImg(); - DWORD pos = pWriteInfo->pCur - pWriteInfo->pBuffer; - - if (pos + Size > pWriteInfo->Size) - { - pWriteInfo->pBuffer = (LPBYTE)realloc(pWriteInfo->pBuffer, pos + Size); - pWriteInfo->pCur = pWriteInfo->pBuffer + pos; - } - memcpy(pWriteInfo->pCur, pData, Size); - pWriteInfo->pCur += Size; - if ((int)pWriteInfo->Size < pWriteInfo->pCur - pWriteInfo->pBuffer) - { - pWriteInfo->Size = pWriteInfo->pCur - pWriteInfo->pBuffer; - } - return Size; -} - -DWORD WINAPI MyThumbWriteFunc(void* AppSpecific, void* pData, DWORD Size) -{ - PVThumbnailerInfo* pThumbInfo = (PVThumbnailerInfo*)AppSpecific; - - if (pThumbInfo->pMsg->HandleProgress(0)) - return 0; - return pThumbInfo->pThumbnailer->ProcessBuffer(pData, Size / pThumbInfo->BytesPerLine) * Size; -} - -BOOL WINAPI ThumbProgressProc(int done, void* data) -{ - PVThumbnailerInfo* pThumbInfo = (PVThumbnailerInfo*)data; - - if (pThumbInfo) - { - return pThumbInfo->pMsg->HandleProgress(done); - } - return FALSE; -} - -bool PVMessage_OpenImageEx::HandleRequest() -{ - PVOpenImageExInfo OpenImageInfo; - PVImageInfo ImageInfo; - LPPVMessageOpenImageEx pHdr = (LPPVMessageOpenImageEx)pData; - - if (!pHdr) - { - return false; - } - - pWImg = new PVWrapperImageHandle(); - if (!pWImg) - { - pHdr->ResultCode = PVC_OOM; - return true; // Message handled - } - - memset(&OpenImageInfo, 0, sizeof(OpenImageInfo)); - OpenImageInfo.cbSize = sizeof(OpenImageInfo); - OpenImageInfo.Flags = pHdr->Flags & ~(PVOF_ATTACH_TO_HANDLE | PVOF_USERDEFINED_INPUT); - if (pHdr->Flags & (PVOF_ATTACH_TO_HANDLE | PVOF_USERDEFINED_INPUT)) - { - pWImg->hFileMap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, pHdr->DataSize, pHdr->FileName); - if (!pWImg->hFileMap) - { - pHdr->ResultCode = PVC_NO_FILES_FOUND; - delete pWImg; - pWImg = NULL; - return true; // Message handled - } - pWImg->pBuffer = pWImg->pCur = (LPBYTE)MapViewOfFile(pWImg->hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, pHdr->DataSize); - OpenImageInfo.DataSize = pWImg->Size = pHdr->DataSize; - OpenImageInfo.Handle = pWImg; - OpenImageInfo.ReadFunc = MyReadFunc; - OpenImageInfo.SeekFunc = MySeekFunc; - OpenImageInfo.Flags |= PVOF_USERDEFINED_INPUT; - } - else - { - OpenImageInfo.FileName = pHdr->FileName; - } - pHdr->ResultCode = PVOpenImageEx(&pWImg->PVHandle, &OpenImageInfo, &ImageInfo, sizeof(ImageInfo)); - if (pHdr->ResultCode == PVC_OK) - { - MarshalImageInfo(&ImageInfo, &pHdr->ImageInfo); - pHdr->PVHandle = (DWORD)pWImg; - } - else - { - delete pWImg; - pWImg = NULL; - } - return true; -} - -bool PVMessage_GetImageInfo::HandleRequest() -{ - PVImageInfo ImageInfo; - LPPVMessageGetImageInfo pHdr = (LPPVMessageGetImageInfo)pData; - - if (!pHdr) - { - return false; - } - pHdr->ResultCode = PVGetImageInfo(pWImg ? pWImg->PVHandle : NULL, &ImageInfo, sizeof(ImageInfo), pHdr->ImageIndex); - if (pHdr->ResultCode == PVC_OK) - { - MarshalImageInfo(&ImageInfo, &pHdr->ImageInfo); - } - return true; -} - -PVMessage_CloseImage::~PVMessage_CloseImage() -{ - delete pWImg; -} - -bool PVMessage_CloseImage::HandleRequest() -{ - LPPVMessageHeader pHdr = (LPPVMessageHeader)pData; - - if (!pHdr) - { - return false; - } - - pHdr->ResultCode = PVCloseImage(pWImg ? pWImg->PVHandle : NULL); - return true; -} - -bool PVMessage_DrawImage::HandleRequest() -{ - LPPVMessageDrawImage pHdr = (LPPVMessageDrawImage)pData; - - if (!pHdr) - { - return false; - } - - HDC hDC = GetDC(NULL), hMemDC; - HBITMAP hOldBmp, hDIB; - BITMAPINFO bi; - LPVOID pvBits = NULL; - DWORD hFMap; - DWORD paintWidth = pHdr->DrawRect.right - pHdr->DrawRect.left; - DWORD paintHeight = pHdr->DrawRect.bottom - pHdr->DrawRect.top; - DWORD frameSize = (((paintWidth * pHdr->BitsPerPixel + 31) >> 3) & ~3) * paintHeight; - - if (!CreateSharedMemoryForBuffer(NULL, frameSize, pHdr->SharedMemoryName, hFMap)) - { - pHdr->ResultCode = PVC_OOM; - return true; - } - pHdr->InternalFileMapHandle = hFMap; - - memset(&bi, 0, sizeof(bi)); - bi.bmiHeader.biSize = sizeof(bi.bmiHeader); - bi.bmiHeader.biWidth = paintWidth; - bi.bmiHeader.biHeight = paintHeight; - bi.bmiHeader.biPlanes = 1; - bi.bmiHeader.biBitCount = (WORD)pHdr->BitsPerPixel; - - hDIB = CreateDIBSection(hDC, &bi, DIB_RGB_COLORS, &pvBits, (HANDLE)hFMap, 0); - - hMemDC = CreateCompatibleDC(hDC); - hOldBmp = (HBITMAP)SelectObject(hMemDC, hDIB); - int X = (int)pHdr->X - pHdr->DrawRect.left; - int Y = (int)pHdr->Y - pHdr->DrawRect.top; - RECT rect; - rect.right = paintWidth; - rect.bottom = paintHeight; - rect.left = rect.top = 0; - pHdr->ResultCode = PVDrawImage(pWImg ? pWImg->PVHandle : NULL, hMemDC, X, Y, &rect); - SelectObject(hMemDC, hOldBmp); - DeleteObject(hDIB); - DeleteDC(hMemDC); - - ReleaseDC(NULL, hDC); - return true; -} - -BOOL WINAPI ProgressProc(int done, void* data) -{ - PVMessageWithProgress* pMsg = (PVMessageWithProgress*)data; - - if (pMsg) - { - return pMsg->HandleProgress(done); - } - return FALSE; -} - -bool PVMessage_ReadImage::HandleRequest() -{ - LPPVMessageReadImage pHdr = (LPPVMessageReadImage)pData; - - if (!pHdr) - { - return false; - } - pHdr->State = PVState_Progressing; - pHdr->ProgressValue = 0; - // FIXME: support drawing on-the-fly - pHdr->ResultCode = PVReadImage2(pWImg ? pWImg->PVHandle : NULL, NULL, NULL, - pHdr->Type == PVMSG_ReadImage ? ProgressProc : NULL, this, pHdr->ImageIndex); - // Image in shared memory no longer needed -> free it - if (pWImg) - pWImg->FreeSharedMemory(); - InterlockedExchange(&pHdr->State, PVState_Finished); - return true; -} - -bool PVMessage_SaveImage::HandleRequest() -{ - LPPVMessageSaveImage pHdr = (LPPVMessageSaveImage)pData; - PVSaveImageInfo SaveImageInfo; - - if (!pHdr) - { - return false; - } - - pHdr->State = PVState_Progressing; - pHdr->ProgressValue = 0; - - memset(&SaveImageInfo, 0, sizeof(SaveImageInfo)); - SaveImageInfo.cbSize = sizeof(SaveImageInfo); - SaveImageInfo.Format = pHdr->Format; - SaveImageInfo.Compression = pHdr->Compression; - SaveImageInfo.Colors = pHdr->Colors; - SaveImageInfo.ColorModel = pHdr->ColorModel; - SaveImageInfo.Flags = pHdr->Flags; - SaveImageInfo.Width = pHdr->Width; - SaveImageInfo.Height = pHdr->Height; - SaveImageInfo.HorDPI = pHdr->HorDPI; - SaveImageInfo.VerDPI = pHdr->VerDPI; - _ASSERTE(sizeof(pHdr->FormatSpecificInfo) == sizeof(SaveImageInfo.Misc)); - memcpy(&SaveImageInfo.Misc, pHdr->FormatSpecificInfo, sizeof(SaveImageInfo.Misc)); - SaveImageInfo.CropLeft = pHdr->CropLeft; - SaveImageInfo.CropTop = pHdr->CropTop; - SaveImageInfo.CropWidth = pHdr->CropWidth; - SaveImageInfo.CropHeight = pHdr->CropHeight; - _ASSERTE(sizeof(pHdr->Transp) == sizeof(SaveImageInfo.Transp)); - memcpy(&SaveImageInfo.Transp, &pHdr->Transp, sizeof(SaveImageInfo.Transp)); - if (SaveImageInfo.CommentSize) - { - SaveImageInfo.CommentSize = pHdr->CommentSize; - SaveImageInfo.Comment = pHdr->Comment; - } - if (SaveImageInfo.Flags & PVSF_USERDEFINED_OUTPUT) - { - SaveImageInfo.WriteFunc = MyAcumulateWriteFunc; - SaveImageInfo.SeekFunc = MyMessageSeekFunc; - SaveImageInfo.ReadFunc = MyMessageReadFunc; - } - - pHdr->ResultCode = PVSaveImage(pWImg ? pWImg->PVHandle : NULL, !(SaveImageInfo.Flags & PVSF_USERDEFINED_OUTPUT) ? pHdr->FileName : NULL, - &SaveImageInfo, pHdr->Type == PVMSG_SaveImage ? ProgressProc : NULL, this, pHdr->ImageIndex); - InterlockedExchange(&pHdr->State, PVState_Finished); - if ((pHdr->ResultCode == PVC_OK) && (SaveImageInfo.Flags & PVSF_USERDEFINED_OUTPUT)) - { - // When the save succeeded and the are user-defined output funcs, we send the back in a shared memory - pHdr->OutFileMapSize = pWImg->Size; - bool ret = CreateSharedMemoryForBuffer(pWImg->pBuffer, pWImg->Size, pHdr->FileName, pHdr->InternalFileMapHandle); - // The shared memory will be freed by the wrapper via the CloseHandle message - pWImg->FreeBuffer(); - } - return true; -} - -bool PVMessage_LoadFromClipboard::HandleRequest() -{ - PVImageInfo ImageInfo; - LPPVMessageLoadFromClipboard pHdr = (LPPVMessageLoadFromClipboard)pData; - - if (!pHdr) - { - return false; - } - pWImg = new PVWrapperImageHandle(); - if (!pWImg) - { - pHdr->ResultCode = PVC_OOM; - return true; // Message handled - } - pHdr->ResultCode = PVLoadFromClipboard(&pWImg->PVHandle, &ImageInfo, sizeof(ImageInfo)); - if (pHdr->ResultCode == PVC_OK) - { - MarshalImageInfo(&ImageInfo, &pHdr->ImageInfo); - pHdr->PVHandle = (DWORD)pWImg; - } - else - { - delete pWImg; - pWImg = NULL; - } - return true; -} - -bool PVMessage_ReadImageSequence::HandleRequest() -{ - LPPVMessageReadImageSequence pHdr = (LPPVMessageReadImageSequence)pData; - - if (!pHdr) - { - return false; - } - - if (!pWImg) - pHdr->ResultCode = PVC_INVALID_HANDLE; - else - { - pHdr->ResultCode = PVReadImageSequence(pWImg->PVHandle, &pWImg->PVImageSequence); - // Image in shared memory no longer needed -> free it - pWImg->FreeSharedMemory(); - } - if (PVC_OK == pHdr->ResultCode) - { - LPPVImageSequence pSeq = pWImg->PVImageSequence; - pHdr->NumberOfFrames = 0; - while (pSeq) - { - pSeq = pSeq->pNext; - pHdr->NumberOfFrames++; - } - } - return true; -} - -bool PVMessage_SetBkHandle::HandleRequest() -{ - LPPVMessageSetBkHandle pHdr = (LPPVMessageSetBkHandle)pData; - - if (!pHdr) - { - return false; - } - // NOTE: The Salamander version of PVW32Cnv.dll expects COLORREF, not HGDIOBJ - pHdr->ResultCode = PVSetBkHandle(pWImg ? pWImg->PVHandle : NULL, (HGDIOBJ)pHdr->BackgroundColor); - return true; -} - -bool PVMessage_GetDLLVersion::HandleRequest() -{ - LPPVMessageGetDLLVersion pHdr = (LPPVMessageGetDLLVersion)pData; - - if (!pHdr) - { - return false; - } - pHdr->DLLVersion = PVGetDLLVersion(); - pHdr->ResultCode = PVC_OK; - return true; -} - -bool PVMessage_SetStretchParameters::HandleRequest() -{ - LPPVMessageSetStretchParameters pHdr = (LPPVMessageSetStretchParameters)pData; - - if (!pHdr) - { - return false; - } - pHdr->ResultCode = PVSetStretchParameters(pWImg ? pWImg->PVHandle : NULL, pHdr->Width, pHdr->Height, pHdr->Mode); - return true; -} - -bool PVMessage_ChangeImage::HandleRequest() -{ - LPPVMessageChangeImage pHdr = (LPPVMessageChangeImage)pData; - - if (!pHdr) - { - return false; - } - pHdr->ResultCode = PVChangeImage(pWImg ? pWImg->PVHandle : NULL, pHdr->Flags); - return true; -} - -bool PVMessage_IsOutCombSupported::HandleRequest() -{ - LPPVMessageIsOutCombSupported pHdr = (LPPVMessageIsOutCombSupported)pData; - - if (!pHdr) - { - return false; - } - pHdr->ResultCode = PVIsOutCombSupported(pHdr->Fmt, pHdr->Compr, pHdr->Colors, pHdr->ColorModel); - return true; -} - -bool PVMessage_CropImage::HandleRequest() -{ - LPPVMessageCropImage pHdr = (LPPVMessageCropImage)pData; - - if (!pHdr) - { - return false; - } - pHdr->ResultCode = PVCropImage(pWImg ? pWImg->PVHandle : NULL, pHdr->Left, pHdr->Top, pHdr->Width, pHdr->Height); - return true; -} - -bool PVMessage_GetRGBAtCursor::HandleRequest() -{ - LPPVMessageGetRGBAtCursor pHdr = (LPPVMessageGetRGBAtCursor)pData; - - if (!pHdr) - { - return false; - } - pHdr->ResultCode = GetRGBAtCursor(pWImg ? pWImg->PVHandle : NULL, pHdr->Colors, pHdr->X, pHdr->Y, &pHdr->RGB, &pHdr->Index) ? PVC_OK : PVC_UNSUP_FILE_TYPE; - return true; -} - -bool PVMessage_CalculateHistogram::HandleRequest() -{ - LPPVMessageCalculateHistogram pHdr = (LPPVMessageCalculateHistogram)pData; - PVImageInfo pvii; - - if (!pHdr) - { - return false; - } - // Provide just the information needed/used, nothing more - memset(&pvii, 0, sizeof(pvii)); - pvii.Colors = pHdr->Colors; - pvii.Width = pHdr->Width; - pvii.Height = pHdr->Height; - pvii.BytesPerLine = pHdr->BytesPerLine; - pHdr->ResultCode = CalculateHistogram(pWImg ? pWImg->PVHandle : NULL, &pvii, pHdr->luminosity, pHdr->red, pHdr->green, pHdr->blue, pHdr->rgb); - return true; -} - -bool PVMessage_CreateThumbnail::HandleRequest() -{ - LPPVMessageCreateThumbnail pHdr = (LPPVMessageCreateThumbnail)pData; - PVSaveImageInfo SaveImageInfo; - PVThumbnailerInfo ti; - - if (!pHdr) - { - return false; - } - - pHdr->State = PVState_Progressing; - pHdr->ProgressValue = 0; - - Thumbnailer.Clear(max(pHdr->ThumbWidth, pHdr->ThumbHeight)); - - if (!Thumbnailer.SetParameters(pHdr->ImageWidth, pHdr->ImageHeight, 0)) - { - pHdr->ResultCode = PVC_OOM; - return true; - } - - // Provide just the information needed/used, nothing more - memset(&SaveImageInfo, 0, sizeof(SaveImageInfo)); - SaveImageInfo.cbSize = sizeof(SaveImageInfo); - SaveImageInfo.Format = PVF_RAW; - SaveImageInfo.Compression = PVCS_NO_COMPRESSION; - SaveImageInfo.Colors = PV_COLOR_TC32; - SaveImageInfo.ColorModel = PVCM_RGB; - SaveImageInfo.Flags = pHdr->Flags | PVSF_USERDEFINED_OUTPUT; - SaveImageInfo.WriteFunc = MyThumbWriteFunc; - SaveImageInfo.SeekFunc = MyDummySeekFunc; - ti.pThumbnailer = &Thumbnailer; - ti.BytesPerLine = pHdr->ImageWidth * 4; - ti.pMsg = this; - pHdr->ResultCode = PVSaveImage(pWImg ? pWImg->PVHandle : NULL, NULL, &SaveImageInfo, ThumbProgressProc, &ti, pHdr->ImageIndex); - if ((pHdr->ResultCode == PVC_OK) || ((pHdr->ResultCode == PVC_WRITING_ERROR) && (pHdr->State != PVState_Cancelled))) - { - pHdr->ResultCode = PVC_OK; - Thumbnailer.HandleIncompleteImages(); - memcpy(pHdr->Buffer, Thumbnailer.GetThumbnailBuffer(), pHdr->ThumbWidth * pHdr->ThumbHeight * 4); - } - InterlockedExchange(&pHdr->State, PVState_Finished); - return true; -} - -bool PVMessage_SimplifyImageSequence::HandleRequest() -{ - LPPVMessageSimplifyImageSequence pHdr = (LPPVMessageSimplifyImageSequence)pData; - LPPVImageSequence pSeq = pWImg->PVImageSequence; - HDC hMemDC, hMemDC2, hTempDC = GetDC(NULL); - HBITMAP hMemBmp, hOldBmp, hTargetBmp; - DWORD PutType; - RECT rct, rctFull; - HBRUSH hBr; - int dispMethod = PVDM_UNDEFINED; - BITMAPINFO bi; - LPVOID pvBits = NULL; - int nFrames, nFrame; - int nFrameSize = ((pHdr->ScreenWidth * 3 + 3) & ~3) * pHdr->ScreenHeight; - DWORD hFMap; - - // Get the number of frames and create shared memory object accomodating all pixels of all frames - nFrames = 0; - while (pSeq) - { - pSeq = pSeq->pNext; - nFrames++; - } - - if (!CreateSharedMemoryForBuffer(NULL, nFrameSize * nFrames, pHdr->SharedMemoryName, hFMap)) - { - pHdr->ResultCode = PVC_OOM; - return true; - } - pHdr->InternalFileMapHandle = hFMap; - - memset(&bi, 0, sizeof(bi)); - bi.bmiHeader.biSize = sizeof(bi.bmiHeader); - bi.bmiHeader.biWidth = pHdr->ScreenWidth; - bi.bmiHeader.biHeight = pHdr->ScreenHeight; - bi.bmiHeader.biPlanes = 1; - pHdr->BitsPerPixel = bi.bmiHeader.biBitCount = 24; - - hMemBmp = CreateCompatibleBitmap(hTempDC, pHdr->ScreenWidth, pHdr->ScreenHeight); - hMemDC = CreateCompatibleDC(hTempDC); - SelectObject(hMemDC, hMemBmp); - - rct.left = rct.top = 0; - rct.right = pHdr->ScreenWidth; - rct.bottom = pHdr->ScreenHeight; - rctFull = rct; - - // hBr = CreateSolidBrush(pvii.FSI->GIF.BgColor); - hBr = CreateSolidBrush(pHdr->BackgroundColor); - FillRect(hMemDC, &rct, hBr); // image area - - hMemDC2 = CreateCompatibleDC(hMemDC); - pSeq = pWImg->PVImageSequence; - nFrame = 0; - while (pSeq) - { - // The GIF89a spec doesn't say anything - // GIF Construction Set help say dispMethod should be applied AFTER - // MSIE, NN, Irfan do apply it AFTER - // XnView (and PictView until 2004.05.24) applies it BEFORE - // 2009.12.08: odrazka.gif: fill only the area of the subimage - if ((dispMethod == PVDM_BACKGROUND) || (dispMethod == PVDM_PREVIOUS) /*|| (dispMethod == PVDM_UNDEFINED)*/) - { - FillRect(hMemDC, &rct, hBr); - } - dispMethod = pSeq->DisposalMethod; - rct = pSeq->Rect; - rct.right -= rct.left; - rct.bottom -= rct.top; - if (pSeq->TransparentHandle) - { - hOldBmp = (HBITMAP)SelectObject(hMemDC2, pSeq->TransparentHandle); - BitBlt(hMemDC, rct.left, rct.top, rct.right, rct.bottom, hMemDC2, 0, 0, SRCAND); - SelectObject(hMemDC2, hOldBmp); - PutType = SRCINVERT; - DeleteObject(pSeq->TransparentHandle); - pSeq->TransparentHandle = NULL; - } - else - { - PutType = SRCCOPY; - } - hOldBmp = (HBITMAP)SelectObject(hMemDC2, pSeq->ImgHandle); - BitBlt(hMemDC, rct.left, rct.top, rct.right, rct.bottom, hMemDC2, 0, 0, PutType); - - // hTargetBmp = CreateCompatibleBitmap(hTempDC, pHdr->ScreenWidth, pHdr->ScreenHeight); - hTargetBmp = CreateDIBSection(hTempDC, &bi, DIB_RGB_COLORS, &pvBits, (HANDLE)hFMap, nFrame * nFrameSize); - - SelectObject(hMemDC2, hTargetBmp); - BitBlt(hMemDC2, 0, 0, pHdr->ScreenWidth, pHdr->ScreenHeight, hMemDC, 0, 0, SRCCOPY); - rct = pSeq->Rect; - pSeq->Rect.left = pSeq->Rect.top = 0; - pSeq->Rect.right = pHdr->ScreenWidth; - pSeq->Rect.bottom = pHdr->ScreenHeight; - pHdr->Delay[nFrame] = pSeq->Delay; - /* hTargetBmp = CreateCompatibleBitmap(dc, rct.right, rct.bottom); - SelectObject(hMemDC2, hTargetBmp); - BitBlt(hMemDC2, 0, 0, rct.right, rct.bottom, hMemDC, rct.left, rct.top, SRCCOPY);*/ - SelectObject(hMemDC2, hOldBmp); - // Delete the DIB Section, we no longer need it, only the pixels in the hFMap object - DeleteObject(hTargetBmp); - - pSeq = pSeq->pNext; - nFrame++; - } - DeleteDC(hMemDC2); - DeleteObject(hBr); - DeleteDC(hMemDC); - DeleteObject(hMemBmp); - - ReleaseDC(NULL, hTempDC); - pHdr->ResultCode = PVC_OK; - return true; -} - -// ====================== PVWrapperImageHandle ====================== -PVWrapperImageHandle::PVWrapperImageHandle() -{ - PVHandle = NULL; - PVImageSequence = NULL; - hFileMap = NULL; - pBuffer = pCur = NULL; - Size = 0; -} - -PVWrapperImageHandle::PVWrapperImageHandle(LPBYTE aBuffer, DWORD aSize) -{ - PVHandle = NULL; - PVImageSequence = NULL; - hFileMap = NULL; - pBuffer = pCur = aBuffer; - Size = aSize; -} - -PVWrapperImageHandle::~PVWrapperImageHandle() -{ - FreeSharedMemory(); - // pBuffer is not NULL when hFileMap was NULL, can occur after wrong use of PVMessage_SaveImage - if (pBuffer) - { - free(pBuffer); - } -} - -void PVWrapperImageHandle::FreeSharedMemory() -{ - if (hFileMap) - { - UnmapViewOfFile(pBuffer); - CloseHandle(hFileMap); - hFileMap = NULL; - pBuffer = pCur = NULL; - } -} - -void PVWrapperImageHandle::FreeBuffer() -{ - if (pBuffer) - { - free(pBuffer); - pBuffer = pCur = NULL; - Size = 0; - } -} - -// Creates a file mapping for the given buffer -bool CreateSharedMemoryForBuffer(LPBYTE pInBuffer, DWORD dataSize, char* fileMapName, DWORD& outFileMap) -{ - HANDLE hFileMap; - static int id = 0; - - sprintf(fileMapName, "%d_env%d", MainThreadID, id++); - hFileMap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, - 0, dataSize, fileMapName); - if (!hFileMap) - { - return false; - } - if (pInBuffer) - { - LPBYTE pBuffer = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, dataSize); - - if (!pBuffer) - { - CloseHandle(hFileMap); - return false; - } - memcpy(pBuffer, pInBuffer, dataSize); - UnmapViewOfFile(pBuffer); - } - outFileMap = (DWORD)hFileMap; - - return true; -} diff --git a/src/plugins/pictview/PVMessageWrapper.cpp b/src/plugins/pictview/PVMessageWrapper.cpp deleted file mode 100644 index 1a9d41d5d..000000000 --- a/src/plugins/pictview/PVMessageWrapper.cpp +++ /dev/null @@ -1,934 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Open Salamander Authors -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "precomp.h" - -#ifdef PICTVIEW_DLL_IN_SEPARATE_PROCESS - -#include "pictview.h" -#include "PVMessage.h" - -// PVWrapperImageHandle wraps LPPVHandle for use by the Wrapper -class PVWrapperImageHandle -{ -public: - PVWrapperImageHandle(DWORD Img); - ~PVWrapperImageHandle(); - - bool CreateSharedMemoryForHBitmap(HBITMAP hBitmap, char* pSharedMemoryName, int maxSharedMemoryName, DWORD* pDataSize); - bool CreateSharedMemoryForMemoryBlock(LPBYTE pBuffer, DWORD dataSize, char* pSharedMemoryName, int maxSharedMemoryName); - void FreeSharedMemory(); - - HANDLE hEnvelopeProcess; - DWORD hPVHandle; // This is LPPVHandle passed from the Envelope to the Wrapper - HANDLE hFileMap; // Memory used to pass data via PVOF_ATTACH_TO_HANDLE - DWORD iFileMapID; // ID used to determine file mapping name - char* Comment; - LPPVFormatSpecificInfo FSI; - LPPVImageSequence PVImageSequence; // Allocated by the wrapper - DWORD NumberOfFrames; - HANDLE hImageSequenceFileMap; -}; - -typedef PVWrapperImageHandle* LPPVWrapperImageHandle; - -PVMessage::PVMessage(ePVMSG type, size_t dataSize, LPPVHandle pvHandle) - : pData(NULL), hFileMap(NULL), pWImg(NULL) -{ - Init(type, dataSize, pvHandle); -} - -bool PVMessage::Init(ePVMSG type, size_t dataSize, LPPVHandle pvHandle) -{ - TCHAR fileMapName[48]; - LPPVMessageHeader pHdr; - - pWImg = (LPPVWrapperImageHandle)pvHandle; - iID = PVWrapper.MessageID++; - _sntprintf(fileMapName, SizeOf(fileMapName), _T("%s_%d"), PVWrapper.MutexName, iID); - hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, - 0, (DWORD)(dataSize + sizeof(PVMessageHeader)), fileMapName); - if (!hFileMap) - { - return false; - } - pData = (LPPVMessageHeader)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, dataSize + sizeof(PVMessageHeader)); - if (!pData) - { - return false; - } - memset(pData, 0, dataSize + sizeof(PVMessageHeader)); - pHdr = (LPPVMessageHeader)pData; - pHdr->cbSize = (DWORD)(dataSize + sizeof(PVMessageHeader)); - pHdr->Type = type; - pHdr->PVHandle = pWImg ? pWImg->hPVHandle : NULL; - pHdr->ResultCode = PVC_ENVELOPE_NOT_LOADED; - return true; -} - -bool PVMessage::IsInited() -{ - if (!hFileMap || !pData) - return false; - if (!LoadEnvelope()) - return false; - return true; -} - -bool PVMessage::Exec() -{ - TCHAR eventName[32]; - HANDLE hEvent; - - _sntprintf(eventName, SizeOf(eventName), _T("%s_ev%x"), PVWrapper.MutexName, iID); - hEvent = CreateEvent(NULL, FALSE, FALSE, eventName); - if (!hEvent) - { - return false; - } - PostThreadMessage(PVWrapper.pi.dwThreadId, WM_USER, pData->cbSize, iID); - HANDLE handles[2] = {hEvent, PVWrapper.pi.hProcess}; - DWORD ret = WaitForMultipleObjects(2, handles, FALSE, INFINITE); - CloseHandle(hEvent); - if (ret == 1) - { - // The remote process died - return false; - } - return ret == 0; -} - -LPPVHandle PVMessage::GetPVHandle() -{ - return pWImg; -} - -bool PVMessage::UnmarshalImageInfo(LPPVImageInfoStruct pInInfo, LPPVImageInfo pOutInfo) -{ - pOutInfo->cbSize = sizeof(PVImageInfo); - pOutInfo->Width = pInInfo->Width; - pOutInfo->Height = pInInfo->Height; - pOutInfo->BytesPerLine = pInInfo->BytesPerLine; - pOutInfo->FileSize = pInInfo->FileSize; - pOutInfo->Colors = pInInfo->Colors; - pOutInfo->Format = pInInfo->Format; - pOutInfo->Flags = pInInfo->Flags; - pOutInfo->ColorModel = pInInfo->ColorModel; - pOutInfo->NumOfImages = pInInfo->NumOfImages; - pOutInfo->CurrentImage = pInInfo->CurrentImage; - strcpy(pOutInfo->Info1, pInInfo->Info1); - strcpy(pOutInfo->Info2, pInInfo->Info2); - strcpy(pOutInfo->Info3, pInInfo->Info3); - pOutInfo->StretchedWidth = pInInfo->StretchedWidth; - pOutInfo->StretchedHeight = pInInfo->StretchedHeight; - pOutInfo->StretchMode = pInInfo->StretchMode; - pOutInfo->HorDPI = pInInfo->HorDPI; - pOutInfo->VerDPI = pInInfo->VerDPI; - pOutInfo->FSI = NULL; - if (pInInfo->bFSI) - { - pOutInfo->FSI = pWImg->FSI = (LPPVFormatSpecificInfo)malloc(sizeof(PVFormatSpecificInfo)); - if (pOutInfo->FSI) - { - memcpy(pOutInfo->FSI, &pInInfo->FSI, sizeof(PVFormatSpecificInfo)); - } - } - pOutInfo->Compression = pInInfo->Compression; - pOutInfo->TotalBitDepth = pInInfo->TotalBitDepth; - pOutInfo->CommentSize = 0; - pOutInfo->Comment = NULL; - if (pInInfo->CommentSize) - { - pOutInfo->Comment = pWImg->Comment = (char*)malloc(pInInfo->CommentSize); - if (pOutInfo->Comment) - { - pOutInfo->CommentSize = pInInfo->CommentSize; - memcpy(pOutInfo->Comment, pInInfo->Comment, pInInfo->CommentSize); - } - } - return true; -} - -// ========================= PVMessageWithProgress ========================= -PVMessageWithProgress::PVMessageWithProgress(ePVMSG type, size_t dataSize, LPPVHandle pvHandle) : PVMessage(type, dataSize, pvHandle) -{ - LPPVMessageWithProgressHeader pHdr = (LPPVMessageWithProgressHeader)pData; - - if (!pHdr) - { - return; - } - pHdr->State = PVState_Started; - pHdr->ProgressValue = 0; -} - -bool PVMessageWithProgress::Exec(TProgressProc Progress, void* AppSpecific) -{ - if (!Progress) - { - // No progress function specified -> process quickly - return PVMessage::Exec(); - } - TCHAR eventName[32]; - HANDLE hEvt; - - _sntprintf(eventName, SizeOf(eventName), _T("%s_ev%x"), PVWrapper.MutexName, iID); - hEvt = CreateEvent(NULL, FALSE, FALSE, eventName); - if (!hEvt) - { - return false; - } - PostThreadMessage(PVWrapper.pi.dwThreadId, WM_USER, pData->cbSize, iID); - HANDLE handles[2] = {hEvt, PVWrapper.pi.hProcess}; - LPPVMessageWithProgressHeader pHdr = (LPPVMessageWithProgressHeader)pData; - DWORD ret = -1; - // Receive progress messages as long as State is not PVState_Finished - for (;;) - { - ret = WaitForMultipleObjects(2, handles, FALSE, INFINITE); - if (ret == 0) - { - if (pHdr->State == PVState_Progressing) - { - // Forward progress update and check for cancellatiom - if (Progress(pHdr->ProgressValue, AppSpecific)) - { - LONG state = InterlockedExchange(&pHdr->State, PVState_Cancelled); - if (state == PVState_Finished) - { - // Envelope finished in the meantime -> restore the Finished state to avoid infinite loop - pHdr->State = PVState_Finished; - } - } - continue; - } - if (pHdr->State == PVState_Cancelled) - { - continue; - } - } - break; - } - CloseHandle(hEvt); - if (ret == 1) - { - // The remote process died - return false; - } - return ret == 0; -} - -// ========================= PVMessage_GetErrorText ========================= -PVMessage_GetErrorText::PVMessage_GetErrorText(DWORD ErrorCode) - : PVMessage(PVMSG_GetErrorText, sizeof(PVMessageGetErrorText)) -{ - LPPVMessageGetErrorText pHdr = (LPPVMessageGetErrorText)pData; - - if (!pHdr) - { - return; - } - pHdr->ErrorCode = ErrorCode; -} - -const char* PVMessage_GetErrorText::GetErrorText() -{ - LPPVMessageGetErrorText pHdr = (LPPVMessageGetErrorText)pData; - - if (!pHdr) - { - return ""; - } - return pHdr->ErrorText; -} - -// ========================= PVMessage_OpenImageEx ========================= -PVMessage_OpenImageEx::PVMessage_OpenImageEx(LPPVOpenImageExInfo pOpenExInfo) - : PVMessage() -{ - LPPVMessageOpenImageEx pHdr; - - pWImg = new PVWrapperImageHandle(NULL); - if (!pWImg) - { - return; - } - - Init(PVMSG_OpenImageEx, sizeof(PVMessageOpenImageEx), pWImg); - pHdr = (LPPVMessageOpenImageEx)pData; - if (!pHdr) - { - return; - } - - pHdr->Flags = pOpenExInfo->Flags; - if (pOpenExInfo->Flags & PVOF_ATTACH_TO_HANDLE) - { - pWImg->CreateSharedMemoryForHBitmap((HBITMAP)pOpenExInfo->Handle, pHdr->FileName, sizeof(pHdr->FileName), &pHdr->DataSize); - } - else if (pOpenExInfo->Flags & PVOF_USERDEFINED_INPUT) - { - psReadMemFuncData rmfd = (psReadMemFuncData)pOpenExInfo->Handle; - _ASSERTE(rmfd && !rmfd->Pos); - pHdr->DataSize = rmfd->Size; - pWImg->CreateSharedMemoryForMemoryBlock((LPBYTE)rmfd->Buffer, rmfd->Size, pHdr->FileName, sizeof(pHdr->FileName)); - } - else - { - strcpy(pHdr->FileName, pOpenExInfo->FileName); - } -} - -bool PVMessage_OpenImageEx::IsInited() -{ - // We do not support custom Read and Seek functions for now - LPPVMessageOpenImageEx pHdr = (LPPVMessageOpenImageEx)pData; - - if (!pData || (!*pHdr->FileName && !(pHdr->Flags & PVOF_ATTACH_TO_HANDLE))) - { - return false; - } - return PVMessage::IsInited(); -} - -bool PVMessage_OpenImageEx::Exec(LPPVImageInfo pImgInfo) -{ - if (!PVMessage::Exec()) - { - return false; - } - if (GetResultCode() == PVC_OK) - { - pWImg->hPVHandle = (((LPPVMessageHeader)pData)->PVHandle); - return UnmarshalImageInfo(&((LPPVMessageOpenImageEx)pData)->ImageInfo, pImgInfo); - } - return true; -} - -// ========================= PVMessage_GetImageInfo ========================= -PVMessage_GetImageInfo::PVMessage_GetImageInfo(LPPVHandle pvHandle, int ImageIndex) - : PVMessage(PVMSG_GetImageInfo, sizeof(PVMessageGetImageInfo), pvHandle) -{ - LPPVMessageGetImageInfo pHdr = (LPPVMessageGetImageInfo)pData; - - if (!pHdr) - { - return; - } - pHdr->ImageIndex = ImageIndex; -} - -bool PVMessage_GetImageInfo::Exec(LPPVImageInfo pImgInfo) -{ - if (!PVMessage::Exec()) - { - return false; - } - return UnmarshalImageInfo(&((LPPVMessageGetImageInfo)pData)->ImageInfo, pImgInfo); -} - -// ========================= PVMessage_ReadImage ========================= -PVMessage_ReadImage::PVMessage_ReadImage(LPPVHandle pvHandle, int ImageIndex, bool bProgress) - : PVMessageWithProgress(bProgress ? PVMSG_ReadImage : PVMSG_ReadImageWithoutProgress, sizeof(PVMessageReadImage), pvHandle) -{ - LPPVMessageReadImage pHdr = (LPPVMessageReadImage)pData; - - if (!pHdr) - { - return; - } - pHdr->ImageIndex = ImageIndex; -} - -bool PVMessage_ReadImage::Exec(HDC PaintDC, RECT* pDRect, TProgressProc Progress, void* AppSpecific) -{ - // FIXME: Paint during loading - return PVMessageWithProgress::Exec(Progress, AppSpecific); -} - -// ========================= PVMessage_CloseImage ========================= -PVMessage_CloseImage::PVMessage_CloseImage(LPPVHandle pvHandle) - : PVMessage(PVMSG_CloseImage, sizeof(PVMessage), pvHandle) -{ -} - -PVMessage_CloseImage::~PVMessage_CloseImage() -{ - delete pWImg; -} - -// ========================= PVMessage_DrawImage ========================= -PVMessage_DrawImage::PVMessage_DrawImage(LPPVHandle pvHandle, HDC PaintDC, int X, int Y, LPRECT pDrawRect) - : PVMessage(PVMSG_DrawImage, sizeof(PVMessage), pvHandle) -{ - LPPVMessageDrawImage pHdr = (LPPVMessageDrawImage)pData; - - if (!pHdr) - { - return; - } - pHdr->X = X; - pHdr->Y = Y; - pHdr->DrawRect = *pDrawRect; - if (PaintDC) - { - pHdr->BitsPerPixel = GetDeviceCaps(PaintDC, BITSPIXEL); - if (pHdr->BitsPerPixel < 15) - { - // 8-bit displays no longer supported... - pHdr->BitsPerPixel = 24; - } - } - else - { - pHdr->BitsPerPixel = 24; - } -} - -bool PVMessage_DrawImage::Exec(HDC PaintDC) -{ - if (!PVMessage::Exec()) - { - return false; - } - if (GetResultCode() == PVC_OK) - { - LPPVMessageDrawImage pHdr = (LPPVMessageDrawImage)pData; - DWORD paintWidth = pHdr->DrawRect.right - pHdr->DrawRect.left; - DWORD paintHeight = pHdr->DrawRect.bottom - pHdr->DrawRect.top; - DWORD frameSize = (((paintWidth * pHdr->BitsPerPixel + 31) >> 3) & ~3) * paintHeight; - HANDLE hFMap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, frameSize, pHdr->SharedMemoryName); - - // The envelope no longer needs to keep the shared memory object -> free it - PVMessage_CloseHandle(pHdr->InternalFileMapHandle).Exec(); - - if (!hFMap) - { - pHdr->ResultCode = PVC_OOM; - return false; - } - - BITMAPINFO bi; - LPVOID pvBits = NULL; - HBITMAP hDIB; - - memset(&bi, 0, sizeof(bi)); - bi.bmiHeader.biSize = sizeof(bi.bmiHeader); - bi.bmiHeader.biWidth = paintWidth; - bi.bmiHeader.biHeight = paintHeight; - bi.bmiHeader.biPlanes = 1; - bi.bmiHeader.biBitCount = (WORD)pHdr->BitsPerPixel; - - hDIB = CreateDIBSection(PaintDC, &bi, DIB_RGB_COLORS, &pvBits, hFMap, 0); - if (hDIB) - { - HDC hMemDC = CreateCompatibleDC(PaintDC); - HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, hDIB); - - BitBlt(PaintDC, pHdr->DrawRect.left, pHdr->DrawRect.top, - paintWidth, paintHeight, hMemDC, 0, 0, SRCCOPY); - SelectObject(hMemDC, hOldBmp); - DeleteDC(hMemDC); - DeleteObject(hDIB); - } - CloseHandle(hFMap); - } - return true; -} - -// ========================= PVMessage_SaveImage ========================= -PVMessage_SaveImage::PVMessage_SaveImage(LPPVHandle pvHandle, const char* FileName, LPPVSaveImageInfo pSii, int ImageIndex, bool bProgress) - : PVMessageWithProgress(bProgress ? PVMSG_SaveImage : PVMSG_SaveImageWithoutProgress, sizeof(PVMessageSaveImage), pvHandle) -{ - LPPVMessageSaveImage pHdr = (LPPVMessageSaveImage)pData; - - if (!pHdr) - { - return; - } - - pHdr->cbSize = pSii->cbSize; - pHdr->Format = pSii->Format; - pHdr->Compression = pSii->Compression; - pHdr->Colors = pSii->Colors; - pHdr->ColorModel = pSii->ColorModel; - pHdr->Flags = pSii->Flags; - pHdr->Width = pSii->Width; - pHdr->Height = pSii->Height; - pHdr->HorDPI = pSii->HorDPI; - pHdr->VerDPI = pSii->VerDPI; - _ASSERTE(sizeof(pHdr->FormatSpecificInfo) == sizeof(pSii->Misc)); - memcpy(pHdr->FormatSpecificInfo, &pSii->Misc, sizeof(pSii->Misc)); - pHdr->CropLeft = pSii->CropLeft; - pHdr->CropTop = pSii->CropTop; - pHdr->CropWidth = pSii->CropWidth; - pHdr->CropHeight = pSii->CropHeight; - _ASSERTE(sizeof(pHdr->Transp) == sizeof(pSii->Transp)); - memcpy(&pHdr->Transp, &pSii->Transp, sizeof(pSii->Transp)); - pHdr->ImageIndex = ImageIndex; - strcpy(pHdr->FileName, FileName ? FileName : ""); - _ASSERTE(pHdr->CommentSize <= sizeof(pHdr->Comment)); - pHdr->CommentSize = min(pSii->CommentSize, sizeof(pHdr->Comment)); - memcpy(pHdr->Comment, pSii->Comment, pHdr->CommentSize); -} - -// ========================= PVMessage_LoadFromClipboard ========================= -PVMessage_LoadFromClipboard::PVMessage_LoadFromClipboard() - : PVMessage(PVMSG_LoadFromClipboard, sizeof(PVMessageLoadFromClipboard)) -{ - pWImg = new PVWrapperImageHandle(NULL); -} - -bool PVMessage_LoadFromClipboard::Exec(LPPVImageInfo pImgInfo) -{ - if (!PVMessage::Exec()) - { - return false; - } - pWImg->hPVHandle = (((LPPVMessageHeader)pData)->PVHandle); - return UnmarshalImageInfo(&((LPPVMessageLoadFromClipboard)pData)->ImageInfo, pImgInfo); -} - -// ========================= PVMessage_ReadImageSequence ========================= -PVMessage_ReadImageSequence::PVMessage_ReadImageSequence(LPPVHandle pvHandle) - : PVMessage(PVMSG_ReadImageSequence, sizeof(PVMessageReadImageSequence), pvHandle) -{ -} - -bool PVMessage_ReadImageSequence::Exec() -{ - if (!PVMessage::Exec()) - { - return false; - } - LPPVMessageReadImageSequence pHdr = (LPPVMessageReadImageSequence)pData; - if (PVC_OK == GetResultCode()) - { - pWImg->NumberOfFrames = pHdr->NumberOfFrames; - } - return true; -} - -// ========================= PVMessage_SetBkHandle ========================= -PVMessage_SetBkHandle::PVMessage_SetBkHandle(LPPVHandle pvHandle, COLORREF bkColor) - : PVMessage(PVMSG_SetBkHandle, sizeof(PVMessageSetBkHandle), pvHandle) -{ - LPPVMessageSetBkHandle pHdr = (LPPVMessageSetBkHandle)pData; - - if (!pHdr) - { - return; - } - - pHdr->BackgroundColor = bkColor; -} - -// ========================= PVMessage_GetDLLVersion ========================= -PVMessage_GetDLLVersion::PVMessage_GetDLLVersion() - : PVMessage(PVMSG_GetDLLVersion, sizeof(PVMessageGetDLLVersion)) -{ -} - -bool PVMessage_GetDLLVersion::Exec(DWORD& Version) -{ - if (!PVMessage::Exec()) - { - return false; - } - Version = ((LPPVMessageGetDLLVersion)pData)->DLLVersion; - - return true; -} - -// ========================= PVMessage_ChangeImage ========================= -PVMessage_SetStretchParameters::PVMessage_SetStretchParameters(LPPVHandle pvHandle, DWORD Width, DWORD Height, DWORD Mode) - : PVMessage(PVMSG_SetStretchParameters, sizeof(PVMessageSetStretchParameters), pvHandle) -{ - LPPVMessageSetStretchParameters pHdr = (LPPVMessageSetStretchParameters)pData; - - if (!pHdr) - { - return; - } - - pHdr->Width = Width; - pHdr->Height = Height; - pHdr->Mode = Mode; -} - -// ========================= PVMessage_ChangeImage ========================= -PVMessage_ChangeImage::PVMessage_ChangeImage(LPPVHandle pvHandle, DWORD Flags) - : PVMessage(PVMSG_ChangeImage, sizeof(PVMessageChangeImage), pvHandle) -{ - LPPVMessageChangeImage pHdr = (LPPVMessageChangeImage)pData; - - if (!pHdr) - { - return; - } - pHdr->Flags = Flags; -} - -// ========================= PVMessage_IsOutCombSupported ========================= -PVMessage_IsOutCombSupported::PVMessage_IsOutCombSupported(int Fmt, int Compr, int Colors, int ColorModel) - : PVMessage(PVMSG_IsOutCombSupported, sizeof(PVMessageIsOutCombSupported)) -{ - LPPVMessageIsOutCombSupported pHdr = (LPPVMessageIsOutCombSupported)pData; - - if (!pHdr) - { - return; - } - pHdr->Fmt = Fmt; - pHdr->Compr = Compr; - pHdr->Colors = Colors; - pHdr->ColorModel = ColorModel; -} - -// ========================= PVMessage_CropImage ========================= -PVMessage_CropImage::PVMessage_CropImage(LPPVHandle pvHandle, int Left, int Top, int Width, int Height) - : PVMessage(PVMSG_CropImage, sizeof(PVMessageCropImage), pvHandle) -{ - LPPVMessageCropImage pHdr = (LPPVMessageCropImage)pData; - - if (!pHdr) - { - return; - } - pHdr->Left = Left; - pHdr->Top = Top; - pHdr->Width = Width; - pHdr->Height = Height; -} - -// ========================= PVMessage_GetRGBAtCursor ========================= -PVMessage_GetRGBAtCursor::PVMessage_GetRGBAtCursor(LPPVHandle pvHandle, DWORD Colors, int x, int y) - : PVMessage(PVMSG_GetRGBAtCursor, sizeof(PVMessageGetRGBAtCursor), pvHandle) -{ - LPPVMessageGetRGBAtCursor pHdr = (LPPVMessageGetRGBAtCursor)pData; - - if (!pHdr) - { - return; - } - pHdr->Colors = Colors; - pHdr->X = x; - pHdr->Y = y; -} - -RGBQUAD* PVMessage_GetRGBAtCursor::GetRGB() -{ - return &((LPPVMessageGetRGBAtCursor)pData)->RGB; -} - -int PVMessage_GetRGBAtCursor::GetIndex() -{ - return ((LPPVMessageGetRGBAtCursor)pData)->Index; -} - -// ========================= PVMessage_CalculateHistogram ========================= -PVMessage_CalculateHistogram::PVMessage_CalculateHistogram(LPPVHandle pvHandle, const PVImageInfo* pvii) - : PVMessage(PVMSG_CalculateHistogram, sizeof(PVMessageCalculateHistogram), pvHandle) -{ - LPPVMessageCalculateHistogram pHdr = (LPPVMessageCalculateHistogram)pData; - - if (!pHdr) - { - return; - } - pHdr->Colors = pvii->Colors; - pHdr->Width = pvii->Width; - pHdr->Height = pvii->Height; - pHdr->BytesPerLine = pvii->BytesPerLine; -} - -void PVMessage_CalculateHistogram::GetResults(LPDWORD luminosity, LPDWORD red, LPDWORD green, LPDWORD blue, LPDWORD rgb) -{ - LPPVMessageCalculateHistogram pHdr = (LPPVMessageCalculateHistogram)pData; - - memcpy(luminosity, pHdr->luminosity, sizeof(pHdr->luminosity)); - memcpy(red, pHdr->red, sizeof(pHdr->red)); - memcpy(green, pHdr->green, sizeof(pHdr->green)); - memcpy(blue, pHdr->blue, sizeof(pHdr->blue)); - memcpy(rgb, pHdr->rgb, sizeof(pHdr->rgb)); -} - -// ========================= PVMessage_CreateThumbnail ========================= -PVMessage_CreateThumbnail::PVMessage_CreateThumbnail(LPPVHandle pvHandle, LPPVSaveImageInfo pSii, - int ImageIndex, DWORD imgWidth, DWORD imgHeight, DWORD thumbWidth, DWORD thumbHeight) - : PVMessageWithProgress(PVMSG_CreateThumbnail, sizeof(PVMessageCreateThumbnail) + thumbWidth * thumbHeight * 4, pvHandle) -{ - LPPVMessageCreateThumbnail pHdr = (LPPVMessageCreateThumbnail)pData; - - if (!pHdr) - { - return; - } - pHdr->Flags = pSii->Flags; - pHdr->ImageWidth = imgWidth; - pHdr->ImageHeight = imgHeight; - pHdr->ThumbWidth = thumbWidth; - pHdr->ThumbHeight = thumbHeight; - pHdr->ImageIndex = ImageIndex; -} - -LPBYTE PVMessage_CreateThumbnail::GetPixelData() -{ - return ((LPPVMessageCreateThumbnail)pData)->Buffer; -} - -// ========================= PVMessage_SimplifyImageSequence ========================= -PVMessage_SimplifyImageSequence::PVMessage_SimplifyImageSequence(LPPVHandle pvHandle, DWORD ScreenWidth, DWORD ScreenHeight, const COLORREF& bgColor) - : PVMessage(PVMSG_SimplifyImageSequence, sizeof(PVMessageSimplifyImageSequence) + sizeof(DWORD) * ((LPPVWrapperImageHandle)pvHandle)->NumberOfFrames, pvHandle) -{ - LPPVMessageSimplifyImageSequence pHdr = (LPPVMessageSimplifyImageSequence)pData; - - if (!pHdr) - { - return; - } - pHdr->ScreenWidth = ScreenWidth; - pHdr->ScreenHeight = ScreenHeight; - pHdr->BackgroundColor = bgColor; -} - -bool PVMessage_SimplifyImageSequence::Exec() -{ - if (!PVMessage::Exec()) - { - return false; - } - if (GetResultCode() == PVC_OK) - { - LPPVMessageSimplifyImageSequence pHdr = (LPPVMessageSimplifyImageSequence)pData; - DWORD frameSize = (((pHdr->ScreenWidth * pHdr->BitsPerPixel + 31) >> 3) & ~3) * pHdr->ScreenHeight; - - pWImg->hImageSequenceFileMap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, pWImg->NumberOfFrames * frameSize, pHdr->SharedMemoryName); - // The envelope no longer needs to keep the shared memory object -> free it - PVMessage_CloseHandle(pHdr->InternalFileMapHandle).Exec(); - - if (!pWImg->hImageSequenceFileMap) - { - return false; - } - - LPPVImageSequence* pCurFrame = &pWImg->PVImageSequence; - BITMAPINFO bi; - LPVOID pvBits = NULL; - - memset(&bi, 0, sizeof(bi)); - bi.bmiHeader.biSize = sizeof(bi.bmiHeader); - bi.bmiHeader.biWidth = pHdr->ScreenWidth; - bi.bmiHeader.biHeight = pHdr->ScreenHeight; - bi.bmiHeader.biPlanes = 1; - bi.bmiHeader.biBitCount = (WORD)pHdr->BitsPerPixel; - - for (DWORD nFrame = 0; nFrame < pWImg->NumberOfFrames; nFrame++) - { - LPPVImageSequence pSeq = (LPPVImageSequence)calloc(1, sizeof(PVImageSequence)); - if (pSeq) - { - pSeq->Rect.right = pHdr->ScreenWidth; - pSeq->Rect.bottom = pHdr->ScreenHeight; - pSeq->Delay = pHdr->Delay[nFrame]; - pSeq->ImgHandle = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, &pvBits, - pWImg->hImageSequenceFileMap, nFrame * frameSize); - *pCurFrame = pSeq; - pCurFrame = &(*pCurFrame)->pNext; - } - } - } - return true; -} - -LPPVImageSequence PVMessage_SimplifyImageSequence::GetImageSequence() -{ - if (pWImg) - { - return pWImg->PVImageSequence; - } - return NULL; -} - -// ========================= PVMessage_CloseHandle ========================= -PVMessage_CloseHandle::PVMessage_CloseHandle(DWORD Handle) : PVMessage() -{ - HandleToBeClosed = Handle; -} - -bool PVMessage_CloseHandle::Exec() -{ - PostThreadMessage(PVWrapper.pi.dwThreadId, WM_USER + 1, PVMSG_CloseHandle, HandleToBeClosed); - return true; -} - -// ========================= PVWrapperImageHandle ========================= -PVWrapperImageHandle::PVWrapperImageHandle(DWORD Img) - : hEnvelopeProcess(PVWrapper.pi.hProcess), hPVHandle(Img), hFileMap(NULL), Comment(NULL), FSI(NULL), - PVImageSequence(NULL), hImageSequenceFileMap(NULL) -{ -} - -PVWrapperImageHandle::~PVWrapperImageHandle() -{ - if (Comment) - free(Comment); - if (FSI) - free(FSI); - if (PVImageSequence) - { - while (PVImageSequence) - { - LPPVImageSequence pSeq = PVImageSequence; - - PVImageSequence = pSeq->pNext; - // Transparent bitmaps are merged by the envelope and not propagated to the wrapper - _ASSERTE(pSeq->ImgHandle && !pSeq->TransparentHandle); - DeleteObject(pSeq->ImgHandle); - free(pSeq); - } - } - if (hImageSequenceFileMap) - { - CloseHandle(hImageSequenceFileMap); - } - FreeSharedMemory(); -} - -void PVWrapperImageHandle::FreeSharedMemory() -{ - if (hFileMap) - { - CloseHandle(hFileMap); - hFileMap = NULL; - } -} - -bool PVWrapperImageHandle::CreateSharedMemoryForHBitmap(HBITMAP hBitmap, char* pSharedMemoryName, int maxSharedMemoryName, DWORD* pDataSize) -{ - BITMAPINFO bmpHeader; - HDC hDC = GetDC(NULL); -#pragma pack(push, 1) - typedef struct - { - WORD Magic; - DWORD Size, Reserved; - DWORD OfsDataInFile; - BITMAPINFO Header; - RGBQUAD Palette[255]; // Color 0 is already in Header - } Bitmap, *LPBitmap; - LPBitmap pBitmap; -#pragma pack(pop) - - *pDataSize = 2 + 4 + 4 + 4 + sizeof(bmpHeader.bmiHeader); - bmpHeader.bmiHeader.biSize = sizeof(bmpHeader.bmiHeader); - bmpHeader.bmiHeader.biBitCount = 0; - if (!GetDIBits(hDC, hBitmap, 0, 0, NULL, &bmpHeader, DIB_RGB_COLORS)) - { - ReleaseDC(NULL, hDC); - return false; - } - if (bmpHeader.bmiHeader.biBitCount <= 8) - { - *pDataSize += 4 * (1 << bmpHeader.bmiHeader.biBitCount); - } - if ((bmpHeader.bmiHeader.biBitCount == 15) | (bmpHeader.bmiHeader.biBitCount == 16) || (bmpHeader.bmiHeader.biBitCount == 32)) - { - *pDataSize += 3 * 4; - } - *pDataSize += bmpHeader.bmiHeader.biSizeImage; - - iFileMapID = PVWrapper.MessageID++; - _snprintf_s(pSharedMemoryName, maxSharedMemoryName, _TRUNCATE, "%s_img%d", PVWrapper.MutexName, iFileMapID); - hFileMap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, *pDataSize, pSharedMemoryName); - if (!hFileMap) - { - ReleaseDC(NULL, hDC); - return false; - } - pBitmap = (LPBitmap)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, *pDataSize); - if (!pBitmap) - { - CloseHandle(hFileMap); - hFileMap = NULL; - ReleaseDC(NULL, hDC); - return false; - } - - pBitmap->Magic = 'MB'; - pBitmap->Size = pBitmap->Reserved = 0; - pBitmap->OfsDataInFile = 2 + 4 + 4 + 4 + bmpHeader.bmiHeader.biSize; - memcpy(&pBitmap->Header, &bmpHeader, sizeof(bmpHeader)); - if (bmpHeader.bmiHeader.biBitCount <= 8) - { - pBitmap->OfsDataInFile += 4 * (1 << bmpHeader.bmiHeader.biBitCount); - // Will this work? - GetDIBits(hDC, hBitmap, 0, 0, NULL, &pBitmap->Header, DIB_RGB_COLORS); - } - if ((bmpHeader.bmiHeader.biBitCount == 15) || (bmpHeader.bmiHeader.biBitCount == 16)) - { - pBitmap->OfsDataInFile += 3 * 4; - *(LPDWORD)(&pBitmap->Header.bmiColors[0]) /*bV4RedMask*/ = 0xf800; - *(LPDWORD)(&pBitmap->Header.bmiColors[1]) /*bV4GreenMask*/ = 0x07e0; - *(LPDWORD)(&pBitmap->Header.bmiColors[2]) /*bV4BlueMask*/ = 0x001f; - HDC DC2 = CreateCompatibleDC(hDC); - HBITMAP hBmp = (HBITMAP)SelectObject(DC2, hBitmap); - COLORREF oldClr = GetPixel(DC2, 0, 0); - COLORREF newClr = SetPixel(DC2, 0, 0, 0xFFF8FF); - // NT4 15bit: newClr is FFFFFF - // NT4 16bit: newClr is FFFBFF (?) - // W95 15bit: newClr is FFF8FF (?) - // W95 16bit: newClr is FFF8FF - if (newClr == 0xFFF8FF) - { - // Running Win95 - newClr = SetPixel(DC2, 0, 0, 0xFFFcFF); - } - if (newClr == 0xFFFFFF) - { - // NT: 5-bit Green was converted to 8-bit green -> it's a 15bit bitmap! (proper scaling-rounding)} - // W95: 6-bit Green was converted to 8-bit green -> it's a 15bit bitmap! (no scaling-rounding} - *(LPDWORD)(&pBitmap->Header.bmiColors[0]) /*bV4RedMask*/ = 0x7c00; - *(LPDWORD)(&pBitmap->Header.bmiColors[1]) /*bV4GreenMask*/ = 0x03e0; - } - SetPixel(DC2, 0, 0, oldClr); // restore the previous color} - SelectObject(DC2, hBmp); - DeleteDC(DC2); - } - if (bmpHeader.bmiHeader.biBitCount == 32) - { - pBitmap->OfsDataInFile += 3 * 4; - *(LPDWORD)(&pBitmap->Header.bmiColors[0]) /*bV4RedMask*/ = 0xff0000; - *(LPDWORD)(&pBitmap->Header.bmiColors[1]) /*bV4GreenMask*/ = 0xff00; - *(LPDWORD)(&pBitmap->Header.bmiColors[2]) /*bV4BlueMask*/ = 0xff; - } - // Finally, get the pixels data - GetDIBits(hDC, hBitmap, 0, bmpHeader.bmiHeader.biHeight, (LPBYTE)pBitmap + pBitmap->OfsDataInFile, &pBitmap->Header, DIB_RGB_COLORS); - ReleaseDC(NULL, hDC); - UnmapViewOfFile(pBitmap); - return true; -} - -bool PVWrapperImageHandle::CreateSharedMemoryForMemoryBlock(LPBYTE pBuffer, DWORD dataSize, char* pSharedMemoryName, int maxSharedMemoryName) -{ - iFileMapID = PVWrapper.MessageID++; - - _snprintf_s(pSharedMemoryName, maxSharedMemoryName, _TRUNCATE, "%s_img%d", PVWrapper.MutexName, iFileMapID); - hFileMap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, dataSize, pSharedMemoryName); - if (!hFileMap) - { - return false; - } - LPBYTE pData = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, dataSize); - if (!pData) - { - CloseHandle(hFileMap); - hFileMap = NULL; - return false; - } - memcpy(pData, pBuffer, dataSize); - UnmapViewOfFile(pData); - return true; -} - -#endif diff --git a/src/plugins/pictview/dialogs.cpp b/src/plugins/pictview/dialogs.cpp index e7e299264..5f5be63bf 100644 --- a/src/plugins/pictview/dialogs.cpp +++ b/src/plugins/pictview/dialogs.cpp @@ -124,7 +124,7 @@ CAboutDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) GetDlgItemText(HWindow, IDC_ABOUT_PVW32, buff, 1000); wsprintf(buff2, buff, PVW32DLL.Version); SetDlgItemText(HWindow, IDC_ABOUT_PVW32, buff2); - // PVW32 will be bold + // Highlight the backend line in bold SalamanderGUI->AttachStaticText(HWindow, IDC_ABOUT_PVW32, STF_BOLD); hl = SalamanderGUI->AttachHyperLink(HWindow, IDC_ABOUT_EMAIL, STF_UNDERLINE | STF_HYPERLINK_COLOR); @@ -1592,7 +1592,13 @@ CExifDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) EXIFGETVERSION getVer = (EXIFGETVERSION)GetProcAddress(EXIFLibrary, "EXIFGetVersion"); EXIFGETINFO getInfo = (EXIFGETINFO)GetProcAddress(EXIFLibrary, "EXIFGetInfo"); - if (getVer != NULL && getInfo != NULL) + EXIFGETINFOW getInfoW = NULL; + EXIFGETINFOFROMDATA getInfoFromData = + (EXIFGETINFOFROMDATA)GetProcAddress(EXIFLibrary, "EXIFGetInfoFromData"); +#ifdef _UNICODE + getInfoW = (EXIFGETINFOW)GetProcAddress(EXIFLibrary, "EXIFGetInfoW"); +#endif + if (getVer != NULL && (getInfo != NULL || getInfoW != NULL)) { DWORD exifVer = getVer(); CALL_STACK_MESSAGE2("EXIFGetInfo() EXIF.DLL version %u", exifVer); @@ -1624,15 +1630,75 @@ CExifDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) } else { -#ifdef _UNICODE - char FileNameA[_MAX_PATH]; + BOOL gotExif = FALSE; + CExifFileBuffer exifBuffer; + bool bufferLoaded = false; - WideCharToMultiByte(CP_ACP, 0, FileName, -1, FileNameA, sizeof(FileNameA), NULL, NULL); - FileNameA[sizeof(FileNameA) - 1] = 0; - getInfo(FileNameA, 0, ExifEnumProc, (LPARAM)this); + if (getInfoFromData) + { + bufferLoaded = exifBuffer.LoadFromFile(FileName); + if (bufferLoaded) + { + int previousCount = Items.Count; + gotExif = getInfoFromData(exifBuffer.GetExifData(), + exifBuffer.GetExifSize(), + ExifEnumProc, + (LPARAM)this); + if (gotExif && Items.Count == previousCount) + { + gotExif = FALSE; + } + } + } +#ifdef _UNICODE + if (!gotExif && getInfoW) + { + int previousCount = Items.Count; + gotExif = getInfoW(FileName, 0, ExifEnumProc, (LPARAM)this); + if (gotExif && Items.Count == previousCount) + { + gotExif = FALSE; + } + } +#endif + if (!gotExif && getInfo) + { +#ifdef _UNICODE + CExifAnsiPath ansiPath; + if (ansiPath.PrepareFromFile(FileName)) + { + int previousCount = Items.Count; + gotExif = getInfo(ansiPath.GetPath(), 0, ExifEnumProc, (LPARAM)this); + if (gotExif && Items.Count == previousCount) + { + gotExif = FALSE; + } + } #else - getInfo(FileName, 0, ExifEnumProc, (LPARAM)this); + int previousCount = Items.Count; + gotExif = getInfo(FileName, 0, ExifEnumProc, (LPARAM)this); + if (gotExif && Items.Count == previousCount) + { + gotExif = FALSE; + } #endif + } + if (!gotExif && getInfoFromData && !bufferLoaded) + { + bufferLoaded = exifBuffer.LoadFromFile(FileName); + if (bufferLoaded) + { + int previousCount = Items.Count; + gotExif = getInfoFromData(exifBuffer.GetExifData(), + exifBuffer.GetExifSize(), + ExifEnumProc, + (LPARAM)this); + if (gotExif && Items.Count == previousCount) + { + gotExif = FALSE; + } + } + } } DisableNotification = FALSE; FillListView(); diff --git a/src/plugins/pictview/exif/exif.c b/src/plugins/pictview/exif/exif.c index 1c67d3bd9..1cb87aa98 100644 --- a/src/plugins/pictview/exif/exif.c +++ b/src/plugins/pictview/exif/exif.c @@ -1,9 +1,11 @@ -// SPDX-FileCopyrightText: 2023 Open Salamander Authors +// SPDX-FileCopyrightText: 2023 Open Salamander Authors // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include +#include +#include #include #include "exif.h" @@ -12,6 +14,10 @@ #include #include +#ifndef ARRAYSIZE +#define ARRAYSIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + #pragma warning(push) #pragma warning(disable : 4267) // FIXME_X64 - warning temporarily suppressed, fix it #pragma warning(disable : 4133) // FIXME_X64 - warning temporarily suppressed, fix it @@ -63,34 +69,18 @@ show_ifd(ExifContent* content, void* data) exif_content_foreach_entry(content, show_entry, data); } -DWORD WINAPI -EXIFGetVersion() +static BOOL +enumerate_exif_data(ExifData* ed, EXIFENUMPROC enumFunc, LPARAM lParam) { - return EXIF_DLL_VERSION; -} + if (!ed) + return FALSE; -BOOL WINAPI -EXIFGetInfo(const char* fileName, int dataLen, EXIFENUMPROC enumFunc, LPARAM lParam) -{ - ExifData* ed; - ExifMnoteData* md; + ExifMnoteData* md = exif_data_get_mnote_data(ed); struct CEnumData data; data.EnumFunc = enumFunc; data.LParam = lParam; - if (dataLen) - { - ed = exif_data_new_from_data(fileName, dataLen); - } - else - { - ed = exif_data_new_from_file(fileName); - } - if (ed == NULL) - return FALSE; - md = exif_data_get_mnote_data(ed); - exif_data_foreach_content(ed, show_ifd, &data); if (md) @@ -129,11 +119,131 @@ EXIFGetInfo(const char* fileName, int dataLen, EXIFENUMPROC enumFunc, LPARAM lPa } } } + exif_data_unref(ed); return TRUE; } +static wchar_t* +duplicate_extended_length_path(const wchar_t* path) +{ + if (!path || !*path) + return NULL; + + if ((wcslen(path) >= 4) && (wcsncmp(path, L"\\\\?\\", 4) == 0)) + return _wcsdup(path); + + size_t length = wcslen(path); + if (length < MAX_PATH) + return _wcsdup(path); + + if ((length >= 2) && (wcsncmp(path, L"\\\\", 2) == 0)) + { + const wchar_t prefix[] = L"\\\\?\\UNC\\"; + size_t prefixLen = ARRAYSIZE(prefix) - 1; + size_t total = prefixLen + length - 2 + 1; + wchar_t* extended = (wchar_t*)malloc(total * sizeof(wchar_t)); + if (!extended) + return NULL; + wcscpy(extended, prefix); + wcscat(extended, path + 2); + return extended; + } + + const wchar_t prefix[] = L"\\\\?\\"; + size_t prefixLen = ARRAYSIZE(prefix) - 1; + size_t total = prefixLen + length + 1; + wchar_t* extended = (wchar_t*)malloc(total * sizeof(wchar_t)); + if (!extended) + return NULL; + + wcscpy(extended, prefix); + wcscat(extended, path); + return extended; +} + +static ExifData* +exif_data_new_from_file_w(const wchar_t* fileName) +{ + if (!fileName) + return NULL; + + ExifLoader* loader = exif_loader_new(); + if (!loader) + return NULL; + + wchar_t* extended = duplicate_extended_length_path(fileName); + const wchar_t* pathToOpen = extended ? extended : fileName; + + HANDLE file = CreateFileW(pathToOpen, + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + if (extended) + free(extended); + + if (file == INVALID_HANDLE_VALUE) + { + exif_loader_unref(loader); + return NULL; + } + + unsigned char buffer[1024]; + DWORD bytesRead = 0; + while (ReadFile(file, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead > 0) + { + if (!exif_loader_write(loader, buffer, bytesRead)) + break; + } + + CloseHandle(file); + + ExifData* ed = exif_loader_get_data(loader); + exif_loader_unref(loader); + return ed; +} + +DWORD WINAPI +EXIFGetVersion() +{ + return EXIF_DLL_VERSION; +} + +BOOL WINAPI +EXIFGetInfo(const char* fileName, int dataLen, EXIFENUMPROC enumFunc, LPARAM lParam) +{ + if (dataLen) + { + return enumerate_exif_data(exif_data_new_from_data(fileName, dataLen), enumFunc, lParam); + } + else + { + return enumerate_exif_data(exif_data_new_from_file(fileName), enumFunc, lParam); + } +} + +BOOL WINAPI +EXIFGetInfoW(const wchar_t* fileName, int dataLen, EXIFENUMPROC enumFunc, LPARAM lParam) +{ + if (dataLen) + return FALSE; + + return enumerate_exif_data(exif_data_new_from_file_w(fileName), enumFunc, lParam); +} + +BOOL WINAPI +EXIFGetInfoFromData(const unsigned char* data, unsigned int dataLen, EXIFENUMPROC enumFunc, LPARAM lParam) +{ + if (!data || !dataLen) + return FALSE; + + return enumerate_exif_data(exif_data_new_from_data(data, dataLen), enumFunc, lParam); +} + // Loads exif data without forced fixup of entries ExifData* get_exif_data_no_fixups(const char* fileName) { @@ -346,17 +456,14 @@ static void orient_enum_ifd(ExifContent* content, void* data) exif_content_foreach_entry(content, orient_enum_entry, data); } -BOOL WINAPI EXIFGetOrientationInfo(const char* fileName, PThumbExifInfo pInfo) +static BOOL +populate_orientation_info(ExifData* ed, PThumbExifInfo pInfo) { - ExifData* ed; + if (!ed) + return FALSE; pInfo->Orient = pInfo->flags = 0; - ed = exif_data_new_from_file(fileName); - - if (ed == NULL) - return FALSE; - exif_data_foreach_content(ed, orient_enum_ifd, pInfo); exif_data_unref(ed); @@ -364,4 +471,24 @@ BOOL WINAPI EXIFGetOrientationInfo(const char* fileName, PThumbExifInfo pInfo) return TRUE; } +BOOL WINAPI EXIFGetOrientationInfo(const char* fileName, PThumbExifInfo pInfo) +{ + return populate_orientation_info(exif_data_new_from_file(fileName), pInfo); +} + +BOOL WINAPI EXIFGetOrientationInfoW(const wchar_t* fileName, PThumbExifInfo pInfo) +{ + return populate_orientation_info(exif_data_new_from_file_w(fileName), pInfo); +} + +BOOL WINAPI EXIFGetOrientationInfoFromData(const unsigned char* buffer, + unsigned int dataLen, + PThumbExifInfo pInfo) +{ + if (!buffer || !dataLen) + return FALSE; + + return populate_orientation_info(exif_data_new_from_data(buffer, dataLen), pInfo); +} + #pragma warning(pop) // FIXME_X64 - warning temporarily suppressed, fix it diff --git a/src/plugins/pictview/exif/exif.def b/src/plugins/pictview/exif/exif.def index 39c6ecbdc..33c2b6ee0 100644 --- a/src/plugins/pictview/exif/exif.def +++ b/src/plugins/pictview/exif/exif.def @@ -1,9 +1,13 @@ LIBRARY EXIF.DLL -VERSION 8.0 +VERSION 9.0 EXPORTS EXIFGetVersion EXPORTS EXIFGetInfo +EXPORTS EXIFGetInfoW +EXPORTS EXIFGetInfoFromData EXPORTS EXIFGetOrientationInfo +EXPORTS EXIFGetOrientationInfoW +EXPORTS EXIFGetOrientationInfoFromData EXPORTS EXIFReplaceThumbnail EXPORTS EXIFInitTranslations EXPORTS ConvertUTF8ToUCS2 diff --git a/src/plugins/pictview/exif/exif.h b/src/plugins/pictview/exif/exif.h index 33b0238f8..878efb176 100644 --- a/src/plugins/pictview/exif/exif.h +++ b/src/plugins/pictview/exif/exif.h @@ -9,7 +9,7 @@ // Version 6: 2007.05.15: ConvertUTF8ToUCS2 is exported // NOTE: the version also needs to be increased in the exif.def file -#define EXIF_DLL_VERSION 8 +#define EXIF_DLL_VERSION 9 #ifndef RC_INVOKED @@ -38,6 +38,16 @@ typedef BOOL(WINAPI* EXIFGETINFO)(const char* fileName, EXIFENUMPROC enumFunc, LPARAM lParam); +typedef BOOL(WINAPI* EXIFGETINFOFROMDATA)(const unsigned char* data, + unsigned int dataLen, + EXIFENUMPROC enumFunc, + LPARAM lParam); + +typedef BOOL(WINAPI* EXIFGETINFOW)(const wchar_t* fileName, + int dataLen, + EXIFENUMPROC enumFunc, + LPARAM lParam); + typedef BOOL(WINAPI* EXIFREPLACETHUMBNAIL)(char* fileName, char* newFile, unsigned char* pData, int size); @@ -62,6 +72,12 @@ typedef struct _thumbExifInfo typedef BOOL(WINAPI* EXIFGETORIENTATIONINFO)(const char* filename, PThumbExifInfo pInfo); +typedef BOOL(WINAPI* EXIFGETORIENTATIONINFOW)(const wchar_t* filename, PThumbExifInfo pInfo); + +typedef BOOL(WINAPI* EXIFGETORIENTATIONINFOFROMDATA)(const unsigned char* data, + unsigned int dataLen, + PThumbExifInfo pInfo); + #ifdef __cplusplus extern "C" { diff --git a/src/plugins/pictview/help/hh/pictview/introduction_intro.htm b/src/plugins/pictview/help/hh/pictview/introduction_intro.htm index 8f435ad49..67aaf7beb 100644 --- a/src/plugins/pictview/help/hh/pictview/introduction_intro.htm +++ b/src/plugins/pictview/help/hh/pictview/introduction_intro.htm @@ -14,11 +14,11 @@

Getting Started with PictView Plugin

The PictView plugin is an image viewer, converter, and thumbnailer. It supports more than -60 file formats. PictView plugin is based on the -famous freeware PictView -(for DOS and Windows) image manipulation program and it utilizes -PVW32Cnv.DLL, -an image rendering library that can be licensed for use by third-party programs.

+60 file formats and relies on the +Windows Imaging Component (WIC) built into Microsoft Windows for decoding and encoding +image data. The original freeware PictView +application inspired this plugin, but the closed-source PVW32Cnv.dll backend has been replaced +with the WIC imaging services that ship with the operating system.

See File Viewer, Menu Extension and Thumbnail Loader sections in Using Plugins diff --git a/src/plugins/pictview/lang/lang.rc b/src/plugins/pictview/lang/lang.rc index 9bbacd99d..c8c95b2b3 100644 --- a/src/plugins/pictview/lang/lang.rc +++ b/src/plugins/pictview/lang/lang.rc @@ -77,12 +77,12 @@ BEGIN ICON "",IDC_ABOUT_ICON,11,9,20,20 LTEXT "PictView %s - Picture Viewer for Open Salamander",IDC_ABOUT_TITLE,46,9,288,8 LTEXT "",IDC_ABOUT_COPYRIGHT,46,19,288,8,SS_NOPREFIX - LTEXT "This product is based on library",IDC_STATIC_2,46,40,123,8 - LTEXT "%hs - Image Processing Engine",IDC_ABOUT_PVW32,46,51,277,8 - LTEXT "Copyright © 1994-2023 Jan Patera",IDC_STATIC_3,46,62,198,8 + LTEXT "This plugin uses the following imaging backend:",IDC_STATIC_2,46,40,190,8 + LTEXT "%hs",IDC_ABOUT_PVW32,46,51,277,8 + LTEXT "Powered by Microsoft Windows Imaging Component",IDC_STATIC_3,46,62,220,8 LTEXT "Email:",IDC_STATIC_6,46,81,30,8 - LTEXT "support@pictview.com",IDC_ABOUT_EMAIL,99,81,85,8,WS_TABSTOP - LTEXT "Home page:",IDC_STATIC_7,46,92,50,8 + LTEXT "support@pictview.com",IDC_ABOUT_EMAIL,99,81,120,8,WS_TABSTOP + LTEXT "Project site:",IDC_STATIC_7,46,92,50,8 LTEXT "www.pictview.com/salamander",IDC_ABOUT_WWW,99,92,122,8,WS_TABSTOP CONTROL "",IDC_STATIC_5,"Static",SS_ETCHEDHORZ | WS_GROUP,2,108,339,1 END diff --git a/src/plugins/pictview/lang/lang.rc2 b/src/plugins/pictview/lang/lang.rc2 index c31286b90..8ca99026e 100644 --- a/src/plugins/pictview/lang/lang.rc2 +++ b/src/plugins/pictview/lang/lang.rc2 @@ -459,7 +459,7 @@ STRINGTABLE IDS_NCOLORS_CMYK, "CMYK" IDS_NCOLORS_LAB, "LAB" - IDS_ERROR_CALLING_PVW32_DLL, "Error calling PVW32Cnv.DLL" + IDS_ERROR_CALLING_PVW32_DLL, "Error calling Windows Imaging Component (WIC)." IDS_CLIPBOARD_TITLE, "" IDS_CAPTURE_TITLE, "" IDS_SCAN_TITLE, "" @@ -701,8 +701,8 @@ STRINGTABLE IDS_HIST_TIP_BLUE, "Blue (B)" IDS_HIST_TIP_RGBSUM, "RGB (A)" IDS_HIST_TIP_OTHERCHANELS, "Show Other Channels (O)" - IDS_DLL_NOTFOUND, "Unable to load image processing library PVW32Cnv.dll." - IDS_DLL_WRONG_VERSION, "The wrong version of PVW32Cnv.dll was found." + IDS_DLL_NOTFOUND, "Unable to initialize the Windows Imaging Component (WIC) imaging engine." + IDS_DLL_WRONG_VERSION, "An incompatible Windows Imaging Component (WIC) version was found." IDS_SELECT_REGION, "Please select a region first." IDS_SAVE_ERR_EXISTS_OVERWRITE, "File ""%s"" already exists.\nDo you want to replace it?" diff --git a/src/plugins/pictview/lib/PVW32DLL.h b/src/plugins/pictview/lib/PVW32DLL.h index 34040a70c..949c8604a 100644 --- a/src/plugins/pictview/lib/PVW32DLL.h +++ b/src/plugins/pictview/lib/PVW32DLL.h @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later /***************************************************************************** - * PVW32DLL.H - C Interface to PVW32.DLL and PVW32Cnv.DLL + * PVW32DLL.H - Compatibility interface mirroring the legacy PVW32Cnv API * * Version 1.56 * diff --git a/src/plugins/pictview/lib/readme.txt b/src/plugins/pictview/lib/readme.txt index a72d1de98..95c538436 100644 --- a/src/plugins/pictview/lib/readme.txt +++ b/src/plugins/pictview/lib/readme.txt @@ -1,7 +1,5 @@ -11/2023 +10/2025 -We have removed PVW32Cnv.lib because its source code is not available, which violates the GPL license. - -TODO: Replace the PictView engine with WIC or another library. - -If you want to compile the PictView project, contact us for PVW32Cnv.lib and PVW32Cnv.dll. \ No newline at end of file +The closed-source PVW32Cnv backend has been fully replaced by an in-tree implementation that +uses the Windows Imaging Component (WIC) APIs available on modern versions of Windows. The +legacy PVW32Cnv.lib import library is no longer required to build the plugin. \ No newline at end of file diff --git a/src/plugins/pictview/pictview.cpp b/src/plugins/pictview/pictview.cpp index d7fb3652b..b3a29e019 100644 --- a/src/plugins/pictview/pictview.cpp +++ b/src/plugins/pictview/pictview.cpp @@ -3,6 +3,12 @@ #include "precomp.h" +#include +#include +#include +#include +#include + #include "lib/pvw32dll.h" #include "pictview.h" #include "dialogs.h" @@ -18,7 +24,7 @@ #include "pictview.rh2" #include "lang/lang.rh" #include "histwnd.h" -#include "PVEXEWrapper.h" +#include "wic/WicBackend.h" #include "PixelAccess.h" // plugin interface object; its methods are called from Salamander @@ -449,7 +455,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) DLLInstance = hinstDLL; } - if (fdwReason == DLL_PROCESS_DETACH) // shutdown (unload) PVW32.SPL + if (fdwReason == DLL_PROCESS_DETACH) // shutdown (unload) PictView.spl { if (EXIFLibrary != NULL) { @@ -457,7 +463,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) EXIFLibrary = NULL; } if (PVW32DLL.Handle != NULL) - FreeLibrary(PVW32DLL.Handle); // release PVW32.DLL as well + FreeLibrary(PVW32DLL.Handle); // release the imaging backend module as well if (G.HAccel != NULL) DestroyAcceleratorTable(G.HAccel); if (G.CaptureAtomID != 0) @@ -1358,59 +1364,12 @@ void WINAPI HTMLHelpCallback(HWND hWindow, UINT helpID) BOOL LoadPictViewDll(HWND hParentWnd) { - TCHAR path[_MAX_PATH]; - - if (!GetModuleFileName(DLLInstance, path, SizeOf(path))) - { - TRACE_E("GetModuleFileName failed"); - return FALSE; - } - _tcsrchr(path, '\\')[0] = 0; -#ifndef PICTVIEW_DLL_IN_SEPARATE_PROCESS - _tcscat(path, _T("\\PVW32Cnv.dll")); - PVW32DLL.Handle = LoadLibrary(path); // load PVW32Cnv.dll - if (!PVW32DLL.Handle) + if (!PictView::Wic::Backend::Instance().Populate(PVW32DLL)) { - TRACE_E("LoadLibrary(PVW32Cnv.dll) failed"); - SalamanderGeneral->SalMessageBox(hParentWnd, LoadStr(IDS_DLL_NOTFOUND), - LoadStr(IDS_ERRORTITLE), MB_ICONSTOP | MB_OK); - return FALSE; - } - PVW32DLL.PVReadImage2 = (TPVReadImage2)GetProcAddress(PVW32DLL.Handle, "PVReadImage2"); - PVW32DLL.PVCloseImage = (TPVCloseImage)GetProcAddress(PVW32DLL.Handle, "PVCloseImage"); - PVW32DLL.PVDrawImage = (TPVDrawImage)GetProcAddress(PVW32DLL.Handle, "PVDrawImage"); - PVW32DLL.PVGetErrorText = (TPVGetErrorText)GetProcAddress(PVW32DLL.Handle, "PVGetErrorText"); - PVW32DLL.PVOpenImageEx = (TPVOpenImageEx)GetProcAddress(PVW32DLL.Handle, "PVOpenImageEx"); - PVW32DLL.PVSetBkHandle = (TPVSetBkHandle)GetProcAddress(PVW32DLL.Handle, "PVSetBkHandle"); - PVW32DLL.PVGetDLLVersion = (TPVGetDLLVersion)GetProcAddress(PVW32DLL.Handle, "PVGetDLLVersion"); - PVW32DLL.PVSetStretchParameters = (TPVSetStretchParameters)GetProcAddress(PVW32DLL.Handle, "PVSetStretchParameters"); - PVW32DLL.PVLoadFromClipboard = (TPVLoadFromClipboard)GetProcAddress(PVW32DLL.Handle, "PVLoadFromClipboard"); - PVW32DLL.PVGetImageInfo = (TPVGetImageInfo)GetProcAddress(PVW32DLL.Handle, "PVGetImageInfo"); - PVW32DLL.PVSetParam = (TPVSetParam)GetProcAddress(PVW32DLL.Handle, "PVSetParam"); - PVW32DLL.PVGetHandles2 = (TPVGetHandles2)GetProcAddress(PVW32DLL.Handle, "PVGetHandles2"); - PVW32DLL.PVSaveImage = (TPVSaveImage)GetProcAddress(PVW32DLL.Handle, "PVSaveImage"); - PVW32DLL.PVChangeImage = (TPVChangeImage)GetProcAddress(PVW32DLL.Handle, "PVChangeImage"); - PVW32DLL.PVIsOutCombSupported = (TPVIsOutCombSupported)GetProcAddress(PVW32DLL.Handle, "PVIsOutCombSupported"); - PVW32DLL.PVReadImageSequence = (TPVReadImageSequence)GetProcAddress(PVW32DLL.Handle, "PVReadImageSequence"); - PVW32DLL.PVCropImage = (TPVCropImage)GetProcAddress(PVW32DLL.Handle, "PVCropImage"); - PVW32DLL.GetRGBAtCursor = GetRGBAtCursor; - PVW32DLL.CalculateHistogram = CalculateHistogram; - PVW32DLL.CreateThumbnail = CreateThumbnail; - PVW32DLL.SimplifyImageSequence = SimplifyImageSequence; - - if (!PVW32DLL.PVReadImage2 || !PVW32DLL.PVIsOutCombSupported || !PVW32DLL.PVChangeImage || !PVW32DLL.PVSetParam || !PVW32DLL.PVGetHandles2 || !PVW32DLL.PVCropImage) - { - TRACE_E("PVW32Cnv was not compiled for Salamander or an old version was found"); - SalamanderGeneral->SalMessageBox(hParentWnd, LoadStr(IDS_DLL_WRONG_VERSION), - LoadStr(IDS_ERRORTITLE), MB_ICONSTOP | MB_OK); + SalamanderGeneral->SalMessageBox(hParentWnd, LoadStr(IDS_DLL_NOTFOUND), LoadStr(IDS_ERRORTITLE), + MB_ICONSTOP | MB_OK); return FALSE; } -#else // PICTVIEW_DLL_IN_SEPARATE_PROCESS - if (!InitPVEXEWrapper(hParentWnd, path)) - { - return FALSE; - } -#endif // PICTVIEW_DLL_IN_SEPARATE_PROCESS return TRUE; } @@ -1459,7 +1418,8 @@ BOOL InitViewer(HWND hParentWnd) } i = PVW32DLL.PVGetDLLVersion(); - sprintf(PVW32DLL.Version, "PVW32Cnv.dll %d.%#02d.%d", i >> 16, i & 255, (i >> 8) & 255); + _snprintf_s(PVW32DLL.Version, SizeOf(PVW32DLL.Version), _TRUNCATE, "WIC backend %u.%02u", + static_cast(PV_VERSION_MAJOR(i)), static_cast(PV_VERSION_MINOR(i))); PVW32DLL.PVSetParam(GetExtText); @@ -1541,11 +1501,571 @@ BOOL InitEXIF(HWND hParent, BOOL bSilent) return TRUE; } -void ReleaseViewer() +bool ConvertPathToExifEncoding(LPCTSTR path, char* buffer, int bufferSize) +{ +#ifdef _UNICODE + if (buffer == NULL || bufferSize <= 0) + return false; + + buffer[0] = '\0'; + + if (path == NULL) + return false; + + BOOL usedDefaultChar = FALSE; + int result = WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, path, -1, buffer, bufferSize, NULL, &usedDefaultChar); + if (result > 0 && !usedDefaultChar) + return true; + + TCHAR shortPath[MAX_PATH]; + DWORD shortLen = GetShortPathName(path, shortPath, SizeOf(shortPath)); + if (shortLen > 0 && shortLen < SizeOf(shortPath)) + { + usedDefaultChar = FALSE; + result = WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, shortPath, -1, buffer, bufferSize, NULL, &usedDefaultChar); + if (result > 0 && !usedDefaultChar) + return true; + } + + WideCharToMultiByte(CP_ACP, 0, path, -1, buffer, bufferSize, NULL, NULL); + return false; +#else + if (buffer == NULL || bufferSize <= 0) + return false; + + if (path == NULL) + { + buffer[0] = '\0'; + return false; + } + + lstrcpyn(buffer, path, bufferSize); + return true; +#endif +} + +#ifdef _UNICODE +static std::wstring BuildExtendedLengthPath(LPCTSTR path) +{ + if (path == NULL || *path == 0) + return std::wstring(); + + if (_tcsncmp(path, _T("\\\\?\\"), 4) == 0) + return std::wstring(path); + + size_t length = _tcslen(path); + if (length < MAX_PATH) + return std::wstring(); + + if (_tcsncmp(path, _T("\\\\"), 2) == 0) + return std::wstring(_T("\\\\?\\UNC\\")) + (path + 2); + + return std::wstring(_T("\\\\?\\")) + path; +} +#else +static std::wstring BuildExtendedLengthPathWide(const wchar_t* path) +{ + if (path == NULL || *path == L'\0') + return std::wstring(); + + if (wcsncmp(path, L"\\\\?\\", 4) == 0) + return std::wstring(path); + + size_t length = wcslen(path); + if (length < MAX_PATH) + return std::wstring(); + + if (wcsncmp(path, L"\\\\", 2) == 0) + return std::wstring(L"\\\\?\\UNC\\") + (path + 2); + + return std::wstring(L"\\\\?\\") + path; +} +#endif + +namespace +{ + bool ReadExact(HANDLE file, void* buffer, DWORD bytes) + { + if (bytes == 0) + return true; + + BYTE* out = static_cast(buffer); + DWORD remaining = bytes; + while (remaining > 0) + { + DWORD chunk = 0; + if (!ReadFile(file, out, remaining, &chunk, NULL) || chunk == 0) + return false; + + out += chunk; + remaining -= chunk; + } + + return true; + } + + bool SkipBytes(HANDLE file, size_t bytes) + { + LARGE_INTEGER distance; + distance.QuadPart = static_cast(bytes); + return SetFilePointerEx(file, distance, NULL, FILE_CURRENT) != 0; + } + + bool LoadExifFromJpegStream(HANDLE file, + size_t maxBytes, + std::vector& buffer, + const unsigned char*& exifData, + unsigned int& exifSize) + { + LARGE_INTEGER zero = {}; + if (!SetFilePointerEx(file, zero, NULL, FILE_BEGIN)) + return false; + + BYTE header[2]; + DWORD read = 0; + if (!ReadFile(file, header, 2, &read, NULL) || read != 2) + return false; + + if (header[0] != 0xFF || header[1] != 0xD8) + return false; + + buffer.clear(); + + for (;;) + { + BYTE prefix = 0; + do + { + if (!ReadFile(file, &prefix, 1, &read, NULL) || read != 1) + return false; + } while (prefix != 0xFF); + + BYTE marker = 0; + do + { + if (!ReadFile(file, &marker, 1, &read, NULL) || read != 1) + return false; + } while (marker == 0xFF); + + if (marker == 0x00) + continue; + if (marker == 0xD8) + continue; + if (marker == 0xD9 || marker == 0xDA) + break; + if (marker == 0x01) + continue; + if (marker >= 0xD0 && marker <= 0xD7) + continue; + + BYTE lengthBytes[2]; + if (!ReadFile(file, lengthBytes, 2, &read, NULL) || read != 2) + return false; + + unsigned int segmentLength = (lengthBytes[0] << 8) | lengthBytes[1]; + if (segmentLength < 2) + return false; + + unsigned int payloadLength = segmentLength - 2; + + if (marker == 0xE1) + { + if (payloadLength < 6) + { + if (!SkipBytes(file, payloadLength)) + return false; + continue; + } + + if (payloadLength > maxBytes) + { + if (!SkipBytes(file, payloadLength)) + return false; + continue; + } + + buffer.resize(payloadLength); + if (!ReadExact(file, buffer.data(), payloadLength)) + { + buffer.clear(); + return false; + } + + if (memcmp(buffer.data(), "Exif\0\0", 6) == 0) + { + exifData = buffer.data(); + exifSize = payloadLength; + return true; + } + + buffer.clear(); + continue; + } + + if (!SkipBytes(file, payloadLength)) + return false; + } + + buffer.clear(); + return false; + } + + bool LoadExifByScanning(HANDLE file, + size_t maxBytes, + std::vector& buffer, + const unsigned char*& exifData, + unsigned int& exifSize) + { + LARGE_INTEGER zero = {}; + if (!SetFilePointerEx(file, zero, NULL, FILE_BEGIN)) + return false; + + buffer.clear(); + + const size_t chunkSize = 64 * 1024; + size_t totalRead = 0; + size_t searchStart = 0; + const unsigned char signature[] = {'E', 'x', 'i', 'f', 0, 0}; + size_t foundOffset = SIZE_MAX; + size_t declaredLength = 0; + bool haveDeclaredLength = false; + + while (totalRead < maxBytes) + { + size_t toRead = chunkSize; + if (toRead > maxBytes - totalRead) + toRead = maxBytes - totalRead; + if (toRead == 0) + break; + + size_t start = buffer.size(); + buffer.resize(start + toRead); + + DWORD bytesRead = 0; + if (!ReadFile(file, &buffer[start], static_cast(toRead), &bytesRead, NULL)) + { + buffer.resize(start); + return false; + } + + if (bytesRead == 0) + { + buffer.resize(start); + break; + } + + buffer.resize(start + bytesRead); + totalRead += bytesRead; + + size_t searchLimit = 0; + if (buffer.size() >= sizeof(signature)) + searchLimit = buffer.size() - sizeof(signature) + 1; + + for (size_t i = searchStart; i < searchLimit; i++) + { + if (memcmp(&buffer[i], signature, sizeof(signature)) == 0) + { + foundOffset = i; + if (i >= 4 && buffer[i - 4] == 0xFF && buffer[i - 3] == 0xE1) + { + size_t declared = (static_cast(buffer[i - 2]) << 8) | buffer[i - 1]; + if (declared >= 2) + { + declaredLength = declared - 2; // payload length excludes the length bytes themselves + haveDeclaredLength = true; + } + else + { + declaredLength = 0; + haveDeclaredLength = false; + } + } + else + { + declaredLength = 0; + haveDeclaredLength = false; + } + break; + } + } + + if (foundOffset != SIZE_MAX) + break; + + searchStart = (buffer.size() > 5) ? buffer.size() - 5 : 0; + + if (bytesRead < toRead) + break; + } + + if (foundOffset == SIZE_MAX) + { + buffer.clear(); + return false; + } + + if (haveDeclaredLength && declaredLength > 0) + { + size_t required = foundOffset + declaredLength; + while (buffer.size() < required && totalRead < maxBytes) + { + size_t toRead = chunkSize; + if (toRead > maxBytes - totalRead) + toRead = maxBytes - totalRead; + if (toRead == 0) + break; + + size_t start = buffer.size(); + buffer.resize(start + toRead); + + DWORD bytesRead = 0; + if (!ReadFile(file, &buffer[start], static_cast(toRead), &bytesRead, NULL)) + { + buffer.resize(start); + break; + } + + if (bytesRead == 0) + { + buffer.resize(start); + break; + } + + buffer.resize(start + bytesRead); + totalRead += bytesRead; + + if (bytesRead < toRead) + break; + } + } + + size_t available = buffer.size() - foundOffset; + size_t finalLength = available; + if (haveDeclaredLength) + { + if (declaredLength > available) + { + buffer.clear(); + return false; + } + finalLength = declaredLength; + } + + if (finalLength == 0) + { + buffer.clear(); + return false; + } + + if (finalLength > static_cast(std::numeric_limits::max())) + finalLength = std::numeric_limits::max(); + + exifData = &buffer[foundOffset]; + exifSize = static_cast(finalLength); + return exifSize != 0; + } +} + +CExifAnsiPath::CExifAnsiPath() { -#ifdef PICTVIEW_DLL_IN_SEPARATE_PROCESS - ReleasePVEXEWrapper(); + Path[0] = '\0'; +#ifdef _UNICODE + TempFile[0] = '\0'; + UsingTempCopy = false; #endif +} + +CExifAnsiPath::~CExifAnsiPath() +{ +#ifdef _UNICODE + if (UsingTempCopy && TempFile[0]) + { + std::wstring extendedTemp = BuildExtendedLengthPath(TempFile); + LPCTSTR deletePath = extendedTemp.empty() ? TempFile : extendedTemp.c_str(); + DeleteFile(deletePath); + TempFile[0] = '\0'; + UsingTempCopy = false; + } +#endif +} + +bool CExifAnsiPath::PrepareFromFile(LPCTSTR sourcePath) +{ + Path[0] = '\0'; +#ifdef _UNICODE + if (UsingTempCopy && TempFile[0]) + { + std::wstring extendedTemp = BuildExtendedLengthPath(TempFile); + LPCTSTR deletePath = extendedTemp.empty() ? TempFile : extendedTemp.c_str(); + DeleteFile(deletePath); + } + UsingTempCopy = false; + TempFile[0] = '\0'; +#endif + + if (sourcePath == NULL) + return false; + + if (ConvertPathToExifEncoding(sourcePath, Path, sizeof(Path))) + return true; + +#ifdef _UNICODE + TCHAR tempDir[MAX_PATH]; + if (!GetTempPath(SizeOf(tempDir), tempDir)) + return false; + + TCHAR tempFile[MAX_PATH]; + if (!GetTempFileName(tempDir, _T("pvx"), 0, tempFile)) + return false; + + std::wstring sourceExtended = BuildExtendedLengthPath(sourcePath); + LPCTSTR copySource = sourceExtended.empty() ? sourcePath : sourceExtended.c_str(); + std::wstring tempExtended = BuildExtendedLengthPath(tempFile); + LPCTSTR copyDest = tempExtended.empty() ? tempFile : tempExtended.c_str(); + + if (!CopyFile(copySource, copyDest, FALSE)) + { + DeleteFile(copyDest); + return false; + } + + if (!ConvertPathToExifEncoding(tempFile, Path, sizeof(Path))) + { + DeleteFile(copyDest); + return false; + } + + lstrcpyn(TempFile, tempFile, SizeOf(TempFile)); + UsingTempCopy = true; + return true; +#else + return false; +#endif +} + +CExifFileBuffer::CExifFileBuffer() +{ + ExifData = NULL; + ExifSize = 0; +} + +bool CExifFileBuffer::LoadFromFile(LPCTSTR sourcePath, size_t maxBytes) +{ + ExifData = NULL; + ExifSize = 0; + Buffer.clear(); + + if (!sourcePath || maxBytes == 0) + return false; + + HANDLE file; +#ifdef _UNICODE + std::wstring extendedPath = BuildExtendedLengthPath(sourcePath); + LPCTSTR openPath = extendedPath.empty() ? sourcePath : extendedPath.c_str(); + file = CreateFileW(openPath, + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, + NULL); +#else + file = CreateFileA(sourcePath, + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, + NULL); +#endif + if (file == INVALID_HANDLE_VALUE) + return false; + + const unsigned char* data = NULL; + unsigned int size = 0; + + bool success = LoadExifFromJpegStream(file, maxBytes, Buffer, data, size); + if (!success) + { + success = LoadExifByScanning(file, maxBytes, Buffer, data, size); + } + + CloseHandle(file); + + if (!success) + { + Buffer.clear(); + return false; + } + + ExifData = data; + ExifSize = size; + return true; +} + +#ifndef _UNICODE +bool CExifFileBuffer::LoadFromWideFile(const wchar_t* sourcePath, size_t maxBytes) +{ + ExifData = NULL; + ExifSize = 0; + Buffer.clear(); + + if (!sourcePath || maxBytes == 0) + return false; + + std::wstring extendedPath = BuildExtendedLengthPathWide(sourcePath); + LPCWSTR openPath = extendedPath.empty() ? sourcePath : extendedPath.c_str(); + + HANDLE file = CreateFileW(openPath, + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + if (file == INVALID_HANDLE_VALUE) + return false; + + const unsigned char* data = NULL; + unsigned int size = 0; + + bool success = LoadExifFromJpegStream(file, maxBytes, Buffer, data, size); + if (!success) + { + success = LoadExifByScanning(file, maxBytes, Buffer, data, size); + } + + CloseHandle(file); + + if (!success) + { + Buffer.clear(); + return false; + } + + ExifData = data; + ExifSize = size; + return true; +} +#endif + +bool CExifFileBuffer::HasExifData() const +{ + return ExifData != NULL && ExifSize != 0; +} + +const unsigned char* CExifFileBuffer::GetExifData() const +{ + return ExifData; +} + +unsigned int CExifFileBuffer::GetExifSize() const +{ + return ExifSize; +} + +void ReleaseViewer() +{ if (!UnregisterClass(TIP_WINDOW_CLASSNAME, DLLInstance)) TRACE_E("UnregisterClass(TIP_WINDOW_CLASSNAME) has failed"); ReleaseWinLib(DLLInstance); @@ -1639,7 +2159,7 @@ class CViewerThread : public CThread /* unsigned WINAPI ViewerThreadBody(void *param) { - CALL_STACK_MESSAGE3(_T("ViewerThreadBody() PictView.spl %s %hs"), VERSINFO_VERSION, PVW32DLL.Version); + CALL_STACK_MESSAGE3(_T("ViewerThreadBody() PictView.spl %s backend %hs"), VERSINFO_VERSION, PVW32DLL.Version); SetThreadNameInVCAndTrace(PLUGIN_NAME_EN); TRACE_I("Begin"); RECT r; @@ -1758,7 +2278,7 @@ unsigned __stdcall ViewerThread(void *param) unsigned CViewerThread::Body() { - CALL_STACK_MESSAGE3(_T("ViewerThreadBody() PictView.spl %s %hs"), VERSINFO_VERSION, PVW32DLL.Version); + CALL_STACK_MESSAGE3(_T("ViewerThreadBody() PictView.spl %s backend %hs"), VERSINFO_VERSION, PVW32DLL.Version); SetThreadNameInVCAndTrace(PLUGIN_NAME_EN); TRACE_I("Begin"); diff --git a/src/plugins/pictview/pictview.h b/src/plugins/pictview/pictview.h index f20d591f1..14ca3d884 100644 --- a/src/plugins/pictview/pictview.h +++ b/src/plugins/pictview/pictview.h @@ -25,7 +25,7 @@ typedef DWORD(WINAPI* TPVIsOutCombSupported)(int Fmt, int Compr, int Colors, int typedef PVCODE(WINAPI* TPVReadImageSequence)(LPPVHandle Img, LPPVImageSequence* ppSeq); typedef PVCODE(WINAPI* TPVCropImage)(LPPVHandle Img, int Left, int Top, int Width, int Height); -// Internal functions, not directly imported from PVW32Cnv.dll +// Internal helper entry points provided by the in-process imaging backend typedef bool (*TPVGetRGBAtCursor)(LPPVHandle Img, DWORD Colors, int x, int y, RGBQUAD* pRGB, int* pIndex); typedef PVCODE (*TPVCalculateHistogram)(LPPVHandle PVHandle, const LPPVImageInfo pvii, LPDWORD luminosity, LPDWORD red, LPDWORD green, LPDWORD blue, LPDWORD rgb); typedef PVCODE (*TPVCreateThumbnail)(LPPVHandle hPVImage, LPPVSaveImageInfo pSii, int imageIndex, DWORD imgWidth, DWORD imgHeight, @@ -60,7 +60,7 @@ struct CPVW32DLL TPVCalculateHistogram CalculateHistogram; TPVCreateThumbnail CreateThumbnail; TPVSimplifyImageSequence SimplifyImageSequence; - HMODULE Handle; // handle of the opened PVW32Cnv.DLL library + HMODULE Handle; // handle of the active imaging backend module char Version[28]; // initialized together with Handle in DllMain on DLL_PROCESS_ATTACH }; @@ -451,6 +451,49 @@ WCHAR* LoadStrW(int resID); BOOL InitViewer(HWND hParentWnd); void ReleaseViewer(); BOOL InitEXIF(HWND hParent, BOOL bSilent); +bool ConvertPathToExifEncoding(LPCTSTR path, char* buffer, int bufferSize); + +#ifndef RC_INVOKED +#include +#endif + +class CExifAnsiPath +{ +public: + CExifAnsiPath(); + ~CExifAnsiPath(); + + bool PrepareFromFile(LPCTSTR sourcePath); + const char* GetPath() const { return Path; } + +private: + char Path[_MAX_PATH]; +#ifdef _UNICODE + TCHAR TempFile[MAX_PATH]; + bool UsingTempCopy; +#endif +}; + +#ifndef RC_INVOKED +class CExifFileBuffer +{ +public: + CExifFileBuffer(); + + bool LoadFromFile(LPCTSTR sourcePath, size_t maxBytes = 16 * 1024 * 1024); +#ifndef _UNICODE + bool LoadFromWideFile(const wchar_t* sourcePath, size_t maxBytes = 16 * 1024 * 1024); +#endif + bool HasExifData() const; + const unsigned char* GetExifData() const; + unsigned int GetExifSize() const; + +private: + std::vector Buffer; + const unsigned char* ExifData; + unsigned int ExifSize; +}; +#endif void OnConfiguration(HWND hParent); BOOL MultipleMonitors(RECT* boundingRect); diff --git a/src/plugins/pictview/pictview.rh2 b/src/plugins/pictview/pictview.rh2 index f4e318fec..d7d90f3b5 100644 --- a/src/plugins/pictview/pictview.rh2 +++ b/src/plugins/pictview/pictview.rh2 @@ -462,7 +462,7 @@ #define IDS_EXCEPT_INFO1 2306 #define IDS_EXCEPT_INFO2 2307 -// Texts for PVW32Cnv.dll - reserved IDs 4000-4999 !!! +// Legacy PVW32Cnv.dll text identifiers reserved for translator compatibility (4000-4999) #define IDS_DLL 4000 // IDcka stranek v html helpu diff --git a/src/plugins/pictview/precomp.h b/src/plugins/pictview/precomp.h index dd4851d52..aa46eb6d3 100644 --- a/src/plugins/pictview/precomp.h +++ b/src/plugins/pictview/precomp.h @@ -3,11 +3,16 @@ #pragma once +#ifndef NOMINMAX +#define NOMINMAX +#endif //#define WIN32_LEAN_AND_MEAN // exclude rarely-used stuff from Windows headers #include #include #include +#include +#include #include #include #include @@ -20,7 +25,6 @@ #endif #ifdef _WIN64 -#define PICTVIEW_DLL_IN_SEPARATE_PROCESS // the x64 version of PictView uses the 32-bit pvw32cnv.dll via IPC (inter-process communication) with salpvenv.exe #define ENABLE_WIA // the x64 version of PictView uses WIA 1.0 for scanning #else // _WIN64 #define ENABLE_TWAIN32 // the x86 version of PictView uses TWAIN 1.x for scanning (which internally also supports WIA) @@ -57,11 +61,32 @@ #define GET_X_LPARAM(x) LOWORD(x) #endif -#ifndef INT32 -#define INT32 int -#define UINT32 unsigned int +#ifdef min +#undef min +#endif + +#ifdef max +#undef max #endif +template +inline constexpr auto min(T a, U b) -> typename std::common_type::type +{ + using R = typename std::common_type::type; + const R ra = static_cast(a); + const R rb = static_cast(b); + return (rb < ra) ? rb : ra; +} + +template +inline constexpr auto max(T a, U b) -> typename std::common_type::type +{ + using R = typename std::common_type::type; + const R ra = static_cast(a); + const R rb = static_cast(b); + return (ra < rb) ? rb : ra; +} + #ifndef SetWindowLongPtr // compiling on VC6 w/o reasonably new SDK #define SetWindowLongPtr SetWindowLong diff --git a/src/plugins/pictview/pvw32cnv.rh2 b/src/plugins/pictview/pvw32cnv.rh2 index d5804a9b0..0e1cf1481 100644 --- a/src/plugins/pictview/pvw32cnv.rh2 +++ b/src/plugins/pictview/pvw32cnv.rh2 @@ -1,6 +1,6 @@ // Petr: tento soubor existuje jen pro generovani symbols\plugins\pictview\symbols.inc davkou tools\export_translator\export_inc.py -// Petr: pridal jsem IDS_PVW32Cnv_XXXX, aby nervala quiet-validace v Translatoru +// Legacy PVW32Cnv identifiers kept for translator validation compatibility #define IDS_PVW32Cnv_4003 4003 #define IDS_PVW32Cnv_4004 4004 #define IDS_PVW32Cnv_4006 4006 diff --git a/src/plugins/pictview/render1.cpp b/src/plugins/pictview/render1.cpp index 476a4befd..580d1c61a 100644 --- a/src/plugins/pictview/render1.cpp +++ b/src/plugins/pictview/render1.cpp @@ -1583,7 +1583,7 @@ LRESULT CRendererWindow::OnPaint() LoadingDC = dc; XStartLoading = XStart; YStartLoading = YStart; - if (pvii.Flags & PVFF_IMAGESEQUENCE) + if ((pvii.Flags & PVFF_IMAGESEQUENCE) && pvii.Format == PVF_GIF) { code = PVW32DLL.PVReadImageSequence(PVHandle, &PVSequence); if (code == PVC_OK) @@ -1609,13 +1609,80 @@ LRESULT CRendererWindow::OnPaint() (pvii.Flags & PVFF_EXIF) && G.AutoRotate && InitEXIF(NULL, TRUE)) { EXIFGETORIENTATIONINFO getInfo = (EXIFGETORIENTATIONINFO)GetProcAddress(EXIFLibrary, "EXIFGetOrientationInfo"); +#ifdef _UNICODE + EXIFGETORIENTATIONINFOW getInfoW = (EXIFGETORIENTATIONINFOW)GetProcAddress(EXIFLibrary, "EXIFGetOrientationInfoW"); +#endif + EXIFGETORIENTATIONINFOFROMDATA getInfoFromData = + (EXIFGETORIENTATIONINFOFROMDATA)GetProcAddress(EXIFLibrary, "EXIFGetOrientationInfoFromData"); + + CExifFileBuffer exifBuffer; + bool bufferLoaded = false; + + SThumbExifInfo info; + ZeroMemory(&info, sizeof(info)); + BOOL gotInfo = FALSE; + + if (getInfoFromData) + { + bufferLoaded = exifBuffer.LoadFromFile(FileName); + if (bufferLoaded) + { + gotInfo = getInfoFromData(exifBuffer.GetExifData(), exifBuffer.GetExifSize(), &info); + if (!gotInfo || !(info.flags & TEI_ORIENT)) + { + gotInfo = FALSE; + ZeroMemory(&info, sizeof(info)); + } + } + } + +#ifdef _UNICODE + if (!gotInfo && getInfoW) + { + gotInfo = getInfoW(FileName, &info); + if (gotInfo && !(info.flags & TEI_ORIENT)) + { + gotInfo = FALSE; + ZeroMemory(&info, sizeof(info)); + } + } +#endif + if (!gotInfo && getInfo) + { +#ifdef _UNICODE + CExifAnsiPath ansiPath; + if (ansiPath.PrepareFromFile(FileName)) + { + gotInfo = getInfo(ansiPath.GetPath(), &info); + } +#else + gotInfo = getInfo(FileName, &info); +#endif + if (gotInfo && !(info.flags & TEI_ORIENT)) + { + gotInfo = FALSE; + ZeroMemory(&info, sizeof(info)); + } + } - if (getInfo) + if (!gotInfo && getInfoFromData && !bufferLoaded) + { + bufferLoaded = exifBuffer.LoadFromFile(FileName); + if (bufferLoaded) + { + gotInfo = getInfoFromData(exifBuffer.GetExifData(), exifBuffer.GetExifSize(), &info); + if (!gotInfo || !(info.flags & TEI_ORIENT)) + { + gotInfo = FALSE; + ZeroMemory(&info, sizeof(info)); + } + } + } + + if (gotInfo) { - SThumbExifInfo info; int cmd; - getInfo(FileName, &info); if ((info.flags & (TEI_WIDTH | TEI_HEIGHT)) == (TEI_WIDTH | TEI_HEIGHT)) { if (((DWORD)info.Width != pvii.Width) || ((DWORD)info.Height != pvii.Height) || (info.Width < info.Height)) @@ -1645,7 +1712,6 @@ LRESULT CRendererWindow::OnPaint() cmd = 0; switch (info.Orient) { - // case 1/*normal case*/" case 2: cmd = CMD_MIRROR_HOR; break; @@ -2200,14 +2266,6 @@ LRESULT CRendererWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) return 0; } - case WM_RBUTTONDOWN: - { - POINT p; - GetCursorPos(&p); - OnContextMenu(&p); - break; - } - case WM_SYSKEYDOWN: case WM_KEYDOWN: { @@ -2272,6 +2330,28 @@ LRESULT CRendererWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) break; } + case WM_CONTEXTMENU: + { + if ((HWND)wParam == HWindow) + { + POINT p; + if ((int)lParam == -1) + { + DWORD pos = GetMessagePos(); + p.x = GET_X_LPARAM(pos); + p.y = GET_Y_LPARAM(pos); + } + else + { + p.x = GET_X_LPARAM(lParam); + p.y = GET_Y_LPARAM(lParam); + } + OnContextMenu(&p); + return 0; + } + break; + } + case WM_SYSKEYUP: case WM_KEYUP: { @@ -2458,7 +2538,7 @@ LRESULT CRendererWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) HScroll(SB_THUMBPOSITION, GetScrollPos(HWindow, SB_HORZ) + pt.x - r.right / 2); VScroll(SB_THUMBPOSITION, GetScrollPos(HWindow, SB_VERT) + pt.y - r.bottom / 2); // NT4: Desktop icons may get repainted as a response to this - // LockWindowUpdate(NULL) if a zoomed image is cached by PVW32Cnv.dll + // LockWindowUpdate(NULL) if a zoomed image is cached by the backend LockWindowUpdate(NULL); } } diff --git a/src/plugins/pictview/renderer.h b/src/plugins/pictview/renderer.h index 2e2cbd1f5..41fd032ef 100644 --- a/src/plugins/pictview/renderer.h +++ b/src/plugins/pictview/renderer.h @@ -87,7 +87,7 @@ class CRendererWindow : public CWindow public: CViewerWindow* Viewer; - // variables for PVW32 + // variables for the imaging backend LPPVHandle PVHandle; // image handle LPPVImageSequence PVSequence, PVCurImgInSeq; BOOL ImageLoaded; diff --git a/src/plugins/pictview/salpvenv.rc b/src/plugins/pictview/salpvenv.rc deleted file mode 100644 index 619b21538..000000000 --- a/src/plugins/pictview/salpvenv.rc +++ /dev/null @@ -1,36 +0,0 @@ -#ifdef APSTUDIO_INVOKED -#error this file is not editable by Microsoft Visual C++ -#endif //APSTUDIO_INVOKED - -#include "..\shared\spl_vers.h" - -CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "salpvenv.xml" - -1 VERSIONINFO - FILEVERSION 1,0,0,VERSINFO_BUILDNUMBER - PRODUCTVERSION VERSINFO_SALAMANDER_MAJOR,VERSINFO_SALAMANDER_MINORA,VERSINFO_SALAMANDER_MINORB,VERSINFO_BUILDNUMBER - FILEFLAGSMASK 0x0L - FILEFLAGS 0x0L - FILEOS 0x4L - FILETYPE 0x1L // VFT_APP=0x1 - FILESUBTYPE 0x0L -{ - BLOCK "StringFileInfo" - { - BLOCK "040904b0" - { - VALUE "CompanyName", "Open Salamander\0" - VALUE "FileDescription", "Envelope for 32-bit PVW32Cnv.dll\0" - VALUE "FileVersion", "1.0\0" - VALUE "InternalName", "SalPVEnv\0" - VALUE "OriginalFilename", "salpvenv.exe\0" - VALUE "LegalCopyright", "Copyright \251 2012-2023 Open Salamander Authors\0" - VALUE "ProductVersion", VERSINFO_SALAMANDER_VERSION "\0" - VALUE "ProductName", "Open Salamander\0" - } - } - BLOCK "VarFileInfo" - { - VALUE "Translation", 0x0409, 0x04b0 - } -} diff --git a/src/plugins/pictview/salpvenv.xml b/src/plugins/pictview/salpvenv.xml deleted file mode 100644 index 83eb7f965..000000000 --- a/src/plugins/pictview/salpvenv.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - -Envelope for 32-bit PVW32Cnv.dll - - - - - - - - - - - - - - - - - - - - - - - - - - true - - - \ No newline at end of file diff --git a/src/plugins/pictview/saveas.cpp b/src/plugins/pictview/saveas.cpp index 2211dd5e2..0d2735b1f 100644 --- a/src/plugins/pictview/saveas.cpp +++ b/src/plugins/pictview/saveas.cpp @@ -985,7 +985,7 @@ int CRendererWindow::SaveImage(LPCTSTR fileName, DWORD format, SAVEAS_INFO_PTR p #endif if (fMirrorHor) { - // Patch: PVW32Cnv.dll will mirror the image in memory + // Legacy patch: the original backend mirrored the image in memory sii.Flags ^= PVSF_FLIP_HOR; fMirrorHor = FALSE; PVW32DLL.PVSetStretchParameters(PVHandle, XStretchedRange, diff --git a/src/plugins/pictview/thumbs.cpp b/src/plugins/pictview/thumbs.cpp index b0da26ed2..e8c831701 100644 --- a/src/plugins/pictview/thumbs.cpp +++ b/src/plugins/pictview/thumbs.cpp @@ -825,6 +825,10 @@ BOOL CPluginInterfaceForThumbLoader::LoadThumbnail(LPCTSTR filename, int thumbWi unsigned char* thumbData; DWORD pictureFlags = 0; + char filenameA[_MAX_PATH]; + bool hasExactExifPath = ConvertPathToExifEncoding(filename, filenameA, sizeof(filenameA)); + const char* filenameForExif = filenameA; + pvoi.DataSize = ExtractWinThumbnail(filename, &thumbData); for (;;) { @@ -832,16 +836,7 @@ BOOL CPluginInterfaceForThumbLoader::LoadThumbnail(LPCTSTR filename, int thumbWi /* PVFF_FAST: Discard/do not alloc all unnecessary info */ // pvoi.Flags = PVFF_FAST | (G.IgnoreThumbnails ? 0 : PVOF_THUMBNAIL); pvoi.Flags = PVFF_FAST | (G.IgnoreThumbnails ? 0 : (fastThumbnail ? PVOF_THUMBNAIL : 0)); - -#ifdef _UNICODE - char filenameA[_MAX_PATH]; - - WideCharToMultiByte(CP_ACP, 0, filename, -1, filenameA, sizeof(filenameA), NULL, NULL); - filenameA[sizeof(filenameA) - 1] = 0; - pvoi.FileName = filenameA; -#else - pvoi.FileName = filename; -#endif + pvoi.FileName = filenameForExif; if (pvoi.DataSize) { pvoi.Flags |= PVOF_USERDEFINED_INPUT; @@ -909,11 +904,100 @@ BOOL CPluginInterfaceForThumbLoader::LoadThumbnail(LPCTSTR filename, int thumbWi if (EXIFLibrary) { EXIFGETORIENTATIONINFO getInfo = (EXIFGETORIENTATIONINFO)GetProcAddress(EXIFLibrary, "EXIFGetOrientationInfo"); - if (getInfo) +#ifdef _UNICODE + EXIFGETORIENTATIONINFOW getInfoW = (EXIFGETORIENTATIONINFOW)GetProcAddress(EXIFLibrary, "EXIFGetOrientationInfoW"); +#endif + EXIFGETORIENTATIONINFOFROMDATA getInfoFromData = + (EXIFGETORIENTATIONINFOFROMDATA)GetProcAddress(EXIFLibrary, "EXIFGetOrientationInfoFromData"); + CExifFileBuffer exifBuffer; + bool bufferLoaded = false; + SThumbExifInfo info; + ZeroMemory(&info, sizeof(info)); + BOOL gotInfo = FALSE; + +#ifdef _UNICODE + if (getInfoFromData) { - SThumbExifInfo info; + bufferLoaded = exifBuffer.LoadFromFile(filename); + if (bufferLoaded) + { + gotInfo = getInfoFromData(exifBuffer.GetExifData(), exifBuffer.GetExifSize(), &info); + if (!gotInfo || !(info.flags & TEI_ORIENT)) + { + gotInfo = FALSE; + ZeroMemory(&info, sizeof(info)); + } + } + } - getInfo(filename, &info); + if (!gotInfo && getInfoW) + { + gotInfo = getInfoW(filename, &info); + if (gotInfo && !(info.flags & TEI_ORIENT)) + { + gotInfo = FALSE; + ZeroMemory(&info, sizeof(info)); + } + } + if (!gotInfo && getInfo) + { + if (hasExactExifPath) + { + gotInfo = getInfo(filenameForExif, &info); + } + else + { + CExifAnsiPath ansiPath; + if (ansiPath.PrepareFromFile(filename)) + { + gotInfo = getInfo(ansiPath.GetPath(), &info); + } + } + if (gotInfo && !(info.flags & TEI_ORIENT)) + { + gotInfo = FALSE; + ZeroMemory(&info, sizeof(info)); + } + } +#else + if (getInfoFromData) + { + bufferLoaded = exifBuffer.LoadFromFile(filename); + if (bufferLoaded) + { + gotInfo = getInfoFromData(exifBuffer.GetExifData(), exifBuffer.GetExifSize(), &info); + if (!gotInfo || !(info.flags & TEI_ORIENT)) + { + gotInfo = FALSE; + ZeroMemory(&info, sizeof(info)); + } + } + } + if (!gotInfo && getInfo) + { + gotInfo = getInfo(filename, &info); + if (gotInfo && !(info.flags & TEI_ORIENT)) + { + gotInfo = FALSE; + ZeroMemory(&info, sizeof(info)); + } + } +#endif + if (!gotInfo && getInfoFromData && !bufferLoaded) + { + bufferLoaded = exifBuffer.LoadFromFile(filename); + if (bufferLoaded) + { + gotInfo = getInfoFromData(exifBuffer.GetExifData(), exifBuffer.GetExifSize(), &info); + if (!gotInfo || !(info.flags & TEI_ORIENT)) + { + gotInfo = FALSE; + ZeroMemory(&info, sizeof(info)); + } + } + } + if (gotInfo) + { if ((info.flags & (TEI_WIDTH | TEI_HEIGHT)) == (TEI_WIDTH | TEI_HEIGHT)) { if (((DWORD)info.Width != pvii.Width) || ((DWORD)info.Height != pvii.Height) || (info.Width < info.Height)) diff --git a/src/plugins/pictview/vcxproj/pictview.vcxproj b/src/plugins/pictview/vcxproj/pictview.vcxproj index ec856bc07..e3b926530 100644 --- a/src/plugins/pictview/vcxproj/pictview.vcxproj +++ b/src/plugins/pictview/vcxproj/pictview.vcxproj @@ -145,11 +145,7 @@ - - - - - + @@ -211,9 +207,7 @@ - - - + @@ -255,10 +249,6 @@ {bc2e9ea5-ebc4-4fdf-b064-e2bbbbcba1d3} false - - {17cf5e05-f29c-4e5d-ba41-83254e342e0b} - false - diff --git a/src/plugins/pictview/vcxproj/pictview.vcxproj.filters b/src/plugins/pictview/vcxproj/pictview.vcxproj.filters index b05cd16ea..10146b6c8 100644 --- a/src/plugins/pictview/vcxproj/pictview.vcxproj.filters +++ b/src/plugins/pictview/vcxproj/pictview.vcxproj.filters @@ -33,13 +33,7 @@ cpp - - cpp - - - cpp - - + cpp @@ -98,10 +92,7 @@ h - - h - - + h diff --git a/src/plugins/pictview/vcxproj/salpvenv.vcxproj b/src/plugins/pictview/vcxproj/salpvenv.vcxproj deleted file mode 100644 index c83a3719f..000000000 --- a/src/plugins/pictview/vcxproj/salpvenv.vcxproj +++ /dev/null @@ -1,101 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - 16.0 - {17CF5E05-F29C-4E5D-BA41-83254E342E0B} - salpvenv - 10.0 - - - - - false - v143 - Application - - - true - v143 - Application - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - stdcpplatest - - - - - stdcpplatest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/plugins/pictview/vcxproj/salpvenv_base.props b/src/plugins/pictview/vcxproj/salpvenv_base.props deleted file mode 100644 index 83c710bd2..000000000 --- a/src/plugins/pictview/vcxproj/salpvenv_base.props +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - $(OPENSAL_BUILD_DIR)salamander\$(Configuration)_x64\plugins\pictview\ - $(OutDir)Intermediate\SalPVEnv_x86\ - false - false - - - - ..\..\shared;%(AdditionalIncludeDirectories) - WIN32;WINVER=0x0601;_WIN32_WINNT=0x0601;_WIN32_IE=0x0800;_CRT_SECURE_NO_WARNINGS;_SCL_SECURE_NO_WARNINGS;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT;BUILD_ENVELOPE;%(PreprocessorDefinitions) - false - Level3 - - - false - - - false - true - Windows - - - WINVER=0x0601;%(PreprocessorDefinitions) - 0x0409 - - - diff --git a/src/plugins/pictview/vcxproj/salpvenv_debug.props b/src/plugins/pictview/vcxproj/salpvenv_debug.props deleted file mode 100644 index 8ad746179..000000000 --- a/src/plugins/pictview/vcxproj/salpvenv_debug.props +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - true - - - - Disabled - _DEBUG;%(PreprocessorDefinitions) - MultiThreadedDebug - - - false - ..\..\shared\baseaddr_$(ShortPlatform).txt,$(ProjectName) - - - _DEBUG;%(PreprocessorDefinitions) - - - \ No newline at end of file diff --git a/src/plugins/pictview/vcxproj/salpvenv_release.props b/src/plugins/pictview/vcxproj/salpvenv_release.props deleted file mode 100644 index e54017471..000000000 --- a/src/plugins/pictview/vcxproj/salpvenv_release.props +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - false - - - - MaxSpeed - true - NDEBUG;%(PreprocessorDefinitions) - true - MultiThreaded - true - - - true - true - true - UseLinkTimeCodeGeneration - - - NDEBUG;%(PreprocessorDefinitions) - - - call ..\..\..\..\tools\codesign\sign_with_retry.cmd "$(TargetPath)" - - - diff --git a/src/plugins/pictview/versinfo.rh2 b/src/plugins/pictview/versinfo.rh2 index 553a53e33..bf8c1dd77 100644 --- a/src/plugins/pictview/versinfo.rh2 +++ b/src/plugins/pictview/versinfo.rh2 @@ -10,8 +10,8 @@ // look at shared\versinfo.rc #define VERSINFO_MAJOR 2 -#define VERSINFO_MINORA 1 -#define VERSINFO_MINORB 3 +#define VERSINFO_MINORA 2 +#define VERSINFO_MINORB 0 #include "spl_vers.h" // get version & build numbers diff --git a/src/plugins/pictview/wic/WicBackend.cpp b/src/plugins/pictview/wic/WicBackend.cpp new file mode 100644 index 000000000..93c5da4e2 --- /dev/null +++ b/src/plugins/pictview/wic/WicBackend.cpp @@ -0,0 +1,6429 @@ +// SPDX-FileCopyrightText: 2024 Open Salamander Authors +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "precomp.h" +#include "WicBackend.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../Thumbnailer.h" + +#pragma comment(lib, "windowscodecs.lib") +#pragma comment(lib, "shlwapi.lib") +#pragma comment(lib, "propsys.lib") + +using namespace std::string_literals; + +namespace PictView::Wic +{ +namespace +{ +constexpr DWORD kBackendVersion = PV_VERSION_156; +constexpr UINT kBytesPerPixel = 4; +constexpr UINT kMaxGdiDimension = static_cast(std::numeric_limits::max()); + +struct GuidMapping +{ + DWORD format; + GUID container; + GUID pixelFormat; +}; + +HRESULT AllocatePixelStorage(FrameData& frame, UINT width, UINT height); +HRESULT FinalizeDecodedFrame(Backend* backend, FrameData& frame); +PVCODE PopulateImageInfo(ImageHandle& handle, LPPVImageInfo info, DWORD bufferSize, bool hasPreviousImage, + DWORD previousImageIndex, int currentImage); +bool TryReadUnsignedMetadata(IWICMetadataQueryReader* reader, LPCWSTR name, UINT& value); +DWORD MapGifDisposalToPv(UINT disposal); +LONG ClampUnsignedToLong(ULONGLONG value); +HRESULT EnsureTransparencyMask(FrameData& frame); +DWORD MapPixelFormatToColors(const GUID& guid); +HRESULT CompositeGifFrame(ImageHandle& handle, size_t index); +HRESULT RestoreGifCanvasState(ImageHandle& handle, size_t index); +void FillBufferWithColor(std::vector& buffer, UINT width, UINT height, BYTE r, BYTE g, BYTE b, BYTE a); +void ClearBufferRect(std::vector& buffer, UINT width, UINT height, const RECT& rect, BYTE r, BYTE g, BYTE b, + BYTE a); +void ZeroTransparentPixels(std::vector& buffer); +void FillTransparentPixelsWithColor(std::vector& buffer, UINT width, UINT height, UINT stride, BYTE r, BYTE g, + BYTE b, BYTE a); +void PremultiplyGifBuffer(std::vector& buffer, UINT width, UINT height, UINT stride); +void BlendPremultipliedPixel(BYTE* dest, const BYTE* src); +HRESULT CreateSequenceBitmaps(const FrameData& frame, const RECT& rect, HBITMAP& colorBitmap, HBITMAP& maskBitmap); +HRESULT EnsureScaledBitmap(ImageHandle& handle, FrameData& frame, UINT width, UINT height); +DWORD DetermineColorCount(const GUID& pixelFormat, UINT bitsPerPixel, UINT paletteColors, DWORD colorModel); +DWORD DetermineColorModelFromPixelFormat(const GUID& pixelFormat); +HRESULT ConvertBgraSourceToCmyk(IWICImagingFactory* factory, IWICBitmapSource* source, + IWICBitmapSource** convertedSource); +bool IsPaletteUnavailable(HRESULT hr); +void UnpremultiplyBuffer(std::vector& buffer, UINT width, UINT height, UINT stride); +HRESULT CopyBgraFromSource(FrameData& frame, IWICBitmapSource* source); +void ApplyGifTransparentIndices(FrameData& frame); +HRESULT PopulateFramePalette(IWICImagingFactory* factory, FrameData& frame); +HPALETTE CreateGdiPalette(const std::vector& entries); +HRESULT BuildIndexedPixelBuffer(FrameData& frame); + +struct PixelFormatSelection +{ + GUID pixelFormat; + UINT paletteEntries; + bool isIndexed; + bool isGray; +}; + +std::wstring ExtractComment(LPPVSaveImageInfo info) +{ + if (!info || !info->Comment || info->CommentSize == 0) + { + return std::wstring(); + } + + size_t length = static_cast(info->CommentSize); + if (length == 0) + { + return std::wstring(); + } + if (info->Comment[length - 1] == '\0') + { + --length; + } + if (length == 0) + { + return std::wstring(); + } + + int required = MultiByteToWideChar(CP_ACP, 0, info->Comment, static_cast(length), nullptr, 0); + if (required <= 0) + { + return std::wstring(); + } + std::wstring result(static_cast(required), L'\0'); + MultiByteToWideChar(CP_ACP, 0, info->Comment, static_cast(length), result.data(), required); + return result; +} + +HRESULT TrySetMetadataString(IWICMetadataQueryWriter* writer, LPCWSTR name, const std::wstring& value) +{ + if (!writer || value.empty()) + { + return S_OK; + } + PROPVARIANT prop; + PropVariantInit(&prop); + prop.vt = VT_BSTR; + prop.bstrVal = SysAllocStringLen(value.data(), static_cast(value.size())); + if (!prop.bstrVal) + { + return E_OUTOFMEMORY; + } + HRESULT hr = writer->SetMetadataByName(name, &prop); + PropVariantClear(&prop); + if (hr == WINCODEC_ERR_PROPERTYNOTSUPPORTED || hr == WINCODEC_ERR_PROPERTYNOTFOUND) + { + return S_OK; + } + return hr; +} + +HRESULT ApplyCommentMetadata(const GUID& container, IWICMetadataQueryWriter* writer, const std::wstring& comment) +{ + if (!writer || comment.empty()) + { + return S_OK; + } + + if (container == GUID_ContainerFormatGif) + { + HRESULT hr = TrySetMetadataString(writer, L"/commentext/{str=Comment}", comment); + if (FAILED(hr)) + { + return hr; + } + } + else if (container == GUID_ContainerFormatPng) + { + HRESULT hr = TrySetMetadataString(writer, L"/tEXt/{str=Comment}", comment); + if (FAILED(hr)) + { + return hr; + } + hr = TrySetMetadataString(writer, L"/tEXt/{str=Description}", comment); + if (FAILED(hr)) + { + return hr; + } + } + else if (container == GUID_ContainerFormatJpeg) + { + HRESULT hr = TrySetMetadataString(writer, L"/comment", comment); + if (FAILED(hr)) + { + return hr; + } + hr = TrySetMetadataString(writer, L"/ifd/{ushort=270}", comment); + if (FAILED(hr)) + { + return hr; + } + hr = TrySetMetadataString(writer, L"/app1/ifd/{ushort=270}", comment); + if (FAILED(hr)) + { + return hr; + } + } + else if (container == GUID_ContainerFormatTiff) + { + HRESULT hr = TrySetMetadataString(writer, L"/ifd/{ushort=270}", comment); + if (FAILED(hr)) + { + return hr; + } + } + else if (container == GUID_ContainerFormatBmp) + { + HRESULT hr = TrySetMetadataString(writer, L"/ifd/{ushort=270}", comment); + if (FAILED(hr)) + { + return hr; + } + } + return S_OK; +} + + +class PropertyBagWriter +{ +public: + void AddFloat(const wchar_t* name, float value) + { + PROPBAG2 option{}; + option.pstrName = const_cast(name); + option.dwType = PROPBAG2_TYPE_DATA; + option.vt = VT_R4; + m_options.push_back(option); + + VARIANT var; + VariantInit(&var); + var.vt = VT_R4; + var.fltVal = value; + m_values.push_back(var); + } + + void AddUInt8(const wchar_t* name, BYTE value) + { + PROPBAG2 option{}; + option.pstrName = const_cast(name); + option.dwType = PROPBAG2_TYPE_DATA; + option.vt = VT_UI1; + m_options.push_back(option); + + VARIANT var; + VariantInit(&var); + var.vt = VT_UI1; + var.bVal = static_cast(value); + m_values.push_back(var); + } + + void AddBool(const wchar_t* name, bool value) + { + PROPBAG2 option{}; + option.pstrName = const_cast(name); + option.dwType = PROPBAG2_TYPE_DATA; + option.vt = VT_BOOL; + m_options.push_back(option); + + VARIANT var; + VariantInit(&var); + var.vt = VT_BOOL; + var.boolVal = value ? VARIANT_TRUE : VARIANT_FALSE; + m_values.push_back(var); + } + + void AddUInt32(const wchar_t* name, UINT value) + { + PROPBAG2 option{}; + option.pstrName = const_cast(name); + option.dwType = PROPBAG2_TYPE_DATA; + option.vt = VT_UI4; + m_options.push_back(option); + + VARIANT var; + VariantInit(&var); + var.vt = VT_UI4; + var.ulVal = value; + m_values.push_back(var); + } + + void AddString(const wchar_t* name, const std::wstring& value) + { + if (value.empty()) + { + return; + } + + PROPBAG2 option{}; + option.pstrName = const_cast(name); + option.dwType = PROPBAG2_TYPE_DATA; + option.vt = VT_BSTR; + m_options.push_back(option); + + VARIANT var; + VariantInit(&var); + var.vt = VT_BSTR; + var.bstrVal = SysAllocStringLen(value.data(), static_cast(value.size())); + if (!var.bstrVal) + { + m_options.pop_back(); + return; + } + m_values.push_back(var); + } + + HRESULT Write(IPropertyBag2* bag) + { + if (!bag || m_options.empty()) + { + return S_OK; + } + + HRESULT hr = bag->Write(static_cast(m_options.size()), m_options.data(), m_values.data()); + if (hr == WINCODEC_ERR_PROPERTYNOTSUPPORTED) + { + return S_OK; + } + return hr; + } + + ~PropertyBagWriter() + { + for (auto& value : m_values) + { + VariantClear(&value); + } + } + +private: + std::vector m_options; + std::vector m_values; +}; + +float ClampQualityToFactor(DWORD quality) +{ + if (quality == 0) + { + return 0.0f; + } + const DWORD clamped = std::min(100, std::max(1, quality)); + return static_cast(clamped) / 100.0f; +} + +std::optional MapSubsamplingToWic(DWORD subsampling) +{ + switch (subsampling) + { + case 0: + return static_cast(WICJpegYCrCbSubsampling422); + case 1: + return static_cast(WICJpegYCrCbSubsampling444); + default: + return std::nullopt; + } +} + +std::optional FindTransparentPixel(IWICBitmapSource* source) +{ + if (!source) + { + return std::nullopt; + } + + UINT width = 0; + UINT height = 0; + HRESULT hr = source->GetSize(&width, &height); + if (FAILED(hr) || width == 0 || height == 0) + { + return std::nullopt; + } + + const size_t stride = static_cast(width) * 4u; + if (stride > std::numeric_limits::max()) + { + return std::nullopt; + } + const size_t bufferSize = stride * static_cast(height); + if (bufferSize > std::numeric_limits::max()) + { + return std::nullopt; + } + + std::vector pixels(bufferSize); + hr = source->CopyPixels(nullptr, static_cast(stride), static_cast(pixels.size()), pixels.data()); + if (FAILED(hr)) + { + return std::nullopt; + } + + for (size_t y = 0; y < height; ++y) + { + const BYTE* row = pixels.data() + y * stride; + for (size_t x = 0; x < width; ++x) + { + const BYTE* pixel = row + x * 4u; + if (pixel[3] == 0) + { + RGBQUAD color{}; + color.rgbBlue = pixel[0]; + color.rgbGreen = pixel[1]; + color.rgbRed = pixel[2]; + color.rgbReserved = 0; + return color; + } + } + } + + return std::nullopt; +} + +BYTE FindClosestPaletteIndex(const std::vector& colors, BYTE red, BYTE green, BYTE blue) +{ + if (colors.empty()) + { + return 0; + } + + BYTE bestIndex = 0; + unsigned int bestDistance = std::numeric_limits::max(); + + for (size_t i = 0; i < colors.size(); ++i) + { + const WICColor color = colors[i]; + const BYTE paletteRed = static_cast((color >> 16) & 0xFF); + const BYTE paletteGreen = static_cast((color >> 8) & 0xFF); + const BYTE paletteBlue = static_cast(color & 0xFF); + + const int dr = static_cast(paletteRed) - static_cast(red); + const int dg = static_cast(paletteGreen) - static_cast(green); + const int db = static_cast(paletteBlue) - static_cast(blue); + + const unsigned int distance = static_cast(dr * dr + dg * dg + db * db); + if (distance < bestDistance) + { + bestDistance = distance; + bestIndex = static_cast(i); + } + } + + return bestIndex; +} + +std::optional DetermineGifTransparency(const PVSaveImageInfo* info, std::vector& colors, + IWICBitmapSource* source) +{ + if (!info) + { + for (size_t i = 0; i < colors.size(); ++i) + { + if (((colors[i] >> 24) & 0xFFu) == 0) + { + return static_cast(i); + } + } + return std::nullopt; + } + + switch (info->Transp.Flags) + { + case PVTF_NONE: + return std::nullopt; + case PVTF_INDEX: + if (info->Transp.Value.Index < colors.size()) + { + return info->Transp.Value.Index; + } + return std::nullopt; + case PVTF_RGB: + { + if (colors.empty()) + { + return std::nullopt; + } + const BYTE red = info->Transp.Value.RGB.Red; + const BYTE green = info->Transp.Value.RGB.Green; + const BYTE blue = info->Transp.Value.RGB.Blue; + const BYTE index = FindClosestPaletteIndex(colors, red, green, blue); + colors[index] = (static_cast(red) << 16) | (static_cast(green) << 8) | + static_cast(blue); + return index; + } + case PVTF_ORIGINAL: + { + auto transparentPixel = FindTransparentPixel(source); + if (!transparentPixel) + { + for (size_t i = 0; i < colors.size(); ++i) + { + if (((colors[i] >> 24) & 0xFFu) == 0) + { + return static_cast(i); + } + } + return std::nullopt; + } + const BYTE index = FindClosestPaletteIndex(colors, transparentPixel->rgbRed, transparentPixel->rgbGreen, + transparentPixel->rgbBlue); + colors[index] = (static_cast(transparentPixel->rgbRed) << 16) | + (static_cast(transparentPixel->rgbGreen) << 8) | + static_cast(transparentPixel->rgbBlue); + return index; + } + default: + return std::nullopt; + } +} + +double ResolveDpiValue(DWORD requested, double fallback, double defaultValue) +{ + if (requested > 0) + { + return static_cast(requested); + } + if (std::isfinite(fallback) && fallback > 0.0) + { + return fallback; + } + return defaultValue; +} + +std::optional MapTiffCompression(DWORD compression) +{ + switch (compression) + { + case PVCS_DEFAULT: + return std::nullopt; + case PVCS_NO_COMPRESSION: + return static_cast(WICTiffCompressionNone); + case PVCS_CCITT_3: + return static_cast(WICTiffCompressionCCITT3); + case PVCS_CCITT_4: + return static_cast(WICTiffCompressionCCITT4); + case PVCS_LZW: + return static_cast(WICTiffCompressionLZW); + case PVCS_RLE: + return static_cast(WICTiffCompressionRLE); + case PVCS_DEFLATE: + return static_cast(WICTiffCompressionZIP); + case PVCS_JPEG_HUFFMAN: +#if defined(WICTiffCompressionJPEG) + return static_cast(WICTiffCompressionJPEG); +#elif defined(WICTiffCompressionJPEGYCBCR) + return static_cast(WICTiffCompressionJPEGYCBCR); +#else + return std::nullopt; +#endif + default: + return std::nullopt; + } +} + +std::mutex g_errorMutex; +std::unordered_map g_errorTexts = { + {PVC_OK, "OK"}, + {PVC_CANNOT_OPEN_FILE, "Unable to open image."}, + {PVC_UNSUP_FILE_TYPE, "Image format is not supported by the WIC backend."}, + {PVC_UNSUP_OUT_PARAMS, "Requested output parameters are not supported by the WIC backend."}, + {PVC_OUT_OF_MEMORY, "Out of memory."}, + {PVC_INVALID_DIMENSIONS, "Requested dimensions are invalid."}, + {PVC_CANCELED, "Operation canceled."}, + {PVC_GDI_ERROR, "A GDI call failed."}, + {PVC_READING_ERROR, "The image file appears to be corrupt or unreadable."}, + {PVC_WRITING_ERROR, "The image could not be written."}, + {PVC_UNEXPECTED_EOF, "The image data ended unexpectedly."}, +}; + +std::unordered_map g_customErrorTexts; + +void ClearCustomErrorText(DWORD code) +{ + std::lock_guard lock(g_errorMutex); + g_customErrorTexts.erase(code); +} + +void RecordDetailedError(DWORD code, HRESULT hr, const char* stage) +{ + std::lock_guard lock(g_errorMutex); + std::string baseText = "Unknown WIC error."; + const auto baseIt = g_errorTexts.find(code); + if (baseIt != g_errorTexts.end()) + { + baseText = baseIt->second; + } + + std::ostringstream stream; + stream << baseText; + if (stage && stage[0] != '\0') + { + stream << " (stage: " << stage; + } + else + { + stream << " (stage: unknown"; + } + stream << ", hr=0x" << std::uppercase << std::setfill('0') << std::setw(8) + << static_cast(static_cast(hr)) << ')'; + g_customErrorTexts[code] = stream.str(); +} + +bool PathLooksLikeExif(LPCWSTR path) +{ + if (!path) + { + return false; + } + if (wcsstr(path, L"exif") != nullptr) + { + return true; + } + if (wcsstr(path, L"{ushort=34665}") != nullptr) + { + return true; + } + return false; +} + +bool QueryReaderContainsExif(IWICMetadataQueryReader* query) +{ + if (!query) + { + return false; + } + static const wchar_t* kProbePaths[] = { + L"/ifd/exif:ExifVersion", + L"/ifd/{ushort=34665}", + L"/app1/ifd/exif:ExifVersion", + L"/app1/{ushort=34665}" + }; + PROPVARIANT value; + for (const auto* path : kProbePaths) + { + PropVariantInit(&value); + const HRESULT hr = query->GetMetadataByName(path, &value); + PropVariantClear(&value); + if (SUCCEEDED(hr)) + { + return true; + } + } + + PropVariantInit(&value); + const HRESULT ifdHr = query->GetMetadataByName(L"/ifd", &value); + if (SUCCEEDED(ifdHr)) + { + bool hasExif = true; + if (value.vt == VT_UNKNOWN && value.punkVal) + { + ComPtr nested; + if (SUCCEEDED(value.punkVal->QueryInterface(IID_PPV_ARGS(&nested))) && nested) + { + hasExif = QueryReaderContainsExif(nested.Get()); + } + } + PropVariantClear(&value); + if (hasExif) + { + return true; + } + } + else + { + PropVariantClear(&value); + } + + ComPtr names; + if (SUCCEEDED(query->GetEnumerator(&names)) && names) + { + LPOLESTR rawName = nullptr; + ULONG fetched = 0; + while (names->Next(1, &rawName, &fetched) == S_OK) + { + if (rawName) + { + const bool looksLikeExif = PathLooksLikeExif(rawName); + if (looksLikeExif) + { + PROPVARIANT enumValue; + PropVariantInit(&enumValue); + const HRESULT hr = query->GetMetadataByName(rawName, &enumValue); + bool hasExif = false; + if (SUCCEEDED(hr)) + { + if (enumValue.vt == VT_UNKNOWN && enumValue.punkVal) + { + ComPtr nested; + if (SUCCEEDED(enumValue.punkVal->QueryInterface(IID_PPV_ARGS(&nested))) && nested) + { + hasExif = QueryReaderContainsExif(nested.Get()); + } + } + else + { + hasExif = true; + } + } + PropVariantClear(&enumValue); + CoTaskMemFree(rawName); + rawName = nullptr; + if (hasExif) + { + return true; + } + } + if (rawName) + { + CoTaskMemFree(rawName); + rawName = nullptr; + } + } + } + } + + return false; +} + +bool ReaderContainsExif(IWICMetadataReader* reader) +{ + if (!reader) + { + return false; + } + GUID format = {}; + if (SUCCEEDED(reader->GetMetadataFormat(&format))) + { + if (format == GUID_MetadataFormatExif || format == GUID_MetadataFormatIfd) + { + return true; + } + } + + ComPtr query; + if (SUCCEEDED(reader->QueryInterface(IID_PPV_ARGS(&query))) && query) + { + if (QueryReaderContainsExif(query.Get())) + { + return true; + } + } + + ComPtr blockReader; + if (SUCCEEDED(reader->QueryInterface(IID_PPV_ARGS(&blockReader))) && blockReader) + { + UINT count = 0; + if (SUCCEEDED(blockReader->GetCount(&count))) + { + for (UINT i = 0; i < count; ++i) + { + ComPtr child; + if (SUCCEEDED(blockReader->GetReaderByIndex(i, &child)) && child) + { + if (ReaderContainsExif(child.Get())) + { + return true; + } + } + } + } + } + + return false; +} + +bool SourceContainsExif(IUnknown* source) +{ + if (!source) + { + return false; + } + ComPtr blockReader; + if (FAILED(source->QueryInterface(IID_PPV_ARGS(&blockReader))) || !blockReader) + { + return false; + } + UINT count = 0; + if (FAILED(blockReader->GetCount(&count))) + { + return false; + } + for (UINT i = 0; i < count; ++i) + { + ComPtr reader; + if (SUCCEEDED(blockReader->GetReaderByIndex(i, &reader)) && reader) + { + if (ReaderContainsExif(reader.Get())) + { + return true; + } + } + } + return false; +} + +bool FrameContainsExif(IWICBitmapFrameDecode* frame) +{ + if (!frame) + { + return false; + } + if (SourceContainsExif(frame)) + { + return true; + } + ComPtr query; + if (SUCCEEDED(frame->GetMetadataQueryReader(&query)) && query) + { + if (QueryReaderContainsExif(query.Get())) + { + return true; + } + } + return false; +} + +bool TryExtractDelayHundredths(const PROPVARIANT& value, UINT& hundredths) +{ + switch (value.vt) + { + case VT_UI1: + hundredths = value.bVal; + return true; + case VT_UI2: + hundredths = value.uiVal; + return true; + case VT_UI4: + hundredths = static_cast(value.ulVal); + return true; + case VT_UI8: + hundredths = static_cast(std::min(value.uhVal.QuadPart, + static_cast(std::numeric_limits::max()))); + return true; + case VT_UINT: + hundredths = value.uintVal; + return true; + case VT_R4: + hundredths = static_cast(value.fltVal); + return true; + case VT_R8: + hundredths = static_cast(value.dblVal); + return true; + case (VT_VECTOR | VT_UI1): + if (value.caub.cElems > 0 && value.caub.pElems) + { + hundredths = value.caub.pElems[0]; + return true; + } + break; + case (VT_VECTOR | VT_UI2): + if (value.caui.cElems > 0 && value.caui.pElems) + { + hundredths = value.caui.pElems[0]; + return true; + } + break; + case (VT_VECTOR | VT_UI4): + if (value.caul.cElems > 0 && value.caul.pElems) + { + hundredths = static_cast(value.caul.pElems[0]); + return true; + } + break; + case (VT_VECTOR | VT_UI8): + if (value.cauh.cElems > 0 && value.cauh.pElems) + { + hundredths = static_cast(std::min(value.cauh.pElems[0].QuadPart, + static_cast(std::numeric_limits::max()))); + return true; + } + break; + default: + break; + } + return false; +} + +bool TryReadDelayHundredths(IWICMetadataQueryReader* reader, LPCWSTR name, UINT& hundredths) +{ + if (!reader || !name) + { + return false; + } + PROPVARIANT value; + PropVariantInit(&value); + const HRESULT hr = reader->GetMetadataByName(name, &value); + if (FAILED(hr)) + { + PropVariantClear(&value); + return false; + } + const bool extracted = TryExtractDelayHundredths(value, hundredths); + PropVariantClear(&value); + return extracted; +} + +bool TryReadUnsignedMetadata(IWICMetadataQueryReader* reader, LPCWSTR name, UINT& value) +{ + if (!reader || !name) + { + return false; + } + + PROPVARIANT rawValue; + PropVariantInit(&rawValue); + const HRESULT hr = reader->GetMetadataByName(name, &rawValue); + if (FAILED(hr)) + { + PropVariantClear(&rawValue); + return false; + } + + UINT extracted = 0; + const HRESULT convertHr = PropVariantToUInt32(rawValue, &extracted); + PropVariantClear(&rawValue); + if (FAILED(convertHr)) + { + return false; + } + + value = extracted; + return true; +} + +DWORD ClampDelayHundredthsToMilliseconds(UINT hundredths) +{ + if (hundredths == 0) + { + hundredths = 10; // default to 100 ms when delay is unspecified + } + const ULONGLONG delayMs64 = static_cast(hundredths) * 10ull; + return delayMs64 > std::numeric_limits::max() ? std::numeric_limits::max() + : static_cast(delayMs64); +} + +DWORD GetFrameDelayMilliseconds(IWICBitmapFrameDecode* frame) +{ + if (!frame) + { + return 0; + } + ComPtr query; + if (FAILED(frame->GetMetadataQueryReader(&query)) || !query) + { + return 0; + } + + UINT hundredths = 0; + static constexpr const wchar_t* kDelayPaths[] = { + L"/grctlext/DelayTime", // Some decoders expose DelayTime + L"/grctlext/Delay", // WIC animated GIF sample uses Delay + L"/ifd/{ushort=0x5100}", // TIFF/PropertyTagFrameDelay + L"/xmp/GIF:DelayTime", // XMP GIF namespace (fallback) + L"/xmp/MM:FrameDelay", // Additional XMP metadata some encoders emit + L"/xmp/extensibility/Animation/FrameDelay" + }; + for (const auto* path : kDelayPaths) + { + if (TryReadDelayHundredths(query.Get(), path, hundredths)) + { + return ClampDelayHundredthsToMilliseconds(hundredths); + } + } + return 0; +} + +DWORD MapGifDisposalToPv(UINT disposal) +{ + switch (disposal & 0x7u) + { + case 1: + return PVDM_UNMODIFIED; + case 2: + return PVDM_BACKGROUND; + case 3: + return PVDM_PREVIOUS; + default: + return PVDM_UNDEFINED; + } +} + +const char* LookupError(DWORD code) +{ + std::lock_guard lock(g_errorMutex); + const auto customIt = g_customErrorTexts.find(code); + if (customIt != g_customErrorTexts.end()) + { + return customIt->second.c_str(); + } + const auto it = g_errorTexts.find(code); + if (it != g_errorTexts.end()) + { + return it->second.c_str(); + } + static std::string fallback = "Unknown WIC error."; + return fallback.c_str(); +} + +ULONGLONG AbsoluteDimension(LONGLONG value) +{ + if (value >= 0) + { + return static_cast(value); + } + if (value == std::numeric_limits::min()) + { + return static_cast(std::numeric_limits::max()) + 1ull; + } + return static_cast(-(value + 1)) + 1; +} + +LONG ClampUnsignedToLong(ULONGLONG value) +{ + return value > static_cast(std::numeric_limits::max()) + ? std::numeric_limits::max() + : static_cast(value); +} + +DWORD ClampToDword(ULONGLONG value) +{ + return value > static_cast(std::numeric_limits::max()) + ? std::numeric_limits::max() + : static_cast(value); +} + +DWORD QueryFileSize(const std::wstring& path) +{ + if (path.empty()) + { + return 0; + } + + WIN32_FILE_ATTRIBUTE_DATA attributes{}; + if (!GetFileAttributesExW(path.c_str(), GetFileExInfoStandard, &attributes)) + { + return 0; + } + + ULARGE_INTEGER size; + size.LowPart = attributes.nFileSizeLow; + size.HighPart = attributes.nFileSizeHigh; + return ClampToDword(size.QuadPart); +} + +size_t NormalizeFrameIndex(const ImageHandle& handle, int requestedIndex, size_t fallbackIndex = 0) +{ + if (handle.frames.empty()) + { + return 0; + } + + size_t index = fallbackIndex; + if (index >= handle.frames.size()) + { + index = handle.frames.size() - 1; + } + + if (requestedIndex >= 0) + { + index = static_cast(requestedIndex); + if (index >= handle.frames.size()) + { + index = handle.frames.size() - 1; + } + } + + return index; +} + +HRESULT PopulateFrameFromBitmapHandle(Backend& backend, FrameData& frame, HBITMAP bitmap) +{ + if (!bitmap) + { + return E_INVALIDARG; + } + + DIBSECTION dib{}; + const int objectSize = GetObjectW(bitmap, sizeof(dib), &dib); + if (objectSize == 0) + { + const DWORD error = GetLastError(); + return HRESULT_FROM_WIN32(error != 0 ? error : ERROR_INVALID_DATA); + } + + const LONG widthLong = dib.dsBm.bmWidth; + const LONG heightLong = dib.dsBm.bmHeight; + if (widthLong <= 0 || heightLong == 0) + { + return WINCODEC_ERR_INVALIDPARAMETER; + } + + const UINT width = static_cast(widthLong); + const UINT height = static_cast(heightLong < 0 ? -heightLong : heightLong); + + HRESULT hr = AllocatePixelStorage(frame, width, height); + if (FAILED(hr)) + { + return hr; + } + + frame.rawWidth = width; + frame.rawHeight = height; + frame.rawStride = frame.stride; + + BITMAPINFO bmi{}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = static_cast(width); + bmi.bmiHeader.biHeight = -static_cast(height); + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + + HDC dc = CreateCompatibleDC(nullptr); + if (!dc) + { + const DWORD error = GetLastError(); + frame.pixels.clear(); + frame.compositedPixels.clear(); + frame.stride = 0; + return HRESULT_FROM_WIN32(error != 0 ? error : ERROR_NOT_ENOUGH_MEMORY); + } + + HGDIOBJ oldBitmap = SelectObject(dc, bitmap); + const int lines = GetDIBits(dc, bitmap, 0, height, frame.pixels.data(), &bmi, DIB_RGB_COLORS); + if (oldBitmap) + { + SelectObject(dc, oldBitmap); + } + DeleteDC(dc); + + if (lines == 0) + { + const DWORD error = GetLastError(); + frame.pixels.clear(); + frame.compositedPixels.clear(); + frame.stride = 0; + return HRESULT_FROM_WIN32(error != 0 ? error : ERROR_INVALID_DATA); + } + + if (dib.dsBm.bmBitsPixel < 32) + { + BYTE* pixels = frame.pixels.data(); + const size_t pixelCount = static_cast(width) * height; + for (size_t i = 0; i < pixelCount; ++i) + { + pixels[i * 4 + 3] = 255; + } + } + + frame.sourcePixelFormat = GUID_WICPixelFormat32bppBGRA; + const UINT sourceBits = dib.dsBm.bmBitsPixel > 0 ? static_cast(dib.dsBm.bmBitsPixel) : 32u; + frame.bitsPerPixel = sourceBits; + frame.reportedBitDepth = sourceBits; + frame.paletteColorCount = 0; + frame.colorModel = PVCM_RGB; + frame.reportedColors = DetermineColorCount(frame.sourcePixelFormat, frame.bitsPerPixel, frame.paletteColorCount, + frame.colorModel); + frame.realizePalette = false; + frame.hasGifFrameRect = false; + frame.gifFrameRect.left = 0; + frame.gifFrameRect.top = 0; + frame.gifFrameRect.right = ClampUnsignedToLong(static_cast(width)); + frame.gifFrameRect.bottom = ClampUnsignedToLong(static_cast(height)); + + frame.rect.left = 0; + frame.rect.top = 0; + frame.rect.right = ClampUnsignedToLong(static_cast(width)); + frame.rect.bottom = ClampUnsignedToLong(static_cast(height)); + frame.disposal = PVDM_UNDEFINED; + + hr = FinalizeDecodedFrame(&backend, frame); + if (FAILED(hr)) + { + return hr; + } + return S_OK; +} + +HRESULT AllocateBuffer(std::vector& buffer, size_t size) +{ + try + { + buffer.resize(size); + } + catch (const std::bad_alloc&) + { + buffer.clear(); + return E_OUTOFMEMORY; + } + return S_OK; +} + +HRESULT AllocatePixelStorage(FrameData& frame, UINT width, UINT height) +{ + if (width == 0 || height == 0) + { + return WINCODEC_ERR_INVALIDPARAMETER; + } + if (width > kMaxGdiDimension || height > kMaxGdiDimension) + { + return WINCODEC_ERR_INVALIDPARAMETER; + } + + const ULONGLONG stride64 = static_cast(width) * kBytesPerPixel; + if (stride64 > std::numeric_limits::max()) + { + return E_OUTOFMEMORY; + } + + const ULONGLONG buffer64 = stride64 * static_cast(height); + if (height != 0 && buffer64 / height != stride64) + { + return E_OUTOFMEMORY; + } + if (buffer64 > static_cast(std::numeric_limits::max())) + { + return E_OUTOFMEMORY; + } + if (buffer64 > static_cast(std::numeric_limits::max())) + { + return E_OUTOFMEMORY; + } + + frame.width = width; + frame.height = height; + frame.stride = static_cast(stride64); + frame.rawWidth = width; + frame.rawHeight = height; + frame.rawStride = static_cast(stride64); + frame.compositedPixels.clear(); + + HRESULT hr = AllocateBuffer(frame.pixels, static_cast(buffer64)); + if (FAILED(hr)) + { + frame.stride = 0; + return hr; + } + return S_OK; +} + +const GuidMapping kEncoderMappings[] = { + {PVF_BMP, GUID_ContainerFormatBmp, GUID_WICPixelFormat32bppBGRA}, + {PVF_PNG, GUID_ContainerFormatPng, GUID_WICPixelFormat32bppBGRA}, + {PVF_JPG, GUID_ContainerFormatJpeg, GUID_WICPixelFormat24bppBGR}, + {PVF_TIFF, GUID_ContainerFormatTiff, GUID_WICPixelFormat32bppBGRA}, + {PVF_GIF, GUID_ContainerFormatGif, GUID_WICPixelFormat8bppIndexed}, + {PVF_ICO, GUID_ContainerFormatIco, GUID_WICPixelFormat32bppBGRA}, +}; + +std::optional DeterminePixelFormat(const GuidMapping& mapping, LPPVSaveImageInfo info) +{ + PixelFormatSelection selection{}; + selection.pixelFormat = mapping.pixelFormat; + selection.paletteEntries = MapPixelFormatToColors(mapping.pixelFormat); + selection.isIndexed = selection.paletteEntries > 0; + selection.isGray = false; + + if (!info) + { + return selection; + } + + auto chooseIndexed = [&](UINT colorCount) { + UINT clamped = std::max(colorCount, 2u); + UINT bits = 0; + while (((1u << bits) < clamped) && bits < 8) + { + ++bits; + } + if (bits == 0) + { + bits = 1; + } + if (bits <= 1) + { + selection.pixelFormat = GUID_WICPixelFormat1bppIndexed; + selection.paletteEntries = 2; + } + else if (bits <= 4) + { + selection.pixelFormat = GUID_WICPixelFormat4bppIndexed; + selection.paletteEntries = 1u << 4; + } + else + { + selection.pixelFormat = GUID_WICPixelFormat8bppIndexed; + selection.paletteEntries = 1u << bits; + if (selection.paletteEntries > 256) + { + selection.paletteEntries = 256; + } + } + selection.isIndexed = true; + }; + + const DWORD colors = info->Colors; + if (info->ColorModel == PVCM_GRAYS) + { + if (colors == 2) + { + chooseIndexed(2); + } + else + { + selection.pixelFormat = GUID_WICPixelFormat8bppGray; + selection.paletteEntries = 0; + selection.isIndexed = false; + } + selection.isGray = true; + return selection; + } + + if (colors != 0 && colors <= 256) + { + chooseIndexed(colors); + return selection; + } + + switch (colors) + { + case PV_COLOR_HC15: + selection.pixelFormat = GUID_WICPixelFormat16bppBGR555; + selection.paletteEntries = 0; + selection.isIndexed = false; + return selection; + case PV_COLOR_HC16: + selection.pixelFormat = GUID_WICPixelFormat16bppBGR565; + selection.paletteEntries = 0; + selection.isIndexed = false; + return selection; + case PV_COLOR_TC24: + selection.pixelFormat = GUID_WICPixelFormat24bppBGR; + selection.paletteEntries = 0; + selection.isIndexed = false; + return selection; + case PV_COLOR_TC32: + if (mapping.container == GUID_ContainerFormatJpeg) + { + if (info->ColorModel == PVCM_GRAYS) + { + selection.pixelFormat = GUID_WICPixelFormat24bppBGR; + } + else + { + selection.pixelFormat = GUID_WICPixelFormat32bppCMYK; + } + } + else + { + selection.pixelFormat = GUID_WICPixelFormat32bppBGRA; + } + selection.paletteEntries = 0; + selection.isIndexed = false; + return selection; + default: + break; + } + + if (mapping.container == GUID_ContainerFormatJpeg) + { + selection.pixelFormat = info->ColorModel == PVCM_GRAYS ? GUID_WICPixelFormat8bppGray : GUID_WICPixelFormat24bppBGR; + selection.isGray = info->ColorModel == PVCM_GRAYS; + selection.paletteEntries = 0; + selection.isIndexed = false; + return selection; + } + + return selection; +} + +HRESULT CreateDecoder(Backend& backend, const std::wstring& path, IWICBitmapDecoder** decoder) +{ + auto factory = backend.Factory(); + if (!factory) + { + return E_POINTER; + } + return factory->CreateDecoderFromFilename(path.c_str(), nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, decoder); +} + +bool IsIgnorableColorProfileError(HRESULT hr) +{ + switch (hr) + { + case WINCODEC_ERR_UNSUPPORTEDOPERATION: + case WINCODEC_ERR_UNSUPPORTEDPIXELFORMAT: + case WINCODEC_ERR_PROPERTYNOTSUPPORTED: + case WINCODEC_ERR_UNSUPPORTEDVERSION: +#ifdef WINCODEC_ERR_PROFILENOTASSOCIATED + case WINCODEC_ERR_PROFILENOTASSOCIATED: +#endif +#ifdef WINCODEC_ERR_PROFILEINVALID + case WINCODEC_ERR_PROFILEINVALID: +#endif + case E_NOTIMPL: + return true; + default: + return false; + } +} + +HRESULT ApplyEmbeddedColorProfile(ImageHandle& handle, FrameData& frame) +{ + if (frame.colorConvertedSource) + { + return S_OK; + } + + IWICImagingFactory* factory = handle.backend->Factory(); + if (!factory) + { + return E_POINTER; + } + + UINT contextCount = 0; + HRESULT hr = frame.frame->GetColorContexts(0, nullptr, &contextCount); + if (FAILED(hr)) + { + return hr; + } + if (contextCount == 0) + { + return WINCODEC_ERR_UNSUPPORTEDOPERATION; + } + + std::vector> sourceContexts(contextCount); + std::vector rawContexts(contextCount); + for (UINT i = 0; i < contextCount; ++i) + { + hr = factory->CreateColorContext(&sourceContexts[i]); + if (FAILED(hr)) + { + return hr; + } + rawContexts[i] = sourceContexts[i].Get(); + } + + hr = frame.frame->GetColorContexts(contextCount, rawContexts.data(), &contextCount); + if (FAILED(hr)) + { + return hr; + } + if (contextCount == 0) + { + return WINCODEC_ERR_UNSUPPORTEDOPERATION; + } + + Microsoft::WRL::ComPtr destinationContext; + hr = factory->CreateColorContext(&destinationContext); + if (FAILED(hr)) + { + return hr; + } + hr = destinationContext->InitializeFromExifColorSpace(0x1); // sRGB + if (FAILED(hr)) + { + return hr; + } + + Microsoft::WRL::ComPtr transform; + static const GUID kCLSID_WICColorTransform = + {0xB66F034F, 0xD0E2, 0x40AB, {0xB4, 0x36, 0x6D, 0xE3, 0x9E, 0x32, 0x1A, 0x94}}; + hr = CoCreateInstance(kCLSID_WICColorTransform, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&transform)); + if (FAILED(hr)) + { + if (hr == REGDB_E_CLASSNOTREG) + { + return WINCODEC_ERR_UNSUPPORTEDOPERATION; + } + return hr; + } + + if (rawContexts.empty()) + { + return WINCODEC_ERR_UNSUPPORTEDOPERATION; + } + IWICColorContext* sourceContext = rawContexts[0]; + hr = transform->Initialize(frame.frame.Get(), sourceContext, destinationContext.Get(), GUID_WICPixelFormat32bppBGRA); + if (FAILED(hr)) + { + return hr; + } + + hr = transform.As(&frame.colorConvertedSource); + return hr; +} + +void UnpremultiplyBuffer(std::vector& buffer, UINT width, UINT height, UINT stride) +{ + if (buffer.empty() || width == 0 || height == 0) + { + return; + } + + const size_t rowStride = static_cast(stride); + const size_t expectedStride = static_cast(width) * kBytesPerPixel; + if (rowStride < expectedStride) + { + return; + } + + BYTE* data = buffer.data(); + for (UINT y = 0; y < height; ++y) + { + BYTE* row = data + rowStride * static_cast(y); + for (UINT x = 0; x < width; ++x) + { + BYTE* pixel = row + static_cast(x) * kBytesPerPixel; + const BYTE alpha = pixel[3]; + if (alpha == 0) + { + pixel[0] = 0; + pixel[1] = 0; + pixel[2] = 0; + continue; + } + if (alpha == 255) + { + continue; + } + + const unsigned int a = alpha; + pixel[0] = static_cast((static_cast(pixel[0]) * 255u + a / 2u) / a); + pixel[1] = static_cast((static_cast(pixel[1]) * 255u + a / 2u) / a); + pixel[2] = static_cast((static_cast(pixel[2]) * 255u + a / 2u) / a); + } + } +} + +void PremultiplyGifBuffer(std::vector& buffer, UINT width, UINT height, UINT stride) +{ + if (buffer.empty() || width == 0 || height == 0) + { + return; + } + + const size_t rowStride = static_cast(stride); + for (UINT y = 0; y < height; ++y) + { + BYTE* row = buffer.data() + static_cast(y) * rowStride; + for (UINT x = 0; x < width; ++x) + { + BYTE* pixel = row + static_cast(x) * kBytesPerPixel; + const BYTE alpha = pixel[3]; + if (alpha < 128) + { + pixel[0] = 0; + pixel[1] = 0; + pixel[2] = 0; + pixel[3] = 0; + continue; + } + if (pixel[3] != 255) + { + pixel[3] = 255; + } + + const unsigned int a = pixel[3]; + pixel[0] = static_cast((static_cast(pixel[0]) * a + 127u) / 255u); + pixel[1] = static_cast((static_cast(pixel[1]) * a + 127u) / 255u); + pixel[2] = static_cast((static_cast(pixel[2]) * a + 127u) / 255u); + } + } +} + +HRESULT CopyBgraFromSource(FrameData& frame, IWICBitmapSource* source) +{ + if (!source) + { + return E_POINTER; + } + + UINT width = 0; + UINT height = 0; + HRESULT hr = source->GetSize(&width, &height); + if (FAILED(hr)) + { + return hr; + } + + UINT targetWidth = width; + UINT targetHeight = height; + if (frame.rawWidth > 0 && frame.rawWidth <= width) + { + targetWidth = frame.rawWidth; + } + if (frame.rawHeight > 0 && frame.rawHeight <= height) + { + targetHeight = frame.rawHeight; + } + + WICRect rect{}; + rect.X = 0; + rect.Y = 0; + rect.Width = static_cast(targetWidth); + rect.Height = static_cast(targetHeight); + + hr = AllocatePixelStorage(frame, targetWidth, targetHeight); + if (FAILED(hr)) + { + return hr; + } + + const UINT bufferSize = static_cast(frame.pixels.size()); + + hr = source->CopyPixels(&rect, frame.stride, bufferSize, frame.pixels.data()); + if (FAILED(hr)) + { + frame.pixels.clear(); + frame.compositedPixels.clear(); + frame.stride = 0; + return hr; + } + + frame.rawWidth = targetWidth; + frame.rawHeight = targetHeight; + frame.rawStride = frame.stride; + + if (frame.pixelsArePremultiplied) + { + UnpremultiplyBuffer(frame.pixels, targetWidth, targetHeight, frame.stride); + } + + ApplyGifTransparentIndices(frame); + + return S_OK; +} + +void ApplyGifTransparentIndices(FrameData& frame) +{ + if (!frame.gifHasTransparentColor || !frame.frame) + { + return; + } + + const UINT width = frame.rawWidth > 0 ? frame.rawWidth : frame.width; + const UINT height = frame.rawHeight > 0 ? frame.rawHeight : frame.height; + if (width == 0 || height == 0) + { + return; + } + + GUID sourceFormat{}; + if (FAILED(frame.frame->GetPixelFormat(&sourceFormat))) + { + return; + } + + UINT bitsPerPixel = 0; + if (sourceFormat == GUID_WICPixelFormat8bppIndexed) + { + bitsPerPixel = 8; + } + else if (sourceFormat == GUID_WICPixelFormat4bppIndexed) + { + bitsPerPixel = 4; + } +#if defined(GUID_WICPixelFormat2bppIndexed) + else if (sourceFormat == GUID_WICPixelFormat2bppIndexed) + { + bitsPerPixel = 2; + } +#endif + else if (sourceFormat == GUID_WICPixelFormat1bppIndexed) + { + bitsPerPixel = 1; + } + else + { + return; + } + + const UINT indexStride = static_cast((static_cast(width) * bitsPerPixel + 7ull) / 8ull); + if (indexStride == 0) + { + return; + } + + const size_t bufferSize = static_cast(indexStride) * static_cast(height); + if (bufferSize == 0) + { + return; + } + + std::vector indices; + if (FAILED(AllocateBuffer(indices, bufferSize))) + { + return; + } + + HRESULT hr = frame.frame->CopyPixels(nullptr, indexStride, static_cast(indices.size()), indices.data()); + if (FAILED(hr)) + { + return; + } + + auto extractIndex = [bitsPerPixel](const BYTE* row, UINT x) -> BYTE { + switch (bitsPerPixel) + { + case 8: + return row[x]; + case 4: + { + const BYTE packed = row[x / 2]; + if ((x & 1u) == 0) + { + return static_cast(packed >> 4); + } + return static_cast(packed & 0x0Fu); + } + case 2: + { + const BYTE packed = row[x / 4]; + const UINT shift = 6u - ((x & 3u) * 2u); + return static_cast((packed >> shift) & 0x03u); + } + case 1: + default: + { + const BYTE packed = row[x / 8]; + const UINT shift = 7u - (x & 7u); + return static_cast((packed >> shift) & 0x01u); + } + } + }; + + BYTE* dstBase = frame.pixels.data(); + const size_t dstStride = static_cast(frame.stride); + const BYTE transparentIndex = frame.gifTransparentIndex; + + for (UINT y = 0; y < height; ++y) + { + const BYTE* srcRow = indices.data() + static_cast(y) * indexStride; + BYTE* dstRow = dstBase + static_cast(y) * dstStride; + for (UINT x = 0; x < width; ++x) + { + const BYTE paletteIndex = extractIndex(srcRow, x); + if (paletteIndex != transparentIndex) + { + continue; + } + + BYTE* pixel = dstRow + static_cast(x) * kBytesPerPixel; + pixel[0] = 0; + pixel[1] = 0; + pixel[2] = 0; + pixel[3] = 0; + } + } +} + +HRESULT CompositeGifFrame(ImageHandle& handle, size_t index) +{ + if (index >= handle.frames.size()) + { + return E_INVALIDARG; + } + + FrameData& frame = handle.frames[index]; + const bool multiFrameAnimation = handle.frames.size() > 1; + const LONG canvasWidthLong = handle.canvasWidth > 0 ? handle.canvasWidth : static_cast(frame.width); + const LONG canvasHeightLong = handle.canvasHeight > 0 ? handle.canvasHeight : static_cast(frame.height); + if (canvasWidthLong <= 0 || canvasHeightLong <= 0) + { + return WINCODEC_ERR_INVALIDPARAMETER; + } + + const UINT canvasWidth = static_cast(canvasWidthLong); + const UINT canvasHeight = static_cast(canvasHeightLong); + const size_t canvasStride = static_cast(canvasWidth) * kBytesPerPixel; + const size_t canvasBytes = canvasStride * static_cast(canvasHeight); + if (canvasStride > static_cast(std::numeric_limits::max())) + { + return E_OUTOFMEMORY; + } + + handle.canvasWidth = canvasWidthLong; + handle.canvasHeight = canvasHeightLong; + + if (handle.gifComposeCanvas.size() != canvasBytes) + { + try + { + handle.gifComposeCanvas.resize(canvasBytes); + } + catch (const std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + } + + const BYTE backgroundR = GetRValue(handle.formatInfo.GIF.BgColor); + const BYTE backgroundG = GetGValue(handle.formatInfo.GIF.BgColor); + const BYTE backgroundB = GetBValue(handle.formatInfo.GIF.BgColor); + const BYTE backgroundA = handle.gifHasBackgroundColor ? handle.gifBackgroundAlpha : 0; + if (index == 0 || !handle.gifCanvasInitialized) + { + FillBufferWithColor(handle.gifComposeCanvas, canvasWidth, canvasHeight, backgroundR, backgroundG, backgroundB, + backgroundA); + handle.gifCanvasInitialized = true; + handle.gifSavedCanvas.clear(); + } + else + { + FrameData& previous = handle.frames[index - 1]; + const RECT& previousLogicalRect = previous.hasGifFrameRect ? previous.gifFrameRect : previous.rect; + switch (previous.disposal) + { + case PVDM_BACKGROUND: + ClearBufferRect(handle.gifComposeCanvas, canvasWidth, canvasHeight, previousLogicalRect, backgroundR, + backgroundG, backgroundB, backgroundA); + handle.gifSavedCanvas.clear(); + break; + case PVDM_PREVIOUS: + if (handle.gifSavedCanvas.size() == canvasBytes) + { + try + { + handle.gifComposeCanvas = handle.gifSavedCanvas; + } + catch (const std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + } + else + { + FillBufferWithColor(handle.gifComposeCanvas, canvasWidth, canvasHeight, backgroundR, backgroundG, + backgroundB, backgroundA); + } + handle.gifSavedCanvas.clear(); + break; + default: + handle.gifSavedCanvas.clear(); + break; + } + } + + if (frame.disposal == PVDM_PREVIOUS) + { + try + { + handle.gifSavedCanvas = handle.gifComposeCanvas; + frame.disposalBuffer = handle.gifSavedCanvas; + } + catch (const std::bad_alloc&) + { + handle.gifSavedCanvas.clear(); + frame.disposalBuffer.clear(); + return E_OUTOFMEMORY; + } + } + else + { + handle.gifSavedCanvas.clear(); + frame.disposalBuffer.clear(); + } + + const UINT sourceWidth = frame.rawWidth > 0 ? frame.rawWidth : frame.width; + const UINT sourceHeight = frame.rawHeight > 0 ? frame.rawHeight : frame.height; + const UINT sourceStride = frame.rawStride > 0 ? frame.rawStride : frame.stride; + std::vector raw; + raw.swap(frame.pixels); + if (!raw.empty()) + { + PremultiplyGifBuffer(raw, sourceWidth, sourceHeight, sourceStride); + } + + const RECT& destinationRect = frame.hasGifFrameRect ? frame.gifFrameRect : frame.rect; + const RECT logicalRect = destinationRect; + + const LONGLONG destLeft64 = static_cast(destinationRect.left); + const LONGLONG destTop64 = static_cast(destinationRect.top); + const LONGLONG destRight64 = destLeft64 + static_cast(sourceWidth); + const LONGLONG destBottom64 = destTop64 + static_cast(sourceHeight); + + const LONGLONG canvasWidth64 = static_cast(canvasWidth); + const LONGLONG canvasHeight64 = static_cast(canvasHeight); + + const LONGLONG startX64 = std::max(0, destLeft64); + const LONGLONG startY64 = std::max(0, destTop64); + const LONGLONG endX64 = std::min(canvasWidth64, destRight64); + const LONGLONG endY64 = std::min(canvasHeight64, destBottom64); + + RECT compositedRect{}; + compositedRect.left = static_cast(std::clamp(destLeft64, 0ll, canvasWidth64)); + compositedRect.top = static_cast(std::clamp(destTop64, 0ll, canvasHeight64)); + compositedRect.right = static_cast(std::clamp(destRight64, 0ll, canvasWidth64)); + compositedRect.bottom = static_cast(std::clamp(destBottom64, 0ll, canvasHeight64)); + + if (sourceWidth > 0 && sourceHeight > 0 && startX64 < endX64 && startY64 < endY64) + { + const LONG startX = static_cast(startX64); + const LONG startY = static_cast(startY64); + const LONG endX = static_cast(endX64); + const LONG endY = static_cast(endY64); + + compositedRect.left = startX; + compositedRect.top = startY; + compositedRect.right = endX; + compositedRect.bottom = endY; + + for (LONG y = startY; y < endY; ++y) + { + const size_t destYOffset = static_cast(y) * canvasStride; + const size_t srcY = static_cast(static_cast(y) - destTop64); + const BYTE* srcRow = raw.data() + srcY * sourceStride; + BYTE* destRow = handle.gifComposeCanvas.data() + destYOffset; + + for (LONG x = startX; x < endX; ++x) + { + const size_t srcX = static_cast(static_cast(x) - destLeft64); + const BYTE* srcPixel = srcRow + srcX * kBytesPerPixel; + if (srcPixel[3] == 0) + { + continue; + } + + BYTE* destPixel = destRow + static_cast(x) * kBytesPerPixel; + BlendPremultipliedPixel(destPixel, srcPixel); + } + } + } + + if (compositedRect.right < compositedRect.left) + { + compositedRect.right = compositedRect.left; + } + if (compositedRect.bottom < compositedRect.top) + { + compositedRect.bottom = compositedRect.top; + } + + frame.rect = compositedRect; + if (!frame.hasGifFrameRect) + { + frame.gifFrameRect = logicalRect; + frame.hasGifFrameRect = true; + } + + frame.width = canvasWidth; + frame.height = canvasHeight; + frame.stride = static_cast(canvasStride); + + std::vector compositedPixels; + try + { + compositedPixels = handle.gifComposeCanvas; + } + catch (const std::bad_alloc&) + { + compositedPixels.clear(); + return E_OUTOFMEMORY; + } + + std::vector displayPixels; + try + { + displayPixels = compositedPixels; + } + catch (const std::bad_alloc&) + { + compositedPixels.clear(); + displayPixels.clear(); + return E_OUTOFMEMORY; + } + + UnpremultiplyBuffer(displayPixels, canvasWidth, canvasHeight, static_cast(canvasStride)); + + const bool fillTransparentWithBackground = multiFrameAnimation && handle.gifHasBackgroundColor; + if (fillTransparentWithBackground) + { + FillTransparentPixelsWithColor(displayPixels, canvasWidth, canvasHeight, static_cast(canvasStride), + backgroundR, backgroundG, backgroundB, backgroundA); + } + else + { + ZeroTransparentPixels(displayPixels); + } + + frame.pixels.swap(displayPixels); + frame.compositedPixels.swap(compositedPixels); + if (multiFrameAnimation) + { + frame.useIndexedPixels = false; + frame.allowIndexedDisplay = false; + frame.realizePalette = false; + frame.indexedPixels.clear(); + frame.indexedStride = 0; + frame.indexedBmi = BITMAPINFOHEADER{}; + frame.displayBmi = BITMAPINFOHEADER{}; + frame.displayStride = 0; + + if (frame.paletteHandle) + { + DeleteObject(frame.paletteHandle); + frame.paletteHandle = nullptr; + } + } + return S_OK; +} + +HRESULT RestoreGifCanvasState(ImageHandle& handle, size_t index) +{ + if (index >= handle.frames.size()) + { + return E_INVALIDARG; + } + + FrameData& frame = handle.frames[index]; + if (frame.compositedPixels.empty()) + { + handle.gifComposeCanvas.clear(); + handle.gifSavedCanvas.clear(); + handle.gifCanvasInitialized = false; + return S_FALSE; + } + + try + { + handle.gifComposeCanvas = frame.compositedPixels; + } + catch (const std::bad_alloc&) + { + handle.gifComposeCanvas.clear(); + handle.gifSavedCanvas.clear(); + handle.gifCanvasInitialized = false; + return E_OUTOFMEMORY; + } + + handle.canvasWidth = static_cast(frame.width); + handle.canvasHeight = static_cast(frame.height); + handle.gifCanvasInitialized = true; + + if (frame.disposal == PVDM_PREVIOUS && !frame.disposalBuffer.empty()) + { + try + { + handle.gifSavedCanvas = frame.disposalBuffer; + } + catch (const std::bad_alloc&) + { + handle.gifSavedCanvas.clear(); + return E_OUTOFMEMORY; + } + } + else + { + handle.gifSavedCanvas.clear(); + } + + return S_OK; +} + +HRESULT EnsureTransparencyMask(FrameData& frame) +{ + if (frame.transparencyMask) + { + DeleteObject(frame.transparencyMask); + frame.transparencyMask = nullptr; + } + frame.hasTransparency = false; + + if (frame.pixels.empty() || frame.width == 0 || frame.height == 0) + { + return S_OK; + } + + bool hasTransparentPixel = false; + for (UINT y = 0; y < frame.height && !hasTransparentPixel; ++y) + { + const BYTE* row = frame.pixels.data() + static_cast(y) * frame.stride; + for (UINT x = 0; x < frame.width; ++x) + { + const BYTE alpha = row[static_cast(x) * 4 + 3]; + if (alpha < 128) + { + hasTransparentPixel = true; + break; + } + } + } + + if (!hasTransparentPixel) + { + return S_OK; + } + + const ULONGLONG unalignedStride = (static_cast(frame.width) + 7ull) / 8ull; + const ULONGLONG alignedStride = (unalignedStride + 3ull) & ~3ull; + if (alignedStride > std::numeric_limits::max()) + { + return E_OUTOFMEMORY; + } + const UINT maskStride = static_cast(alignedStride); + const ULONGLONG maskSize64 = alignedStride * static_cast(frame.height); + if (frame.height != 0 && maskSize64 / frame.height != alignedStride) + { + return E_OUTOFMEMORY; + } + if (maskSize64 > static_cast(std::numeric_limits::max())) + { + return E_OUTOFMEMORY; + } + + std::vector maskBuffer; + HRESULT hr = AllocateBuffer(maskBuffer, static_cast(maskSize64)); + if (FAILED(hr)) + { + return hr; + } + + std::fill(maskBuffer.begin(), maskBuffer.end(), 0); + + for (UINT y = 0; y < frame.height; ++y) + { + const BYTE* srcRow = frame.pixels.data() + static_cast(y) * frame.stride; + BYTE* dstRow = maskBuffer.data() + static_cast(y) * maskStride; + for (UINT x = 0; x < frame.width; ++x) + { + const BYTE alpha = srcRow[static_cast(x) * 4 + 3]; + if (alpha < 128) + { + dstRow[x / 8] |= static_cast(0x80u >> (x % 8)); + } + } + } + + BITMAPINFO maskInfo{}; + maskInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + maskInfo.bmiHeader.biWidth = static_cast(frame.width); + maskInfo.bmiHeader.biHeight = -static_cast(frame.height); + maskInfo.bmiHeader.biPlanes = 1; + maskInfo.bmiHeader.biBitCount = 1; + maskInfo.bmiHeader.biCompression = BI_RGB; + maskInfo.bmiHeader.biSizeImage = maskSize64 > std::numeric_limits::max() + ? 0 + : static_cast(maskSize64); + maskInfo.bmiHeader.biXPelsPerMeter = 0; + maskInfo.bmiHeader.biYPelsPerMeter = 0; + maskInfo.bmiHeader.biClrUsed = 2; + maskInfo.bmiHeader.biClrImportant = 2; + maskInfo.bmiColors[0].rgbBlue = 0; + maskInfo.bmiColors[0].rgbGreen = 0; + maskInfo.bmiColors[0].rgbRed = 0; + maskInfo.bmiColors[0].rgbReserved = 0; + maskInfo.bmiColors[1].rgbBlue = 255; + maskInfo.bmiColors[1].rgbGreen = 255; + maskInfo.bmiColors[1].rgbRed = 255; + maskInfo.bmiColors[1].rgbReserved = 0; + + void* bits = nullptr; + HBITMAP mask = CreateDIBSection(nullptr, &maskInfo, DIB_RGB_COLORS, &bits, nullptr, 0); + if (!mask || !bits) + { + if (mask) + { + DeleteObject(mask); + } + return E_OUTOFMEMORY; + } + + memcpy(bits, maskBuffer.data(), maskBuffer.size()); + + frame.transparencyMask = mask; + frame.hasTransparency = true; + return S_OK; +} + +void FillBufferWithColor(std::vector& buffer, UINT width, UINT height, BYTE r, BYTE g, BYTE b, BYTE a) +{ + if (buffer.empty() || width == 0 || height == 0) + { + return; + } + const unsigned int alpha = a; + const BYTE fillR = static_cast((static_cast(r) * alpha + 127u) / 255u); + const BYTE fillG = static_cast((static_cast(g) * alpha + 127u) / 255u); + const BYTE fillB = static_cast((static_cast(b) * alpha + 127u) / 255u); + const size_t stride = static_cast(width) * kBytesPerPixel; + for (UINT y = 0; y < height; ++y) + { + BYTE* row = buffer.data() + static_cast(y) * stride; + for (UINT x = 0; x < width; ++x) + { + BYTE* pixel = row + static_cast(x) * 4; + pixel[0] = fillB; + pixel[1] = fillG; + pixel[2] = fillR; + pixel[3] = a; + } + } +} + +void ClearBufferRect(std::vector& buffer, UINT width, UINT height, const RECT& rect, BYTE r, BYTE g, BYTE b, + BYTE a) +{ + if (buffer.empty() || width == 0 || height == 0) + { + return; + } + + const LONG maxX = static_cast(width); + const LONG maxY = static_cast(height); + const LONG left = std::clamp(rect.left, 0L, maxX); + const LONG top = std::clamp(rect.top, 0L, maxY); + const LONG right = std::clamp(rect.right, left, maxX); + const LONG bottom = std::clamp(rect.bottom, top, maxY); + if (right <= left || bottom <= top) + { + return; + } + + const unsigned int alpha = a; + const BYTE fillR = static_cast((static_cast(r) * alpha + 127u) / 255u); + const BYTE fillG = static_cast((static_cast(g) * alpha + 127u) / 255u); + const BYTE fillB = static_cast((static_cast(b) * alpha + 127u) / 255u); + const size_t stride = static_cast(width) * kBytesPerPixel; + for (LONG y = top; y < bottom; ++y) + { + BYTE* row = buffer.data() + static_cast(y) * stride + static_cast(left) * 4; + for (LONG x = left; x < right; ++x) + { + row[0] = fillB; + row[1] = fillG; + row[2] = fillR; + row[3] = a; + row += 4; + } + } +} + +void ZeroTransparentPixels(std::vector& buffer) +{ + if (buffer.empty()) + { + return; + } + + const size_t totalPixels = buffer.size() / kBytesPerPixel; + BYTE* data = buffer.data(); + for (size_t i = 0; i < totalPixels; ++i) + { + BYTE* pixel = data + i * kBytesPerPixel; + if (pixel[3] == 0) + { + pixel[0] = 0; + pixel[1] = 0; + pixel[2] = 0; + } + } +} + +void FillTransparentPixelsWithColor(std::vector& buffer, UINT width, UINT height, UINT stride, BYTE r, BYTE g, + BYTE b, BYTE a) +{ + if (buffer.empty() || width == 0 || height == 0) + { + return; + } + + (void)a; + + const size_t rowStride = static_cast(stride); + const size_t expectedStride = static_cast(width) * kBytesPerPixel; + if (rowStride < expectedStride) + { + return; + } + + for (UINT y = 0; y < height; ++y) + { + BYTE* row = buffer.data() + rowStride * static_cast(y); + for (UINT x = 0; x < width; ++x) + { + BYTE* pixel = row + static_cast(x) * kBytesPerPixel; + if (pixel[3] == 0) + { + pixel[0] = b; + pixel[1] = g; + pixel[2] = r; + pixel[3] = 0; + } + } + } +} + +void BlendPremultipliedPixel(BYTE* dest, const BYTE* src) +{ + const unsigned int srcAlpha = src[3]; + if (srcAlpha == 0) + { + return; + } + + if (srcAlpha == 255) + { + dest[0] = src[0]; + dest[1] = src[1]; + dest[2] = src[2]; + dest[3] = 255; + return; + } + + const unsigned int destAlpha = dest[3]; + const unsigned int invSrcAlpha = 255u - srcAlpha; + + unsigned int outAlpha = srcAlpha + (destAlpha * invSrcAlpha + 127u) / 255u; + if (outAlpha > 255u) + { + outAlpha = 255u; + } + + const unsigned int destBlueScaled = (static_cast(dest[0]) * invSrcAlpha + 127u) / 255u; + const unsigned int destGreenScaled = (static_cast(dest[1]) * invSrcAlpha + 127u) / 255u; + const unsigned int destRedScaled = (static_cast(dest[2]) * invSrcAlpha + 127u) / 255u; + + unsigned int outBlue = static_cast(src[0]) + destBlueScaled; + unsigned int outGreen = static_cast(src[1]) + destGreenScaled; + unsigned int outRed = static_cast(src[2]) + destRedScaled; + + if (outBlue > outAlpha) + { + outBlue = outAlpha; + } + if (outGreen > outAlpha) + { + outGreen = outAlpha; + } + if (outRed > outAlpha) + { + outRed = outAlpha; + } + + dest[0] = static_cast(outBlue); + dest[1] = static_cast(outGreen); + dest[2] = static_cast(outRed); + dest[3] = static_cast(outAlpha); +} + +bool IsPaletteUnavailable(HRESULT hr) +{ +#if defined(WINCODEC_ERR_PALETTEUNAVAILABLE) + if (hr == WINCODEC_ERR_PALETTEUNAVAILABLE) + { + return true; + } +#endif +#if defined(WINCODEC_ERR_NOTINITIALIZED) + if (hr == WINCODEC_ERR_NOTINITIALIZED) + { + return true; + } +#endif +#if defined(WINCODEC_ERR_PROPERTYNOTFOUND) + if (hr == WINCODEC_ERR_PROPERTYNOTFOUND) + { + return true; + } +#endif +#if defined(WINCODEC_ERR_UNSUPPORTEDOPERATION) + if (hr == WINCODEC_ERR_UNSUPPORTEDOPERATION) + { + return true; + } +#endif + return false; +} + +HPALETTE CreateGdiPalette(const std::vector& entries) +{ + if (entries.empty()) + { + return nullptr; + } + if (entries.size() > 256) + { + return nullptr; + } + + const size_t paletteSize = sizeof(LOGPALETTE) + (entries.size() - 1) * sizeof(PALETTEENTRY); + std::vector buffer; + try + { + buffer.resize(paletteSize); + } + catch (const std::bad_alloc&) + { + return nullptr; + } + + auto* logPalette = reinterpret_cast(buffer.data()); + logPalette->palVersion = 0x300; + logPalette->palNumEntries = static_cast(entries.size()); + for (size_t i = 0; i < entries.size(); ++i) + { + const RGBQUAD& quad = entries[i]; + logPalette->palPalEntry[i].peRed = quad.rgbRed; + logPalette->palPalEntry[i].peGreen = quad.rgbGreen; + logPalette->palPalEntry[i].peBlue = quad.rgbBlue; + logPalette->palPalEntry[i].peFlags = PC_NOCOLLAPSE; + } + + return CreatePalette(logPalette); +} + +class PaletteSelector +{ +public: + PaletteSelector(HDC dc, HPALETTE palette, bool enable) + : m_dc(dc) + , m_previous(nullptr) + , m_active(false) + { + if (!enable || !m_dc || !palette) + { + return; + } + + if ((GetDeviceCaps(m_dc, RASTERCAPS) & RC_PALETTE) == 0) + { + return; + } + + m_previous = SelectPalette(m_dc, palette, FALSE); + RealizePalette(m_dc); + m_active = true; + } + + PaletteSelector(const PaletteSelector&) = delete; + PaletteSelector& operator=(const PaletteSelector&) = delete; + + ~PaletteSelector() + { + if (!m_active || !m_dc) + { + return; + } + + HPALETTE restore = m_previous ? m_previous : static_cast(GetStockObject(DEFAULT_PALETTE)); + if (restore) + { + SelectPalette(m_dc, restore, TRUE); + RealizePalette(m_dc); + } + } + +private: + HDC m_dc; + HPALETTE m_previous; + bool m_active; +}; + +HRESULT PopulateFramePalette(IWICImagingFactory* factory, FrameData& frame) +{ + if (frame.paletteHandle) + { + DeleteObject(frame.paletteHandle); + frame.paletteHandle = nullptr; + } + frame.palette.clear(); + + frame.useIndexedPixels = false; + frame.indexedPixels.clear(); + frame.indexedStride = 0; + frame.indexedBmi = BITMAPINFOHEADER{}; + frame.displayBmi = BITMAPINFOHEADER{}; + frame.displayStride = 0; + + const bool needPalette = frame.allowIndexedDisplay || frame.realizePalette; + if (!needPalette) + { + frame.paletteColorCount = 0; + return S_OK; + } + + if (!factory || !frame.frame) + { + frame.paletteColorCount = 0; + frame.realizePalette = false; + return S_OK; + } + + if (frame.paletteColorCount == 0 && frame.bitsPerPixel > 8) + { + frame.realizePalette = false; + return S_OK; + } + + Microsoft::WRL::ComPtr palette; + HRESULT hr = factory->CreatePalette(&palette); + if (FAILED(hr)) + { + return hr; + } + + hr = frame.frame->CopyPalette(palette.Get()); + if (FAILED(hr)) + { + if (IsPaletteUnavailable(hr)) + { + frame.paletteColorCount = 0; + frame.realizePalette = false; + return S_OK; + } + return hr; + } + + UINT colorCount = 0; + hr = palette->GetColorCount(&colorCount); + if (FAILED(hr)) + { + return hr; + } + if (colorCount == 0) + { + frame.paletteColorCount = 0; + frame.realizePalette = false; + return S_OK; + } + + std::vector colors(colorCount); + UINT actual = colorCount; + hr = palette->GetColors(colorCount, colors.data(), &actual); + if (FAILED(hr)) + { + return hr; + } + colors.resize(actual); + + if (frame.gifHasTransparentColor) + { + const size_t transparent = static_cast(frame.gifTransparentIndex); + if (transparent < colors.size()) + { + colors[transparent] = 0u; + } + } + + try + { + frame.palette.resize(colors.size()); + } + catch (const std::bad_alloc&) + { + frame.palette.clear(); + return E_OUTOFMEMORY; + } + + for (size_t i = 0; i < colors.size(); ++i) + { + const WICColor color = colors[i]; + RGBQUAD quad{}; + quad.rgbRed = static_cast((color >> 16) & 0xFF); + quad.rgbGreen = static_cast((color >> 8) & 0xFF); + quad.rgbBlue = static_cast(color & 0xFF); + quad.rgbReserved = static_cast((color >> 24) & 0xFF); + frame.palette[i] = quad; + } + + frame.paletteColorCount = static_cast(colors.size()); + if (!frame.palette.empty()) + { + HPALETTE paletteHandle = CreateGdiPalette(frame.palette); + if (paletteHandle) + { + frame.paletteHandle = paletteHandle; + } + else if (frame.palette.size() <= 256) + { + const DWORD error = GetLastError(); + return HRESULT_FROM_WIN32(error != 0 ? error : ERROR_NOT_ENOUGH_MEMORY); + } + } + + if (frame.bitsPerPixel > 0) + { + frame.reportedColors = + DetermineColorCount(frame.sourcePixelFormat, frame.bitsPerPixel, frame.paletteColorCount, frame.colorModel); + } + + frame.realizePalette = frame.realizePalette && frame.paletteColorCount > 0; + return S_OK; +} + +HRESULT BuildIndexedPixelBuffer(FrameData& frame) +{ + frame.useIndexedPixels = false; + frame.indexedPixels.clear(); + frame.indexedStride = 0; + frame.indexedBmi = BITMAPINFOHEADER{}; + + if (!frame.allowIndexedDisplay) + { + return S_FALSE; + } + + if (frame.hasTransparency) + { + return S_FALSE; + } + + if (frame.bitsPerPixel > 8) + { + return S_FALSE; + } + if (frame.width == 0 || frame.height == 0) + { + return S_FALSE; + } + + if (frame.paletteColorCount == 0) + { + return S_FALSE; + } + + const size_t alignedStride = (static_cast(frame.width) + 3u) & ~static_cast(3u); + if (alignedStride > static_cast(std::numeric_limits::max())) + { + return E_OUTOFMEMORY; + } + const size_t totalSize = alignedStride * static_cast(frame.height); + try + { + frame.indexedPixels.resize(totalSize); + } + catch (const std::bad_alloc&) + { + frame.indexedPixels.clear(); + return E_OUTOFMEMORY; + } + + std::unordered_map colorToIndex; + colorToIndex.reserve(std::min(frame.paletteColorCount != 0 ? frame.paletteColorCount : 256u, 256u)); + std::vector paletteFromPixels; + paletteFromPixels.reserve(256); + + const BYTE* srcBase = frame.pixels.data(); + for (UINT y = 0; y < frame.height; ++y) + { + const BYTE* srcRow = srcBase + static_cast(y) * frame.stride; + BYTE* dstRow = frame.indexedPixels.data() + static_cast(y) * alignedStride; + for (UINT x = 0; x < frame.width; ++x) + { + const BYTE* srcPixel = srcRow + static_cast(x) * kBytesPerPixel; + const BYTE alpha = srcPixel[3]; + const DWORD key = (static_cast(alpha) << 24) | (static_cast(srcPixel[2]) << 16) | + (static_cast(srcPixel[1]) << 8) | static_cast(srcPixel[0]); + + BYTE paletteIndex = 0; + auto it = colorToIndex.find(key); + if (it == colorToIndex.end()) + { + if (colorToIndex.size() >= 256) + { + frame.indexedPixels.clear(); + frame.indexedStride = 0; + return S_FALSE; + } + + paletteIndex = static_cast(colorToIndex.size()); + colorToIndex.emplace(key, paletteIndex); + + RGBQUAD quad{}; + quad.rgbBlue = srcPixel[0]; + quad.rgbGreen = srcPixel[1]; + quad.rgbRed = srcPixel[2]; + quad.rgbReserved = alpha; + paletteFromPixels.push_back(quad); + } + else + { + paletteIndex = it->second; + } + + dstRow[x] = paletteIndex; + } + + if (alignedStride > static_cast(frame.width)) + { + memset(dstRow + frame.width, 0, alignedStride - static_cast(frame.width)); + } + } + + if (!paletteFromPixels.empty()) + { + if (frame.paletteHandle) + { + DeleteObject(frame.paletteHandle); + frame.paletteHandle = nullptr; + } + + frame.palette = paletteFromPixels; + frame.paletteColorCount = static_cast(frame.palette.size()); + + HPALETTE newPaletteHandle = CreateGdiPalette(frame.palette); + if (!frame.palette.empty() && !newPaletteHandle) + { + const DWORD error = GetLastError(); + return HRESULT_FROM_WIN32(error != 0 ? error : ERROR_NOT_ENOUGH_MEMORY); + } + frame.paletteHandle = newPaletteHandle; + } + else + { + frame.palette.clear(); + frame.paletteColorCount = 0; + if (frame.paletteHandle) + { + DeleteObject(frame.paletteHandle); + frame.paletteHandle = nullptr; + } + } + + if (frame.bitsPerPixel > 0) + { + frame.reportedColors = + DetermineColorCount(frame.sourcePixelFormat, frame.bitsPerPixel, frame.paletteColorCount, frame.colorModel); + } + + frame.useIndexedPixels = true; + frame.indexedStride = static_cast(alignedStride); + frame.indexedBmi.biSize = sizeof(BITMAPINFOHEADER); + frame.indexedBmi.biWidth = static_cast(frame.width); + frame.indexedBmi.biHeight = -static_cast(frame.height); + frame.indexedBmi.biPlanes = 1; + frame.indexedBmi.biBitCount = 8; + frame.indexedBmi.biCompression = BI_RGB; + frame.indexedBmi.biSizeImage = totalSize > std::numeric_limits::max() + ? 0 + : static_cast(totalSize); + frame.indexedBmi.biXPelsPerMeter = frame.bmi.biXPelsPerMeter; + frame.indexedBmi.biYPelsPerMeter = frame.bmi.biYPelsPerMeter; + const UINT paletteUsed = std::min(frame.paletteColorCount, 256u); + frame.indexedBmi.biClrUsed = paletteUsed; + frame.indexedBmi.biClrImportant = paletteUsed; + return S_OK; +} + + +HRESULT CreateSequenceBitmaps(const FrameData& frame, const RECT& rect, HBITMAP& colorBitmap, HBITMAP& maskBitmap) +{ + colorBitmap = nullptr; + maskBitmap = nullptr; + + if (frame.pixels.empty() || frame.stride == 0 || frame.width == 0 || frame.height == 0) + { + return E_INVALIDARG; + } + + const LONG maxWidth = static_cast(frame.width); + const LONG maxHeight = static_cast(frame.height); + + const LONG left = std::clamp(rect.left, 0L, maxWidth); + const LONG top = std::clamp(rect.top, 0L, maxHeight); + const LONG right = std::clamp(rect.right, left, maxWidth); + const LONG bottom = std::clamp(rect.bottom, top, maxHeight); + + const LONG visibleWidth = right - left; + const LONG visibleHeight = bottom - top; + const LONG bitmapWidth = std::max(visibleWidth, 1); + const LONG bitmapHeight = std::max(visibleHeight, 1); + + const size_t dstStride = static_cast(bitmapWidth) * kBytesPerPixel; + const size_t bitmapSize = dstStride * static_cast(bitmapHeight); + + BITMAPINFO colorInfo{}; + colorInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + colorInfo.bmiHeader.biWidth = bitmapWidth; + colorInfo.bmiHeader.biHeight = -bitmapHeight; + colorInfo.bmiHeader.biPlanes = 1; + colorInfo.bmiHeader.biBitCount = 32; + colorInfo.bmiHeader.biCompression = BI_RGB; + colorInfo.bmiHeader.biSizeImage = bitmapSize > std::numeric_limits::max() ? 0 + : static_cast(bitmapSize); + + void* colorBits = nullptr; + HBITMAP color = CreateDIBSection(nullptr, &colorInfo, DIB_RGB_COLORS, &colorBits, nullptr, 0); + if (!color || !colorBits) + { + if (color) + { + DeleteObject(color); + } + return E_OUTOFMEMORY; + } + + BYTE* dstBase = reinterpret_cast(colorBits); + memset(dstBase, 0, bitmapSize); + + if (visibleWidth > 0 && visibleHeight > 0) + { + for (LONG y = 0; y < visibleHeight; ++y) + { + const BYTE* srcRow = frame.pixels.data() + + (static_cast(top + y) * frame.stride) + + static_cast(left) * kBytesPerPixel; + BYTE* dstRow = dstBase + static_cast(y) * dstStride; + memcpy(dstRow, srcRow, static_cast(visibleWidth) * kBytesPerPixel); + } + } + + const ULONGLONG maskStride64 = ((static_cast(bitmapWidth) + 7ull) / 8ull + 3ull) & ~3ull; + if (maskStride64 > static_cast(std::numeric_limits::max())) + { + DeleteObject(color); + return E_OUTOFMEMORY; + } + const UINT maskStride = static_cast(maskStride64); + const ULONGLONG maskSize64 = maskStride64 * static_cast(bitmapHeight); + if (bitmapHeight != 0 && maskSize64 / bitmapHeight != maskStride64) + { + DeleteObject(color); + return E_OUTOFMEMORY; + } + if (maskSize64 > static_cast(std::numeric_limits::max())) + { + DeleteObject(color); + return E_OUTOFMEMORY; + } + + std::vector maskBuffer; + HRESULT hr = AllocateBuffer(maskBuffer, static_cast(maskSize64)); + if (FAILED(hr)) + { + DeleteObject(color); + return hr; + } + memset(maskBuffer.data(), 0, maskBuffer.size()); + + bool hasTransparency = false; + if (visibleWidth > 0 && visibleHeight > 0) + { + for (LONG y = 0; y < visibleHeight; ++y) + { + const BYTE* srcRow = frame.pixels.data() + + (static_cast(top + y) * frame.stride) + + static_cast(left) * kBytesPerPixel; + BYTE* maskRow = maskBuffer.data() + static_cast(y) * maskStride; + for (LONG x = 0; x < visibleWidth; ++x) + { + if (srcRow[static_cast(x) * 4 + 3] == 0) + { + maskRow[x / 8] |= static_cast(0x80u >> (x % 8)); + hasTransparency = true; + } + } + } + } + else + { + if (bitmapWidth > 0 && bitmapHeight > 0) + { + for (LONG y = 0; y < bitmapHeight; ++y) + { + BYTE* maskRow = maskBuffer.data() + static_cast(y) * maskStride; + maskRow[0] = 0xFF; + } + hasTransparency = true; + } + } + + HBITMAP mask = nullptr; + if (hasTransparency) + { + BITMAPINFO maskInfo{}; + maskInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + maskInfo.bmiHeader.biWidth = bitmapWidth; + maskInfo.bmiHeader.biHeight = -bitmapHeight; + maskInfo.bmiHeader.biPlanes = 1; + maskInfo.bmiHeader.biBitCount = 1; + maskInfo.bmiHeader.biCompression = BI_RGB; + maskInfo.bmiHeader.biSizeImage = maskSize64 > std::numeric_limits::max() + ? 0 + : static_cast(maskSize64); + maskInfo.bmiColors[0].rgbBlue = 0; + maskInfo.bmiColors[0].rgbGreen = 0; + maskInfo.bmiColors[0].rgbRed = 0; + maskInfo.bmiColors[0].rgbReserved = 0; + maskInfo.bmiColors[1].rgbBlue = 255; + maskInfo.bmiColors[1].rgbGreen = 255; + maskInfo.bmiColors[1].rgbRed = 255; + maskInfo.bmiColors[1].rgbReserved = 0; + + void* maskBits = nullptr; + mask = CreateDIBSection(nullptr, &maskInfo, DIB_RGB_COLORS, &maskBits, nullptr, 0); + if (!mask || !maskBits) + { + if (mask) + { + DeleteObject(mask); + } + DeleteObject(color); + return E_OUTOFMEMORY; + } + memcpy(maskBits, maskBuffer.data(), maskBuffer.size()); + } + + colorBitmap = color; + maskBitmap = mask; + return S_OK; +} + +HRESULT EnsureScaledBitmap(ImageHandle& handle, FrameData& frame, UINT width, UINT height) +{ + if (width == 0 || height == 0) + { + return E_INVALIDARG; + } + + if (frame.scaledBitmap && frame.scaledWidth == width && frame.scaledHeight == height) + { + return S_OK; + } + + if (frame.scaledBitmap) + { + DeleteObject(frame.scaledBitmap); + frame.scaledBitmap = nullptr; + } + frame.scaledPixels.clear(); + frame.scaledStride = 0; + frame.scaledWidth = 0; + frame.scaledHeight = 0; + + IWICImagingFactory* factory = handle.backend ? handle.backend->Factory() : nullptr; + if (!factory) + { + return E_FAIL; + } + + if (frame.width > std::numeric_limits::max() || frame.height > std::numeric_limits::max()) + { + return E_OUTOFMEMORY; + } + const bool usePremultipliedSource = frame.hasTransparency && !frame.compositedPixels.empty(); + const std::vector& sourceBuffer = usePremultipliedSource ? frame.compositedPixels : frame.pixels; + if (sourceBuffer.empty() || frame.width == 0 || frame.height == 0) + { + return E_FAIL; + } + const size_t pixelBytes = sourceBuffer.size(); + if (pixelBytes > static_cast(std::numeric_limits::max())) + { + return E_OUTOFMEMORY; + } + + std::vector premultipliedBuffer; + GUID bitmapPixelFormat = usePremultipliedSource ? GUID_WICPixelFormat32bppPBGRA : GUID_WICPixelFormat32bppBGRA; + BYTE* bitmapMemory = const_cast(sourceBuffer.data()); + if (!usePremultipliedSource && frame.hasTransparency) + { + try + { + premultipliedBuffer.assign(sourceBuffer.begin(), sourceBuffer.end()); + } + catch (const std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + + const size_t pixelCount = premultipliedBuffer.size() / kBytesPerPixel; + for (size_t i = 0; i < pixelCount; ++i) + { + BYTE* pixel = premultipliedBuffer.data() + i * kBytesPerPixel; + const BYTE alpha = pixel[3]; + if (alpha == 0) + { + pixel[0] = 0; + pixel[1] = 0; + pixel[2] = 0; + continue; + } + if (alpha == 255) + { + continue; + } + + const unsigned int a = alpha; + pixel[0] = static_cast((static_cast(pixel[0]) * a + 127u) / 255u); + pixel[1] = static_cast((static_cast(pixel[1]) * a + 127u) / 255u); + pixel[2] = static_cast((static_cast(pixel[2]) * a + 127u) / 255u); + } + + bitmapPixelFormat = GUID_WICPixelFormat32bppPBGRA; + bitmapMemory = premultipliedBuffer.data(); + } + + Microsoft::WRL::ComPtr memoryBitmap; + HRESULT hr = factory->CreateBitmapFromMemory(frame.width, frame.height, bitmapPixelFormat, frame.stride, + static_cast(pixelBytes), bitmapMemory, &memoryBitmap); + if (FAILED(hr)) + { + return hr; + } + + Microsoft::WRL::ComPtr scaler; + hr = factory->CreateBitmapScaler(&scaler); + if (FAILED(hr)) + { + return hr; + } + + hr = scaler->Initialize(memoryBitmap.Get(), width, height, WICBitmapInterpolationModeHighQualityCubic); + if (FAILED(hr)) + { + return hr; + } + + const ULONGLONG stride64 = static_cast(width) * kBytesPerPixel; + if (stride64 > static_cast(std::numeric_limits::max())) + { + return E_OUTOFMEMORY; + } + const UINT stride = static_cast(stride64); + const ULONGLONG bufferSize64 = stride64 * static_cast(height); + if (height != 0 && bufferSize64 / height != stride64) + { + return E_OUTOFMEMORY; + } + if (bufferSize64 > static_cast(std::numeric_limits::max())) + { + return E_OUTOFMEMORY; + } + + try + { + frame.scaledPixels.resize(static_cast(bufferSize64)); + } + catch (const std::bad_alloc&) + { + frame.scaledPixels.clear(); + return E_OUTOFMEMORY; + } + + hr = scaler->CopyPixels(nullptr, stride, static_cast(frame.scaledPixels.size()), frame.scaledPixels.data()); + if (FAILED(hr)) + { + frame.scaledPixels.clear(); + return hr; + } + + BITMAPINFO scaledInfo{}; + scaledInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + scaledInfo.bmiHeader.biWidth = static_cast(width); + scaledInfo.bmiHeader.biHeight = -static_cast(height); + scaledInfo.bmiHeader.biPlanes = 1; + scaledInfo.bmiHeader.biBitCount = 32; + scaledInfo.bmiHeader.biCompression = BI_RGB; + scaledInfo.bmiHeader.biSizeImage = frame.scaledPixels.size() > std::numeric_limits::max() + ? 0 + : static_cast(frame.scaledPixels.size()); + scaledInfo.bmiHeader.biXPelsPerMeter = frame.bmi.biXPelsPerMeter; + scaledInfo.bmiHeader.biYPelsPerMeter = frame.bmi.biYPelsPerMeter; + + void* bits = nullptr; + HBITMAP scaledBitmap = CreateDIBSection(nullptr, &scaledInfo, DIB_RGB_COLORS, &bits, nullptr, 0); + if (!scaledBitmap || !bits) + { + if (scaledBitmap) + { + DeleteObject(scaledBitmap); + } + frame.scaledPixels.clear(); + return E_OUTOFMEMORY; + } + + memcpy(bits, frame.scaledPixels.data(), frame.scaledPixels.size()); + if (frame.hasTransparency) + { + BYTE* target = static_cast(bits); + const size_t pixelCount = frame.scaledPixels.size() / kBytesPerPixel; + for (size_t i = 0; i < pixelCount; ++i) + { + BYTE* pixel = target + i * kBytesPerPixel; + if (pixel[3] == 0) + { + pixel[0] = 0; + pixel[1] = 0; + pixel[2] = 0; + } + } + + UnpremultiplyBuffer(frame.scaledPixels, width, height, stride); + ZeroTransparentPixels(frame.scaledPixels); + } + + frame.scaledBitmap = scaledBitmap; + frame.scaledStride = stride; + frame.scaledWidth = width; + frame.scaledHeight = height; + return S_OK; +} + +DWORD DetermineColorModelFromPixelFormat(const GUID& pixelFormat) +{ + if (IsEqualGUID(pixelFormat, GUID_WICPixelFormatBlackWhite) || + IsEqualGUID(pixelFormat, GUID_WICPixelFormat2bppGray) || + IsEqualGUID(pixelFormat, GUID_WICPixelFormat4bppGray) || + IsEqualGUID(pixelFormat, GUID_WICPixelFormat8bppGray) || + IsEqualGUID(pixelFormat, GUID_WICPixelFormat16bppGray) || +#ifdef GUID_WICPixelFormat16bppGrayFixedPoint + IsEqualGUID(pixelFormat, GUID_WICPixelFormat16bppGrayFixedPoint) || +#endif +#ifdef GUID_WICPixelFormat16bppGrayHalf + IsEqualGUID(pixelFormat, GUID_WICPixelFormat16bppGrayHalf) || +#endif +#ifdef GUID_WICPixelFormat32bppGrayFloat + IsEqualGUID(pixelFormat, GUID_WICPixelFormat32bppGrayFloat) || +#endif + IsEqualGUID(pixelFormat, GUID_WICPixelFormat8bppY)) + { + return PVCM_GRAYS; + } + + if (IsEqualGUID(pixelFormat, GUID_WICPixelFormat32bppCMYK) || + IsEqualGUID(pixelFormat, GUID_WICPixelFormat40bppCMYKAlpha) +#ifdef GUID_WICPixelFormat64bppCMYK + || IsEqualGUID(pixelFormat, GUID_WICPixelFormat64bppCMYK) +#endif +#ifdef GUID_WICPixelFormat80bppCMYKAlpha + || IsEqualGUID(pixelFormat, GUID_WICPixelFormat80bppCMYKAlpha) +#endif +#ifdef GUID_WICPixelFormat64bppCMYKFixedPoint + || IsEqualGUID(pixelFormat, GUID_WICPixelFormat64bppCMYKFixedPoint) +#endif +#ifdef GUID_WICPixelFormat128bppCMYKFixedPoint + || IsEqualGUID(pixelFormat, GUID_WICPixelFormat128bppCMYKFixedPoint) +#endif + ) + { + return PVCM_CMYK; + } + + return PVCM_RGB; +} + +DWORD DetermineColorCount(const GUID& pixelFormat, UINT bitsPerPixel, UINT paletteColors, DWORD colorModel) +{ + if (paletteColors > 0) + { + return paletteColors; + } + + if (bitsPerPixel == 0) + { + return PV_COLOR_TC32; + } + + if (bitsPerPixel <= 8) + { + const UINT rawColors = 1u << bitsPerPixel; + return std::max(2u, rawColors); + } + + if (bitsPerPixel == 15) + { + return PV_COLOR_HC15; + } + + if (bitsPerPixel == 16) + { + if (IsEqualGUID(pixelFormat, GUID_WICPixelFormat16bppBGR555) +#ifdef GUID_WICPixelFormat16bppBGRA5551 + || IsEqualGUID(pixelFormat, GUID_WICPixelFormat16bppBGRA5551) +#endif + ) + { + return PV_COLOR_HC15; + } + return PV_COLOR_HC16; + } + + if (bitsPerPixel == 24) + { + return PV_COLOR_TC24; + } + + if (bitsPerPixel == 32) + { + return PV_COLOR_TC32; + } + + if (colorModel == PVCM_GRAYS && bitsPerPixel <= 16) + { + if (bitsPerPixel >= 31) + { + return PV_COLOR_TC32; + } + const UINT rawColors = 1u << bitsPerPixel; + return std::max(2u, rawColors); + } + + if (bitsPerPixel > 32) + { + return PV_COLOR_TC32; + } + + return PV_COLOR_TC32; +} + +HRESULT ConvertBgraSourceToCmyk(IWICImagingFactory* factory, IWICBitmapSource* source, + IWICBitmapSource** convertedSource) +{ + if (!factory || !source || !convertedSource) + { + return E_INVALIDARG; + } + + *convertedSource = nullptr; + + UINT width = 0; + UINT height = 0; + HRESULT hr = source->GetSize(&width, &height); + if (FAILED(hr)) + { + return hr; + } + if (width == 0 || height == 0) + { + return WINCODEC_ERR_INVALIDPARAMETER; + } + + const ULONGLONG stride64 = static_cast(width) * 4ull; + if (stride64 > static_cast(std::numeric_limits::max())) + { + return E_OUTOFMEMORY; + } + const UINT stride = static_cast(stride64); + const ULONGLONG buffer64 = stride64 * static_cast(height); + if (height != 0 && buffer64 / height != stride64) + { + return E_OUTOFMEMORY; + } + if (buffer64 > static_cast(std::numeric_limits::max())) + { + return E_OUTOFMEMORY; + } + + std::vector bgra(static_cast(buffer64)); + WICRect rect{0, 0, static_cast(width), static_cast(height)}; + hr = source->CopyPixels(&rect, stride, static_cast(bgra.size()), bgra.data()); + if (FAILED(hr)) + { + return hr; + } + + std::vector cmyk(bgra.size()); + for (UINT y = 0; y < height; ++y) + { + const BYTE* srcRow = bgra.data() + static_cast(y) * stride; + BYTE* dstRow = cmyk.data() + static_cast(y) * stride; + for (UINT x = 0; x < width; ++x) + { + const BYTE* pixel = srcRow + static_cast(x) * 4; + BYTE* out = dstRow + static_cast(x) * 4; + + const BYTE alpha = pixel[3]; + BYTE r = pixel[2]; + BYTE g = pixel[1]; + BYTE b = pixel[0]; + if (alpha < 255) + { + const unsigned int invAlpha = 255u - static_cast(alpha); + r = static_cast((static_cast(r) * alpha + 255u * invAlpha + 127u) / 255u); + g = static_cast((static_cast(g) * alpha + 255u * invAlpha + 127u) / 255u); + b = static_cast((static_cast(b) * alpha + 255u * invAlpha + 127u) / 255u); + } + + const double rf = static_cast(r) / 255.0; + const double gf = static_cast(g) / 255.0; + const double bf = static_cast(b) / 255.0; + const double maxRgb = std::max({rf, gf, bf}); + const double k = 1.0 - maxRgb; + + double c = 0.0; + double m = 0.0; + double yVal = 0.0; + if (k < 0.999999) + { + const double inv = 1.0 - k; + c = (1.0 - rf - k) / inv; + m = (1.0 - gf - k) / inv; + yVal = (1.0 - bf - k) / inv; + } + + const auto clampByte = [](double value) -> BYTE { + if (value <= 0.0) + { + return 0; + } + if (value >= 1.0) + { + return 255; + } + return static_cast(std::floor(value * 255.0 + 0.5)); + }; + + out[0] = clampByte(c); + out[1] = clampByte(m); + out[2] = clampByte(yVal); + out[3] = clampByte(k); + } + } + + Microsoft::WRL::ComPtr bitmap; + hr = factory->CreateBitmapFromMemory(width, height, GUID_WICPixelFormat32bppCMYK, stride, + static_cast(cmyk.size()), cmyk.data(), &bitmap); + if (FAILED(hr)) + { + return hr; + } + + return bitmap.CopyTo(convertedSource); +} + +HRESULT FinalizeDecodedFrame(Backend* backend, FrameData& frame) +{ + const size_t lineCount = static_cast(frame.height); + if (lineCount > frame.linePointers.max_size()) + { + return E_OUTOFMEMORY; + } + IWICImagingFactory* factory = backend ? backend->Factory() : nullptr; + HRESULT paletteHr = PopulateFramePalette(factory, frame); + if (FAILED(paletteHr)) + { + return paletteHr; + } + HRESULT maskHr = EnsureTransparencyMask(frame); + if (FAILED(maskHr)) + { + return maskHr; + } + HRESULT indexedHr = BuildIndexedPixelBuffer(frame); + if (FAILED(indexedHr) && indexedHr != S_FALSE) + { + return indexedHr; + } + try + { + frame.linePointers.resize(lineCount); + } + catch (const std::bad_alloc&) + { + frame.linePointers.clear(); + return E_OUTOFMEMORY; + } + + const bool useIndexed = frame.useIndexedPixels && !frame.indexedPixels.empty(); + const size_t lineStride = useIndexed ? static_cast(frame.indexedStride) : frame.stride; + BYTE* baseLine = useIndexed ? frame.indexedPixels.data() : frame.pixels.data(); + for (UINT y = 0; y < frame.height; ++y) + { + frame.linePointers[y] = baseLine + static_cast(y) * lineStride; + } + + frame.bmi.biSize = sizeof(BITMAPINFOHEADER); + frame.bmi.biWidth = static_cast(frame.width); + frame.bmi.biHeight = -static_cast(frame.height); + frame.bmi.biPlanes = 1; + frame.bmi.biBitCount = 32; + frame.bmi.biCompression = BI_RGB; + const size_t pixelBytes = frame.pixels.size(); + frame.bmi.biSizeImage = pixelBytes > std::numeric_limits::max() ? 0 + : static_cast(pixelBytes); + frame.bmi.biXPelsPerMeter = 0; + frame.bmi.biYPelsPerMeter = 0; + if (useIndexed) + { + frame.displayStride = frame.indexedStride; + frame.displayBmi = frame.indexedBmi; + } + else + { + frame.displayStride = frame.stride; + frame.displayBmi = frame.bmi; + } + + if (frame.hbitmap) + { + DeleteObject(frame.hbitmap); + frame.hbitmap = nullptr; + } + if (frame.scaledBitmap) + { + DeleteObject(frame.scaledBitmap); + frame.scaledBitmap = nullptr; + } + frame.scaledPixels.clear(); + frame.scaledStride = 0; + frame.scaledWidth = 0; + frame.scaledHeight = 0; + + void* bits = nullptr; + BITMAPINFO bmi{}; + bmi.bmiHeader = frame.bmi; + frame.hbitmap = CreateDIBSection(nullptr, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0); + if (!frame.hbitmap) + { + return E_OUTOFMEMORY; + } + if (bits && !frame.pixels.empty()) + { + memcpy(bits, frame.pixels.data(), frame.pixels.size()); + if (frame.hasTransparency) + { + BYTE* target = static_cast(bits); + const size_t pixelCount = frame.pixels.size() / kBytesPerPixel; + for (size_t i = 0; i < pixelCount; ++i) + { + BYTE* pixel = target + i * kBytesPerPixel; + const BYTE alpha = pixel[3]; + if (alpha == 0) + { + pixel[0] = 0; + pixel[1] = 0; + pixel[2] = 0; + continue; + } + if (alpha == 255) + { + continue; + } + const unsigned int a = alpha; + pixel[0] = static_cast((static_cast(pixel[0]) * a + 127u) / 255u); + pixel[1] = static_cast((static_cast(pixel[1]) * a + 127u) / 255u); + pixel[2] = static_cast((static_cast(pixel[2]) * a + 127u) / 255u); + } + } + } + + frame.decoded = true; + return S_OK; +} + +inline BYTE CombineCmykChannel(BYTE component, BYTE black) +{ + const int c = 255 - component; + const int k = 255 - black; + const int value = c * k + 127; + return static_cast(value / 255); +} + +inline BYTE ToByteFromWord(UINT16 value) +{ + return static_cast((static_cast(value) + 128u) / 257u); +} + +HRESULT DecodeUnsupportedPixelFormat(FrameData& frame) +{ + GUID pixelFormat{}; + HRESULT hr = frame.frame->GetPixelFormat(&pixelFormat); + if (FAILED(hr)) + { + return hr; + } + + if (pixelFormat == GUID_WICPixelFormat32bppCMYK) + { + UINT width = 0; + UINT height = 0; + hr = frame.frame->GetSize(&width, &height); + if (FAILED(hr)) + { + return hr; + } + + const ULONGLONG sourceStride64 = static_cast(width) * 4ull; + if (sourceStride64 > std::numeric_limits::max()) + { + return E_OUTOFMEMORY; + } + const UINT sourceStride = static_cast(sourceStride64); + const ULONGLONG sourceSize64 = sourceStride64 * static_cast(height); + if (height != 0 && sourceSize64 / height != sourceStride64) + { + return E_OUTOFMEMORY; + } + if (sourceSize64 > static_cast(std::numeric_limits::max()) || + sourceSize64 > static_cast(std::numeric_limits::max())) + { + return E_OUTOFMEMORY; + } + + std::vector cmyk; + hr = AllocateBuffer(cmyk, static_cast(sourceSize64)); + if (FAILED(hr)) + { + return hr; + } + + WICRect rect{0, 0, static_cast(width), static_cast(height)}; + hr = frame.frame->CopyPixels(&rect, sourceStride, static_cast(cmyk.size()), cmyk.data()); + if (FAILED(hr)) + { + return hr; + } + + hr = AllocatePixelStorage(frame, width, height); + if (FAILED(hr)) + { + return hr; + } + + for (UINT y = 0; y < frame.height; ++y) + { + const BYTE* src = cmyk.data() + static_cast(y) * sourceStride; + BYTE* dst = frame.pixels.data() + static_cast(y) * frame.stride; + for (UINT x = 0; x < frame.width; ++x) + { + const BYTE c = src[x * 4 + 0]; + const BYTE m = src[x * 4 + 1]; + const BYTE yComp = src[x * 4 + 2]; + const BYTE k = src[x * 4 + 3]; + dst[x * 4 + 0] = CombineCmykChannel(yComp, k); + dst[x * 4 + 1] = CombineCmykChannel(m, k); + dst[x * 4 + 2] = CombineCmykChannel(c, k); + dst[x * 4 + 3] = 255; + } + } + return S_OK; + } + + if (pixelFormat == GUID_WICPixelFormat64bppCMYK) + { + UINT width = 0; + UINT height = 0; + hr = frame.frame->GetSize(&width, &height); + if (FAILED(hr)) + { + return hr; + } + + const ULONGLONG sourceStride64 = static_cast(width) * 8ull; + if (sourceStride64 > std::numeric_limits::max()) + { + return E_OUTOFMEMORY; + } + const UINT sourceStride = static_cast(sourceStride64); + const ULONGLONG sourceSize64 = sourceStride64 * static_cast(height); + if (height != 0 && sourceSize64 / height != sourceStride64) + { + return E_OUTOFMEMORY; + } + if (sourceSize64 > static_cast(std::numeric_limits::max()) || + sourceSize64 > static_cast(std::numeric_limits::max())) + { + return E_OUTOFMEMORY; + } + + std::vector cmyk; + hr = AllocateBuffer(cmyk, static_cast(sourceSize64)); + if (FAILED(hr)) + { + return hr; + } + + WICRect rect{0, 0, static_cast(width), static_cast(height)}; + hr = frame.frame->CopyPixels(&rect, sourceStride, static_cast(cmyk.size()), cmyk.data()); + if (FAILED(hr)) + { + return hr; + } + + hr = AllocatePixelStorage(frame, width, height); + if (FAILED(hr)) + { + return hr; + } + + for (UINT y = 0; y < frame.height; ++y) + { + const UINT16* src = reinterpret_cast(cmyk.data() + static_cast(y) * sourceStride); + BYTE* dst = frame.pixels.data() + static_cast(y) * frame.stride; + for (UINT x = 0; x < frame.width; ++x) + { + const BYTE c = ToByteFromWord(src[x * 4 + 0]); + const BYTE m = ToByteFromWord(src[x * 4 + 1]); + const BYTE yComp = ToByteFromWord(src[x * 4 + 2]); + const BYTE k = ToByteFromWord(src[x * 4 + 3]); + dst[x * 4 + 0] = CombineCmykChannel(yComp, k); + dst[x * 4 + 1] = CombineCmykChannel(m, k); + dst[x * 4 + 2] = CombineCmykChannel(c, k); + dst[x * 4 + 3] = 255; + } + } + return S_OK; + } + + return WINCODEC_ERR_UNSUPPORTEDPIXELFORMAT; +} + +bool IsConverterFormatFailure(HRESULT hr) +{ + if (hr == WINCODEC_ERR_UNSUPPORTEDPIXELFORMAT) + { + return true; + } +#if defined(WINCODEC_ERR_INVALIDPARAMETER) + if (hr == WINCODEC_ERR_INVALIDPARAMETER) + { + return true; + } +#endif +#if defined(WINCODEC_ERR_UNSUPPORTEDOPERATION) + if (hr == WINCODEC_ERR_UNSUPPORTEDOPERATION) + { + return true; + } +#endif + return false; +} + +HRESULT EnsureConverter(ImageHandle& handle, size_t index) +{ + if (index >= handle.frames.size()) + { + return E_INVALIDARG; + } + FrameData& frame = handle.frames[index]; + if (frame.converter) + { + return S_OK; + } + IWICImagingFactory* factory = handle.backend->Factory(); + if (!factory) + { + return E_FAIL; + } + + Microsoft::WRL::ComPtr converter; + HRESULT hr = factory->CreateFormatConverter(&converter); + if (FAILED(hr)) + { + return hr; + } + const bool isGif = handle.baseInfo.Format == PVF_GIF; + const GUID targetFormat = isGif ? GUID_WICPixelFormat32bppBGRA : GUID_WICPixelFormat32bppPBGRA; + frame.pixelsArePremultiplied = !isGif; + hr = converter->Initialize(frame.frame.Get(), targetFormat, WICBitmapDitherTypeNone, nullptr, 0.0, + WICBitmapPaletteTypeCustom); + if (FAILED(hr) && IsConverterFormatFailure(hr)) + { + HRESULT profileHr = ApplyEmbeddedColorProfile(handle, frame); + if (SUCCEEDED(profileHr) && frame.colorConvertedSource) + { + hr = converter->Initialize(frame.colorConvertedSource.Get(), targetFormat, WICBitmapDitherTypeNone, nullptr, + 0.0, WICBitmapPaletteTypeCustom); + } + else if (FAILED(profileHr) && !IsIgnorableColorProfileError(profileHr)) + { + return profileHr; + } + } + if (FAILED(hr)) + { + return hr; + } + + frame.converter = converter; + return S_OK; +} + +HRESULT DecodeFrame(ImageHandle& handle, size_t index) +{ + if (index >= handle.frames.size()) + { + return E_INVALIDARG; + } + FrameData& frame = handle.frames[index]; + if (frame.decoded) + { + if (handle.baseInfo.Format == PVF_GIF) + { + HRESULT restoreHr = RestoreGifCanvasState(handle, index); + if (FAILED(restoreHr) && restoreHr != S_FALSE) + { + return restoreHr; + } + } + return S_OK; + } + + if (handle.baseInfo.Format == PVF_GIF && index > 0) + { + HRESULT previousHr = DecodeFrame(handle, index - 1); + if (FAILED(previousHr)) + { + return previousHr; + } + } + + HRESULT hr = EnsureConverter(handle, index); + if (FAILED(hr)) + { + if (IsConverterFormatFailure(hr)) + { + hr = DecodeUnsupportedPixelFormat(frame); + if (FAILED(hr)) + { + return hr; + } + hr = FinalizeDecodedFrame(handle.backend, frame); + return hr; + } + return hr; + } + + hr = CopyBgraFromSource(frame, frame.converter.Get()); + if (FAILED(hr)) + { + return hr; + } + + if (handle.baseInfo.Format == PVF_GIF) + { + hr = CompositeGifFrame(handle, index); + if (FAILED(hr)) + { + return hr; + } + } + + return FinalizeDecodedFrame(handle.backend, frame); +} + +PVCODE HResultToPvCode(HRESULT hr) +{ + if (SUCCEEDED(hr)) + { + return PVC_OK; + } + if (hr == E_OUTOFMEMORY +#if defined(WINCODEC_ERR_OUTOFMEMORY) + || hr == WINCODEC_ERR_OUTOFMEMORY +#endif +#if defined(WINCODEC_ERR_INSUFFICIENTBUFFER) + || hr == WINCODEC_ERR_INSUFFICIENTBUFFER +#endif + ) + { + return PVC_OUT_OF_MEMORY; + } + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) || + hr == HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) || hr == STG_E_FILENOTFOUND || + hr == STG_E_ACCESSDENIED) + { + return PVC_CANNOT_OPEN_FILE; + } + if (hr == E_INVALIDARG +#if defined(WINCODEC_ERR_INVALIDPARAMETER) + || hr == WINCODEC_ERR_INVALIDPARAMETER +#endif +#if defined(WINCODEC_ERR_VALUEOUTOFRANGE) + || hr == WINCODEC_ERR_VALUEOUTOFRANGE +#endif + ) + { + return PVC_INVALID_DIMENSIONS; + } + if ( +#if defined(WINCODEC_ERR_BADHEADER) + hr == WINCODEC_ERR_BADHEADER || +#endif +#if defined(WINCODEC_ERR_BADIMAGE) + hr == WINCODEC_ERR_BADIMAGE || +#endif +#if defined(WINCODEC_ERR_BADMETADATAHEADER) + hr == WINCODEC_ERR_BADMETADATAHEADER || +#endif +#if defined(WINCODEC_ERR_BADSTREAMDATA) + hr == WINCODEC_ERR_BADSTREAMDATA || +#endif +#if defined(WINCODEC_ERR_STREAMREAD) + hr == WINCODEC_ERR_STREAMREAD || +#endif +#if defined(WINCODEC_ERR_STREAMWRITE) + hr == WINCODEC_ERR_STREAMWRITE || +#endif +#if defined(WINCODEC_ERR_STREAMNOTAVAILABLE) + hr == WINCODEC_ERR_STREAMNOTAVAILABLE || +#endif +#if defined(WINCODEC_ERR_UNEXPECTEDMETADATAFORM) + hr == WINCODEC_ERR_UNEXPECTEDMETADATAFORM || +#endif +#if defined(WINCODEC_ERR_INTERNALERROR) + hr == WINCODEC_ERR_INTERNALERROR || +#endif +#if defined(WINCODEC_ERR_INVALIDPROGRESSIVELEVEL) + hr == WINCODEC_ERR_INVALIDPROGRESSIVELEVEL || +#endif +#if defined(WINCODEC_ERR_UNSUPPORTEDVERSION) + hr == WINCODEC_ERR_UNSUPPORTEDVERSION || +#endif + hr == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF) || hr == HRESULT_FROM_WIN32(ERROR_CRC) || hr == E_FAIL) + { + return (hr == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF)) ? PVC_UNEXPECTED_EOF : PVC_READING_ERROR; + } + if (hr == WINCODEC_ERR_UNSUPPORTEDPIXELFORMAT +#if defined(WINCODEC_ERR_COMPONENTNOTFOUND) + || hr == WINCODEC_ERR_COMPONENTNOTFOUND +#endif +#if defined(WINCODEC_ERR_UNSUPPORTEDOPERATION) + || hr == WINCODEC_ERR_UNSUPPORTEDOPERATION +#endif +#if defined(WINCODEC_ERR_CODECNOTFOUND) + || hr == WINCODEC_ERR_CODECNOTFOUND +#endif +#if defined(WINCODEC_ERR_DECODERNOTFOUND) + || hr == WINCODEC_ERR_DECODERNOTFOUND +#endif +#if defined(WINCODEC_ERR_UNKNOWNIMAGEFORMAT) + || hr == WINCODEC_ERR_UNKNOWNIMAGEFORMAT +#endif +#if defined(WINCODEC_ERR_PROPERTYNOTSUPPORTED) + || hr == WINCODEC_ERR_PROPERTYNOTSUPPORTED +#endif + ) + { + return PVC_UNSUP_FILE_TYPE; + } + return PVC_EXCEPTION; +} + +std::wstring Utf8ToWide(const char* path) +{ + if (!path) + { + return std::wstring(); + } + int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, -1, nullptr, 0); + if (len <= 0) + { + len = MultiByteToWideChar(CP_ACP, 0, path, -1, nullptr, 0); + if (len <= 0) + { + return std::wstring(); + } + std::wstring wide(static_cast(len), L'\0'); + MultiByteToWideChar(CP_ACP, 0, path, -1, wide.data(), len); + if (!wide.empty() && wide.back() == L'\0') + { + wide.pop_back(); + } + return wide; + } + std::wstring wide(static_cast(len), L'\0'); + MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, -1, wide.data(), len); + if (!wide.empty() && wide.back() == L'\0') + { + wide.pop_back(); + } + return wide; +} + +PVCODE PopulateImageInfo(ImageHandle& handle, LPPVImageInfo info, DWORD bufferSize, bool hasPreviousImage, + DWORD previousImageIndex, int currentImage) +{ + if (!info) + { + return PVC_INVALID_HANDLE; + } + if (bufferSize < sizeof(PVImageInfo)) + { + return PVC_INVALID_HANDLE; + } + + const DWORD bytesToClear = std::min(bufferSize, static_cast(sizeof(PVImageInfo))); + ZeroMemory(info, bytesToClear); + info->cbSize = sizeof(PVImageInfo); + info->FileSize = handle.baseInfo.FileSize; + info->Colors = PV_COLOR_TC32; + info->Format = handle.baseInfo.Format; + info->Flags = handle.baseInfo.Flags; + info->ColorModel = PVCM_RGB; + info->NumOfImages = static_cast(handle.frames.size()); + info->StretchMode = handle.stretchMode; + info->TotalBitDepth = 32; + info->FSI = handle.hasFormatSpecificInfo ? &handle.formatInfo : nullptr; + + if (!handle.frames.empty()) + { + info->Colors = handle.frames[0].reportedColors; + info->ColorModel = handle.frames[0].colorModel; + info->TotalBitDepth = handle.frames[0].reportedBitDepth; + } + + struct FormatLabel + { + DWORD format; + const char* label; + }; + + static constexpr FormatLabel kFormatLabels[] = { + {PVF_BMP, "BMP"}, + {PVF_PNG, "PNG"}, + {PVF_JPG, "JPEG"}, + {PVF_TIFF, "TIFF"}, + {PVF_GIF, "GIF"}, + {PVF_ICO, "ICO"}, + }; + + const char* info1 = "WIC"; + for (const auto& entry : kFormatLabels) + { + if (entry.format == info->Format) + { + info1 = entry.label; + break; + } + } + StringCchCopyA(info->Info1, PV_MAX_INFO_LEN, info1); + + if (handle.frames.empty()) + { + info->CurrentImage = 0; + info->Width = 0; + info->Height = 0; + info->BytesPerLine = 0; + info->StretchedWidth = 0; + info->StretchedHeight = 0; + return PVC_OK; + } + + size_t fallbackIndex = 0; + if (!handle.frames.empty() && hasPreviousImage) + { + fallbackIndex = std::min(static_cast(previousImageIndex), handle.frames.size() - 1); + } + + const size_t normalized = NormalizeFrameIndex(handle, currentImage, fallbackIndex); + const FrameData& frame = handle.frames[normalized]; + + info->CurrentImage = static_cast(normalized); + info->Colors = frame.reportedColors; + info->ColorModel = frame.colorModel; + info->TotalBitDepth = frame.reportedBitDepth; + info->Width = frame.width; + info->Height = frame.height; + info->BytesPerLine = frame.displayStride != 0 ? frame.displayStride : frame.stride; + + double dpiX = 0.0; + double dpiY = 0.0; + if (frame.frame) + { + if (SUCCEEDED(frame.frame->GetResolution(&dpiX, &dpiY))) + { + auto clampDpi = [](double value) -> DWORD { + if (!std::isfinite(value) || value <= 0.0) + { + return 0; + } + const double rounded = std::floor(value + 0.5); + if (rounded <= 0.0) + { + return 0; + } + if (rounded > static_cast(std::numeric_limits::max())) + { + return std::numeric_limits::max(); + } + return static_cast(rounded); + }; + + info->HorDPI = clampDpi(dpiX); + info->VerDPI = clampDpi(dpiY); + } + } + + const LONGLONG stretchWidthSigned = handle.stretchWidth ? static_cast(handle.stretchWidth) + : static_cast(frame.width); + const LONGLONG stretchHeightSigned = handle.stretchHeight ? static_cast(handle.stretchHeight) + : static_cast(frame.height); + const ULONGLONG stretchWidthAbs = AbsoluteDimension(stretchWidthSigned); + const ULONGLONG stretchHeightAbs = AbsoluteDimension(stretchHeightSigned); + info->StretchedWidth = stretchWidthAbs > std::numeric_limits::max() + ? std::numeric_limits::max() + : static_cast(stretchWidthAbs); + info->StretchedHeight = stretchHeightAbs > std::numeric_limits::max() + ? std::numeric_limits::max() + : static_cast(stretchHeightAbs); + return PVC_OK; +} + +DWORD MapFormatToPvFormat(const GUID& container) +{ + if (container == GUID_ContainerFormatBmp) + return PVF_BMP; + if (container == GUID_ContainerFormatPng) + return PVF_PNG; + if (container == GUID_ContainerFormatJpeg) + return PVF_JPG; + if (container == GUID_ContainerFormatGif) + return PVF_GIF; + if (container == GUID_ContainerFormatTiff) + return PVF_TIFF; + if (container == GUID_ContainerFormatIco) + return PVF_ICO; + return PVF_BMP; +} + +HRESULT CollectFrames(Backend& backend, IWICBitmapDecoder* decoder, ImageHandle& handle) +{ + UINT frameCount = 0; + handle.baseInfo.Flags = 0; + + HRESULT hr = decoder->GetFrameCount(&frameCount); + if (FAILED(hr)) + { + return hr; + } + handle.frames.resize(frameCount); + handle.hasFormatSpecificInfo = false; + handle.formatInfo = {}; + handle.formatInfo.cbSize = sizeof(PVFormatSpecificInfo); + handle.formatInfo.GIF.DisposalMethod = PVDM_UNDEFINED; + handle.baseInfo.FSI = nullptr; + handle.canvasWidth = 0; + handle.canvasHeight = 0; + handle.gifComposeCanvas.clear(); + handle.gifSavedCanvas.clear(); + handle.gifCanvasInitialized = false; + + GUID container = {}; + if (FAILED(decoder->GetContainerFormat(&container))) + { + container = GUID_ContainerFormatBmp; + } + const DWORD mappedFormat = MapFormatToPvFormat(container); + handle.baseInfo.Format = mappedFormat; + const bool isGifContainer = mappedFormat == PVF_GIF; + + ComPtr decoderQuery; + if (FAILED(decoder->GetMetadataQueryReader(&decoderQuery))) + { + decoderQuery = nullptr; + } + + bool hasExif = SourceContainsExif(decoder); + if (!hasExif && decoderQuery) + { + hasExif = QueryReaderContainsExif(decoderQuery.Get()); + } + + UINT logicalScreenWidth = 0; + UINT logicalScreenHeight = 0; + bool hasLogicalScreenWidth = false; + bool hasLogicalScreenHeight = false; + UINT backgroundIndex = 0; + bool hasBackgroundIndex = false; + if (decoderQuery) + { + UINT value = 0; + if (TryReadUnsignedMetadata(decoderQuery.Get(), L"/logscrdesc/Width", value)) + { + logicalScreenWidth = value; + hasLogicalScreenWidth = true; + } + if (TryReadUnsignedMetadata(decoderQuery.Get(), L"/logscrdesc/Height", value)) + { + logicalScreenHeight = value; + hasLogicalScreenHeight = true; + } + if (TryReadUnsignedMetadata(decoderQuery.Get(), L"/logscrdesc/BackgroundColorIndex", value)) + { + backgroundIndex = value; + hasBackgroundIndex = true; + } + } + + COLORREF backgroundColor = RGB(0, 0, 0); + handle.gifHasBackgroundColor = false; + handle.gifBackgroundAlpha = 0; + handle.gifHasBackgroundIndex = false; + handle.gifBackgroundIndex = 0; + if (hasBackgroundIndex) + { + handle.gifHasBackgroundIndex = true; + handle.gifBackgroundIndex = static_cast(backgroundIndex & 0xFFu); + ComPtr palette; + if (SUCCEEDED(backend.Factory()->CreatePalette(&palette)) && palette) + { + if (SUCCEEDED(decoder->CopyPalette(palette.Get()))) + { + UINT paletteCount = 0; + if (SUCCEEDED(palette->GetColorCount(&paletteCount)) && paletteCount > backgroundIndex) + { + std::vector colors(paletteCount); + UINT actualCount = paletteCount; + if (SUCCEEDED(palette->GetColors(paletteCount, colors.data(), &actualCount)) && + actualCount > backgroundIndex) + { + const WICColor color = colors[backgroundIndex]; + const BYTE a = static_cast((color >> 24) & 0xFF); + const BYTE r = static_cast((color >> 16) & 0xFF); + const BYTE g = static_cast((color >> 8) & 0xFF); + const BYTE b = static_cast(color & 0xFF); + backgroundColor = RGB(r, g, b); + handle.gifHasBackgroundColor = true; + handle.gifBackgroundAlpha = a; + } + } + } + } + } + + if (hasLogicalScreenWidth) + { + handle.canvasWidth = ClampUnsignedToLong(logicalScreenWidth); + } + if (hasLogicalScreenHeight) + { + handle.canvasHeight = ClampUnsignedToLong(logicalScreenHeight); + } + + const auto clampEdge = [](LONG origin, LONG extent, LONG limit) -> LONG { + if (extent <= 0) + { + return origin; + } + LONGLONG sum = static_cast(origin) + static_cast(extent); + if (limit > 0) + { + if (origin >= limit) + { + return limit; + } + if (sum > limit) + { + sum = limit; + } + } + if (sum > static_cast(std::numeric_limits::max())) + { + sum = std::numeric_limits::max(); + } + return static_cast(sum); + }; + for (UINT i = 0; i < frameCount; ++i) + { + FrameData data; + hr = decoder->GetFrame(i, &data.frame); + if (FAILED(hr)) + { + return hr; + } + UINT width = 0; + UINT height = 0; + hr = data.frame->GetSize(&width, &height); + if (FAILED(hr)) + { + return hr; + } + if (width == 0 || height == 0) + { + return WINCODEC_ERR_INVALIDPARAMETER; + } + data.width = width; + data.height = height; + data.rawWidth = width; + data.rawHeight = height; + data.rawStride = 0; + + data.sourcePixelFormat = GUID_WICPixelFormat32bppBGRA; + data.bitsPerPixel = 0; + data.paletteColorCount = 0; + data.colorModel = PVCM_RGB; + data.reportedBitDepth = 32; + + GUID pixelFormat{}; + if (SUCCEEDED(data.frame->GetPixelFormat(&pixelFormat))) + { + data.sourcePixelFormat = pixelFormat; + IWICImagingFactory* factory = backend.Factory(); + if (factory) + { + Microsoft::WRL::ComPtr componentInfo; + if (SUCCEEDED(factory->CreateComponentInfo(pixelFormat, &componentInfo))) + { + Microsoft::WRL::ComPtr pixelInfo; + if (SUCCEEDED(componentInfo.As(&pixelInfo))) + { + UINT bitsPerPixel = 0; + if (SUCCEEDED(pixelInfo->GetBitsPerPixel(&bitsPerPixel))) + { + data.bitsPerPixel = bitsPerPixel; + data.reportedBitDepth = bitsPerPixel; + } + } + } + + Microsoft::WRL::ComPtr framePalette; + if (SUCCEEDED(factory->CreatePalette(&framePalette)) && framePalette) + { + if (SUCCEEDED(data.frame->CopyPalette(framePalette.Get()))) + { + UINT paletteCount = 0; + if (SUCCEEDED(framePalette->GetColorCount(&paletteCount))) + { + data.paletteColorCount = paletteCount; + } + } + } + } + + data.colorModel = DetermineColorModelFromPixelFormat(pixelFormat); + } + + if (data.bitsPerPixel == 0) + { + data.bitsPerPixel = 32; + } + if (data.reportedBitDepth == 0) + { + data.reportedBitDepth = data.bitsPerPixel; + } + data.reportedColors = DetermineColorCount(data.sourcePixelFormat, data.bitsPerPixel, data.paletteColorCount, + data.colorModel); + const bool palettedSource = (data.bitsPerPixel > 0 && data.bitsPerPixel <= 8 && data.paletteColorCount > 0); + data.allowIndexedDisplay = palettedSource; + data.realizePalette = palettedSource; + + data.delayMs = GetFrameDelayMilliseconds(data.frame.Get()); + if (frameCount > 1 && data.delayMs == 0) + { + data.delayMs = 100; + } + data.rect.left = 0; + data.rect.top = 0; + data.rect.right = ClampUnsignedToLong(static_cast(width)); + data.rect.bottom = ClampUnsignedToLong(static_cast(height)); + data.disposal = PVDM_UNDEFINED; + + ULONGLONG left64 = 0; + ULONGLONG top64 = 0; + bool leftSpecified = false; + bool topSpecified = false; + ULONGLONG rectWidth64 = static_cast(width); + ULONGLONG rectHeight64 = static_cast(height); + data.gifHasTransparentColor = false; + data.gifTransparentIndex = 0; + ComPtr frameQuery; + if (SUCCEEDED(data.frame->GetMetadataQueryReader(&frameQuery)) && frameQuery) + { + UINT value = 0; + if (TryReadUnsignedMetadata(frameQuery.Get(), L"/imgdesc/Left", value)) + { + left64 = value; + leftSpecified = true; + } + if (TryReadUnsignedMetadata(frameQuery.Get(), L"/imgdesc/Top", value)) + { + top64 = value; + topSpecified = true; + } + if (TryReadUnsignedMetadata(frameQuery.Get(), L"/imgdesc/Width", value) && value > 0) + { + rectWidth64 = value; + } + if (TryReadUnsignedMetadata(frameQuery.Get(), L"/imgdesc/Height", value) && value > 0) + { + rectHeight64 = value; + } + if (TryReadUnsignedMetadata(frameQuery.Get(), L"/grctlext/Disposal", value)) + { + data.disposal = MapGifDisposalToPv(value); + } + bool transparencySpecified = false; + bool transparencyFlag = false; + if (TryReadUnsignedMetadata(frameQuery.Get(), L"/grctlext/TransparentColorFlag", value) || + TryReadUnsignedMetadata(frameQuery.Get(), L"/grctlext/TransparencyFlag", value)) + { + transparencySpecified = true; + transparencyFlag = (value != 0); + } + UINT transparentIndex = 0; + bool hasTransparentIndex = false; + if (TryReadUnsignedMetadata(frameQuery.Get(), L"/grctlext/TransparentColorIndex", value)) + { + transparentIndex = value; + hasTransparentIndex = true; + } + if (hasTransparentIndex && (!transparencySpecified || transparencyFlag)) + { + data.gifHasTransparentColor = true; + data.gifTransparentIndex = static_cast(transparentIndex & 0xFFu); + if (handle.gifHasBackgroundColor && handle.gifBackgroundAlpha != 0 && + handle.gifHasBackgroundIndex && + data.gifTransparentIndex == handle.gifBackgroundIndex) + { + handle.gifBackgroundAlpha = 0; + } + } + } + LONG rectLeft = ClampUnsignedToLong(leftSpecified ? left64 : 0ull); + LONG rectTop = ClampUnsignedToLong(topSpecified ? top64 : 0ull); + if (handle.canvasWidth > 0) + { + if (rectLeft < 0) + { + rectLeft = 0; + } + else if (rectLeft > handle.canvasWidth) + { + rectLeft = handle.canvasWidth; + } + } + if (handle.canvasHeight > 0) + { + if (rectTop < 0) + { + rectTop = 0; + } + else if (rectTop > handle.canvasHeight) + { + rectTop = handle.canvasHeight; + } + } + data.rect.left = rectLeft; + data.rect.top = rectTop; + const LONG rectWidthLong = ClampUnsignedToLong(rectWidth64); + const LONG rectHeightLong = ClampUnsignedToLong(rectHeight64); + if (rectWidthLong > 0) + { + data.rawWidth = static_cast(std::min(rectWidthLong, static_cast(width))); + } + if (rectHeightLong > 0) + { + data.rawHeight = static_cast(std::min(rectHeightLong, static_cast(height))); + } + data.rect.right = clampEdge(rectLeft, rectWidthLong, handle.canvasWidth); + data.rect.bottom = clampEdge(rectTop, rectHeightLong, handle.canvasHeight); + if (isGifContainer) + { + data.gifFrameRect = data.rect; + data.hasGifFrameRect = true; + } + if (!hasExif && FrameContainsExif(data.frame.Get())) + { + hasExif = true; + } + handle.frames[i] = std::move(data); + } + if (handle.canvasWidth <= 0 && !handle.frames.empty()) + { + handle.canvasWidth = ClampUnsignedToLong(static_cast(handle.frames[0].width)); + } + if (handle.canvasHeight <= 0 && !handle.frames.empty()) + { + handle.canvasHeight = ClampUnsignedToLong(static_cast(handle.frames[0].height)); + } + handle.baseInfo.NumOfImages = frameCount; + handle.baseInfo.FileSize = QueryFileSize(handle.fileName); + + if (isGifContainer) + { + handle.hasFormatSpecificInfo = true; + const LONG screenWidth = std::max(0, handle.canvasWidth); + const LONG screenHeight = std::max(0, handle.canvasHeight); + handle.formatInfo.GIF.ScreenWidth = static_cast(screenWidth); + handle.formatInfo.GIF.ScreenHeight = static_cast(screenHeight); + handle.formatInfo.GIF.XPosition = 0; + handle.formatInfo.GIF.YPosition = 0; + handle.formatInfo.GIF.Delay = 0; + handle.formatInfo.GIF.TranspIndex = 0; + handle.formatInfo.GIF.BgColor = backgroundColor; + handle.baseInfo.FSI = &handle.formatInfo; + } + else + { + handle.hasFormatSpecificInfo = false; + handle.baseInfo.FSI = nullptr; + } + + if (!hasExif && handle.baseInfo.Format == PVF_JPG) + { + CExifFileBuffer buffer; + bool loaded = false; +#ifdef _UNICODE + loaded = buffer.LoadFromFile(handle.fileName.c_str()); +#else + loaded = buffer.LoadFromWideFile(handle.fileName.c_str()); +#endif + if (loaded) + { + hasExif = buffer.HasExifData(); + } + } + if (hasExif) + { + handle.baseInfo.Flags |= PVFF_EXIF; + } + if (frameCount > 1 && handle.baseInfo.Format == PVF_GIF) + { + handle.baseInfo.Flags |= PVFF_IMAGESEQUENCE; + } + return S_OK; +} + +PVCODE DrawFrame(ImageHandle& handle, FrameData& frame, HDC dc, int x, int y, LPRECT rect) +{ + if (!dc) + { + return PVC_OK; + } + + const LONGLONG stretchWidthSigned = handle.stretchWidth ? static_cast(handle.stretchWidth) + : static_cast(frame.width); + const LONGLONG stretchHeightSigned = handle.stretchHeight ? static_cast(handle.stretchHeight) + : static_cast(frame.height); + const ULONGLONG stretchWidthAbs = AbsoluteDimension(stretchWidthSigned); + const ULONGLONG stretchHeightAbs = AbsoluteDimension(stretchHeightSigned); + if (stretchWidthAbs == 0 || stretchHeightAbs == 0) + { + return PVC_OK; + } + + if (stretchWidthAbs > static_cast(std::numeric_limits::max()) || + stretchHeightAbs > static_cast(std::numeric_limits::max())) + { + return PVC_INVALID_DIMENSIONS; + } + if (frame.width > static_cast(std::numeric_limits::max()) || + frame.height > static_cast(std::numeric_limits::max())) + { + return PVC_INVALID_DIMENSIONS; + } + + RECT imageRect; + imageRect.left = x; + imageRect.top = y; + const LONGLONG imageRight = static_cast(x) + static_cast(stretchWidthAbs); + const LONGLONG imageBottom = static_cast(y) + static_cast(stretchHeightAbs); + if (imageRight > std::numeric_limits::max() || imageRight < std::numeric_limits::min() || + imageBottom > std::numeric_limits::max() || imageBottom < std::numeric_limits::min()) + { + return PVC_INVALID_DIMENSIONS; + } + imageRect.right = static_cast(imageRight); + imageRect.bottom = static_cast(imageBottom); + + RECT clipRect = imageRect; + if (rect) + { + if (!IntersectRect(&clipRect, &imageRect, rect)) + { + return PVC_OK; + } + } + + int savedState = 0; + bool resetClip = false; + if (rect) + { + savedState = SaveDC(dc); + if (savedState == 0) + { + resetClip = true; + } + const int clipResult = IntersectClipRect(dc, clipRect.left, clipRect.top, clipRect.right, clipRect.bottom); + if (clipResult == ERROR) + { + if (savedState > 0) + { + RestoreDC(dc, savedState); + } + else if (resetClip) + { + SelectClipRgn(dc, nullptr); + } + return PVC_GDI_ERROR; + } + if (clipResult == NULLREGION) + { + if (savedState > 0) + { + RestoreDC(dc, savedState); + } + else if (resetClip) + { + SelectClipRgn(dc, nullptr); + } + return PVC_OK; + } + } + + if (frame.hasTransparency) + { + RECT fillRect = clipRect; + HBRUSH brush = CreateSolidBrush(handle.background); + if (brush) + { + FillRect(dc, &fillRect, brush); + DeleteObject(brush); + } + } + + int previousMode = SetStretchBltMode(dc, handle.stretchMode ? static_cast(handle.stretchMode) : COLORONCOLOR); + std::vector bmiBuffer; + BITMAPINFO bmi{}; + BITMAPINFO* bmiPtr = nullptr; + const bool useIndexed = frame.useIndexedPixels && !frame.indexedPixels.empty(); + if (useIndexed) + { + const size_t paletteCount = std::min(frame.palette.size(), std::min(frame.paletteColorCount, 256)); + const size_t bmiSize = sizeof(BITMAPINFOHEADER) + paletteCount * sizeof(RGBQUAD); + try + { + bmiBuffer.resize(bmiSize); + } + catch (const std::bad_alloc&) + { + return PVC_EXCEPTION; + } + bmiPtr = reinterpret_cast(bmiBuffer.data()); + bmiPtr->bmiHeader = frame.displayBmi; + bmiPtr->bmiHeader.biClrUsed = static_cast(paletteCount); + bmiPtr->bmiHeader.biClrImportant = static_cast(paletteCount); + for (size_t i = 0; i < paletteCount; ++i) + { + RGBQUAD entry = frame.palette[i]; + entry.rgbReserved = 0; + bmiPtr->bmiColors[i] = entry; + } + } + else + { + bmi.bmiHeader = frame.bmi; + bmiPtr = &bmi; + } + + const bool realizePalette = useIndexed && frame.paletteHandle; + PaletteSelector paletteScope(dc, frame.paletteHandle, realizePalette); + + const int destX = stretchWidthSigned >= 0 ? imageRect.left : imageRect.right - 1; + const int destY = stretchHeightSigned >= 0 ? imageRect.top : imageRect.bottom - 1; + const int destWidth = stretchWidthSigned >= 0 ? static_cast(stretchWidthAbs) + : -static_cast(stretchWidthAbs); + const int destHeight = stretchHeightSigned >= 0 ? static_cast(stretchHeightAbs) + : -static_cast(stretchHeightAbs); + + const UINT targetWidth = static_cast(stretchWidthAbs); + const UINT targetHeight = static_cast(stretchHeightAbs); + const bool requiresScaling = (!useIndexed && (frame.width != targetWidth || frame.height != targetHeight)); + + BITMAPINFO scaledBmi{}; + const BYTE* stretchSource = useIndexed ? frame.indexedPixels.data() : frame.pixels.data(); + UINT sourceWidthForStretch = frame.width; + UINT sourceHeightForStretch = frame.height; + + HBITMAP blendBitmap = frame.hbitmap; + UINT sourceWidthForBlend = frame.width; + UINT sourceHeightForBlend = frame.height; + + if (!useIndexed && requiresScaling) + { + if (SUCCEEDED(EnsureScaledBitmap(handle, frame, targetWidth, targetHeight))) + { + if (frame.scaledBitmap) + { + blendBitmap = frame.scaledBitmap; + sourceWidthForBlend = frame.scaledWidth; + sourceHeightForBlend = frame.scaledHeight; + } + if (!frame.scaledPixels.empty()) + { + stretchSource = frame.scaledPixels.data(); + scaledBmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + scaledBmi.bmiHeader.biWidth = static_cast(frame.scaledWidth); + scaledBmi.bmiHeader.biHeight = -static_cast(frame.scaledHeight); + scaledBmi.bmiHeader.biPlanes = 1; + scaledBmi.bmiHeader.biBitCount = 32; + scaledBmi.bmiHeader.biCompression = BI_RGB; + scaledBmi.bmiHeader.biSizeImage = frame.scaledPixels.size() > std::numeric_limits::max() + ? 0 + : static_cast(frame.scaledPixels.size()); + scaledBmi.bmiHeader.biXPelsPerMeter = frame.bmi.biXPelsPerMeter; + scaledBmi.bmiHeader.biYPelsPerMeter = frame.bmi.biYPelsPerMeter; + bmiPtr = &scaledBmi; + sourceWidthForStretch = frame.scaledWidth; + sourceHeightForStretch = frame.scaledHeight; + } + } + } + + int result = GDI_ERROR; + const bool canAlphaBlend = frame.hasTransparency && blendBitmap && !useIndexed && destWidth >= 0 && destHeight >= 0; + if (canAlphaBlend) + { + HDC sourceDc = CreateCompatibleDC(dc); + if (!sourceDc) + { + result = GDI_ERROR; + } + else + { + HGDIOBJ oldBitmap = SelectObject(sourceDc, blendBitmap); + BLENDFUNCTION blend{}; + blend.BlendOp = AC_SRC_OVER; + blend.BlendFlags = 0; + blend.SourceConstantAlpha = 255; + blend.AlphaFormat = AC_SRC_ALPHA; + const BOOL blendResult = GdiAlphaBlend(dc, destX, destY, destWidth, destHeight, sourceDc, 0, 0, + static_cast(sourceWidthForBlend), + static_cast(sourceHeightForBlend), blend); + SelectObject(sourceDc, oldBitmap); + DeleteDC(sourceDc); + result = blendResult ? 0 : GDI_ERROR; + } + } + + if (!canAlphaBlend || result == GDI_ERROR) + { + if (!stretchSource) + { + return PVC_INVALID_HANDLE; + } + + result = StretchDIBits(dc, destX, destY, destWidth, destHeight, 0, 0, sourceWidthForStretch, + sourceHeightForStretch, stretchSource, bmiPtr, DIB_RGB_COLORS, SRCCOPY); + } + + if (previousMode > 0) + { + SetStretchBltMode(dc, previousMode); + } + if (savedState > 0) + { + RestoreDC(dc, savedState); + } + else if (resetClip) + { + SelectClipRgn(dc, nullptr); + } + if (result == GDI_ERROR) + { + return PVC_GDI_ERROR; + } + return PVC_OK; +} + +PVCODE CreateSequenceNodes(ImageHandle& handle, LPPVImageSequence* seq) +{ + if (!seq) + { + return PVC_INVALID_HANDLE; + } + *seq = nullptr; + LPPVImageSequence head = nullptr; + LPPVImageSequence* tail = seq; + for (size_t i = 0; i < handle.frames.size(); ++i) + { + FrameData& frame = handle.frames[i]; + HRESULT hr = DecodeFrame(handle, i); + if (FAILED(hr)) + { + return HResultToPvCode(hr); + } + auto node = std::make_unique(); + node->pNext = nullptr; + node->Rect = frame.rect; + node->Delay = frame.delayMs; + node->DisposalMethod = frame.disposal; + node->ImgHandle = nullptr; + node->TransparentHandle = nullptr; + HBITMAP subFrame = nullptr; + HBITMAP subMask = nullptr; + hr = CreateSequenceBitmaps(frame, frame.rect, subFrame, subMask); + if (FAILED(hr)) + { + if (subFrame) + { + DeleteObject(subFrame); + } + if (subMask) + { + DeleteObject(subMask); + } + return HResultToPvCode(hr); + } + node->ImgHandle = subFrame; + node->TransparentHandle = subMask; + *tail = node.release(); + tail = &((*tail)->pNext); + } + *tail = nullptr; + return PVC_OK; +} + +PVCODE SaveFrame(ImageHandle& handle, int imageIndex, const wchar_t* path, const GuidMapping& mapping, + LPPVSaveImageInfo info) +{ + if (handle.frames.empty()) + { + return PVC_INVALID_HANDLE; + } + + ClearCustomErrorText(PVC_READING_ERROR); + ClearCustomErrorText(PVC_WRITING_ERROR); + ClearCustomErrorText(PVC_EXCEPTION); + + const size_t normalizedIndex = NormalizeFrameIndex(handle, imageIndex, 0); + FrameData& frame = handle.frames[normalizedIndex]; + + auto recordFailure = [&](HRESULT failureHr, const char* stage) -> PVCODE { + const PVCODE code = HResultToPvCode(failureHr); + RecordDetailedError(code, failureHr, stage); + return code; + }; + + HRESULT hr = DecodeFrame(handle, normalizedIndex); + if (FAILED(hr)) + { + return recordFailure(hr, "DecodeFrame"); + } + + Microsoft::WRL::ComPtr encoder; + hr = handle.backend->Factory()->CreateEncoder(mapping.container, nullptr, &encoder); + if (FAILED(hr)) + { + return recordFailure(hr, "CreateEncoder"); + } + + Microsoft::WRL::ComPtr stream; + hr = handle.backend->Factory()->CreateStream(&stream); + if (FAILED(hr)) + { + return recordFailure(hr, "CreateStream"); + } + + hr = stream->InitializeFromFilename(path, GENERIC_WRITE); + if (FAILED(hr)) + { + return recordFailure(hr, "InitializeFromFilename"); + } + + hr = encoder->Initialize(stream.Get(), WICBitmapEncoderNoCache); + if (FAILED(hr)) + { + return recordFailure(hr, "Encoder::Initialize"); + } + + UINT processedWidth = frame.width; + UINT processedHeight = frame.height; + UINT processedStride = frame.stride; + const BYTE* pixelData = frame.pixels.data(); + std::vector workingPixels; + + const DWORD flags = info ? info->Flags : 0; + + if (info && info->CropWidth != 0 && info->CropHeight != 0) + { + if (info->CropLeft >= processedWidth || info->CropTop >= processedHeight) + { + return PVC_UNSUP_OUT_PARAMS; + } + + const UINT maxCropWidth = processedWidth - info->CropLeft; + const UINT maxCropHeight = processedHeight - info->CropTop; + const UINT cropWidth = std::min(static_cast(info->CropWidth), maxCropWidth); + const UINT cropHeight = std::min(static_cast(info->CropHeight), maxCropHeight); + if (cropWidth == 0 || cropHeight == 0) + { + return PVC_UNSUP_OUT_PARAMS; + } + + std::vector cropped; + const size_t rowBytes = static_cast(cropWidth) * kBytesPerPixel; + const size_t totalBytes = rowBytes * cropHeight; + try + { + cropped.resize(totalBytes); + } + catch (const std::bad_alloc&) + { + return PVC_OUT_OF_MEMORY; + } + + for (UINT y = 0; y < cropHeight; ++y) + { + const BYTE* src = pixelData + (static_cast(info->CropTop + y) * processedStride) + + static_cast(info->CropLeft) * kBytesPerPixel; + BYTE* dst = cropped.data() + static_cast(y) * rowBytes; + memcpy(dst, src, rowBytes); + } + + workingPixels.swap(cropped); + pixelData = workingPixels.data(); + processedWidth = cropWidth; + processedHeight = cropHeight; + processedStride = cropWidth * kBytesPerPixel; + } + + auto ensureMutablePixels = [&]() -> BYTE* { + if (workingPixels.empty()) + { + const size_t totalBytes = static_cast(processedStride) * processedHeight; + try + { + workingPixels.assign(pixelData, pixelData + totalBytes); + } + catch (const std::bad_alloc&) + { + return nullptr; + } + pixelData = workingPixels.data(); + } + return workingPixels.data(); + }; + + if (flags & PVSF_ROTATE90) + { + const UINT newWidth = processedHeight; + const UINT newHeight = processedWidth; + std::vector rotated; + const size_t totalBytes = static_cast(newWidth) * newHeight * kBytesPerPixel; + try + { + rotated.resize(totalBytes); + } + catch (const std::bad_alloc&) + { + return PVC_OUT_OF_MEMORY; + } + + for (UINT y = 0; y < processedHeight; ++y) + { + for (UINT x = 0; x < processedWidth; ++x) + { + const BYTE* src = pixelData + static_cast(y) * processedStride + + static_cast(x) * kBytesPerPixel; + const UINT dstX = newWidth - 1 - y; + const UINT dstY = x; + BYTE* dst = rotated.data() + + (static_cast(dstY) * newWidth + dstX) * kBytesPerPixel; + memcpy(dst, src, kBytesPerPixel); + } + } + + workingPixels.swap(rotated); + pixelData = workingPixels.data(); + processedWidth = newWidth; + processedHeight = newHeight; + processedStride = newWidth * kBytesPerPixel; + } + + if (flags & PVSF_FLIP_VERT) + { + BYTE* mutablePixels = ensureMutablePixels(); + if (!mutablePixels) + { + return PVC_OUT_OF_MEMORY; + } + + const size_t rowBytes = static_cast(processedWidth) * kBytesPerPixel; + for (UINT y = 0; y < processedHeight / 2; ++y) + { + BYTE* top = mutablePixels + static_cast(y) * processedStride; + BYTE* bottom = mutablePixels + static_cast(processedHeight - 1 - y) * processedStride; + for (size_t i = 0; i < rowBytes; ++i) + { + std::swap(top[i], bottom[i]); + } + } + } + + if (flags & PVSF_FLIP_HOR) + { + BYTE* mutablePixels = ensureMutablePixels(); + if (!mutablePixels) + { + return PVC_OUT_OF_MEMORY; + } + + for (UINT y = 0; y < processedHeight; ++y) + { + BYTE* row = mutablePixels + static_cast(y) * processedStride; + for (UINT x = 0; x < processedWidth / 2; ++x) + { + BYTE* left = row + static_cast(x) * kBytesPerPixel; + BYTE* right = row + static_cast(processedWidth - 1 - x) * kBytesPerPixel; + for (UINT c = 0; c < kBytesPerPixel; ++c) + { + std::swap(left[c], right[c]); + } + } + } + } + + if (flags & PVSF_INVERT) + { + BYTE* mutablePixels = ensureMutablePixels(); + if (!mutablePixels) + { + return PVC_OUT_OF_MEMORY; + } + + const size_t pixelCount = static_cast(processedWidth) * processedHeight; + for (size_t i = 0; i < pixelCount; ++i) + { + BYTE* pixel = mutablePixels + i * kBytesPerPixel; + pixel[0] = static_cast(0xFFu - pixel[0]); + pixel[1] = static_cast(0xFFu - pixel[1]); + pixel[2] = static_cast(0xFFu - pixel[2]); + } + } + + UINT targetWidth = processedWidth; + UINT targetHeight = processedHeight; + if (info && info->Width != 0 && info->Height != 0) + { + targetWidth = info->Width; + targetHeight = info->Height; + } + + const auto selectionOpt = DeterminePixelFormat(mapping, info); + if (!selectionOpt) + { + return PVC_UNSUP_OUT_PARAMS; + } + const PixelFormatSelection& selection = *selectionOpt; + const std::wstring comment = ExtractComment(info); + const bool useUniformPalette = info && (info->Flags & PVSF_UNIFORM_PALETTE) != 0; + + std::vector paletteColors; + Microsoft::WRL::ComPtr palette; + Microsoft::WRL::ComPtr quantizedPaletteSource; + std::optional gifTransparencyIndex; + bool gifTransparencyEnabled = false; + const WICBitmapDitherType paletteDither = + useUniformPalette ? WICBitmapDitherTypeNone : WICBitmapDitherTypeErrorDiffusion; + + PropertyBagWriter bagWriter; + bool hasGifInterlaceFlag = false; + bool gifInterlaceFlag = false; + if (info) + { + if (mapping.container == GUID_ContainerFormatJpeg) + { + const float quality = ClampQualityToFactor(info->Misc.JPEG.Quality); + if (quality > 0.0f) + { + bagWriter.AddFloat(L"ImageQuality", quality); + } + if (const auto subsampling = MapSubsamplingToWic(info->Misc.JPEG.SubSampling)) + { + bagWriter.AddUInt8(L"JpegYCrCbSubsampling", *subsampling); + } + } + else if (mapping.container == GUID_ContainerFormatGif) + { + hasGifInterlaceFlag = true; + gifInterlaceFlag = (info->Flags & PVSF_INTERLACE) != 0; + } + else if (mapping.container == GUID_ContainerFormatTiff) + { + if (info->Compression == PVCS_JPEG_HUFFMAN) + { + const float quality = ClampQualityToFactor(info->Misc.TIFF.JPEGQuality); + if (quality > 0.0f) + { + bagWriter.AddFloat(L"ImageQuality", quality); + } + if (const auto subsampling = MapSubsamplingToWic(info->Misc.TIFF.JPEGSubSampling)) + { + bagWriter.AddUInt8(L"JpegYCrCbSubsampling", *subsampling); + } + } + + const std::optional compressionOption = MapTiffCompression(info->Compression); + if (!compressionOption.has_value() && info->Compression != PVCS_DEFAULT) + { + return PVC_UNSUP_OUT_PARAMS; + } + if (compressionOption.has_value()) + { + bagWriter.AddUInt8(L"TiffCompressionMethod", compressionOption.value()); + } + } + + } + + const size_t processedBufferSize = static_cast(processedStride) * processedHeight; + if (processedBufferSize > std::numeric_limits::max()) + { + return PVC_OUT_OF_MEMORY; + } + + Microsoft::WRL::ComPtr bitmap; + hr = handle.backend->Factory()->CreateBitmapFromMemory( + processedWidth, processedHeight, GUID_WICPixelFormat32bppBGRA, processedStride, + static_cast(processedBufferSize), const_cast(pixelData), &bitmap); + if (FAILED(hr)) + { + return recordFailure(hr, "CreateBitmapFromMemory"); + } + + Microsoft::WRL::ComPtr source; + hr = bitmap.As(&source); + if (FAILED(hr)) + { + return recordFailure(hr, "Bitmap::AsBitmapSource"); + } + + if ((targetWidth != processedWidth || targetHeight != processedHeight) && source) + { + Microsoft::WRL::ComPtr scaler; + hr = handle.backend->Factory()->CreateBitmapScaler(&scaler); + if (FAILED(hr)) + { + return recordFailure(hr, "CreateBitmapScaler"); + } + + hr = scaler->Initialize(source.Get(), targetWidth, targetHeight, WICBitmapInterpolationModeFant); + if (FAILED(hr)) + { + return recordFailure(hr, "Scaler::Initialize"); + } + + Microsoft::WRL::ComPtr scaledSource; + hr = scaler.As(&scaledSource); + if (FAILED(hr)) + { + return recordFailure(hr, "Scaler::AsBitmapSource"); + } + source = scaledSource; + } + + if (selection.isIndexed) + { + hr = handle.backend->Factory()->CreatePalette(&palette); + if (FAILED(hr)) + { + return recordFailure(hr, "CreatePalette"); + } + + UINT desiredEntries = selection.paletteEntries > 0 ? selection.paletteEntries : 256; + const char* paletteStage = nullptr; + if (useUniformPalette) + { + hr = palette->InitializePredefined(WICBitmapPaletteTypeFixedWebPalette, FALSE); + paletteStage = "Palette::InitializePredefined"; + } + else + { + hr = palette->InitializeFromBitmap(source.Get(), desiredEntries, FALSE); + paletteStage = "Palette::InitializeFromBitmap"; + } + if (FAILED(hr)) + { + return recordFailure(hr, paletteStage ? paletteStage : "Palette::Initialize"); + } + + UINT paletteCount = 0; + hr = palette->GetColorCount(&paletteCount); + if (FAILED(hr)) + { + return recordFailure(hr, "Palette::GetColorCount"); + } + + std::vector colors(paletteCount); + if (paletteCount > 0) + { + UINT actual = paletteCount; + hr = palette->GetColors(paletteCount, colors.data(), &actual); + if (FAILED(hr)) + { + return recordFailure(hr, "Palette::GetColors"); + } + colors.resize(actual); + } + + const UINT requiredEntries = selection.paletteEntries > 0 ? selection.paletteEntries : static_cast(colors.size()); + if (requiredEntries > 0) + { + if (colors.empty()) + { + colors.resize(requiredEntries, 0); + } + if (colors.size() < requiredEntries) + { + const WICColor fill = colors.empty() ? 0 : colors.back(); + colors.resize(requiredEntries, fill); + } + else if (colors.size() > requiredEntries) + { + colors.resize(requiredEntries); + } + } + + if (mapping.container == GUID_ContainerFormatGif) + { + gifTransparencyIndex = DetermineGifTransparency(info, colors, source.Get()); + gifTransparencyEnabled = gifTransparencyIndex.has_value(); + for (size_t i = 0; i < colors.size(); ++i) + { + const bool isTransparent = gifTransparencyEnabled && i == gifTransparencyIndex.value(); + const WICColor rgb = colors[i] & 0x00FFFFFFu; + colors[i] = rgb | (isTransparent ? 0x00000000u : 0xFF000000u); + } + } + else if (selection.isGray && requiredEntries == 2 && colors.size() >= 2) + { + colors[0] = 0xFF000000u; + colors[1] = 0xFFFFFFFFu; + } + + if (!colors.empty()) + { + hr = palette->InitializeCustom(colors.data(), static_cast(colors.size())); + if (FAILED(hr)) + { + return recordFailure(hr, "Palette::InitializeCustom"); + } + } + paletteColors = std::move(colors); + + Microsoft::WRL::ComPtr paletteQuantizer; + hr = handle.backend->Factory()->CreateFormatConverter(&paletteQuantizer); + if (FAILED(hr)) + { + return recordFailure(hr, "CreateFormatConverter (palette quantize)"); + } + + const WICBitmapPaletteType quantizePaletteType = + useUniformPalette ? WICBitmapPaletteTypeFixedWebPalette : WICBitmapPaletteTypeCustom; + hr = paletteQuantizer->Initialize(source.Get(), selection.pixelFormat, paletteDither, palette.Get(), 0.0, + quantizePaletteType); + if (FAILED(hr)) + { + return recordFailure(hr, "FormatConverter::Initialize (palette quantize)"); + } + + hr = paletteQuantizer.As(&quantizedPaletteSource); + if (FAILED(hr)) + { + return recordFailure(hr, "FormatConverter::As (palette quantize)"); + } + } + + Microsoft::WRL::ComPtr encoderMetadataWriter; + if (mapping.container == GUID_ContainerFormatGif) + { + if (FAILED(encoder->GetMetadataQueryWriter(&encoderMetadataWriter))) + { + encoderMetadataWriter.Reset(); + } + } + + if (mapping.container == GUID_ContainerFormatGif && palette) + { + // The GIF encoder expects its global palette to be registered before any frames are + // negotiated so that the encoder advertises an indexed pixel format compatible with the + // chosen palette. Register it now, before creating the frame, to keep subsequent + // WritePixels calls from failing with WRONGSTATE. + hr = encoder->SetPalette(palette.Get()); + if (FAILED(hr)) + { + return recordFailure(hr, "Encoder::SetPalette"); + } + } + + Microsoft::WRL::ComPtr frameEncode; + Microsoft::WRL::ComPtr bag; + hr = encoder->CreateNewFrame(&frameEncode, &bag); + if (FAILED(hr)) + { + return recordFailure(hr, "Encoder::CreateNewFrame"); + } + + hr = bagWriter.Write(bag.Get()); + if (FAILED(hr)) + { + return recordFailure(hr, "PropertyBagWriter::Write"); + } + + hr = frameEncode->Initialize(bag.Get()); + if (FAILED(hr)) + { + return recordFailure(hr, "FrameEncode::Initialize"); + } + + hr = frameEncode->SetSize(targetWidth, targetHeight); + if (FAILED(hr)) + { + return recordFailure(hr, "FrameEncode::SetSize"); + } + + GUID pixelFormat = selection.pixelFormat; + hr = frameEncode->SetPixelFormat(&pixelFormat); + if (FAILED(hr)) + { + return recordFailure(hr, "FrameEncode::SetPixelFormat"); + } + const bool encoderIsIndexed = MapPixelFormatToColors(pixelFormat) > 0; + + UINT bitsPerPixel = 0; + { + Microsoft::WRL::ComPtr componentInfo; + hr = handle.backend->Factory()->CreateComponentInfo(pixelFormat, &componentInfo); + if (FAILED(hr)) + { + return recordFailure(hr, "CreateComponentInfo"); + } + Microsoft::WRL::ComPtr pixelInfo; + hr = componentInfo.As(&pixelInfo); + if (FAILED(hr)) + { + return recordFailure(hr, "ComponentInfo::AsPixelFormatInfo"); + } + hr = pixelInfo->GetBitsPerPixel(&bitsPerPixel); + if (FAILED(hr)) + { + return recordFailure(hr, "PixelFormatInfo::GetBitsPerPixel"); + } + } + + if (bitsPerPixel == 0) + { + return PVC_UNSUP_OUT_PARAMS; + } + const ULONGLONG bitsPerRow = static_cast(targetWidth) * bitsPerPixel; + const ULONGLONG stride64 = (bitsPerRow + 7ull) / 8ull; + if (stride64 > static_cast(std::numeric_limits::max())) + { + return PVC_OUT_OF_MEMORY; + } + const UINT encodedStride = static_cast(stride64); + + Microsoft::WRL::ComPtr baseSource = + (selection.isIndexed && quantizedPaletteSource) ? quantizedPaletteSource : source; + Microsoft::WRL::ComPtr frameSource = baseSource; + Microsoft::WRL::ComPtr framePalette = palette; + + if (encoderIsIndexed) + { + UINT encoderPaletteEntries = MapPixelFormatToColors(pixelFormat); + if (!framePalette) + { + hr = handle.backend->Factory()->CreatePalette(&framePalette); + if (FAILED(hr)) + { + return recordFailure(hr, "CreatePaletteForFrame"); + } + } + + if (encoderPaletteEntries > 0) + { + if (paletteColors.empty()) + { + UINT paletteCount = 0; + hr = framePalette->GetColorCount(&paletteCount); + if (FAILED(hr)) + { + return recordFailure(hr, "FramePalette::GetColorCount"); + } + if (paletteCount > 0) + { + paletteColors.resize(paletteCount); + UINT actual = paletteCount; + hr = framePalette->GetColors(paletteCount, paletteColors.data(), &actual); + if (FAILED(hr)) + { + return recordFailure(hr, "FramePalette::GetColors"); + } + paletteColors.resize(actual); + } + } + + if (paletteColors.empty()) + { + paletteColors.resize(encoderPaletteEntries, 0); + } + if (paletteColors.size() < encoderPaletteEntries) + { + const WICColor fill = paletteColors.empty() ? 0 : paletteColors.back(); + paletteColors.resize(encoderPaletteEntries, fill); + } + else if (paletteColors.size() > encoderPaletteEntries) + { + paletteColors.resize(encoderPaletteEntries); + } + + if (gifTransparencyIndex.has_value()) + { + if (paletteColors.empty()) + { + gifTransparencyIndex.reset(); + } + else if (gifTransparencyIndex.value() >= paletteColors.size()) + { + const BYTE newIndex = static_cast(paletteColors.size() - 1); + gifTransparencyIndex = newIndex; + paletteColors[newIndex] &= 0x00FFFFFFu; + } + } + + if (!paletteColors.empty()) + { + hr = framePalette->InitializeCustom(paletteColors.data(), static_cast(paletteColors.size())); + if (FAILED(hr)) + { + return recordFailure(hr, "FramePalette::InitializeCustom"); + } + } + } + + if (framePalette && mapping.container == GUID_ContainerFormatGif) + { + // The GIF encoder caches the palette when SetPalette is called. Re-register the palette + // after aligning it with the negotiated pixel format so the encoder sees the final colors. + hr = encoder->SetPalette(framePalette.Get()); + if (FAILED(hr)) + { + return recordFailure(hr, "Encoder::SetFramePalette"); + } + } + + Microsoft::WRL::ComPtr converter; + hr = handle.backend->Factory()->CreateFormatConverter(&converter); + if (FAILED(hr)) + { + return recordFailure(hr, "CreateFormatConverter (indexed)"); + } + + const WICBitmapPaletteType paletteType = + useUniformPalette ? WICBitmapPaletteTypeFixedWebPalette : WICBitmapPaletteTypeCustom; + hr = converter->Initialize(baseSource.Get(), pixelFormat, paletteDither, framePalette.Get(), 0.0, paletteType); + if (FAILED(hr)) + { + return recordFailure(hr, "FormatConverter::Initialize (indexed)"); + } + + hr = converter.As(&frameSource); + if (FAILED(hr)) + { + return recordFailure(hr, "FormatConverter::As (indexed)"); + } + } + else + { + framePalette.Reset(); + + WICPixelFormatGUID baseFormat{}; + hr = baseSource->GetPixelFormat(&baseFormat); + if (FAILED(hr)) + { + return recordFailure(hr, "BaseSource::GetPixelFormat"); + } + + if (IsEqualGUID(baseFormat, pixelFormat)) + { + frameSource = baseSource; + } + else + { + Microsoft::WRL::ComPtr converter; + hr = handle.backend->Factory()->CreateFormatConverter(&converter); + if (FAILED(hr)) + { + return recordFailure(hr, "CreateFormatConverter (non-indexed)"); + } + + const bool encoderIsGray = (pixelFormat == GUID_WICPixelFormat8bppGray); + const WICBitmapPaletteType paletteType = + encoderIsGray ? WICBitmapPaletteTypeFixedGray256 : WICBitmapPaletteTypeCustom; + IWICPalette* conversionPalette = nullptr; + if (selection.isIndexed && palette) + { + conversionPalette = palette.Get(); + } + if (IsEqualGUID(pixelFormat, GUID_WICPixelFormat32bppCMYK)) + { + frameSource.Reset(); + hr = ConvertBgraSourceToCmyk(handle.backend->Factory(), baseSource.Get(), + frameSource.ReleaseAndGetAddressOf()); + if (FAILED(hr)) + { + return recordFailure(hr, "ConvertBgraSourceToCmyk"); + } + } + else + { + hr = converter->Initialize(baseSource.Get(), pixelFormat, WICBitmapDitherTypeNone, conversionPalette, 0.0, + paletteType); + if (FAILED(hr)) + { + return recordFailure(hr, "FormatConverter::Initialize (non-indexed)"); + } + + hr = converter.As(&frameSource); + if (FAILED(hr)) + { + return recordFailure(hr, "FormatConverter::As (non-indexed)"); + } + } + } + } + + gifTransparencyEnabled = gifTransparencyIndex.has_value(); + + if (encoderIsIndexed && framePalette) + { + hr = frameEncode->SetPalette(framePalette.Get()); + if (FAILED(hr)) + { + return recordFailure(hr, "FrameEncode::SetPalette"); + } + } + + double sourceDpiX = 0.0; + double sourceDpiY = 0.0; + if (frame.frame) + { + frame.frame->GetResolution(&sourceDpiX, &sourceDpiY); + } + + const DWORD requestedDpiX = info ? info->HorDPI : 0; + const DWORD requestedDpiY = info ? info->VerDPI : 0; + const double dpiX = ResolveDpiValue(requestedDpiX, sourceDpiX, 96.0); + const double dpiY = ResolveDpiValue(requestedDpiY, sourceDpiY, 96.0); + + hr = frameEncode->SetResolution(dpiX, dpiY); + if (FAILED(hr)) + { +#if defined(WINCODEC_ERR_UNSUPPORTEDOPERATION) + if (hr != WINCODEC_ERR_UNSUPPORTEDOPERATION) + { + return recordFailure(hr, "FrameEncode::SetResolution"); + } + // Some encoders (e.g., GIF, ICO) do not support DPI metadata. In that + // case, leave the encoder's default resolution untouched. +#else + return recordFailure(hr, "FrameEncode::SetResolution"); +#endif + } + + Microsoft::WRL::ComPtr metadataWriter; + if (FAILED(frameEncode->GetMetadataQueryWriter(&metadataWriter))) + { + metadataWriter.Reset(); + } + if (metadataWriter) + { + HRESULT metaHr = ApplyCommentMetadata(mapping.container, metadataWriter.Get(), comment); + if (FAILED(metaHr)) + { + return recordFailure(metaHr, "ApplyCommentMetadata"); + } + + if (mapping.container == GUID_ContainerFormatGif) + { + Microsoft::WRL::ComPtr gifMetadataWriter = encoderMetadataWriter; + if (!gifMetadataWriter) + { + gifMetadataWriter = metadataWriter; + } + + if (info) + { + if (gifMetadataWriter) + { + if (gifMetadataWriter.Get() == encoderMetadataWriter.Get()) + { + const UINT gifMaxDimension = static_cast(std::numeric_limits::max()); + if (targetWidth > gifMaxDimension || targetHeight > gifMaxDimension) + { + return recordFailure(WINCODEC_ERR_INVALIDPARAMETER, "GIF Logical Screen too large"); + } + + PROPVARIANT prop; + PropVariantInit(&prop); + prop.vt = VT_UI2; + prop.uiVal = static_cast(targetWidth); + metaHr = gifMetadataWriter->SetMetadataByName(L"/logscrdesc/Width", &prop); + PropVariantClear(&prop); + if (FAILED(metaHr) && metaHr != WINCODEC_ERR_PROPERTYNOTSUPPORTED && + metaHr != WINCODEC_ERR_PROPERTYNOTFOUND) + { + return recordFailure(metaHr, "Set GIF LogicalScreenWidth"); + } + + PropVariantInit(&prop); + prop.vt = VT_UI2; + prop.uiVal = static_cast(targetHeight); + metaHr = gifMetadataWriter->SetMetadataByName(L"/logscrdesc/Height", &prop); + PropVariantClear(&prop); + if (FAILED(metaHr) && metaHr != WINCODEC_ERR_PROPERTYNOTSUPPORTED && + metaHr != WINCODEC_ERR_PROPERTYNOTFOUND) + { + return recordFailure(metaHr, "Set GIF LogicalScreenHeight"); + } + } + + PROPVARIANT prop; + PropVariantInit(&prop); + prop.vt = VT_UI1 | VT_VECTOR; + prop.caub.cElems = 3; + prop.caub.pElems = + static_cast(CoTaskMemAlloc(prop.caub.cElems * sizeof(BYTE))); + if (prop.caub.pElems) + { + const char* version = (info->Flags & PVSF_GIF89) != 0 ? "89a" : "87a"; + memcpy(prop.caub.pElems, version, prop.caub.cElems); + metaHr = gifMetadataWriter->SetMetadataByName(L"/logscrdesc/Version", &prop); + } + else + { + metaHr = E_OUTOFMEMORY; + } + PropVariantClear(&prop); + if (FAILED(metaHr) && metaHr != WINCODEC_ERR_PROPERTYNOTSUPPORTED && + metaHr != WINCODEC_ERR_PROPERTYNOTFOUND && metaHr != E_INVALIDARG && + metaHr != HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER)) + { + return recordFailure(metaHr, "Set GIF Version"); + } + } + } + + if (hasGifInterlaceFlag) + { + PROPVARIANT prop; + PropVariantInit(&prop); + prop.vt = VT_BOOL; + prop.boolVal = gifInterlaceFlag ? VARIANT_TRUE : VARIANT_FALSE; + metaHr = metadataWriter->SetMetadataByName(L"/imgdesc/InterlaceFlag", &prop); + PropVariantClear(&prop); + if (FAILED(metaHr) && metaHr != WINCODEC_ERR_PROPERTYNOTSUPPORTED && + metaHr != WINCODEC_ERR_PROPERTYNOTFOUND) + { + return recordFailure(metaHr, "Set GIF InterlaceFlag"); + } + } + + PROPVARIANT prop; + PropVariantInit(&prop); + prop.vt = VT_BOOL; + prop.boolVal = gifTransparencyEnabled ? VARIANT_TRUE : VARIANT_FALSE; + metaHr = metadataWriter->SetMetadataByName(L"/grctlext/TransparencyFlag", &prop); + PropVariantClear(&prop); + if (FAILED(metaHr) && metaHr != WINCODEC_ERR_PROPERTYNOTSUPPORTED && metaHr != WINCODEC_ERR_PROPERTYNOTFOUND) + { + return recordFailure(metaHr, "Set GIF TransparencyFlag"); + } + + if (gifTransparencyIndex.has_value()) + { + PropVariantInit(&prop); + prop.vt = VT_UI1; + prop.bVal = static_cast(gifTransparencyIndex.value()); + metaHr = metadataWriter->SetMetadataByName(L"/grctlext/TransparentColorIndex", &prop); + PropVariantClear(&prop); + if (FAILED(metaHr) && metaHr != WINCODEC_ERR_PROPERTYNOTSUPPORTED && metaHr != WINCODEC_ERR_PROPERTYNOTFOUND) + { + return recordFailure(metaHr, "Set GIF TransparentColorIndex"); + } + } + } + + if (mapping.container == GUID_ContainerFormatTiff && info) + { + UINT rowsPerStrip = 0; + if ((info->Flags & PVSF_DO_NOT_STRIP) != 0) + { + rowsPerStrip = targetHeight; + } + else if (info->Misc.TIFF.StripSize != 0 && encodedStride != 0) + { + const ULONGLONG stripBytes = static_cast(info->Misc.TIFF.StripSize) * 1024ull; + if (stripBytes > 0) + { + ULONGLONG rows = stripBytes / encodedStride; + if (rows == 0) + { + rows = 1; + } + if (rows > targetHeight) + { + rows = targetHeight; + } + rowsPerStrip = static_cast(rows); + } + } + if (rowsPerStrip > 0) + { + PROPVARIANT prop; + PropVariantInit(&prop); + prop.vt = VT_UI4; + prop.ulVal = rowsPerStrip; + metaHr = metadataWriter->SetMetadataByName(L"/ifd/{ushort=278}", &prop); + PropVariantClear(&prop); + if (FAILED(metaHr) && metaHr != WINCODEC_ERR_PROPERTYNOTSUPPORTED && metaHr != WINCODEC_ERR_PROPERTYNOTFOUND) + { + return recordFailure(metaHr, "Set TIFF RowsPerStrip"); + } + } + } + } + + hr = frameEncode->WriteSource(frameSource.Get(), nullptr); + if (FAILED(hr)) + { + return recordFailure(hr, "FrameEncode::WriteSource"); + } + + hr = frameEncode->Commit(); + if (FAILED(hr)) + { + return recordFailure(hr, "FrameEncode::Commit"); + } + hr = encoder->Commit(); + if (FAILED(hr)) + { + return recordFailure(hr, "Encoder::Commit"); + } + return PVC_OK; +} + +DWORD MapPixelFormatToColors(const GUID& guid) +{ + if (guid == GUID_WICPixelFormat1bppIndexed) + return 2; + if (guid == GUID_WICPixelFormat4bppIndexed) + return 16; + if (guid == GUID_WICPixelFormat8bppIndexed) + return 256; + return 0; +} + +} // namespace + +ScopedCoInit::ScopedCoInit() + : m_hr(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)) + , m_needUninit(false) +{ + if (m_hr == RPC_E_CHANGED_MODE) + { + m_hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + } + if (m_hr == S_OK || m_hr == S_FALSE) + { + m_needUninit = true; + } +} + +ScopedCoInit::~ScopedCoInit() +{ + if (m_needUninit) + { + CoUninitialize(); + } +} + +Backend::Backend() +{ + if (!m_comScope.Succeeded()) + { + return; + } + auto hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_factory)); + if (FAILED(hr)) + { + m_factory.Reset(); + } +} + +Backend& Backend::Instance() +{ + static Backend instance; + return instance; +} + +bool Backend::Populate(CPVW32DLL& table) +{ + if (!m_factory) + { + return false; + } + + table.PVOpenImageEx = &Backend::sPVOpenImageEx; + table.PVCloseImage = &Backend::sPVCloseImage; + table.PVReadImage2 = &Backend::sPVReadImage2; + table.PVDrawImage = &Backend::sPVDrawImage; + table.PVGetErrorText = &Backend::sPVGetErrorText; + table.PVSetBkHandle = &Backend::sPVSetBkHandle; + table.PVGetDLLVersion = &Backend::sPVGetDLLVersion; + table.PVSetStretchParameters = &Backend::sPVSetStretchParameters; + table.PVLoadFromClipboard = &Backend::sPVLoadFromClipboard; + table.PVGetImageInfo = &Backend::sPVGetImageInfo; + table.PVSetParam = &Backend::sPVSetParam; + table.PVGetHandles2 = &Backend::sPVGetHandles2; + table.PVSaveImage = &Backend::sPVSaveImage; + table.PVChangeImage = &Backend::sPVChangeImage; + table.PVIsOutCombSupported = &Backend::sPVIsOutCombSupported; + table.PVReadImageSequence = &Backend::sPVReadImageSequence; + table.PVCropImage = &Backend::sPVCropImage; + table.GetRGBAtCursor = &Backend::sGetRGBAtCursor; + table.CalculateHistogram = &Backend::sCalculateHistogram; + table.CreateThumbnail = &Backend::sCreateThumbnail; + table.SimplifyImageSequence = &Backend::sSimplifyImageSequence; + table.Handle = nullptr; + StringCchCopyA(table.Version, SizeOf(table.Version), "WIC 1.0"); + return true; +} + +ImageHandle* Backend::FromHandle(LPPVHandle handle) +{ + return reinterpret_cast(handle); +} + +PVCODE WINAPI Backend::sPVOpenImageEx(LPPVHandle* Img, LPPVOpenImageExInfo pOpenExInfo, LPPVImageInfo pImgInfo, int size) +{ + if (!Img || !pOpenExInfo) + { + return PVC_INVALID_HANDLE; + } + *Img = nullptr; + if (!(pOpenExInfo->Flags & PVOF_ATTACH_TO_HANDLE) && !pOpenExInfo->FileName) + { + return PVC_UNSUP_FILE_TYPE; + } + + ScopedCoInit init; + if (!init.Succeeded()) + { + return PVC_EXCEPTION; + } + + auto& backend = Backend::Instance(); + auto image = std::make_unique(); + image->backend = &backend; + image->openFlags = pOpenExInfo->Flags; + + if (pOpenExInfo->Flags & PVOF_ATTACH_TO_HANDLE) + { + HBITMAP bitmap = reinterpret_cast(pOpenExInfo->Handle); + if (!bitmap) + { + return PVC_INVALID_HANDLE; + } + + FrameData frame; + HRESULT hr = PopulateFrameFromBitmapHandle(backend, frame, bitmap); + if (FAILED(hr)) + { + return HResultToPvCode(hr); + } + + image->frames.push_back(std::move(frame)); + image->baseInfo.Format = PVF_BMP; + image->baseInfo.Flags = 0; + image->baseInfo.NumOfImages = 1; + image->baseInfo.FileSize = 0; + } + else + { + image->fileName = Utf8ToWide(pOpenExInfo->FileName); + if (image->fileName.empty()) + { + return PVC_CANNOT_OPEN_FILE; + } + image->baseInfo.FileSize = QueryFileSize(image->fileName); + + Microsoft::WRL::ComPtr decoder; + HRESULT hr = CreateDecoder(backend, image->fileName, &decoder); + if (FAILED(hr)) + { + return HResultToPvCode(hr); + } + hr = CollectFrames(backend, decoder.Get(), *image); + if (FAILED(hr)) + { + return HResultToPvCode(hr); + } + } + + if (image->frames.empty()) + { + return PVC_UNSUP_FILE_TYPE; + } + + image->baseInfo.NumOfImages = static_cast(image->frames.size()); + + HRESULT hr = DecodeFrame(*image, 0); + if (FAILED(hr)) + { + return HResultToPvCode(hr); + } + + if (pImgInfo) + { + const DWORD bufferSize = size > 0 ? static_cast(size) : 0; + PopulateImageInfo(*image, pImgInfo, bufferSize, false, 0, 0); + } + + *Img = reinterpret_cast(image.release()); + return PVC_OK; +} + +PVCODE WINAPI Backend::sPVCloseImage(LPPVHandle Img) +{ + auto handle = FromHandle(Img); + if (!handle) + { + return PVC_INVALID_HANDLE; + } + for (auto& frame : handle->frames) + { + frame.converter.Reset(); + frame.colorConvertedSource.Reset(); + if (frame.hbitmap) + { + DeleteObject(frame.hbitmap); + frame.hbitmap = nullptr; + } + if (frame.scaledBitmap) + { + DeleteObject(frame.scaledBitmap); + frame.scaledBitmap = nullptr; + } + if (frame.transparencyMask) + { + DeleteObject(frame.transparencyMask); + frame.transparencyMask = nullptr; + } + if (frame.paletteHandle) + { + DeleteObject(frame.paletteHandle); + frame.paletteHandle = nullptr; + } + } + delete handle; + return PVC_OK; +} + +PVCODE WINAPI Backend::sPVReadImage2(LPPVHandle Img, HDC paintDC, RECT* dRect, TProgressProc /*progress*/, void* /*appSpecific*/, + int imageIndex) +{ + auto handle = FromHandle(Img); + if (!handle) + { + return PVC_INVALID_HANDLE; + } + ScopedCoInit init; + if (!init.Succeeded()) + { + return PVC_EXCEPTION; + } + if (handle->frames.empty()) + { + return PVC_INVALID_HANDLE; + } + const size_t normalizedIndex = NormalizeFrameIndex(*handle, imageIndex, 0); + HRESULT hr = DecodeFrame(*handle, normalizedIndex); + if (FAILED(hr)) + { + return HResultToPvCode(hr); + } + return DrawFrame(*handle, handle->frames[normalizedIndex], paintDC, dRect ? dRect->left : 0, + dRect ? dRect->top : 0, dRect); +} + +PVCODE WINAPI Backend::sPVDrawImage(LPPVHandle Img, HDC paintDC, int x, int y, LPRECT rect) +{ + auto handle = FromHandle(Img); + if (!handle) + { + return PVC_INVALID_HANDLE; + } + ScopedCoInit init; + if (!init.Succeeded()) + { + return PVC_EXCEPTION; + } + HRESULT hr = DecodeFrame(*handle, 0); + if (FAILED(hr)) + { + return HResultToPvCode(hr); + } + return DrawFrame(*handle, handle->frames[0], paintDC, x, y, rect); +} + +const char* WINAPI Backend::sPVGetErrorText(DWORD errorCode) +{ + return LookupError(errorCode); +} + +PVCODE WINAPI Backend::sPVSetBkHandle(LPPVHandle Img, COLORREF bkColor) +{ + auto handle = FromHandle(Img); + if (!handle) + { + return PVC_INVALID_HANDLE; + } + handle->background = bkColor; + return PVC_OK; +} + +DWORD WINAPI Backend::sPVGetDLLVersion() +{ + return kBackendVersion; +} + +PVCODE WINAPI Backend::sPVSetStretchParameters(LPPVHandle Img, DWORD width, DWORD height, DWORD mode) +{ + auto handle = FromHandle(Img); + if (!handle) + { + return PVC_INVALID_HANDLE; + } + const auto convert = [](DWORD value) -> LONG { + if (value == 0 || value == 0x80000000u) + { + return 0; + } + const LONG signedValue = static_cast(value); + if ((value & 0x80000000u) != 0u) + { + return signedValue; + } + if (value > static_cast(std::numeric_limits::max())) + { + return std::numeric_limits::max(); + } + return signedValue; + }; + + handle->stretchWidth = convert(width); + handle->stretchHeight = convert(height); + handle->stretchMode = mode; + return PVC_OK; +} + +PVCODE WINAPI Backend::sPVLoadFromClipboard(LPPVHandle* /*Img*/, LPPVImageInfo /*pImgInfo*/, int /*size*/) +{ + return PVC_UNSUP_FILE_TYPE; +} + +PVCODE WINAPI Backend::sPVGetImageInfo(LPPVHandle Img, LPPVImageInfo pImgInfo, int size, int imageIndex) +{ + auto handle = FromHandle(Img); + if (!handle || !pImgInfo) + { + return PVC_INVALID_HANDLE; + } + ScopedCoInit init; + if (!init.Succeeded()) + { + return PVC_EXCEPTION; + } + const DWORD bufferSize = size > 0 ? static_cast(size) : 0; + DWORD previousIndex = 0; + bool hasPreviousIndex = false; + if (bufferSize >= sizeof(PVImageInfo) && pImgInfo->cbSize == sizeof(PVImageInfo)) + { + previousIndex = pImgInfo->CurrentImage; + hasPreviousIndex = true; + } + size_t fallbackIndex = 0; + if (!handle->frames.empty()) + { + const size_t lastIndex = handle->frames.size() - 1; + fallbackIndex = std::min(static_cast(previousIndex), lastIndex); + } + else + { + return PVC_INVALID_HANDLE; + } + const size_t normalizedIndex = NormalizeFrameIndex(*handle, imageIndex, fallbackIndex); + HRESULT hr = DecodeFrame(*handle, normalizedIndex); + if (FAILED(hr)) + { + return HResultToPvCode(hr); + } + return PopulateImageInfo(*handle, pImgInfo, bufferSize, hasPreviousIndex, previousIndex, imageIndex); +} + +PVCODE WINAPI Backend::sPVSetParam(LPPVHandle /*Img*/) +{ + return PVC_OK; +} + +PVCODE WINAPI Backend::sPVGetHandles2(LPPVHandle Img, LPPVImageHandles* pHandles) +{ + auto handle = FromHandle(Img); + if (!handle || !pHandles) + { + return PVC_INVALID_HANDLE; + } + ScopedCoInit init; + if (!init.Succeeded()) + { + return PVC_EXCEPTION; + } + HRESULT hr = DecodeFrame(*handle, 0); + if (FAILED(hr)) + { + return HResultToPvCode(hr); + } + auto& frame = handle->frames[0]; + PVImageHandles& handles = handle->handles; + ZeroMemory(&handles, sizeof(PVImageHandles)); + const bool hasIndexedPixels = frame.useIndexedPixels && !frame.indexedPixels.empty(); + const bool providePaletteHandle = hasIndexedPixels || (frame.realizePalette && frame.paletteHandle); + + handles.TransparentHandle = frame.hasTransparency ? frame.transparencyMask : nullptr; + handles.TransparentBackgroundHandle = frame.hbitmap; + handles.StretchedHandle = frame.hbitmap; + handles.StretchedTransparentHandle = frame.hbitmap; + handles.HPal = providePaletteHandle ? frame.paletteHandle : nullptr; + handles.Palette = frame.palette.empty() ? nullptr : frame.palette.data(); + handles.pLines = frame.linePointers.empty() ? nullptr : frame.linePointers.data(); + *pHandles = &handles; + return PVC_OK; +} + +PVCODE WINAPI Backend::sPVSaveImage(LPPVHandle Img, const char* outFileName, LPPVSaveImageInfo pSii, TProgressProc /*progress*/, + void* /*appSpecific*/, int imageIndex) +{ + auto handle = FromHandle(Img); + if (!handle) + { + return PVC_INVALID_HANDLE; + } + ScopedCoInit init; + if (!init.Succeeded()) + { + return PVC_EXCEPTION; + } + if (!outFileName) + { + return PVC_UNSUP_OUT_PARAMS; + } + const auto fileName = Utf8ToWide(outFileName); + const GuidMapping* mapping = nullptr; + for (const auto& item : kEncoderMappings) + { + if (item.format == pSii->Format) + { + mapping = &item; + break; + } + } + if (!mapping) + { + return PVC_UNSUP_OUT_PARAMS; + } + return SaveFrame(*handle, imageIndex, fileName.c_str(), *mapping, pSii); +} + +PVCODE WINAPI Backend::sPVChangeImage(LPPVHandle Img, DWORD flags) +{ + auto handle = FromHandle(Img); + if (!handle) + { + return PVC_INVALID_HANDLE; + } + ScopedCoInit init; + if (!init.Succeeded()) + { + return PVC_EXCEPTION; + } + if (handle->frames.empty()) + { + return PVC_INVALID_HANDLE; + } + FrameData& frame = handle->frames[0]; + HRESULT hr = DecodeFrame(*handle, 0); + if (FAILED(hr)) + { + return HResultToPvCode(hr); + } + frame.converter.Reset(); + frame.colorConvertedSource.Reset(); + if (!(flags & (PVCF_ROTATE90CW | PVCF_ROTATE90CCW))) + { + return PVC_OK; + } + + const UINT newWidth = frame.height; + const UINT newHeight = frame.width; + std::vector rotated(frame.pixels.size()); + for (UINT y = 0; y < frame.height; ++y) + { + for (UINT x = 0; x < frame.width; ++x) + { + BYTE* src = frame.pixels.data() + y * frame.stride + x * 4; + UINT dstX = flags & PVCF_ROTATE90CW ? (frame.height - 1 - y) : y; + UINT dstY = flags & PVCF_ROTATE90CW ? x : (frame.width - 1 - x); + BYTE* dst = rotated.data() + dstY * newWidth * 4 + dstX * 4; + memcpy(dst, src, 4); + } + } + frame.width = newWidth; + frame.height = newHeight; + frame.stride = frame.width * 4; + frame.pixels.swap(rotated); + frame.disposalBuffer.clear(); + frame.compositedPixels.clear(); + handle->gifComposeCanvas.clear(); + handle->gifSavedCanvas.clear(); + handle->gifCanvasInitialized = false; + HRESULT finalizeHr = FinalizeDecodedFrame(handle->backend, frame); + if (FAILED(finalizeHr)) + { + return HResultToPvCode(finalizeHr); + } + frame.rect.left = 0; + frame.rect.top = 0; + frame.rect.right = ClampUnsignedToLong(static_cast(frame.width)); + frame.rect.bottom = ClampUnsignedToLong(static_cast(frame.height)); + frame.disposal = PVDM_UNDEFINED; + return PVC_OK; +} + +DWORD WINAPI Backend::sPVIsOutCombSupported(int format, int /*compression*/, int /*colors*/, int /*colorModel*/) +{ + for (const auto& item : kEncoderMappings) + { + if (item.format == static_cast(format)) + { + return 0; + } + } + return static_cast(-1); +} + +PVCODE WINAPI Backend::sPVReadImageSequence(LPPVHandle Img, LPPVImageSequence* seq) +{ + auto handle = FromHandle(Img); + if (!handle) + { + return PVC_INVALID_HANDLE; + } + ScopedCoInit init; + if (!init.Succeeded()) + { + return PVC_EXCEPTION; + } + return CreateSequenceNodes(*handle, seq); +} + +PVCODE WINAPI Backend::sPVCropImage(LPPVHandle Img, int left, int top, int width, int height) +{ + auto handle = FromHandle(Img); + if (!handle) + { + return PVC_INVALID_HANDLE; + } + ScopedCoInit init; + if (!init.Succeeded()) + { + return PVC_EXCEPTION; + } + FrameData& frame = handle->frames[0]; + HRESULT hr = DecodeFrame(*handle, 0); + if (FAILED(hr)) + { + return HResultToPvCode(hr); + } + frame.converter.Reset(); + frame.colorConvertedSource.Reset(); + if (left < 0 || top < 0 || width <= 0 || height <= 0 || left + width > static_cast(frame.width) || + top + height > static_cast(frame.height)) + { + return PVC_INVALID_DIMENSIONS; + } + std::vector cropped(static_cast(width) * static_cast(height) * 4); + const UINT newStride = width * 4; + for (int y = 0; y < height; ++y) + { + const BYTE* src = frame.pixels.data() + (top + y) * frame.stride + left * 4; + BYTE* dst = cropped.data() + y * newStride; + memcpy(dst, src, newStride); + } + frame.width = static_cast(width); + frame.height = static_cast(height); + frame.stride = newStride; + frame.pixels.swap(cropped); + frame.disposalBuffer.clear(); + frame.compositedPixels.clear(); + handle->gifComposeCanvas.clear(); + handle->gifSavedCanvas.clear(); + handle->gifCanvasInitialized = false; + HRESULT finalizeHr = FinalizeDecodedFrame(handle->backend, frame); + if (FAILED(finalizeHr)) + { + return HResultToPvCode(finalizeHr); + } + frame.rect.left = 0; + frame.rect.top = 0; + frame.rect.right = ClampUnsignedToLong(static_cast(frame.width)); + frame.rect.bottom = ClampUnsignedToLong(static_cast(frame.height)); + frame.disposal = PVDM_UNDEFINED; + return PVC_OK; +} + +bool Backend::sGetRGBAtCursor(LPPVHandle Img, DWORD /*colors*/, int x, int y, RGBQUAD* rgb, int* /*index*/) +{ + auto handle = FromHandle(Img); + if (!handle) + { + return false; + } + ScopedCoInit init; + if (!init.Succeeded()) + { + return false; + } + FrameData& frame = handle->frames[0]; + if (!frame.decoded) + { + if (FAILED(DecodeFrame(*handle, 0))) + { + return false; + } + } + if (x < 0 || y < 0 || x >= static_cast(frame.width) || y >= static_cast(frame.height)) + { + return false; + } + const BYTE* src = frame.pixels.data() + y * frame.stride + x * 4; + if (rgb) + { + rgb->rgbBlue = src[0]; + rgb->rgbGreen = src[1]; + rgb->rgbRed = src[2]; + rgb->rgbReserved = src[3]; + } + return true; +} + +PVCODE Backend::sCalculateHistogram(LPPVHandle Img, const LPPVImageInfo /*info*/, LPDWORD luminosity, LPDWORD red, LPDWORD green, + LPDWORD blue, LPDWORD rgb) +{ + auto handle = FromHandle(Img); + if (!handle) + { + return PVC_INVALID_HANDLE; + } + ScopedCoInit init; + if (!init.Succeeded()) + { + return PVC_EXCEPTION; + } + FrameData& frame = handle->frames[0]; + if (!frame.decoded) + { + if (FAILED(DecodeFrame(*handle, 0))) + { + return PVC_EXCEPTION; + } + } + std::fill(luminosity, luminosity + 256, 0); + std::fill(red, red + 256, 0); + std::fill(green, green + 256, 0); + std::fill(blue, blue + 256, 0); + std::fill(rgb, rgb + 256, 0); + + for (UINT y = 0; y < frame.height; ++y) + { + const BYTE* src = frame.pixels.data() + y * frame.stride; + for (UINT x = 0; x < frame.width; ++x) + { + BYTE b = src[x * 4 + 0]; + BYTE g = src[x * 4 + 1]; + BYTE r = src[x * 4 + 2]; + BYTE l = static_cast((static_cast(r) * 30 + static_cast(g) * 59 + static_cast(b) * 11) / 100); + luminosity[l]++; + red[r]++; + green[g]++; + blue[b]++; + rgb[(r + g + b) / 3]++; + } + } + return PVC_OK; +} + +PVCODE Backend::sCreateThumbnail(LPPVHandle Img, LPPVSaveImageInfo /*sii*/, int imageIndex, DWORD imgWidth, DWORD imgHeight, + int thumbWidth, int thumbHeight, CSalamanderThumbnailMakerAbstract* thumbMaker, + DWORD thumbFlags, TProgressProc progressProc, void* progressProcArg) +{ + auto handle = FromHandle(Img); + if (!handle || !thumbMaker) + { + return PVC_INVALID_HANDLE; + } + ScopedCoInit init; + if (!init.Succeeded()) + { + return PVC_EXCEPTION; + } + if (handle->frames.empty()) + { + return PVC_INVALID_HANDLE; + } + const size_t normalizedIndex = NormalizeFrameIndex(*handle, imageIndex, 0); + HRESULT hr = DecodeFrame(*handle, normalizedIndex); + if (FAILED(hr)) + { + return HResultToPvCode(hr); + } + FrameData& frame = handle->frames[normalizedIndex]; + + int targetWidth = thumbWidth > 0 ? thumbWidth : static_cast(frame.width); + int targetHeight = thumbHeight > 0 ? thumbHeight : static_cast(frame.height); + if (targetWidth <= 0) + { + targetWidth = static_cast(frame.width); + } + if (targetHeight <= 0) + { + targetHeight = static_cast(frame.height); + } + + const auto calculateThumbnailSize = [](int originalWidth, int originalHeight, int maxWidth, int maxHeight, + int& thumbWidth, int& thumbHeight) { + if (originalWidth <= maxWidth && originalHeight <= maxHeight) + { + thumbWidth = originalWidth; + thumbHeight = originalHeight; + return false; + } + + const double aspect = static_cast(originalWidth) / static_cast(originalHeight); + const double bounds = static_cast(maxWidth) / static_cast(maxHeight); + if (bounds < aspect) + { + thumbWidth = maxWidth; + thumbHeight = static_cast(static_cast(maxWidth) / aspect); + } + else + { + thumbHeight = maxHeight; + thumbWidth = static_cast(static_cast(maxHeight) * aspect); + } + + if (thumbWidth < 1) + { + thumbWidth = 1; + } + if (thumbHeight < 1) + { + thumbHeight = 1; + } + return true; + }; + + calculateThumbnailSize(static_cast(imgWidth), static_cast(imgHeight), targetWidth, targetHeight, targetWidth, + targetHeight); + + if (!thumbMaker->SetParameters(targetWidth, targetHeight, thumbFlags)) + { + return PVC_OUT_OF_MEMORY; + } + + const UINT desiredWidth = static_cast(targetWidth); + const UINT desiredHeight = static_cast(targetHeight); + + const BYTE* source = frame.pixels.data(); + std::vector scaled; + if (desiredWidth != frame.width || desiredHeight != frame.height) + { + Microsoft::WRL::ComPtr scaleSource; + if (frame.converter) + { + scaleSource = frame.converter; + } + else + { + Microsoft::WRL::ComPtr bitmap; + hr = handle->backend->Factory()->CreateBitmapFromMemory(frame.width, frame.height, GUID_WICPixelFormat32bppBGRA, + frame.stride, static_cast(frame.pixels.size()), + frame.pixels.data(), &bitmap); + if (FAILED(hr)) + { + return HResultToPvCode(hr); + } + scaleSource = bitmap; + } + + Microsoft::WRL::ComPtr scaler; + hr = handle->backend->Factory()->CreateBitmapScaler(&scaler); + if (FAILED(hr)) + { + return HResultToPvCode(hr); + } + hr = scaler->Initialize(scaleSource.Get(), desiredWidth, desiredHeight, WICBitmapInterpolationModeFant); + if (FAILED(hr)) + { + return HResultToPvCode(hr); + } + + scaled.resize(static_cast(desiredWidth) * static_cast(desiredHeight) * 4); + WICRect rect{0, 0, static_cast(desiredWidth), static_cast(desiredHeight)}; + hr = scaler->CopyPixels(&rect, desiredWidth * 4, static_cast(scaled.size()), scaled.data()); + if (FAILED(hr)) + { + return HResultToPvCode(hr); + } + + source = scaled.data(); + } + + if (progressProc && !progressProc(100, progressProcArg)) + { + return PVC_CANCELED; + } + + // The thumbnail maker expects rows in top-down order; feed them in manageable batches + // so it can honour cancellation requests. + const size_t rowBytes = static_cast(desiredWidth) * 4; + int processedRows = 0; + while (processedRows < targetHeight) + { + if (thumbMaker->GetCancelProcessing()) + { + return PVC_CANCELED; + } + + const int batch = std::min(32, targetHeight - processedRows); + BYTE* chunk = const_cast(source + static_cast(processedRows) * rowBytes); + thumbMaker->ProcessBuffer(chunk, batch); + processedRows += batch; + } + return PVC_OK; +} + +PVCODE Backend::sSimplifyImageSequence(LPPVHandle /*Img*/, HDC /*dc*/, int /*screenWidth*/, int /*screenHeight*/, + LPPVImageSequence& /*seq*/, const COLORREF& /*bgColor*/) +{ + return PVC_OK; +} + +} // namespace PictView::Wic diff --git a/src/plugins/pictview/wic/WicBackend.h b/src/plugins/pictview/wic/WicBackend.h new file mode 100644 index 000000000..14e3022f9 --- /dev/null +++ b/src/plugins/pictview/wic/WicBackend.h @@ -0,0 +1,175 @@ +// SPDX-FileCopyrightText: 2024 Open Salamander Authors +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../lib/PVW32DLL.h" +#include "../pictview.h" + +namespace PictView::Wic +{ +class Backend; + +struct ImageHandle; + +using Microsoft::WRL::ComPtr; + +struct FrameData +{ + ComPtr frame; + ComPtr converter; + ComPtr colorConvertedSource; + UINT width = 0; + UINT height = 0; + UINT stride = 0; + UINT rawWidth = 0; + UINT rawHeight = 0; + UINT rawStride = 0; + std::vector pixels; + std::vector compositedPixels; + std::vector indexedPixels; + std::vector linePointers; + std::vector palette; + std::vector disposalBuffer; + std::vector scaledPixels; + BITMAPINFOHEADER bmi{}; + BITMAPINFOHEADER indexedBmi{}; + BITMAPINFOHEADER displayBmi{}; + HBITMAP hbitmap = nullptr; + HBITMAP transparencyMask = nullptr; + HBITMAP scaledBitmap = nullptr; + HPALETTE paletteHandle = nullptr; + DWORD delayMs = 0; + RECT rect{}; + RECT gifFrameRect{}; + DWORD disposal = PVDM_UNDEFINED; + GUID sourcePixelFormat{}; + DWORD reportedColors = PV_COLOR_TC32; + DWORD reportedBitDepth = 32; + DWORD colorModel = PVCM_RGB; + UINT paletteColorCount = 0; + UINT bitsPerPixel = 0; + UINT indexedStride = 0; + UINT displayStride = 0; + UINT scaledStride = 0; + UINT scaledWidth = 0; + UINT scaledHeight = 0; + bool hasGifFrameRect = false; + bool decoded = false; + bool hasTransparency = false; + bool useIndexedPixels = false; + bool allowIndexedDisplay = true; + bool realizePalette = false; + bool gifHasTransparentColor = false; + BYTE gifTransparentIndex = 0; + bool pixelsArePremultiplied = true; +}; + +struct ImageHandle +{ + Backend* backend = nullptr; + std::wstring fileName; + DWORD openFlags = 0; + std::vector frames; + LONG stretchWidth = 0; + LONG stretchHeight = 0; + DWORD stretchMode = PV_STRETCH_NO; + COLORREF background = RGB(0, 0, 0); + PVImageInfo baseInfo{}; + PVImageHandles handles{}; + PVFormatSpecificInfo formatInfo{}; + bool hasFormatSpecificInfo = false; + LONG canvasWidth = 0; + LONG canvasHeight = 0; + bool gifHasBackgroundColor = false; + BYTE gifBackgroundAlpha = 0; + bool gifHasBackgroundIndex = false; + BYTE gifBackgroundIndex = 0; + std::vector gifComposeCanvas; + std::vector gifSavedCanvas; + bool gifCanvasInitialized = false; +}; + +/** + * Lightweight RAII helper around CoInitialize/CoUninitialize. The viewer + * already initialises COM on most threads, but the WIC backend may also be + * invoked from helper worker threads that do not call into COM yet. We keep + * this helper header-only to avoid the dependency on ATL. + */ +class ScopedCoInit +{ +public: + ScopedCoInit(); + ScopedCoInit(const ScopedCoInit&) = delete; + ScopedCoInit& operator=(const ScopedCoInit&) = delete; + ~ScopedCoInit(); + + bool Succeeded() const + { + return m_hr == S_OK || m_hr == S_FALSE; + } + +private: + HRESULT m_hr; + bool m_needUninit; +}; + +class Backend +{ +public: + Backend(); + Backend(const Backend&) = delete; + Backend& operator=(const Backend&) = delete; + + static Backend& Instance(); + + bool Populate(CPVW32DLL& table); + + IWICImagingFactory* Factory() const { return m_factory.Get(); } + +private: + static PVCODE WINAPI sPVOpenImageEx(LPPVHandle* Img, LPPVOpenImageExInfo pOpenExInfo, LPPVImageInfo pImgInfo, int size); + static PVCODE WINAPI sPVCloseImage(LPPVHandle Img); + static PVCODE WINAPI sPVReadImage2(LPPVHandle Img, HDC paintDC, RECT* dRect, TProgressProc progress, void* appSpecific, + int imageIndex); + static PVCODE WINAPI sPVDrawImage(LPPVHandle Img, HDC paintDC, int x, int y, LPRECT rect); + static const char* WINAPI sPVGetErrorText(DWORD errorCode); + static PVCODE WINAPI sPVSetBkHandle(LPPVHandle Img, COLORREF bkColor); + static DWORD WINAPI sPVGetDLLVersion(); + static PVCODE WINAPI sPVSetStretchParameters(LPPVHandle Img, DWORD width, DWORD height, DWORD mode); + static PVCODE WINAPI sPVLoadFromClipboard(LPPVHandle* Img, LPPVImageInfo pImgInfo, int size); + static PVCODE WINAPI sPVGetImageInfo(LPPVHandle Img, LPPVImageInfo pImgInfo, int size, int imageIndex); + static PVCODE WINAPI sPVSetParam(LPPVHandle Img); + static PVCODE WINAPI sPVGetHandles2(LPPVHandle Img, LPPVImageHandles* pHandles); + static PVCODE WINAPI sPVSaveImage(LPPVHandle Img, const char* outFileName, LPPVSaveImageInfo pSii, TProgressProc progress, + void* appSpecific, int imageIndex); + static PVCODE WINAPI sPVChangeImage(LPPVHandle Img, DWORD flags); + static DWORD WINAPI sPVIsOutCombSupported(int format, int compression, int colors, int colorModel); + static PVCODE WINAPI sPVReadImageSequence(LPPVHandle Img, LPPVImageSequence* seq); + static PVCODE WINAPI sPVCropImage(LPPVHandle Img, int left, int top, int width, int height); + static bool sGetRGBAtCursor(LPPVHandle Img, DWORD colors, int x, int y, RGBQUAD* rgb, int* index); + static PVCODE sCalculateHistogram(LPPVHandle Img, const LPPVImageInfo info, LPDWORD luminosity, LPDWORD red, LPDWORD green, + LPDWORD blue, LPDWORD rgb); + static PVCODE sCreateThumbnail(LPPVHandle Img, LPPVSaveImageInfo sii, int imageIndex, DWORD imgWidth, DWORD imgHeight, + int thumbWidth, int thumbHeight, CSalamanderThumbnailMakerAbstract* thumbMaker, + DWORD thumbFlags, TProgressProc progressProc, void* progressProcArg); + static PVCODE sSimplifyImageSequence(LPPVHandle Img, HDC dc, int screenWidth, int screenHeight, LPPVImageSequence& seq, + const COLORREF& bgColor); + + static ImageHandle* FromHandle(LPPVHandle handle); + + ScopedCoInit m_comScope; + ComPtr m_factory; +}; + +} // namespace PictView::Wic diff --git a/src/plugins/shared/baseaddr_x64.txt b/src/plugins/shared/baseaddr_x64.txt index 256af78f1..e9056afff 100644 --- a/src/plugins/shared/baseaddr_x64.txt +++ b/src/plugins/shared/baseaddr_x64.txt @@ -71,7 +71,6 @@ lang_mmviewer 0x0000010033800000 salrtl9 0x0000010023900000 salrtlp9 0x0000010023b00000 ero_dlls 0x0000010024000000 0x04000000 -pvw32cnv 0x0000010028100000 undelete 0x0000010028200000 lang_undelete 0x0000010038200000 7zip 0x0000010028300000 @@ -91,7 +90,6 @@ lang_nethood 0x0000010038e00000 salmon 0x0000010029000000 fcremote 0x0000010029200000 sqlite 0x0000010029300000 -salpvenv 0x0000010029400000 zip2sfx 0x0000010029500000 salopen 0x0000010029600000 salspawn 0x0000010029700000 diff --git a/src/plugins/shared/baseaddr_x86.txt b/src/plugins/shared/baseaddr_x86.txt index 989f3760f..cc6690540 100644 --- a/src/plugins/shared/baseaddr_x86.txt +++ b/src/plugins/shared/baseaddr_x86.txt @@ -71,7 +71,6 @@ lang_mmviewer 0x33800000 salrtl9 0x23900000 salrtlp9 0x23b00000 ero_dlls 0x24000000 0x04000000 -pvw32cnv 0x28100000 undelete 0x28200000 lang_undelete 0x38200000 7zip 0x28300000 @@ -91,7 +90,6 @@ lang_nethood 0x38e00000 salmon 0x29000000 fcremote 0x29200000 sqlite 0x29300000 -salpvenv 0x29400000 ; dummy - x86 version doesn't exist zip2sfx 0x29500000 salopen 0x29600000 salspawn 0x29700000 diff --git a/src/vcxproj/salamand.sln b/src/vcxproj/salamand.sln index f45010095..543edd15d 100644 --- a/src/vcxproj/salamand.sln +++ b/src/vcxproj/salamand.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.9.34310.174 @@ -163,8 +163,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "7zwrapper", "..\plugins\7zi EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fcremote", "..\plugins\filecomp\vcxproj\fcremote\fcremote.vcxproj", "{89CD2440-2844-4C0B-9CDE-1045334E9EC0}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "salpvenv", "..\plugins\pictview\vcxproj\salpvenv.vcxproj", "{17CF5E05-F29C-4E5D-BA41-83254E342E0B}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sqlite", "sqlite\sqlite.vcxproj", "{49D6CEED-804F-4EA6-BD12-2D825E13DAE3}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lang_portables", "..\plugins\portables\vcxproj\lang_portables.vcxproj", "{3F2F1440-A2C9-47F0-BE4E-3E2794BEDF07}" @@ -627,7 +625,9 @@ Global {17CF5E04-F29C-4E5D-BA41-83254E342E0B}.Release|x64.ActiveCfg = Release|x64 {17CF5E04-F29C-4E5D-BA41-83254E342E0B}.Release|x64.Build.0 = Release|x64 {17CF5E04-F29C-4E5D-BA41-83254E342E0B}.Utils (Release)|Win32.ActiveCfg = Release|Win32 + {17CF5E04-F29C-4E5D-BA41-83254E342E0B}.Utils (Release)|Win32.Build.0 = Release|Win32 {17CF5E04-F29C-4E5D-BA41-83254E342E0B}.Utils (Release)|x64.ActiveCfg = Release|x64 + {17CF5E04-F29C-4E5D-BA41-83254E342E0B}.Utils (Release)|x64.Build.0 = Release|x64 {C3D5458E-BF71-4C7D-AF45-F502A14EB528}.Debug|Win32.ActiveCfg = Debug|Win32 {C3D5458E-BF71-4C7D-AF45-F502A14EB528}.Debug|Win32.Build.0 = Debug|Win32 {C3D5458E-BF71-4C7D-AF45-F502A14EB528}.Debug|x64.ActiveCfg = Debug|x64 @@ -988,12 +988,6 @@ Global {89CD2440-2844-4C0B-9CDE-1045334E9EC0}.Release|x64.Build.0 = Release|x64 {89CD2440-2844-4C0B-9CDE-1045334E9EC0}.Utils (Release)|Win32.ActiveCfg = Release|Win32 {89CD2440-2844-4C0B-9CDE-1045334E9EC0}.Utils (Release)|x64.ActiveCfg = Release|x64 - {17CF5E05-F29C-4E5D-BA41-83254E342E0B}.Debug|Win32.ActiveCfg = Debug|Win32 - {17CF5E05-F29C-4E5D-BA41-83254E342E0B}.Debug|x64.ActiveCfg = Debug|Win32 - {17CF5E05-F29C-4E5D-BA41-83254E342E0B}.Release|Win32.ActiveCfg = Release|Win32 - {17CF5E05-F29C-4E5D-BA41-83254E342E0B}.Release|x64.ActiveCfg = Release|Win32 - {17CF5E05-F29C-4E5D-BA41-83254E342E0B}.Utils (Release)|Win32.ActiveCfg = Release|Win32 - {17CF5E05-F29C-4E5D-BA41-83254E342E0B}.Utils (Release)|x64.ActiveCfg = Release|Win32 {49D6CEED-804F-4EA6-BD12-2D825E13DAE3}.Debug|Win32.ActiveCfg = Debug|Win32 {49D6CEED-804F-4EA6-BD12-2D825E13DAE3}.Debug|Win32.Build.0 = Debug|Win32 {49D6CEED-804F-4EA6-BD12-2D825E13DAE3}.Debug|x64.ActiveCfg = Debug|x64