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