From 5b6e8e0eb10e8351ebde8e4af3779e2bb5f3cdfc Mon Sep 17 00:00:00 2001 From: Viliam Lejcik Date: Mon, 27 Jan 2025 08:47:47 +0100 Subject: [PATCH 1/9] pictview: added support for viewing .heif and .webp files (experimental) --- src/plugins/pictview/PVMessage.h | 46 +- src/plugins/pictview/PVOverrider.cpp | 434 ++++++++++++++++++ src/plugins/pictview/PVOverrider.h | 9 + src/plugins/pictview/heif.cpp | 137 ++++++ src/plugins/pictview/heif.h | 22 + src/plugins/pictview/histwnd.cpp | 1 - src/plugins/pictview/pictview.cpp | 15 +- src/plugins/pictview/pvtwain.cpp | 1 - src/plugins/pictview/statsbar.cpp | 1 - src/plugins/pictview/vcpkg.json | 7 + src/plugins/pictview/vcxproj/pictview.vcxproj | 10 + .../pictview/vcxproj/pictview.vcxproj.filters | 18 + src/plugins/pictview/webp.cpp | 138 ++++++ src/plugins/pictview/webp.h | 27 ++ src/plugins/pictview/wiawrap.cpp | 1 - 15 files changed, 838 insertions(+), 29 deletions(-) create mode 100644 src/plugins/pictview/PVOverrider.cpp create mode 100644 src/plugins/pictview/PVOverrider.h create mode 100644 src/plugins/pictview/heif.cpp create mode 100644 src/plugins/pictview/heif.h create mode 100644 src/plugins/pictview/vcpkg.json create mode 100644 src/plugins/pictview/webp.cpp create mode 100644 src/plugins/pictview/webp.h diff --git a/src/plugins/pictview/PVMessage.h b/src/plugins/pictview/PVMessage.h index 2e15a6016..262a8bc9b 100644 --- a/src/plugins/pictview/PVMessage.h +++ b/src/plugins/pictview/PVMessage.h @@ -5,7 +5,7 @@ #include "lib/pvw32dll.h" -#define PVC_ENVELOPE_NOT_LOADED ((PVCODE)-123) +#define PVC_ENVELOPE_NOT_LOADED ((PVCODE) - 123) struct CPVWrapper { @@ -125,7 +125,7 @@ class PVMessage_InitTexts : public PVMessage { public: PVMessage_InitTexts(); - PVMessage_InitTexts(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_InitTexts(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool HandleRequest(); private: @@ -140,7 +140,7 @@ class PVMessage_GetErrorText : public PVMessage { public: PVMessage_GetErrorText(DWORD ErrorCode); - PVMessage_GetErrorText(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_GetErrorText(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool HandleRequest(); const char* GetErrorText(); @@ -156,7 +156,7 @@ class PVMessage_OpenImageEx : public PVMessage { public: PVMessage_OpenImageEx(LPPVOpenImageExInfo pOpenExInfo); - PVMessage_OpenImageEx(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_OpenImageEx(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool IsInited(); bool Exec(LPPVImageInfo pImgInfo); bool HandleRequest(); @@ -177,7 +177,7 @@ class PVMessage_GetImageInfo : public PVMessage { public: PVMessage_GetImageInfo(LPPVHandle pvHandle, int ImageIndex); - PVMessage_GetImageInfo(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_GetImageInfo(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool Exec(LPPVImageInfo pImgInfo); bool HandleRequest(); @@ -193,7 +193,7 @@ class PVMessageWithProgress : public PVMessage { public: PVMessageWithProgress(ePVMSG type, size_t dataSize, LPPVHandle pvHandle = NULL); - PVMessageWithProgress(LPPVMessageHeader pHdr, HANDLE Event) : PVMessage(pHdr), hEvent(Event){}; + PVMessageWithProgress(LPPVMessageHeader pHdr, HANDLE Event) : PVMessage(pHdr), hEvent(Event) {}; bool Exec(TProgressProc Progress, void* AppSpecific); BOOL HandleProgress(int done); @@ -217,7 +217,7 @@ class PVMessage_ReadImage : public PVMessageWithProgress { public: PVMessage_ReadImage(LPPVHandle pvHandle, int ImageIndex, bool bProgress); - PVMessage_ReadImage(LPPVMessageHeader pHdr, HANDLE hEvent) : PVMessageWithProgress(pHdr, hEvent){}; + PVMessage_ReadImage(LPPVMessageHeader pHdr, HANDLE hEvent) : PVMessageWithProgress(pHdr, hEvent) {}; bool Exec(HDC PaintDC, RECT* pDRect, TProgressProc Progress, void* AppSpecific); bool HandleRequest(); @@ -232,7 +232,7 @@ class PVMessage_CloseImage : public PVMessage { public: PVMessage_CloseImage(LPPVHandle pvHandle); - PVMessage_CloseImage(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_CloseImage(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; ~PVMessage_CloseImage(); bool HandleRequest(); }; @@ -241,7 +241,7 @@ class PVMessage_DrawImage : public PVMessage { public: PVMessage_DrawImage(LPPVHandle pvHandle, HDC PaintDC, int X, int Y, LPRECT pDrawRect); - PVMessage_DrawImage(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_DrawImage(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool Exec(HDC PaintDC); bool HandleRequest(); @@ -260,7 +260,7 @@ 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){}; + PVMessage_SaveImage(LPPVMessageHeader pHdr, HANDLE hEvent) : PVMessageWithProgress(pHdr, hEvent) {}; bool HandleRequest(); private: @@ -309,7 +309,7 @@ class PVMessage_LoadFromClipboard : public PVMessage { public: PVMessage_LoadFromClipboard(); - PVMessage_LoadFromClipboard(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_LoadFromClipboard(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool Exec(LPPVImageInfo pImgInfo); bool HandleRequest(); @@ -324,7 +324,7 @@ class PVMessage_ReadImageSequence : public PVMessage { public: PVMessage_ReadImageSequence(LPPVHandle pvHandle); - PVMessage_ReadImageSequence(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_ReadImageSequence(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool Exec(); bool HandleRequest(); @@ -339,7 +339,7 @@ class PVMessage_SetBkHandle : public PVMessage { public: PVMessage_SetBkHandle(LPPVHandle pvHandle, COLORREF bkColor); - PVMessage_SetBkHandle(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_SetBkHandle(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool HandleRequest(); private: @@ -353,7 +353,7 @@ class PVMessage_GetDLLVersion : public PVMessage { public: PVMessage_GetDLLVersion(); - PVMessage_GetDLLVersion(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_GetDLLVersion(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool Exec(DWORD& Version); bool HandleRequest(); @@ -368,7 +368,7 @@ class PVMessage_SetStretchParameters : public PVMessage { public: PVMessage_SetStretchParameters(LPPVHandle pvHandle, DWORD Width, DWORD Height, DWORD Mode); - PVMessage_SetStretchParameters(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_SetStretchParameters(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool HandleRequest(); private: @@ -384,7 +384,7 @@ class PVMessage_ChangeImage : public PVMessage { public: PVMessage_ChangeImage(LPPVHandle pvHandle, DWORD Flags); - PVMessage_ChangeImage(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_ChangeImage(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool HandleRequest(); private: @@ -398,7 +398,7 @@ class PVMessage_IsOutCombSupported : public PVMessage { public: PVMessage_IsOutCombSupported(int Fmt, int Compr, int Colors, int ColorModel); - PVMessage_IsOutCombSupported(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_IsOutCombSupported(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool HandleRequest(); private: @@ -415,7 +415,7 @@ class PVMessage_CropImage : public PVMessage { public: PVMessage_CropImage(LPPVHandle pvHandle, int Left, int Top, int Width, int Height); - PVMessage_CropImage(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_CropImage(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool HandleRequest(); private: @@ -432,7 +432,7 @@ class PVMessage_GetRGBAtCursor : public PVMessage { public: PVMessage_GetRGBAtCursor(LPPVHandle pvHandle, DWORD Colors, int x, int y); - PVMessage_GetRGBAtCursor(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_GetRGBAtCursor(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool HandleRequest(); RGBQUAD* GetRGB(); @@ -453,7 +453,7 @@ class PVMessage_CalculateHistogram : public PVMessage { public: PVMessage_CalculateHistogram(LPPVHandle pvHandle, const PVImageInfo* pvii); - PVMessage_CalculateHistogram(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_CalculateHistogram(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool HandleRequest(); void GetResults(LPDWORD luminosity, LPDWORD red, LPDWORD green, LPDWORD blue, LPDWORD rgb); @@ -477,7 +477,7 @@ 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){}; + PVMessage_CreateThumbnail(LPPVMessageHeader pHdr, HANDLE hEvent) : PVMessageWithProgress(pHdr, hEvent) {}; bool HandleRequest(); LPBYTE GetPixelData(); @@ -498,7 +498,7 @@ class PVMessage_SimplifyImageSequence : public PVMessage { public: PVMessage_SimplifyImageSequence(LPPVHandle pvHandle, DWORD ScreenWidth, DWORD ScreenHeight, const COLORREF& bgColor); - PVMessage_SimplifyImageSequence(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_SimplifyImageSequence(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool Exec(); bool HandleRequest(); LPPVImageSequence GetImageSequence(); @@ -521,7 +521,7 @@ class PVMessage_CloseHandle : public PVMessage { public: PVMessage_CloseHandle(DWORD Handle); - PVMessage_CloseHandle(LPPVMessageHeader pHdr) : PVMessage(pHdr){}; + PVMessage_CloseHandle(LPPVMessageHeader pHdr) : PVMessage(pHdr) {}; bool Exec(); bool HandleRequest(); diff --git a/src/plugins/pictview/PVOverrider.cpp b/src/plugins/pictview/PVOverrider.cpp new file mode 100644 index 000000000..8631b1172 --- /dev/null +++ b/src/plugins/pictview/PVOverrider.cpp @@ -0,0 +1,434 @@ +// SPDX-FileCopyrightText: 2023 Open Salamander Authors +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "precomp.h" +#include "PVOverrider.h" +#include "pictview.h" +#include "heif.h" +#include "webp.h" +#include +#include +#include + +namespace +{ + + // original function pointers + CPVW32DLL origPVW32DLL{}; + + enum class HandleType + { + PictView = 0, // the original PictView handle + Heif, + Webp + }; + + struct StretchParams + { + DWORD Width; + DWORD Height; + DWORD Mode; + }; + + // overridden PVHandle + struct Handle + { + HandleType type{HandleType::PictView}; + std::optional bkColor; + std::optional stretch; + std::variant data; + }; + + PVCODE OpenImageEx(LPPVHandle* Img, LPPVOpenImageExInfo pOpenExInfo, LPPVImageInfo pImgInfo, int Size) + { + PVCODE result; + assert(Img); + assert(pOpenExInfo); + assert(pImgInfo); + + // initialize the input handle + *Img = nullptr; + + // create a new handle + auto* handle = new Handle{}; + if (!handle) + return PVC_OOM; + + // additional supported image formats can be loaded only from a file, not from a buffer + if (pOpenExInfo->FileName && !(pOpenExInfo->Flags & PVOF_USERDEFINED_INPUT)) + { + PVImageInfo info; + + { + // try out to load the image as HEIF + handle->type = HandleType::Heif; + handle->data.emplace(); + auto& heif = std::get(handle->data); + result = heif.Open(pOpenExInfo->FileName, info); + if (result == PVC_OK) + { + // image opened successfully + *Img = reinterpret_cast(handle); + *pImgInfo = info; + return PVC_OK; + } + } + + { + // try out to load the image as WebP + handle->type = HandleType::Webp; + handle->data.emplace(); + auto& webp = std::get(handle->data); + result = webp.Open(pOpenExInfo->FileName, info); + if (result == PVC_OK) + { + // image opened successfully + *Img = reinterpret_cast(handle); + *pImgInfo = info; + return PVC_OK; + } + } + } + + // call the original function + handle->type = HandleType::PictView; + handle->data = LPPVHandle{nullptr}; + result = origPVW32DLL.PVOpenImageEx(&std::get(handle->data), pOpenExInfo, pImgInfo, Size); + if (result == PVC_OK) + { + // image opened successfully + *Img = reinterpret_cast(handle); + } + else + { + // error, delete the handle + origPVW32DLL.PVCloseImage(std::get(handle->data)); + delete handle; + } + + return result; + } + + PVCODE _ReadImage(Handle* handle, TProgressProc Progress, void* AppSpecific, int ImageIndex) + { + assert(handle); + assert(handle->type != HandleType::PictView); + // TODO: handle non primary ImageIndex + assert(ImageIndex == 0); + + HBITMAP bmp{}; + PVCODE result; + + switch (handle->type) + { + case HandleType::Heif: + result = std::get(handle->data).Read(bmp, Progress, AppSpecific); + break; + case HandleType::Webp: + result = std::get(handle->data).Read(bmp, Progress, AppSpecific); + break; + default: + assert(0 || "Invalid image type"); + return PVC_INVALID_HANDLE; + } + + if (result == PVC_OK) + { + LPPVHandle tmpHandle{}; + // image loaded successfully, load the bitmap by original PVW32DLL + PVOpenImageExInfo oiei{}; + oiei.cbSize = sizeof(oiei); + oiei.Flags = PVOF_ATTACH_TO_HANDLE; + oiei.Handle = bmp; + PVImageInfo pvii{}; + result = origPVW32DLL.PVOpenImageEx(&tmpHandle, &oiei, &pvii, sizeof(pvii)); + if (result == PVC_OK) + { + // apply background color and stretching if it was set + if (handle->bkColor.has_value()) + { + origPVW32DLL.PVSetBkHandle(tmpHandle, handle->bkColor.value()); + handle->bkColor.reset(); + } + if (handle->stretch.has_value()) + { + const auto& params = handle->stretch.value(); + origPVW32DLL.PVSetStretchParameters(tmpHandle, params.Width, params.Height, params.Mode); + handle->stretch.reset(); + } + // read the image content + result = origPVW32DLL.PVReadImage2(tmpHandle, 0, NULL, Progress, AppSpecific, 0); + } + + if (result == PVC_OK) + { + // success, swap the handles + handle->type = HandleType::PictView; + handle->data = tmpHandle; + } + else + { + origPVW32DLL.PVCloseImage(tmpHandle); + } + } + if (bmp) + DeleteObject(bmp); + return result; + } + + PVCODE ReadImage(LPPVHandle Img, HDC PaintDC, RECT* pDRect, TProgressProc Progress, void* AppSpecific, int ImageIndex) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = reinterpret_cast(Img); + if (handle->type != HandleType::PictView) + { + // read the image content + auto result = _ReadImage(handle, Progress, AppSpecific, ImageIndex); + if (result == PVC_OK) + { + // ensure that the image handle is converted to the PictView type + assert(handle->type == HandleType::PictView); + // announce reading done + if (Progress) + Progress(100, AppSpecific); + // draw the loaded image + origPVW32DLL.PVDrawImage(std::get(handle->data), PaintDC, pDRect->left, pDRect->top, pDRect); + } + return result; + } + // call the original function + return origPVW32DLL.PVReadImage2(std::get(handle->data), PaintDC, pDRect, Progress, AppSpecific, ImageIndex); + } + + PVCODE DrawImage(LPPVHandle Img, HDC PaintDC, int X, int Y, LPRECT rect) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = reinterpret_cast(Img); + if (handle->type != HandleType::PictView) + { + // TODO: draw background during loading + return PVC_INVALID_HANDLE; + } + // call the original function + return origPVW32DLL.PVDrawImage(std::get(handle->data), PaintDC, X, Y, rect); + } + + PVCODE CloseImage(LPPVHandle Img) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = reinterpret_cast(Img); + if (handle->type != HandleType::PictView) + { + delete handle; + return PVC_OK; + } + // call the original function + PVCODE result = origPVW32DLL.PVCloseImage(std::get(handle->data)); + delete handle; + return result; + } + + PVCODE SetBkHandle(LPPVHandle Img, COLORREF BkColor) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = reinterpret_cast(Img); + if (handle->type != HandleType::PictView) + { + // cache the background color + handle->bkColor = BkColor; + return PVC_OK; + } + // call the original function + return origPVW32DLL.PVSetBkHandle(std::get(handle->data), BkColor); + } + + PVCODE GetHandles2(LPPVHandle Img, LPPVImageHandles* pHandles) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = reinterpret_cast(Img); + if (handle->type != HandleType::PictView) + return PVC_INVALID_HANDLE; + // call the original function + return origPVW32DLL.PVGetHandles2(std::get(handle->data), pHandles); + } + + PVCODE GetImageInfo(LPPVHandle Img, LPPVImageInfo pImgInfo, int cbSize, int ImageIndex) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = reinterpret_cast(Img); + if (handle->type != HandleType::PictView) + return PVC_INVALID_HANDLE; + // call the original function + return origPVW32DLL.PVGetImageInfo(std::get(handle->data), pImgInfo, cbSize, ImageIndex); + } + + PVCODE SetStretchParameters(LPPVHandle Img, DWORD Width, DWORD Height, DWORD Mode) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = reinterpret_cast(Img); + if (handle->type != HandleType::PictView) + { + // cache the parameters + handle->stretch = StretchParams{Width, Height, Mode}; + return PVC_OK; + } + // call the original function + return origPVW32DLL.PVSetStretchParameters(std::get(handle->data), Width, Height, Mode); + } + + PVCODE LoadFromClipboard(LPPVHandle* Img, LPPVImageInfo pImgInfo, int cbSize) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = reinterpret_cast(Img); + if (handle->type != HandleType::PictView) + return PVC_INVALID_HANDLE; + // call the original function + return origPVW32DLL.PVLoadFromClipboard(Img, pImgInfo, cbSize); + } + + PVCODE SaveImage(LPPVHandle Img, const char* OutFName, LPPVSaveImageInfo pSii, TProgressProc Progress, void* AppSpecific, int ImageIndex) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = reinterpret_cast(Img); + if (handle->type != HandleType::PictView) + return PVC_INVALID_HANDLE; + // call the original function + return origPVW32DLL.PVSaveImage(std::get(handle->data), OutFName, pSii, Progress, AppSpecific, ImageIndex); + } + + PVCODE ChangeImage(LPPVHandle Img, DWORD Flags) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = reinterpret_cast(Img); + if (handle->type != HandleType::PictView) + return PVC_INVALID_HANDLE; + // call the original function + return origPVW32DLL.PVChangeImage(std::get(handle->data), Flags); + } + + PVCODE ReadImageSequence(LPPVHandle Img, LPPVImageSequence* ppSeq) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = reinterpret_cast(Img); + if (handle->type != HandleType::PictView) + return PVC_INVALID_HANDLE; + // call the original function + return origPVW32DLL.PVReadImageSequence(Img, ppSeq); + } + + PVCODE CropImage(LPPVHandle Img, int Left, int Top, int Width, int Height) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = reinterpret_cast(Img); + if (handle->type != HandleType::PictView) + return PVC_INVALID_HANDLE; + // call the original function + return origPVW32DLL.PVCropImage(std::get(handle->data), Left, Top, Width, Height); + } + + bool _GetRGBAtCursor(LPPVHandle Img, DWORD Colors, int x, int y, RGBQUAD* pRGB, int* pIndex) + { + if (!Img) + return false; + auto* handle = reinterpret_cast(Img); + if (handle->type != HandleType::PictView) + return false; + // call the original function + return origPVW32DLL.GetRGBAtCursor(std::get(handle->data), Colors, x, y, pRGB, pIndex); + } + + PVCODE _CalculateHistogram(LPPVHandle PVHandle, const LPPVImageInfo pvii, LPDWORD luminosity, + LPDWORD red, LPDWORD green, LPDWORD blue, LPDWORD rgb) + { + if (!PVHandle) + return PVC_INVALID_HANDLE; + auto* handle = reinterpret_cast(PVHandle); + if (handle->type != HandleType::PictView) + return PVC_INVALID_HANDLE; + // call the original function + return origPVW32DLL.CalculateHistogram(std::get(handle->data), pvii, luminosity, red, green, blue, rgb); + } + + PVCODE _CreateThumbnail(LPPVHandle hPVImage, LPPVSaveImageInfo pSii, int imageIndex, DWORD imgWidth, DWORD imgHeight, + int thumbWidth, int thumbHeight, CSalamanderThumbnailMakerAbstract* thumbMaker, + DWORD thumbFlags, TProgressProc progressProc, void* progressProcArg) + { + if (!hPVImage) + return PVC_INVALID_HANDLE; + auto* handle = reinterpret_cast(hPVImage); + if (handle->type != HandleType::PictView) + { + // set the thumbnail size + thumbMaker->SetParameters(thumbWidth, thumbHeight, 0); + + if (_ReadImage(handle, nullptr, nullptr, imageIndex) != PVC_OK) + { + thumbMaker->SetError(); + return PVC_INVALID_HANDLE; + } + if (progressProc) + progressProc(0, progressProcArg); + } + // call the original function + return origPVW32DLL.CreateThumbnail(std::get(handle->data), pSii, imageIndex, imgWidth, imgHeight, thumbWidth, + thumbHeight, thumbMaker, thumbFlags, progressProc, progressProcArg); + } + + PVCODE _SimplifyImageSequence(LPPVHandle hPVImage, HDC dc, int ScreenWidth, int ScreenHeight, LPPVImageSequence& pSeq, + const COLORREF& bgColor) + { + if (!hPVImage) + return PVC_INVALID_HANDLE; + auto* handle = reinterpret_cast(hPVImage); + if (handle->type != HandleType::PictView) + return PVC_INVALID_HANDLE; + // call the original function + return origPVW32DLL.SimplifyImageSequence(std::get(handle->data), dc, ScreenWidth, ScreenHeight, pSeq, bgColor); + } + +} // namespace + +////////////////////////////////////////////////////////////////////////// + +void InitializePvOverrider() +{ + // save the original pointers + origPVW32DLL = PVW32DLL; + + PVW32DLL.PVOpenImageEx = OpenImageEx; + PVW32DLL.PVReadImage2 = ReadImage; + PVW32DLL.PVDrawImage = DrawImage; + PVW32DLL.PVCloseImage = CloseImage; + PVW32DLL.PVSetBkHandle = SetBkHandle; + PVW32DLL.PVGetHandles2 = GetHandles2; + PVW32DLL.PVGetImageInfo = GetImageInfo; + PVW32DLL.PVSetStretchParameters = SetStretchParameters; + PVW32DLL.PVLoadFromClipboard = LoadFromClipboard; + PVW32DLL.PVSaveImage = SaveImage; + PVW32DLL.PVChangeImage = ChangeImage; + PVW32DLL.PVReadImageSequence = ReadImageSequence; + PVW32DLL.PVCropImage = CropImage; + + PVW32DLL.GetRGBAtCursor = _GetRGBAtCursor; + PVW32DLL.CalculateHistogram = _CalculateHistogram; + PVW32DLL.CreateThumbnail = _CreateThumbnail; + PVW32DLL.SimplifyImageSequence = _SimplifyImageSequence; +} + +void UninitializePvOverrider() +{ + // restore the original pointers + PVW32DLL = origPVW32DLL; +} diff --git a/src/plugins/pictview/PVOverrider.h b/src/plugins/pictview/PVOverrider.h new file mode 100644 index 000000000..6b0ab8c77 --- /dev/null +++ b/src/plugins/pictview/PVOverrider.h @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2023 Open Salamander Authors +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +// overrider for the pict-view library, which adds support for viewing new image formats + +void InitializePvOverrider(); +void UninitializePvOverrider(); diff --git a/src/plugins/pictview/heif.cpp b/src/plugins/pictview/heif.cpp new file mode 100644 index 000000000..0e6f3488c --- /dev/null +++ b/src/plugins/pictview/heif.cpp @@ -0,0 +1,137 @@ +// SPDX-FileCopyrightText: 2023 Open Salamander Authors +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "precomp.h" +#include "heif.h" +#include +#include + +ImageHeif::~ImageHeif() +{ + if (m_image) + heif_image_release(m_image); + if (m_handle) + heif_image_handle_release(m_handle); + if (m_ctx) + heif_context_free(m_ctx); +} + +PVCODE ImageHeif::Open(const char* filename, PVImageInfo& pvii) +{ + pvii = {}; + + // initialize libheif context + m_ctx = heif_context_alloc(); + if (!m_ctx) + return PVC_OUT_OF_MEMORY; + heif_error err = heif_context_read_from_file(m_ctx, filename, nullptr); + if (err.code != heif_error_Ok) + { + switch (err.code) + { + case heif_error_Invalid_input: + return PVC_UNKNOWN_FILE_STRUCT; + case heif_error_Memory_allocation_error: + return PVC_OUT_OF_MEMORY; + default: + return PVC_CANNOT_OPEN_FILE; + } + } + + // get the primary image handle + err = heif_context_get_primary_image_handle(m_ctx, &m_handle); + if (err.code != heif_error_Ok) + return PVC_UNKNOWN_FILE_STRUCT; + + // fill out the image info + pvii.cbSize = sizeof(pvii); + pvii.Format = PVF_BMP; + pvii.Width = heif_image_handle_get_width(m_handle); + pvii.Height = heif_image_handle_get_height(m_handle); + const int luma_bits = heif_image_handle_get_luma_bits_per_pixel(m_handle); + const int chroma_bits = heif_image_handle_get_chroma_bits_per_pixel(m_handle); + pvii.Colors = (1 << luma_bits) * (1 << chroma_bits); + pvii.NumOfImages = 1; + + // compute the number of bytes per line (stride) + const int bytes_per_pixel = (luma_bits + chroma_bits) / 8; // assuming 24-bit RGB format + pvii.BytesPerLine = pvii.Width * bytes_per_pixel; + pvii.Colors = PV_COLOR_TC24; + pvii.ColorModel = PVCM_RGB; + pvii.Compression = PVCS_NO_COMPRESSION; + + // get the file size + if (std::ifstream file(filename, std::ios::binary | std::ios::ate); + file.is_open()) + { + file.seekg(0, std::ios::end); + pvii.FileSize = static_cast(file.tellg()); + } + + strcpy_s(pvii.Info1, "HEIF"); + + return PVC_OK; +} + +PVCODE ImageHeif::Read(HBITMAP& bmp, TProgressProc Progress, void* AppSpecific) +{ + assert(m_ctx); + assert(m_handle); + +#if 0 + // TODO: decoding progress and cancellation doesn't work yet! + heif_decoding_options* options = heif_decoding_options_alloc(); + if (!options) + return PVC_OUT_OF_MEMORY; + + options->version = 1; + options->start_progress = start_progress; + options->on_progress = on_progress; + options->end_progress = end_progress; + options->cancel_decoding = cancel_decoding; + options->progress_user_data = &progress_data; +#endif + + // decode the image + heif_error err = heif_decode_image(m_handle, &m_image, heif_colorspace_RGB, heif_chroma_interleaved_RGB, nullptr); + if (err.code != heif_error_Ok) + return PVC_READING_ERROR; + + // check image bit depth + const int bitDepth = heif_image_get_bits_per_pixel(m_image, heif_channel_interleaved); + if (bitDepth != 24) + return PVC_UNSUP_COLOR_DEPTH; + + // get image dimensions + const int width = heif_image_get_width(m_image, heif_channel_interleaved); + const int height = heif_image_get_height(m_image, heif_channel_interleaved); + + // get image data + int stride{}; + const uint8_t* data = heif_image_get_plane_readonly(m_image, heif_channel_interleaved, &stride); + if (!data) + return PVC_READING_ERROR; + + // convert the decoded image to a bitmap + BITMAPINFO bmi = {0}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = -height; // Negative to indicate top-down DIB + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 24; // 24-bit RGB + bmi.bmiHeader.biCompression = BI_RGB; + + HDC hdc = GetDC(nullptr); + void* bits = nullptr; + bmp = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0); + ReleaseDC(nullptr, hdc); + + // copy image data to the DIB + for (int y = 0; y < height; ++y) + { + memcpy(static_cast(bits) + y * width * 3, data + y * stride, width * 3); + } + + // image loaded successfully + return PVC_OK; +} diff --git a/src/plugins/pictview/heif.h b/src/plugins/pictview/heif.h new file mode 100644 index 000000000..50b028c9c --- /dev/null +++ b/src/plugins/pictview/heif.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Open Salamander Authors +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "lib/pvw32dll.h" +#include + +class ImageHeif +{ +public: + ImageHeif() = default; + ~ImageHeif(); + + PVCODE Open(const char* filename, PVImageInfo& pvii); + PVCODE Read(HBITMAP& bmp, TProgressProc Progress, void* AppSpecific); + +private: + heif_context* m_ctx{}; + heif_image_handle* m_handle{}; + heif_image* m_image{}; +}; diff --git a/src/plugins/pictview/histwnd.cpp b/src/plugins/pictview/histwnd.cpp index f2f7886cb..b114f43fb 100644 --- a/src/plugins/pictview/histwnd.cpp +++ b/src/plugins/pictview/histwnd.cpp @@ -3,7 +3,6 @@ #include "precomp.h" -#include "lib\\pvw32dll.h" #include "pictview.h" #include "dialogs.h" #include "histwnd.h" diff --git a/src/plugins/pictview/pictview.cpp b/src/plugins/pictview/pictview.cpp index 874a04cfb..d6962e0c1 100644 --- a/src/plugins/pictview/pictview.cpp +++ b/src/plugins/pictview/pictview.cpp @@ -20,6 +20,7 @@ #include "histwnd.h" #include "PVEXEWrapper.h" #include "PixelAccess.h" +#include "PVOverrider.h" // objekt interfacu pluginu, jeho metody se volaji ze Salamandera CPluginInterface PluginInterface; @@ -71,7 +72,7 @@ BOOL SalamanderRegistered = FALSE; // 22 - Salamander 2.52 B1: Added *.BLP files used by Blizzard Entertainment in World of Wordcraft int ConfigVersion = 0; -#define CURRENT_CONFIG_VERSION 22 +#define CURRENT_CONFIG_VERSION 23 SGlobals G; // inicializovano v InitViewer TDirectArray ExifHighlights(20, 10); @@ -1063,6 +1064,11 @@ void CPluginInterface::Connect(HWND parent, CSalamanderConnectAbstract* salamand salamander->AddViewer("*.blp", TRUE); } + if (ConfigVersion < 23) // Added HEIF and WebP formats + { + salamander->AddViewer("*.heic;*.heif;*.webp", TRUE); + } + /* slouzi pro skript export_mnu.py, ktery generuje salmenu.mnu pro Translator udrzovat synchronizovane s volani salamander->AddMenuItem() dole... MENU_TEMPLATE_ITEM PluginMenu[] = @@ -1113,7 +1119,7 @@ MENU_TEMPLATE_ITEM PluginMenu[] = "*.flc;*.fli;*.gem;*.gif;*.ham;*.hmr;*.hrz;*.icn;*.ico;*.iff;*.img;" "*.cdt;*.cel;*.clp;*.cit;*.cmx;*.cot;*.cpt;*.cur;*.cut;*.dcx;*.dib;" "*.82i;*.83i;*.85i;*.86i;*.89i;*.92i;*.awd;*.bmi;*.bmp;*.cal;*.cdr;" - "*.arw;*.blp;*.cr2;*.dng;*.orf;*.pef"); + "*.arw;*.blp;*.cr2;*.dng;*.orf;*.pef;*.heic;*.heif;*.webp"); } void CPluginInterface::ClearHistory(HWND parent) @@ -1411,6 +1417,9 @@ BOOL LoadPictViewDll(HWND hParentWnd) return FALSE; } #endif // PICTVIEW_DLL_IN_SEPARATE_PROCESS + + // inject the new image file formats support into the viewer + InitializePvOverrider(); return TRUE; } @@ -1543,6 +1552,8 @@ BOOL InitEXIF(HWND hParent, BOOL bSilent) void ReleaseViewer() { + UninitializePvOverrider(); + #ifdef PICTVIEW_DLL_IN_SEPARATE_PROCESS ReleasePVEXEWrapper(); #endif diff --git a/src/plugins/pictview/pvtwain.cpp b/src/plugins/pictview/pvtwain.cpp index 7e716577c..a50de4bdd 100644 --- a/src/plugins/pictview/pvtwain.cpp +++ b/src/plugins/pictview/pvtwain.cpp @@ -5,7 +5,6 @@ #ifdef ENABLE_TWAIN32 -#include "lib/pvw32dll.h" #include "renderer.h" #include "dialogs.h" #include "pictview.h" diff --git a/src/plugins/pictview/statsbar.cpp b/src/plugins/pictview/statsbar.cpp index a15c96c42..a9706bbac 100644 --- a/src/plugins/pictview/statsbar.cpp +++ b/src/plugins/pictview/statsbar.cpp @@ -3,7 +3,6 @@ #include "precomp.h" -#include "lib\\pvw32dll.h" #include "renderer.h" #include "pictview.h" #include "pictview.rh" diff --git a/src/plugins/pictview/vcpkg.json b/src/plugins/pictview/vcpkg.json new file mode 100644 index 000000000..98ad3cc60 --- /dev/null +++ b/src/plugins/pictview/vcpkg.json @@ -0,0 +1,7 @@ +{ + "dependencies": [ + "libheif", + "libwebp" + ], + "builtin-baseline": "6f29f12e82a8293156836ad81cc9bf5af41fe836" +} diff --git a/src/plugins/pictview/vcxproj/pictview.vcxproj b/src/plugins/pictview/vcxproj/pictview.vcxproj index ec856bc07..a2e1017e2 100644 --- a/src/plugins/pictview/vcxproj/pictview.vcxproj +++ b/src/plugins/pictview/vcxproj/pictview.vcxproj @@ -100,6 +100,10 @@ + + true + true + stdcpplatest @@ -131,6 +135,7 @@ + @@ -151,6 +156,7 @@ + @@ -167,6 +173,7 @@ + @@ -201,6 +208,7 @@ + @@ -215,6 +223,7 @@ + @@ -225,6 +234,7 @@ + diff --git a/src/plugins/pictview/vcxproj/pictview.vcxproj.filters b/src/plugins/pictview/vcxproj/pictview.vcxproj.filters index b05cd16ea..3244a623e 100644 --- a/src/plugins/pictview/vcxproj/pictview.vcxproj.filters +++ b/src/plugins/pictview/vcxproj/pictview.vcxproj.filters @@ -81,6 +81,15 @@ cpp + + cpp + + + cpp + + + cpp + @@ -167,6 +176,15 @@ h + + h + + + h + + + h + diff --git a/src/plugins/pictview/webp.cpp b/src/plugins/pictview/webp.cpp new file mode 100644 index 000000000..5afb95006 --- /dev/null +++ b/src/plugins/pictview/webp.cpp @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: 2023 Open Salamander Authors +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "precomp.h" +#include "webp.h" +#include +#include + +ImageWebp::ImageWebp() +{ + WebPInitDecoderConfig(&m_config); +} + +ImageWebp::~ImageWebp() +{ + if (m_idec) + WebPIDelete(m_idec); + WebPFreeDecBuffer(&m_config.output); +} + +PVCODE ImageWebp::Open(const char* filename, PVImageInfo& pvii) +{ + pvii = {}; + + m_file.open(filename, std::ios::binary); + if (!m_file.is_open()) + return PVC_CANNOT_OPEN_FILE; + + // get the file size + m_file.seekg(0, std::ios::end); + m_fileSize = static_cast(m_file.tellg()); + m_file.seekg(0, std::ios::beg); + + // read the first 30 bytes of the file + const size_t HeaderSize = 30; + std::array buffer; + if (!m_file.read(reinterpret_cast(buffer.data()), buffer.size())) + return PVC_READING_ERROR; + m_file.seekg(0, std::ios::beg); + + // get the image features + auto result = WebPGetFeatures(buffer.data(), buffer.size(), &m_config.input); + if (result != VP8_STATUS_OK) + { + switch (result) + { + case VP8_STATUS_OUT_OF_MEMORY: + return PVC_OUT_OF_MEMORY; + case VP8_STATUS_BITSTREAM_ERROR: + return PVC_UNKNOWN_FILE_STRUCT; + default: + return PVC_READING_ERROR; + } + } + + // fill out the image info + pvii.cbSize = sizeof(pvii); + pvii.Format = PVF_BMP; + pvii.Width = m_config.input.width; + pvii.Height = m_config.input.height; + pvii.BytesPerLine = m_config.input.width * 4; // 32-bit RGBA format + pvii.Colors = PV_COLOR_TC32; + pvii.ColorModel = PVCM_RGB; + pvii.Compression = PVCS_NO_COMPRESSION; + pvii.FileSize = static_cast(m_fileSize); + pvii.NumOfImages = 1; + + strcpy_s(pvii.Info1, "WebP"); + + return PVC_OK; +} + +PVCODE ImageWebp::Read(HBITMAP& bmp, TProgressProc Progress, void* AppSpecific) +{ + assert(m_file.is_open()); + + // initialize the decoder + m_config.output.colorspace = MODE_BGRA; + m_idec = WebPIDecode(nullptr, 0, &m_config); + if (!m_idec) + return PVC_EXCEPTION; + + const size_t ChunkSize = 1024; + std::array buffer; + VP8StatusCode result = VP8_STATUS_SUSPENDED; + + // read file by chunks + size_t totalBytesRead = 0; + int progressRound = 0; + while (!m_file.eof() && result == VP8_STATUS_SUSPENDED) + { + m_file.read(reinterpret_cast(buffer.data()), buffer.size()); + if (m_file.bad()) + return PVC_READING_ERROR; + const auto bytesRead = m_file.gcount(); + if (bytesRead == 0) + return PVC_UNEXPECTED_EOF; + totalBytesRead += bytesRead; + + result = WebPIAppend(m_idec, buffer.data(), bytesRead); + + // report progress + if (Progress && (progressRound++ > 10)) + { + progressRound = 0; + const auto progressPercentage = static_cast(totalBytesRead * 100 / m_fileSize); + if (Progress(progressPercentage, AppSpecific)) + return PVC_CANCELED; + } + } + + if (result != VP8_STATUS_OK) + return PVC_READING_ERROR; + + // convert the decoded image to a bitmap + BITMAPINFO bmi = {0}; + bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); + bmi.bmiHeader.biWidth = m_config.input.width; + bmi.bmiHeader.biHeight = -m_config.input.height; // Negative height to indicate a top-down DIB + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; // 32-bit BGRA + bmi.bmiHeader.biCompression = BI_RGB; + + HDC hdc = GetDC(nullptr); + if (!hdc) + return PVC_GDI_ERROR; + bmp = CreateDIBitmap(hdc, &bmi.bmiHeader, CBM_INIT, m_config.output.u.RGBA.rgba, + &bmi, DIB_RGB_COLORS); + ReleaseDC(nullptr, hdc); + if (!bmp) + return PVC_GDI_ERROR; + + // reading done + if (Progress) + Progress(100, AppSpecific); + // image loaded successfully + return PVC_OK; +} diff --git a/src/plugins/pictview/webp.h b/src/plugins/pictview/webp.h new file mode 100644 index 000000000..0aa9ac1f8 --- /dev/null +++ b/src/plugins/pictview/webp.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2023 Open Salamander Authors +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "lib/pvw32dll.h" +#include +#include + +class ImageWebp +{ +public: + ImageWebp(); + ~ImageWebp(); + + PVCODE Open(const char* filename, PVImageInfo& pvii); + PVCODE Read(HBITMAP& bmp, TProgressProc Progress, void* AppSpecific); + +private: + void MoveFrom(ImageWebp& other); + + // image decoder + WebPIDecoder* m_idec{nullptr}; + WebPDecoderConfig m_config{}; + std::ifstream m_file; + size_t m_fileSize{}; +}; diff --git a/src/plugins/pictview/wiawrap.cpp b/src/plugins/pictview/wiawrap.cpp index 1035a17c4..5c7cb01a4 100644 --- a/src/plugins/pictview/wiawrap.cpp +++ b/src/plugins/pictview/wiawrap.cpp @@ -59,7 +59,6 @@ void* just_to_show_error = &change_default_value_of_WIN32_WINNT; #include -#include "lib/pvw32dll.h" #include "renderer.h" #include "dialogs.h" #include "pictview.h" From d30251a813613f91aefa151ed3c7d26139053c4d Mon Sep 17 00:00:00 2001 From: Viliam Lejcik Date: Mon, 27 Jan 2025 21:29:57 +0100 Subject: [PATCH 2/9] cast correction --- src/plugins/pictview/webp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/pictview/webp.cpp b/src/plugins/pictview/webp.cpp index 5afb95006..af203676d 100644 --- a/src/plugins/pictview/webp.cpp +++ b/src/plugins/pictview/webp.cpp @@ -92,7 +92,7 @@ PVCODE ImageWebp::Read(HBITMAP& bmp, TProgressProc Progress, void* AppSpecific) m_file.read(reinterpret_cast(buffer.data()), buffer.size()); if (m_file.bad()) return PVC_READING_ERROR; - const auto bytesRead = m_file.gcount(); + const auto bytesRead = static_cast(m_file.gcount()); if (bytesRead == 0) return PVC_UNEXPECTED_EOF; totalBytesRead += bytesRead; From 3cda29eb349e9bb427e0aaaa606370b8a598c33d Mon Sep 17 00:00:00 2001 From: Viliam Lejcik Date: Mon, 27 Jan 2025 21:37:07 +0100 Subject: [PATCH 3/9] a few more improvements for pict-view overrider --- src/plugins/pictview/PVOverrider.cpp | 127 ++++++++++++++++++--------- 1 file changed, 87 insertions(+), 40 deletions(-) diff --git a/src/plugins/pictview/PVOverrider.cpp b/src/plugins/pictview/PVOverrider.cpp index 8631b1172..e01ff6636 100644 --- a/src/plugins/pictview/PVOverrider.cpp +++ b/src/plugins/pictview/PVOverrider.cpp @@ -39,7 +39,11 @@ namespace std::variant data; }; - PVCODE OpenImageEx(LPPVHandle* Img, LPPVOpenImageExInfo pOpenExInfo, LPPVImageInfo pImgInfo, int Size) + // utilities + Handle* _ConvertHandle(LPPVHandle hPVImage); + LPPVHandle _GetHandleForLocalCall(Handle* handle); + + PVCODE WINAPI OpenImageEx(LPPVHandle* Img, LPPVOpenImageExInfo pOpenExInfo, LPPVImageInfo pImgInfo, int Size) { PVCODE result; assert(Img); @@ -109,7 +113,7 @@ namespace return result; } - PVCODE _ReadImage(Handle* handle, TProgressProc Progress, void* AppSpecific, int ImageIndex) + PVCODE WINAPI _ReadImage(Handle* handle, TProgressProc Progress, void* AppSpecific, int ImageIndex) { assert(handle); assert(handle->type != HandleType::PictView); @@ -176,11 +180,11 @@ namespace return result; } - PVCODE ReadImage(LPPVHandle Img, HDC PaintDC, RECT* pDRect, TProgressProc Progress, void* AppSpecific, int ImageIndex) + PVCODE WINAPI ReadImage(LPPVHandle Img, HDC PaintDC, RECT* pDRect, TProgressProc Progress, void* AppSpecific, int ImageIndex) { if (!Img) return PVC_INVALID_HANDLE; - auto* handle = reinterpret_cast(Img); + auto* handle = _ConvertHandle(Img); if (handle->type != HandleType::PictView) { // read the image content @@ -201,11 +205,11 @@ namespace return origPVW32DLL.PVReadImage2(std::get(handle->data), PaintDC, pDRect, Progress, AppSpecific, ImageIndex); } - PVCODE DrawImage(LPPVHandle Img, HDC PaintDC, int X, int Y, LPRECT rect) + PVCODE WINAPI DrawImage(LPPVHandle Img, HDC PaintDC, int X, int Y, LPRECT rect) { if (!Img) return PVC_INVALID_HANDLE; - auto* handle = reinterpret_cast(Img); + auto* handle = _ConvertHandle(Img); if (handle->type != HandleType::PictView) { // TODO: draw background during loading @@ -215,11 +219,11 @@ namespace return origPVW32DLL.PVDrawImage(std::get(handle->data), PaintDC, X, Y, rect); } - PVCODE CloseImage(LPPVHandle Img) + PVCODE WINAPI CloseImage(LPPVHandle Img) { if (!Img) return PVC_INVALID_HANDLE; - auto* handle = reinterpret_cast(Img); + auto* handle = _ConvertHandle(Img); if (handle->type != HandleType::PictView) { delete handle; @@ -231,11 +235,11 @@ namespace return result; } - PVCODE SetBkHandle(LPPVHandle Img, COLORREF BkColor) + PVCODE WINAPI SetBkHandle(LPPVHandle Img, COLORREF BkColor) { if (!Img) return PVC_INVALID_HANDLE; - auto* handle = reinterpret_cast(Img); + auto* handle = _ConvertHandle(Img); if (handle->type != HandleType::PictView) { // cache the background color @@ -246,33 +250,33 @@ namespace return origPVW32DLL.PVSetBkHandle(std::get(handle->data), BkColor); } - PVCODE GetHandles2(LPPVHandle Img, LPPVImageHandles* pHandles) + PVCODE WINAPI GetHandles2(LPPVHandle Img, LPPVImageHandles* pHandles) { if (!Img) return PVC_INVALID_HANDLE; - auto* handle = reinterpret_cast(Img); + auto* handle = _ConvertHandle(Img); if (handle->type != HandleType::PictView) return PVC_INVALID_HANDLE; // call the original function return origPVW32DLL.PVGetHandles2(std::get(handle->data), pHandles); } - PVCODE GetImageInfo(LPPVHandle Img, LPPVImageInfo pImgInfo, int cbSize, int ImageIndex) + PVCODE WINAPI GetImageInfo(LPPVHandle Img, LPPVImageInfo pImgInfo, int cbSize, int ImageIndex) { if (!Img) return PVC_INVALID_HANDLE; - auto* handle = reinterpret_cast(Img); + auto* handle = _ConvertHandle(Img); if (handle->type != HandleType::PictView) return PVC_INVALID_HANDLE; // call the original function return origPVW32DLL.PVGetImageInfo(std::get(handle->data), pImgInfo, cbSize, ImageIndex); } - PVCODE SetStretchParameters(LPPVHandle Img, DWORD Width, DWORD Height, DWORD Mode) + PVCODE WINAPI SetStretchParameters(LPPVHandle Img, DWORD Width, DWORD Height, DWORD Mode) { if (!Img) return PVC_INVALID_HANDLE; - auto* handle = reinterpret_cast(Img); + auto* handle = _ConvertHandle(Img); if (handle->type != HandleType::PictView) { // cache the parameters @@ -283,55 +287,69 @@ namespace return origPVW32DLL.PVSetStretchParameters(std::get(handle->data), Width, Height, Mode); } - PVCODE LoadFromClipboard(LPPVHandle* Img, LPPVImageInfo pImgInfo, int cbSize) + PVCODE WINAPI LoadFromClipboard(LPPVHandle* Img, LPPVImageInfo pImgInfo, int cbSize) { - if (!Img) - return PVC_INVALID_HANDLE; - auto* handle = reinterpret_cast(Img); - if (handle->type != HandleType::PictView) - return PVC_INVALID_HANDLE; - // call the original function - return origPVW32DLL.PVLoadFromClipboard(Img, pImgInfo, cbSize); + assert(Img); + + // initialize the handle + *Img = nullptr; + + // call the original function with a temporary handle + LPPVHandle tempHandle = nullptr; + auto result = origPVW32DLL.PVLoadFromClipboard(&tempHandle, pImgInfo, cbSize); + if (result != PVC_OK) + return result; + + // create a new handle + auto* handle = new Handle{}; + if (!handle) + return PVC_OOM; + + // image loaded successfully + handle->type = HandleType::PictView; + handle->data = tempHandle; + *Img = reinterpret_cast(handle); + return result; } - PVCODE SaveImage(LPPVHandle Img, const char* OutFName, LPPVSaveImageInfo pSii, TProgressProc Progress, void* AppSpecific, int ImageIndex) + PVCODE WINAPI SaveImage(LPPVHandle Img, const char* OutFName, LPPVSaveImageInfo pSii, TProgressProc Progress, void* AppSpecific, int ImageIndex) { if (!Img) return PVC_INVALID_HANDLE; - auto* handle = reinterpret_cast(Img); + auto* handle = _ConvertHandle(Img); if (handle->type != HandleType::PictView) return PVC_INVALID_HANDLE; // call the original function return origPVW32DLL.PVSaveImage(std::get(handle->data), OutFName, pSii, Progress, AppSpecific, ImageIndex); } - PVCODE ChangeImage(LPPVHandle Img, DWORD Flags) + PVCODE WINAPI ChangeImage(LPPVHandle Img, DWORD Flags) { if (!Img) return PVC_INVALID_HANDLE; - auto* handle = reinterpret_cast(Img); + auto* handle = _ConvertHandle(Img); if (handle->type != HandleType::PictView) return PVC_INVALID_HANDLE; // call the original function return origPVW32DLL.PVChangeImage(std::get(handle->data), Flags); } - PVCODE ReadImageSequence(LPPVHandle Img, LPPVImageSequence* ppSeq) + PVCODE WINAPI ReadImageSequence(LPPVHandle Img, LPPVImageSequence* ppSeq) { if (!Img) return PVC_INVALID_HANDLE; - auto* handle = reinterpret_cast(Img); + auto* handle = _ConvertHandle(Img); if (handle->type != HandleType::PictView) return PVC_INVALID_HANDLE; // call the original function - return origPVW32DLL.PVReadImageSequence(Img, ppSeq); + return origPVW32DLL.PVReadImageSequence(std::get(handle->data), ppSeq); } - PVCODE CropImage(LPPVHandle Img, int Left, int Top, int Width, int Height) + PVCODE WINAPI CropImage(LPPVHandle Img, int Left, int Top, int Width, int Height) { if (!Img) return PVC_INVALID_HANDLE; - auto* handle = reinterpret_cast(Img); + auto* handle = _ConvertHandle(Img); if (handle->type != HandleType::PictView) return PVC_INVALID_HANDLE; // call the original function @@ -342,11 +360,11 @@ namespace { if (!Img) return false; - auto* handle = reinterpret_cast(Img); + auto* handle = _ConvertHandle(Img); if (handle->type != HandleType::PictView) return false; // call the original function - return origPVW32DLL.GetRGBAtCursor(std::get(handle->data), Colors, x, y, pRGB, pIndex); + return origPVW32DLL.GetRGBAtCursor(_GetHandleForLocalCall(handle), Colors, x, y, pRGB, pIndex); } PVCODE _CalculateHistogram(LPPVHandle PVHandle, const LPPVImageInfo pvii, LPDWORD luminosity, @@ -354,11 +372,11 @@ namespace { if (!PVHandle) return PVC_INVALID_HANDLE; - auto* handle = reinterpret_cast(PVHandle); + auto* handle = _ConvertHandle(PVHandle); if (handle->type != HandleType::PictView) return PVC_INVALID_HANDLE; // call the original function - return origPVW32DLL.CalculateHistogram(std::get(handle->data), pvii, luminosity, red, green, blue, rgb); + return origPVW32DLL.CalculateHistogram(_GetHandleForLocalCall(handle), pvii, luminosity, red, green, blue, rgb); } PVCODE _CreateThumbnail(LPPVHandle hPVImage, LPPVSaveImageInfo pSii, int imageIndex, DWORD imgWidth, DWORD imgHeight, @@ -367,7 +385,7 @@ namespace { if (!hPVImage) return PVC_INVALID_HANDLE; - auto* handle = reinterpret_cast(hPVImage); + auto* handle = _ConvertHandle(hPVImage); if (handle->type != HandleType::PictView) { // set the thumbnail size @@ -381,8 +399,9 @@ namespace if (progressProc) progressProc(0, progressProcArg); } + // call the original function - return origPVW32DLL.CreateThumbnail(std::get(handle->data), pSii, imageIndex, imgWidth, imgHeight, thumbWidth, + return origPVW32DLL.CreateThumbnail(_GetHandleForLocalCall(handle), pSii, imageIndex, imgWidth, imgHeight, thumbWidth, thumbHeight, thumbMaker, thumbFlags, progressProc, progressProcArg); } @@ -391,13 +410,41 @@ namespace { if (!hPVImage) return PVC_INVALID_HANDLE; - auto* handle = reinterpret_cast(hPVImage); + auto* handle = _ConvertHandle(hPVImage); if (handle->type != HandleType::PictView) return PVC_INVALID_HANDLE; // call the original function return origPVW32DLL.SimplifyImageSequence(std::get(handle->data), dc, ScreenWidth, ScreenHeight, pSeq, bgColor); } + // utilities + + Handle* _ConvertHandle(LPPVHandle hPVImage) + { + auto* handle = reinterpret_cast(hPVImage); + + // check that the handle type is valid + assert(handle->type == HandleType::PictView || + handle->type == HandleType::Heif || + handle->type == HandleType::Webp); + + return handle; + } + + LPPVHandle _GetHandleForLocalCall(Handle* handle) + { + // HACK: resolve the handle +#ifdef _WIN64 + // on x64, the image handle is processed in a separate process, so it has to be + // of type compatible with pict-view library + return std::get(handle->data); +#else + // on x86, the image handle is just passed into a local function, and later it is passed + // as an argument into a PVW32DLL.PVxxx() call, so the handle is converted there + return handle; +#endif + } + } // namespace ////////////////////////////////////////////////////////////////////////// From 89964753377b3b461e8eed4af18b5fdc9f64e158 Mon Sep 17 00:00:00 2001 From: Viliam Lejcik Date: Mon, 27 Jan 2025 21:40:38 +0100 Subject: [PATCH 4/9] minor cleanup --- src/plugins/pictview/webp.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/plugins/pictview/webp.h b/src/plugins/pictview/webp.h index 0aa9ac1f8..d41ea3799 100644 --- a/src/plugins/pictview/webp.h +++ b/src/plugins/pictview/webp.h @@ -17,8 +17,6 @@ class ImageWebp PVCODE Read(HBITMAP& bmp, TProgressProc Progress, void* AppSpecific); private: - void MoveFrom(ImageWebp& other); - // image decoder WebPIDecoder* m_idec{nullptr}; WebPDecoderConfig m_config{}; From c22fae6e4fb17d22727e3e5483d0b5a5f55e4384 Mon Sep 17 00:00:00 2001 From: Viliam Lejcik Date: Mon, 27 Jan 2025 21:59:04 +0100 Subject: [PATCH 5/9] use vcpkg manifest file --- src/plugins/pictview/vcxproj/pictview.vcxproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/pictview/vcxproj/pictview.vcxproj b/src/plugins/pictview/vcxproj/pictview.vcxproj index a2e1017e2..0978fcb11 100644 --- a/src/plugins/pictview/vcxproj/pictview.vcxproj +++ b/src/plugins/pictview/vcxproj/pictview.vcxproj @@ -103,6 +103,7 @@ true true + true From 9cb8ca077bf3e290ebd3720aae3948345d8adc90 Mon Sep 17 00:00:00 2001 From: Viliam Lejcik Date: Mon, 27 Jan 2025 23:06:22 +0100 Subject: [PATCH 6/9] clang-formated PVW32DLL.h --- src/plugins/pictview/lib/PVW32DLL.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/plugins/pictview/lib/PVW32DLL.h b/src/plugins/pictview/lib/PVW32DLL.h index 9c8864cf5..34040a70c 100644 --- a/src/plugins/pictview/lib/PVW32DLL.h +++ b/src/plugins/pictview/lib/PVW32DLL.h @@ -38,20 +38,20 @@ #define PV_VERSION_155 0x10037 #define PV_VERSION_156 0x10038 #define PV_VERSION_MAJOR(x) ((x) >> 16) -#define PV_VERSION_MINOR(x) ((x)&0xFFFF) +#define PV_VERSION_MINOR(x) ((x) & 0xFFFF) #define PV_MAXIMUM_BITMAP_SIZE 0xFFFFFFFF /* Error codes: */ -#define PVC_EXPIRED ((PVCODE)-8) /* Demo version has expired */ -#define PVC_GDI_ERROR ((PVCODE)-7) /* GDI function like SelectObject failed */ -#define PVC_EXCEPTION ((PVCODE)-6) /* Internal error */ -#define PVC_CANCELED ((PVCODE)-5) /* Read canceled - not yet used */ -#define PVC_NO_MORE_IMAGES ((PVCODE)-4) /* No more images in the file */ -#define PVC_OOM ((PVCODE)-3) /* Out of memory */ -#define PVC_SYNC_ERROR ((PVCODE)-2) -#define PVC_INVALID_HANDLE ((PVCODE)-1) +#define PVC_EXPIRED ((PVCODE) - 8) /* Demo version has expired */ +#define PVC_GDI_ERROR ((PVCODE) - 7) /* GDI function like SelectObject failed */ +#define PVC_EXCEPTION ((PVCODE) - 6) /* Internal error */ +#define PVC_CANCELED ((PVCODE) - 5) /* Read canceled - not yet used */ +#define PVC_NO_MORE_IMAGES ((PVCODE) - 4) /* No more images in the file */ +#define PVC_OOM ((PVCODE) - 3) /* Out of memory */ +#define PVC_SYNC_ERROR ((PVCODE) - 2) +#define PVC_INVALID_HANDLE ((PVCODE) - 1) #define PVC_OK 0 #define PVC_UNKNOWN_COMPRESSION 6 #define PVC_OUT_OF_MEMORY 7 From 755f8540cd435ceceb7866637823b5d265777e93 Mon Sep 17 00:00:00 2001 From: Viliam Lejcik Date: Sun, 14 Dec 2025 12:12:38 +0100 Subject: [PATCH 7/9] removed redundant call --- src/plugins/pictview/webp.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/plugins/pictview/webp.cpp b/src/plugins/pictview/webp.cpp index af203676d..c906ff863 100644 --- a/src/plugins/pictview/webp.cpp +++ b/src/plugins/pictview/webp.cpp @@ -130,9 +130,6 @@ PVCODE ImageWebp::Read(HBITMAP& bmp, TProgressProc Progress, void* AppSpecific) if (!bmp) return PVC_GDI_ERROR; - // reading done - if (Progress) - Progress(100, AppSpecific); // image loaded successfully return PVC_OK; } From 5b43ed1341c4f14a3ec65f82d20972d9ff166cf1 Mon Sep 17 00:00:00 2001 From: Viliam Lejcik Date: Sun, 14 Dec 2025 12:13:16 +0100 Subject: [PATCH 8/9] heif: show progress and allow cancellation during picture loading some picture decoders that are supported by libheif ignore the heif_decoding_options, and that we can't get loading progress and we can't cancel the loading procedure! --- src/plugins/pictview/heif.cpp | 58 +++++++++++++++++++++++++++++------ src/plugins/pictview/heif.h | 4 +++ 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/plugins/pictview/heif.cpp b/src/plugins/pictview/heif.cpp index 0e6f3488c..ffa90c054 100644 --- a/src/plugins/pictview/heif.cpp +++ b/src/plugins/pictview/heif.cpp @@ -6,6 +6,14 @@ #include #include +struct HeifProgressData +{ + TProgressProc progressProc; + void* appSpecific; + int cancelFlag{false}; + int maxProgress{0}; +}; + ImageHeif::~ImageHeif() { if (m_image) @@ -78,22 +86,26 @@ PVCODE ImageHeif::Read(HBITMAP& bmp, TProgressProc Progress, void* AppSpecific) assert(m_ctx); assert(m_handle); -#if 0 - // TODO: decoding progress and cancellation doesn't work yet! + // decoding progress and cancellation doesn't work yet! heif_decoding_options* options = heif_decoding_options_alloc(); if (!options) return PVC_OUT_OF_MEMORY; - options->version = 1; - options->start_progress = start_progress; - options->on_progress = on_progress; - options->end_progress = end_progress; - options->cancel_decoding = cancel_decoding; + options->version = 6; + options->start_progress = &ImageHeif::StartProgress; + options->on_progress = &ImageHeif::OnProgress; + // options->end_progress = &ImageHeif::EndProgress; + options->cancel_decoding = &ImageHeif::CancelDecoding; + + HeifProgressData progress_data{ + .progressProc = Progress, + .appSpecific = AppSpecific + }; options->progress_user_data = &progress_data; -#endif // decode the image - heif_error err = heif_decode_image(m_handle, &m_image, heif_colorspace_RGB, heif_chroma_interleaved_RGB, nullptr); + heif_error err = heif_decode_image(m_handle, &m_image, heif_colorspace_RGB, heif_chroma_interleaved_RGB, options); + heif_decoding_options_free(options); if (err.code != heif_error_Ok) return PVC_READING_ERROR; @@ -135,3 +147,31 @@ PVCODE ImageHeif::Read(HBITMAP& bmp, TProgressProc Progress, void* AppSpecific) // image loaded successfully return PVC_OK; } + +void ImageHeif::StartProgress(heif_progress_step step, int max_progress, void* user) +{ + if (step != heif_progress_step_total) + return; + auto* data = static_cast(user); + data->maxProgress = max_progress; +} + +void ImageHeif::OnProgress(heif_progress_step step, int progress, void* user) +{ + if (step != heif_progress_step_total) + return; + + auto* data = static_cast(user); + if (data->progressProc && data->maxProgress != 0) + { + const int percent = progress * 100 / data->maxProgress; + if (data->progressProc(percent, data->appSpecific) && data->cancelFlag) + data->cancelFlag = true; + } +} + +int ImageHeif::CancelDecoding(void* user) +{ + const auto* data = static_cast(user); + return data->cancelFlag; +} diff --git a/src/plugins/pictview/heif.h b/src/plugins/pictview/heif.h index 50b028c9c..c4db2290c 100644 --- a/src/plugins/pictview/heif.h +++ b/src/plugins/pictview/heif.h @@ -16,6 +16,10 @@ class ImageHeif PVCODE Read(HBITMAP& bmp, TProgressProc Progress, void* AppSpecific); private: + static void StartProgress(enum heif_progress_step step, int max_progress, void* user); + static void OnProgress(enum heif_progress_step step, int progress, void* user); + static int CancelDecoding(void* user); + heif_context* m_ctx{}; heif_image_handle* m_handle{}; heif_image* m_image{}; From 40d3c5dbd14bd88187ebb23b8696b4032ade32ad Mon Sep 17 00:00:00 2001 From: Viliam Lejcik Date: Sun, 15 Feb 2026 22:05:36 +0100 Subject: [PATCH 9/9] added support for viewing xpm image files --- src/plugins/pictview/PVOverrider.cpp | 27 +- src/plugins/pictview/pictview.cpp | 4 +- src/plugins/pictview/vcxproj/pictview.vcxproj | 6 + .../pictview/vcxproj/pictview.vcxproj.filters | 18 + src/plugins/pictview/x11-colormap.cpp | 822 ++++++++++++++++++ src/plugins/pictview/x11-colormap.h | 8 + src/plugins/pictview/xpm-lite.cpp | 531 +++++++++++ src/plugins/pictview/xpm-lite.h | 78 ++ src/plugins/pictview/xpm.cpp | 125 +++ src/plugins/pictview/xpm.h | 20 + 10 files changed, 1634 insertions(+), 5 deletions(-) create mode 100644 src/plugins/pictview/x11-colormap.cpp create mode 100644 src/plugins/pictview/x11-colormap.h create mode 100644 src/plugins/pictview/xpm-lite.cpp create mode 100644 src/plugins/pictview/xpm-lite.h create mode 100644 src/plugins/pictview/xpm.cpp create mode 100644 src/plugins/pictview/xpm.h diff --git a/src/plugins/pictview/PVOverrider.cpp b/src/plugins/pictview/PVOverrider.cpp index e01ff6636..dc166c274 100644 --- a/src/plugins/pictview/PVOverrider.cpp +++ b/src/plugins/pictview/PVOverrider.cpp @@ -6,6 +6,7 @@ #include "pictview.h" #include "heif.h" #include "webp.h" +#include "xpm.h" #include #include #include @@ -20,7 +21,8 @@ namespace { PictView = 0, // the original PictView handle Heif, - Webp + Webp, + Xpm }; struct StretchParams @@ -36,7 +38,7 @@ namespace HandleType type{HandleType::PictView}; std::optional bkColor; std::optional stretch; - std::variant data; + std::variant data; }; // utilities @@ -92,6 +94,21 @@ namespace return PVC_OK; } } + + { + // try out to load the image as XPM + handle->type = HandleType::Xpm; + handle->data.emplace(); + auto& xpm = std::get(handle->data); + result = xpm.Open(pOpenExInfo->FileName, info); + if (result == PVC_OK) + { + // image opened successfully + *Img = reinterpret_cast(handle); + *pImgInfo = info; + return PVC_OK; + } + } } // call the original function @@ -131,6 +148,9 @@ namespace case HandleType::Webp: result = std::get(handle->data).Read(bmp, Progress, AppSpecific); break; + case HandleType::Xpm: + result = std::get(handle->data).Read(bmp, Progress, AppSpecific); + break; default: assert(0 || "Invalid image type"); return PVC_INVALID_HANDLE; @@ -426,7 +446,8 @@ namespace // check that the handle type is valid assert(handle->type == HandleType::PictView || handle->type == HandleType::Heif || - handle->type == HandleType::Webp); + handle->type == HandleType::Webp || + handle->type == HandleType::Xpm); return handle; } diff --git a/src/plugins/pictview/pictview.cpp b/src/plugins/pictview/pictview.cpp index d6962e0c1..c8063dc32 100644 --- a/src/plugins/pictview/pictview.cpp +++ b/src/plugins/pictview/pictview.cpp @@ -1066,7 +1066,7 @@ void CPluginInterface::Connect(HWND parent, CSalamanderConnectAbstract* salamand if (ConfigVersion < 23) // Added HEIF and WebP formats { - salamander->AddViewer("*.heic;*.heif;*.webp", TRUE); + salamander->AddViewer("*.heic;*.heif;*.webp;*.xpm", TRUE); } /* slouzi pro skript export_mnu.py, ktery generuje salmenu.mnu pro Translator @@ -1119,7 +1119,7 @@ MENU_TEMPLATE_ITEM PluginMenu[] = "*.flc;*.fli;*.gem;*.gif;*.ham;*.hmr;*.hrz;*.icn;*.ico;*.iff;*.img;" "*.cdt;*.cel;*.clp;*.cit;*.cmx;*.cot;*.cpt;*.cur;*.cut;*.dcx;*.dib;" "*.82i;*.83i;*.85i;*.86i;*.89i;*.92i;*.awd;*.bmi;*.bmp;*.cal;*.cdr;" - "*.arw;*.blp;*.cr2;*.dng;*.orf;*.pef;*.heic;*.heif;*.webp"); + "*.arw;*.blp;*.cr2;*.dng;*.orf;*.pef;*.heic;*.heif;*.webp;*.xpm"); } void CPluginInterface::ClearHistory(HWND parent) diff --git a/src/plugins/pictview/vcxproj/pictview.vcxproj b/src/plugins/pictview/vcxproj/pictview.vcxproj index 0978fcb11..8771ac049 100644 --- a/src/plugins/pictview/vcxproj/pictview.vcxproj +++ b/src/plugins/pictview/vcxproj/pictview.vcxproj @@ -177,6 +177,9 @@ + + + @@ -238,6 +241,9 @@ + + + diff --git a/src/plugins/pictview/vcxproj/pictview.vcxproj.filters b/src/plugins/pictview/vcxproj/pictview.vcxproj.filters index 3244a623e..4055d25fd 100644 --- a/src/plugins/pictview/vcxproj/pictview.vcxproj.filters +++ b/src/plugins/pictview/vcxproj/pictview.vcxproj.filters @@ -90,6 +90,15 @@ cpp + + cpp + + + cpp + + + cpp + @@ -185,6 +194,15 @@ h + + h + + + h + + + h + diff --git a/src/plugins/pictview/x11-colormap.cpp b/src/plugins/pictview/x11-colormap.cpp new file mode 100644 index 000000000..db8552831 --- /dev/null +++ b/src/plugins/pictview/x11-colormap.cpp @@ -0,0 +1,822 @@ +// SPDX-FileCopyrightText: 2023 Open Salamander Authors +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "precomp.h" +#include "x11-colormap.h" + +#ifdef _DEBUG +#include // call_once +#include +// debug-time checker to ensure color names are unique +std::once_flag g_hashCollisionCheckFlag; +#endif // _DEBUG + +// compile-time FNV-1a hash function for color name lookup (case insensitive) +static constexpr uint32_t HashColorName(std::string_view str) +{ + uint32_t hash = 2166136261u; + for (const auto c : str) + { + // convert to lowercase during hashing + const char ch = (c >= 'A' && c <= 'Z') ? (c + 32) : c; + hash ^= static_cast(ch); + hash *= 16777619u; + } + return hash; +} + +// X11 color database - hash to RGB mapping (compile-time computed) +struct X11ColorEntry +{ + uint32_t hash; + COLORREF rgb; +}; + +// X11 color definitions, taken from /usr/share/X11/rgb.txt +static constexpr const X11ColorEntry g_X11Colors[] = +{ + {HashColorName("snow"), RGB(255, 250, 250)}, + {HashColorName("ghost white"), RGB(248, 248, 255)}, + {HashColorName("GhostWhite"), RGB(248, 248, 255)}, + {HashColorName("white smoke"), RGB(245, 245, 245)}, + {HashColorName("WhiteSmoke"), RGB(245, 245, 245)}, + {HashColorName("gainsboro"), RGB(220, 220, 220)}, + {HashColorName("floral white"), RGB(255, 250, 240)}, + {HashColorName("FloralWhite"), RGB(255, 250, 240)}, + {HashColorName("old lace"), RGB(253, 245, 230)}, + {HashColorName("OldLace"), RGB(253, 245, 230)}, + {HashColorName("linen"), RGB(250, 240, 230)}, + {HashColorName("antique white"), RGB(250, 235, 215)}, + {HashColorName("AntiqueWhite"), RGB(250, 235, 215)}, + {HashColorName("papaya whip"), RGB(255, 239, 213)}, + {HashColorName("PapayaWhip"), RGB(255, 239, 213)}, + {HashColorName("blanched almond"), RGB(255, 235, 205)}, + {HashColorName("BlanchedAlmond"), RGB(255, 235, 205)}, + {HashColorName("bisque"), RGB(255, 228, 196)}, + {HashColorName("peach puff"), RGB(255, 218, 185)}, + {HashColorName("PeachPuff"), RGB(255, 218, 185)}, + {HashColorName("navajo white"), RGB(255, 222, 173)}, + {HashColorName("NavajoWhite"), RGB(255, 222, 173)}, + {HashColorName("moccasin"), RGB(255, 228, 181)}, + {HashColorName("cornsilk"), RGB(255, 248, 220)}, + {HashColorName("ivory"), RGB(255, 255, 240)}, + {HashColorName("lemon chiffon"), RGB(255, 250, 205)}, + {HashColorName("LemonChiffon"), RGB(255, 250, 205)}, + {HashColorName("seashell"), RGB(255, 245, 238)}, + {HashColorName("honeydew"), RGB(240, 255, 240)}, + {HashColorName("mint cream"), RGB(245, 255, 250)}, + {HashColorName("MintCream"), RGB(245, 255, 250)}, + {HashColorName("azure"), RGB(240, 255, 255)}, + {HashColorName("alice blue"), RGB(240, 248, 255)}, + {HashColorName("AliceBlue"), RGB(240, 248, 255)}, + {HashColorName("lavender"), RGB(230, 230, 250)}, + {HashColorName("lavender blush"), RGB(255, 240, 245)}, + {HashColorName("LavenderBlush"), RGB(255, 240, 245)}, + {HashColorName("misty rose"), RGB(255, 228, 225)}, + {HashColorName("MistyRose"), RGB(255, 228, 225)}, + {HashColorName("white"), RGB(255, 255, 255)}, + {HashColorName("black"), RGB(0, 0, 0)}, + {HashColorName("dark slate gray"), RGB(47, 79, 79)}, + {HashColorName("DarkSlateGray"), RGB(47, 79, 79)}, + {HashColorName("dark slate grey"), RGB(47, 79, 79)}, + {HashColorName("DarkSlateGrey"), RGB(47, 79, 79)}, + {HashColorName("dim gray"), RGB(105, 105, 105)}, + {HashColorName("DimGray"), RGB(105, 105, 105)}, + {HashColorName("dim grey"), RGB(105, 105, 105)}, + {HashColorName("DimGrey"), RGB(105, 105, 105)}, + {HashColorName("slate gray"), RGB(112, 128, 144)}, + {HashColorName("SlateGray"), RGB(112, 128, 144)}, + {HashColorName("slate grey"), RGB(112, 128, 144)}, + {HashColorName("SlateGrey"), RGB(112, 128, 144)}, + {HashColorName("light slate gray"), RGB(119, 136, 153)}, + {HashColorName("LightSlateGray"), RGB(119, 136, 153)}, + {HashColorName("light slate grey"), RGB(119, 136, 153)}, + {HashColorName("LightSlateGrey"), RGB(119, 136, 153)}, + {HashColorName("gray"), RGB(190, 190, 190)}, + {HashColorName("grey"), RGB(190, 190, 190)}, + {HashColorName("light grey"), RGB(211, 211, 211)}, + {HashColorName("LightGrey"), RGB(211, 211, 211)}, + {HashColorName("light gray"), RGB(211, 211, 211)}, + {HashColorName("LightGray"), RGB(211, 211, 211)}, + {HashColorName("midnight blue"), RGB(25, 25, 112)}, + {HashColorName("MidnightBlue"), RGB(25, 25, 112)}, + {HashColorName("navy"), RGB(0, 0, 128)}, + {HashColorName("navy blue"), RGB(0, 0, 128)}, + {HashColorName("NavyBlue"), RGB(0, 0, 128)}, + {HashColorName("cornflower blue"), RGB(100, 149, 237)}, + {HashColorName("CornflowerBlue"), RGB(100, 149, 237)}, + {HashColorName("dark slate blue"), RGB(72, 61, 139)}, + {HashColorName("DarkSlateBlue"), RGB(72, 61, 139)}, + {HashColorName("slate blue"), RGB(106, 90, 205)}, + {HashColorName("SlateBlue"), RGB(106, 90, 205)}, + {HashColorName("medium slate blue"), RGB(123, 104, 238)}, + {HashColorName("MediumSlateBlue"), RGB(123, 104, 238)}, + {HashColorName("light slate blue"), RGB(132, 112, 255)}, + {HashColorName("LightSlateBlue"), RGB(132, 112, 255)}, + {HashColorName("medium blue"), RGB(0, 0, 205)}, + {HashColorName("MediumBlue"), RGB(0, 0, 205)}, + {HashColorName("royal blue"), RGB(65, 105, 225)}, + {HashColorName("RoyalBlue"), RGB(65, 105, 225)}, + {HashColorName("blue"), RGB(0, 0, 255)}, + {HashColorName("dodger blue"), RGB(30, 144, 255)}, + {HashColorName("DodgerBlue"), RGB(30, 144, 255)}, + {HashColorName("deep sky blue"), RGB(0, 191, 255)}, + {HashColorName("DeepSkyBlue"), RGB(0, 191, 255)}, + {HashColorName("sky blue"), RGB(135, 206, 235)}, + {HashColorName("SkyBlue"), RGB(135, 206, 235)}, + {HashColorName("light sky blue"), RGB(135, 206, 250)}, + {HashColorName("LightSkyBlue"), RGB(135, 206, 250)}, + {HashColorName("steel blue"), RGB(70, 130, 180)}, + {HashColorName("SteelBlue"), RGB(70, 130, 180)}, + {HashColorName("light steel blue"), RGB(176, 196, 222)}, + {HashColorName("LightSteelBlue"), RGB(176, 196, 222)}, + {HashColorName("light blue"), RGB(173, 216, 230)}, + {HashColorName("LightBlue"), RGB(173, 216, 230)}, + {HashColorName("powder blue"), RGB(176, 224, 230)}, + {HashColorName("PowderBlue"), RGB(176, 224, 230)}, + {HashColorName("pale turquoise"), RGB(175, 238, 238)}, + {HashColorName("PaleTurquoise"), RGB(175, 238, 238)}, + {HashColorName("dark turquoise"), RGB(0, 206, 209)}, + {HashColorName("DarkTurquoise"), RGB(0, 206, 209)}, + {HashColorName("medium turquoise"), RGB(72, 209, 204)}, + {HashColorName("MediumTurquoise"), RGB(72, 209, 204)}, + {HashColorName("turquoise"), RGB(64, 224, 208)}, + {HashColorName("cyan"), RGB(0, 255, 255)}, + {HashColorName("light cyan"), RGB(224, 255, 255)}, + {HashColorName("LightCyan"), RGB(224, 255, 255)}, + {HashColorName("cadet blue"), RGB(95, 158, 160)}, + {HashColorName("CadetBlue"), RGB(95, 158, 160)}, + {HashColorName("medium aquamarine"), RGB(102, 205, 170)}, + {HashColorName("MediumAquamarine"), RGB(102, 205, 170)}, + {HashColorName("aquamarine"), RGB(127, 255, 212)}, + {HashColorName("dark green"), RGB(0, 100, 0)}, + {HashColorName("DarkGreen"), RGB(0, 100, 0)}, + {HashColorName("dark olive green"), RGB(85, 107, 47)}, + {HashColorName("DarkOliveGreen"), RGB(85, 107, 47)}, + {HashColorName("dark sea green"), RGB(143, 188, 143)}, + {HashColorName("DarkSeaGreen"), RGB(143, 188, 143)}, + {HashColorName("sea green"), RGB(46, 139, 87)}, + {HashColorName("SeaGreen"), RGB(46, 139, 87)}, + {HashColorName("medium sea green"), RGB(60, 179, 113)}, + {HashColorName("MediumSeaGreen"), RGB(60, 179, 113)}, + {HashColorName("light sea green"), RGB(32, 178, 170)}, + {HashColorName("LightSeaGreen"), RGB(32, 178, 170)}, + {HashColorName("pale green"), RGB(152, 251, 152)}, + {HashColorName("PaleGreen"), RGB(152, 251, 152)}, + {HashColorName("spring green"), RGB(0, 255, 127)}, + {HashColorName("SpringGreen"), RGB(0, 255, 127)}, + {HashColorName("lawn green"), RGB(124, 252, 0)}, + {HashColorName("LawnGreen"), RGB(124, 252, 0)}, + {HashColorName("green"), RGB(0, 255, 0)}, + {HashColorName("chartreuse"), RGB(127, 255, 0)}, + {HashColorName("medium spring green"), RGB(0, 250, 154)}, + {HashColorName("MediumSpringGreen"), RGB(0, 250, 154)}, + {HashColorName("green yellow"), RGB(173, 255, 47)}, + {HashColorName("GreenYellow"), RGB(173, 255, 47)}, + {HashColorName("lime green"), RGB(50, 205, 50)}, + {HashColorName("LimeGreen"), RGB(50, 205, 50)}, + {HashColorName("yellow green"), RGB(154, 205, 50)}, + {HashColorName("YellowGreen"), RGB(154, 205, 50)}, + {HashColorName("forest green"), RGB(34, 139, 34)}, + {HashColorName("ForestGreen"), RGB(34, 139, 34)}, + {HashColorName("olive drab"), RGB(107, 142, 35)}, + {HashColorName("OliveDrab"), RGB(107, 142, 35)}, + {HashColorName("dark khaki"), RGB(189, 183, 107)}, + {HashColorName("DarkKhaki"), RGB(189, 183, 107)}, + {HashColorName("khaki"), RGB(240, 230, 140)}, + {HashColorName("pale goldenrod"), RGB(238, 232, 170)}, + {HashColorName("PaleGoldenrod"), RGB(238, 232, 170)}, + {HashColorName("light goldenrod yellow"), RGB(250, 250, 210)}, + {HashColorName("LightGoldenrodYellow"), RGB(250, 250, 210)}, + {HashColorName("light yellow"), RGB(255, 255, 224)}, + {HashColorName("LightYellow"), RGB(255, 255, 224)}, + {HashColorName("yellow"), RGB(255, 255, 0)}, + {HashColorName("gold"), RGB(255, 215, 0)}, + {HashColorName("light goldenrod"), RGB(238, 221, 130)}, + {HashColorName("LightGoldenrod"), RGB(238, 221, 130)}, + {HashColorName("goldenrod"), RGB(218, 165, 32)}, + {HashColorName("dark goldenrod"), RGB(184, 134, 11)}, + {HashColorName("DarkGoldenrod"), RGB(184, 134, 11)}, + {HashColorName("rosy brown"), RGB(188, 143, 143)}, + {HashColorName("RosyBrown"), RGB(188, 143, 143)}, + {HashColorName("indian red"), RGB(205, 92, 92)}, + {HashColorName("IndianRed"), RGB(205, 92, 92)}, + {HashColorName("saddle brown"), RGB(139, 69, 19)}, + {HashColorName("SaddleBrown"), RGB(139, 69, 19)}, + {HashColorName("sienna"), RGB(160, 82, 45)}, + {HashColorName("peru"), RGB(205, 133, 63)}, + {HashColorName("burlywood"), RGB(222, 184, 135)}, + {HashColorName("beige"), RGB(245, 245, 220)}, + {HashColorName("wheat"), RGB(245, 222, 179)}, + {HashColorName("sandy brown"), RGB(244, 164, 96)}, + {HashColorName("SandyBrown"), RGB(244, 164, 96)}, + {HashColorName("tan"), RGB(210, 180, 140)}, + {HashColorName("chocolate"), RGB(210, 105, 30)}, + {HashColorName("firebrick"), RGB(178, 34, 34)}, + {HashColorName("brown"), RGB(165, 42, 42)}, + {HashColorName("dark salmon"), RGB(233, 150, 122)}, + {HashColorName("DarkSalmon"), RGB(233, 150, 122)}, + {HashColorName("salmon"), RGB(250, 128, 114)}, + {HashColorName("light salmon"), RGB(255, 160, 122)}, + {HashColorName("LightSalmon"), RGB(255, 160, 122)}, + {HashColorName("orange"), RGB(255, 165, 0)}, + {HashColorName("dark orange"), RGB(255, 140, 0)}, + {HashColorName("DarkOrange"), RGB(255, 140, 0)}, + {HashColorName("coral"), RGB(255, 127, 80)}, + {HashColorName("light coral"), RGB(240, 128, 128)}, + {HashColorName("LightCoral"), RGB(240, 128, 128)}, + {HashColorName("tomato"), RGB(255, 99, 71)}, + {HashColorName("orange red"), RGB(255, 69, 0)}, + {HashColorName("OrangeRed"), RGB(255, 69, 0)}, + {HashColorName("red"), RGB(255, 0, 0)}, + {HashColorName("hot pink"), RGB(255, 105, 180)}, + {HashColorName("HotPink"), RGB(255, 105, 180)}, + {HashColorName("deep pink"), RGB(255, 20, 147)}, + {HashColorName("DeepPink"), RGB(255, 20, 147)}, + {HashColorName("pink"), RGB(255, 192, 203)}, + {HashColorName("light pink"), RGB(255, 182, 193)}, + {HashColorName("LightPink"), RGB(255, 182, 193)}, + {HashColorName("pale violet red"), RGB(219, 112, 147)}, + {HashColorName("PaleVioletRed"), RGB(219, 112, 147)}, + {HashColorName("maroon"), RGB(176, 48, 96)}, + {HashColorName("medium violet red"), RGB(199, 21, 133)}, + {HashColorName("MediumVioletRed"), RGB(199, 21, 133)}, + {HashColorName("violet red"), RGB(208, 32, 144)}, + {HashColorName("VioletRed"), RGB(208, 32, 144)}, + {HashColorName("magenta"), RGB(255, 0, 255)}, + {HashColorName("violet"), RGB(238, 130, 238)}, + {HashColorName("plum"), RGB(221, 160, 221)}, + {HashColorName("orchid"), RGB(218, 112, 214)}, + {HashColorName("medium orchid"), RGB(186, 85, 211)}, + {HashColorName("MediumOrchid"), RGB(186, 85, 211)}, + {HashColorName("dark orchid"), RGB(153, 50, 204)}, + {HashColorName("DarkOrchid"), RGB(153, 50, 204)}, + {HashColorName("dark violet"), RGB(148, 0, 211)}, + {HashColorName("DarkViolet"), RGB(148, 0, 211)}, + {HashColorName("blue violet"), RGB(138, 43, 226)}, + {HashColorName("BlueViolet"), RGB(138, 43, 226)}, + {HashColorName("purple"), RGB(160, 32, 240)}, + {HashColorName("medium purple"), RGB(147, 112, 219)}, + {HashColorName("MediumPurple"), RGB(147, 112, 219)}, + {HashColorName("thistle"), RGB(216, 191, 216)}, + {HashColorName("snow1"), RGB(255, 250, 250)}, + {HashColorName("snow2"), RGB(238, 233, 233)}, + {HashColorName("snow3"), RGB(205, 201, 201)}, + {HashColorName("snow4"), RGB(139, 137, 137)}, + {HashColorName("seashell1"), RGB(255, 245, 238)}, + {HashColorName("seashell2"), RGB(238, 229, 222)}, + {HashColorName("seashell3"), RGB(205, 197, 191)}, + {HashColorName("seashell4"), RGB(139, 134, 130)}, + {HashColorName("AntiqueWhite1"), RGB(255, 239, 219)}, + {HashColorName("AntiqueWhite2"), RGB(238, 223, 204)}, + {HashColorName("AntiqueWhite3"), RGB(205, 192, 176)}, + {HashColorName("AntiqueWhite4"), RGB(139, 131, 120)}, + {HashColorName("bisque1"), RGB(255, 228, 196)}, + {HashColorName("bisque2"), RGB(238, 213, 183)}, + {HashColorName("bisque3"), RGB(205, 183, 158)}, + {HashColorName("bisque4"), RGB(139, 125, 107)}, + {HashColorName("PeachPuff1"), RGB(255, 218, 185)}, + {HashColorName("PeachPuff2"), RGB(238, 203, 173)}, + {HashColorName("PeachPuff3"), RGB(205, 175, 149)}, + {HashColorName("PeachPuff4"), RGB(139, 119, 101)}, + {HashColorName("NavajoWhite1"), RGB(255, 222, 173)}, + {HashColorName("NavajoWhite2"), RGB(238, 207, 161)}, + {HashColorName("NavajoWhite3"), RGB(205, 179, 139)}, + {HashColorName("NavajoWhite4"), RGB(139, 121, 94)}, + {HashColorName("LemonChiffon1"), RGB(255, 250, 205)}, + {HashColorName("LemonChiffon2"), RGB(238, 233, 191)}, + {HashColorName("LemonChiffon3"), RGB(205, 201, 165)}, + {HashColorName("LemonChiffon4"), RGB(139, 137, 112)}, + {HashColorName("cornsilk1"), RGB(255, 248, 220)}, + {HashColorName("cornsilk2"), RGB(238, 232, 205)}, + {HashColorName("cornsilk3"), RGB(205, 200, 177)}, + {HashColorName("cornsilk4"), RGB(139, 136, 120)}, + {HashColorName("ivory1"), RGB(255, 255, 240)}, + {HashColorName("ivory2"), RGB(238, 238, 224)}, + {HashColorName("ivory3"), RGB(205, 205, 193)}, + {HashColorName("ivory4"), RGB(139, 139, 131)}, + {HashColorName("honeydew1"), RGB(240, 255, 240)}, + {HashColorName("honeydew2"), RGB(224, 238, 224)}, + {HashColorName("honeydew3"), RGB(193, 205, 193)}, + {HashColorName("honeydew4"), RGB(131, 139, 131)}, + {HashColorName("LavenderBlush1"), RGB(255, 240, 245)}, + {HashColorName("LavenderBlush2"), RGB(238, 224, 229)}, + {HashColorName("LavenderBlush3"), RGB(205, 193, 197)}, + {HashColorName("LavenderBlush4"), RGB(139, 131, 134)}, + {HashColorName("MistyRose1"), RGB(255, 228, 225)}, + {HashColorName("MistyRose2"), RGB(238, 213, 210)}, + {HashColorName("MistyRose3"), RGB(205, 183, 181)}, + {HashColorName("MistyRose4"), RGB(139, 125, 123)}, + {HashColorName("azure1"), RGB(240, 255, 255)}, + {HashColorName("azure2"), RGB(224, 238, 238)}, + {HashColorName("azure3"), RGB(193, 205, 205)}, + {HashColorName("azure4"), RGB(131, 139, 139)}, + {HashColorName("SlateBlue1"), RGB(131, 111, 255)}, + {HashColorName("SlateBlue2"), RGB(122, 103, 238)}, + {HashColorName("SlateBlue3"), RGB(105, 89, 205)}, + {HashColorName("SlateBlue4"), RGB(71, 60, 139)}, + {HashColorName("RoyalBlue1"), RGB(72, 118, 255)}, + {HashColorName("RoyalBlue2"), RGB(67, 110, 238)}, + {HashColorName("RoyalBlue3"), RGB(58, 95, 205)}, + {HashColorName("RoyalBlue4"), RGB(39, 64, 139)}, + {HashColorName("blue1"), RGB(0, 0, 255)}, + {HashColorName("blue2"), RGB(0, 0, 238)}, + {HashColorName("blue3"), RGB(0, 0, 205)}, + {HashColorName("blue4"), RGB(0, 0, 139)}, + {HashColorName("DodgerBlue1"), RGB(30, 144, 255)}, + {HashColorName("DodgerBlue2"), RGB(28, 134, 238)}, + {HashColorName("DodgerBlue3"), RGB(24, 116, 205)}, + {HashColorName("DodgerBlue4"), RGB(16, 78, 139)}, + {HashColorName("SteelBlue1"), RGB(99, 184, 255)}, + {HashColorName("SteelBlue2"), RGB(92, 172, 238)}, + {HashColorName("SteelBlue3"), RGB(79, 148, 205)}, + {HashColorName("SteelBlue4"), RGB(54, 100, 139)}, + {HashColorName("DeepSkyBlue1"), RGB(0, 191, 255)}, + {HashColorName("DeepSkyBlue2"), RGB(0, 178, 238)}, + {HashColorName("DeepSkyBlue3"), RGB(0, 154, 205)}, + {HashColorName("DeepSkyBlue4"), RGB(0, 104, 139)}, + {HashColorName("SkyBlue1"), RGB(135, 206, 255)}, + {HashColorName("SkyBlue2"), RGB(126, 192, 238)}, + {HashColorName("SkyBlue3"), RGB(108, 166, 205)}, + {HashColorName("SkyBlue4"), RGB(74, 112, 139)}, + {HashColorName("LightSkyBlue1"), RGB(176, 226, 255)}, + {HashColorName("LightSkyBlue2"), RGB(164, 211, 238)}, + {HashColorName("LightSkyBlue3"), RGB(141, 182, 205)}, + {HashColorName("LightSkyBlue4"), RGB(96, 123, 139)}, + {HashColorName("SlateGray1"), RGB(198, 226, 255)}, + {HashColorName("SlateGray2"), RGB(185, 211, 238)}, + {HashColorName("SlateGray3"), RGB(159, 182, 205)}, + {HashColorName("SlateGray4"), RGB(108, 123, 139)}, + {HashColorName("LightSteelBlue1"), RGB(202, 225, 255)}, + {HashColorName("LightSteelBlue2"), RGB(188, 210, 238)}, + {HashColorName("LightSteelBlue3"), RGB(162, 181, 205)}, + {HashColorName("LightSteelBlue4"), RGB(110, 123, 139)}, + {HashColorName("LightBlue1"), RGB(191, 239, 255)}, + {HashColorName("LightBlue2"), RGB(178, 223, 238)}, + {HashColorName("LightBlue3"), RGB(154, 192, 205)}, + {HashColorName("LightBlue4"), RGB(104, 131, 139)}, + {HashColorName("LightCyan1"), RGB(224, 255, 255)}, + {HashColorName("LightCyan2"), RGB(209, 238, 238)}, + {HashColorName("LightCyan3"), RGB(180, 205, 205)}, + {HashColorName("LightCyan4"), RGB(122, 139, 139)}, + {HashColorName("PaleTurquoise1"), RGB(187, 255, 255)}, + {HashColorName("PaleTurquoise2"), RGB(174, 238, 238)}, + {HashColorName("PaleTurquoise3"), RGB(150, 205, 205)}, + {HashColorName("PaleTurquoise4"), RGB(102, 139, 139)}, + {HashColorName("CadetBlue1"), RGB(152, 245, 255)}, + {HashColorName("CadetBlue2"), RGB(142, 229, 238)}, + {HashColorName("CadetBlue3"), RGB(122, 197, 205)}, + {HashColorName("CadetBlue4"), RGB(83, 134, 139)}, + {HashColorName("turquoise1"), RGB(0, 245, 255)}, + {HashColorName("turquoise2"), RGB(0, 229, 238)}, + {HashColorName("turquoise3"), RGB(0, 197, 205)}, + {HashColorName("turquoise4"), RGB(0, 134, 139)}, + {HashColorName("cyan1"), RGB(0, 255, 255)}, + {HashColorName("cyan2"), RGB(0, 238, 238)}, + {HashColorName("cyan3"), RGB(0, 205, 205)}, + {HashColorName("cyan4"), RGB(0, 139, 139)}, + {HashColorName("DarkSlateGray1"), RGB(151, 255, 255)}, + {HashColorName("DarkSlateGray2"), RGB(141, 238, 238)}, + {HashColorName("DarkSlateGray3"), RGB(121, 205, 205)}, + {HashColorName("DarkSlateGray4"), RGB(82, 139, 139)}, + {HashColorName("aquamarine1"), RGB(127, 255, 212)}, + {HashColorName("aquamarine2"), RGB(118, 238, 198)}, + {HashColorName("aquamarine3"), RGB(102, 205, 170)}, + {HashColorName("aquamarine4"), RGB(69, 139, 116)}, + {HashColorName("DarkSeaGreen1"), RGB(193, 255, 193)}, + {HashColorName("DarkSeaGreen2"), RGB(180, 238, 180)}, + {HashColorName("DarkSeaGreen3"), RGB(155, 205, 155)}, + {HashColorName("DarkSeaGreen4"), RGB(105, 139, 105)}, + {HashColorName("SeaGreen1"), RGB(84, 255, 159)}, + {HashColorName("SeaGreen2"), RGB(78, 238, 148)}, + {HashColorName("SeaGreen3"), RGB(67, 205, 128)}, + {HashColorName("SeaGreen4"), RGB(46, 139, 87)}, + {HashColorName("PaleGreen1"), RGB(154, 255, 154)}, + {HashColorName("PaleGreen2"), RGB(144, 238, 144)}, + {HashColorName("PaleGreen3"), RGB(124, 205, 124)}, + {HashColorName("PaleGreen4"), RGB(84, 139, 84)}, + {HashColorName("SpringGreen1"), RGB(0, 255, 127)}, + {HashColorName("SpringGreen2"), RGB(0, 238, 118)}, + {HashColorName("SpringGreen3"), RGB(0, 205, 102)}, + {HashColorName("SpringGreen4"), RGB(0, 139, 69)}, + {HashColorName("green1"), RGB(0, 255, 0)}, + {HashColorName("green2"), RGB(0, 238, 0)}, + {HashColorName("green3"), RGB(0, 205, 0)}, + {HashColorName("green4"), RGB(0, 139, 0)}, + {HashColorName("chartreuse1"), RGB(127, 255, 0)}, + {HashColorName("chartreuse2"), RGB(118, 238, 0)}, + {HashColorName("chartreuse3"), RGB(102, 205, 0)}, + {HashColorName("chartreuse4"), RGB(69, 139, 0)}, + {HashColorName("OliveDrab1"), RGB(192, 255, 62)}, + {HashColorName("OliveDrab2"), RGB(179, 238, 58)}, + {HashColorName("OliveDrab3"), RGB(154, 205, 50)}, + {HashColorName("OliveDrab4"), RGB(105, 139, 34)}, + {HashColorName("DarkOliveGreen1"), RGB(202, 255, 112)}, + {HashColorName("DarkOliveGreen2"), RGB(188, 238, 104)}, + {HashColorName("DarkOliveGreen3"), RGB(162, 205, 90)}, + {HashColorName("DarkOliveGreen4"), RGB(110, 139, 61)}, + {HashColorName("khaki1"), RGB(255, 246, 143)}, + {HashColorName("khaki2"), RGB(238, 230, 133)}, + {HashColorName("khaki3"), RGB(205, 198, 115)}, + {HashColorName("khaki4"), RGB(139, 134, 78)}, + {HashColorName("LightGoldenrod1"), RGB(255, 236, 139)}, + {HashColorName("LightGoldenrod2"), RGB(238, 220, 130)}, + {HashColorName("LightGoldenrod3"), RGB(205, 190, 112)}, + {HashColorName("LightGoldenrod4"), RGB(139, 129, 76)}, + {HashColorName("LightYellow1"), RGB(255, 255, 224)}, + {HashColorName("LightYellow2"), RGB(238, 238, 209)}, + {HashColorName("LightYellow3"), RGB(205, 205, 180)}, + {HashColorName("LightYellow4"), RGB(139, 139, 122)}, + {HashColorName("yellow1"), RGB(255, 255, 0)}, + {HashColorName("yellow2"), RGB(238, 238, 0)}, + {HashColorName("yellow3"), RGB(205, 205, 0)}, + {HashColorName("yellow4"), RGB(139, 139, 0)}, + {HashColorName("gold1"), RGB(255, 215, 0)}, + {HashColorName("gold2"), RGB(238, 201, 0)}, + {HashColorName("gold3"), RGB(205, 173, 0)}, + {HashColorName("gold4"), RGB(139, 117, 0)}, + {HashColorName("goldenrod1"), RGB(255, 193, 37)}, + {HashColorName("goldenrod2"), RGB(238, 180, 34)}, + {HashColorName("goldenrod3"), RGB(205, 155, 29)}, + {HashColorName("goldenrod4"), RGB(139, 105, 20)}, + {HashColorName("DarkGoldenrod1"), RGB(255, 185, 15)}, + {HashColorName("DarkGoldenrod2"), RGB(238, 173, 14)}, + {HashColorName("DarkGoldenrod3"), RGB(205, 149, 12)}, + {HashColorName("DarkGoldenrod4"), RGB(139, 101, 8)}, + {HashColorName("RosyBrown1"), RGB(255, 193, 193)}, + {HashColorName("RosyBrown2"), RGB(238, 180, 180)}, + {HashColorName("RosyBrown3"), RGB(205, 155, 155)}, + {HashColorName("RosyBrown4"), RGB(139, 105, 105)}, + {HashColorName("IndianRed1"), RGB(255, 106, 106)}, + {HashColorName("IndianRed2"), RGB(238, 99, 99)}, + {HashColorName("IndianRed3"), RGB(205, 85, 85)}, + {HashColorName("IndianRed4"), RGB(139, 58, 58)}, + {HashColorName("sienna1"), RGB(255, 130, 71)}, + {HashColorName("sienna2"), RGB(238, 121, 66)}, + {HashColorName("sienna3"), RGB(205, 104, 57)}, + {HashColorName("sienna4"), RGB(139, 71, 38)}, + {HashColorName("burlywood1"), RGB(255, 211, 155)}, + {HashColorName("burlywood2"), RGB(238, 197, 145)}, + {HashColorName("burlywood3"), RGB(205, 170, 125)}, + {HashColorName("burlywood4"), RGB(139, 115, 85)}, + {HashColorName("wheat1"), RGB(255, 231, 186)}, + {HashColorName("wheat2"), RGB(238, 216, 174)}, + {HashColorName("wheat3"), RGB(205, 186, 150)}, + {HashColorName("wheat4"), RGB(139, 126, 102)}, + {HashColorName("tan1"), RGB(255, 165, 79)}, + {HashColorName("tan2"), RGB(238, 154, 73)}, + {HashColorName("tan3"), RGB(205, 133, 63)}, + {HashColorName("tan4"), RGB(139, 90, 43)}, + {HashColorName("chocolate1"), RGB(255, 127, 36)}, + {HashColorName("chocolate2"), RGB(238, 118, 33)}, + {HashColorName("chocolate3"), RGB(205, 102, 29)}, + {HashColorName("chocolate4"), RGB(139, 69, 19)}, + {HashColorName("firebrick1"), RGB(255, 48, 48)}, + {HashColorName("firebrick2"), RGB(238, 44, 44)}, + {HashColorName("firebrick3"), RGB(205, 38, 38)}, + {HashColorName("firebrick4"), RGB(139, 26, 26)}, + {HashColorName("brown1"), RGB(255, 64, 64)}, + {HashColorName("brown2"), RGB(238, 59, 59)}, + {HashColorName("brown3"), RGB(205, 51, 51)}, + {HashColorName("brown4"), RGB(139, 35, 35)}, + {HashColorName("salmon1"), RGB(255, 140, 105)}, + {HashColorName("salmon2"), RGB(238, 130, 98)}, + {HashColorName("salmon3"), RGB(205, 112, 84)}, + {HashColorName("salmon4"), RGB(139, 76, 57)}, + {HashColorName("LightSalmon1"), RGB(255, 160, 122)}, + {HashColorName("LightSalmon2"), RGB(238, 149, 114)}, + {HashColorName("LightSalmon3"), RGB(205, 129, 98)}, + {HashColorName("LightSalmon4"), RGB(139, 87, 66)}, + {HashColorName("orange1"), RGB(255, 165, 0)}, + {HashColorName("orange2"), RGB(238, 154, 0)}, + {HashColorName("orange3"), RGB(205, 133, 0)}, + {HashColorName("orange4"), RGB(139, 90, 0)}, + {HashColorName("DarkOrange1"), RGB(255, 127, 0)}, + {HashColorName("DarkOrange2"), RGB(238, 118, 0)}, + {HashColorName("DarkOrange3"), RGB(205, 102, 0)}, + {HashColorName("DarkOrange4"), RGB(139, 69, 0)}, + {HashColorName("coral1"), RGB(255, 114, 86)}, + {HashColorName("coral2"), RGB(238, 106, 80)}, + {HashColorName("coral3"), RGB(205, 91, 69)}, + {HashColorName("coral4"), RGB(139, 62, 47)}, + {HashColorName("tomato1"), RGB(255, 99, 71)}, + {HashColorName("tomato2"), RGB(238, 92, 66)}, + {HashColorName("tomato3"), RGB(205, 79, 57)}, + {HashColorName("tomato4"), RGB(139, 54, 38)}, + {HashColorName("OrangeRed1"), RGB(255, 69, 0)}, + {HashColorName("OrangeRed2"), RGB(238, 64, 0)}, + {HashColorName("OrangeRed3"), RGB(205, 55, 0)}, + {HashColorName("OrangeRed4"), RGB(139, 37, 0)}, + {HashColorName("red1"), RGB(255, 0, 0)}, + {HashColorName("red2"), RGB(238, 0, 0)}, + {HashColorName("red3"), RGB(205, 0, 0)}, + {HashColorName("red4"), RGB(139, 0, 0)}, + {HashColorName("DebianRed"), RGB(215, 7, 81)}, + {HashColorName("DeepPink1"), RGB(255, 20, 147)}, + {HashColorName("DeepPink2"), RGB(238, 18, 137)}, + {HashColorName("DeepPink3"), RGB(205, 16, 118)}, + {HashColorName("DeepPink4"), RGB(139, 10, 80)}, + {HashColorName("HotPink1"), RGB(255, 110, 180)}, + {HashColorName("HotPink2"), RGB(238, 106, 167)}, + {HashColorName("HotPink3"), RGB(205, 96, 144)}, + {HashColorName("HotPink4"), RGB(139, 58, 98)}, + {HashColorName("pink1"), RGB(255, 181, 197)}, + {HashColorName("pink2"), RGB(238, 169, 184)}, + {HashColorName("pink3"), RGB(205, 145, 158)}, + {HashColorName("pink4"), RGB(139, 99, 108)}, + {HashColorName("LightPink1"), RGB(255, 174, 185)}, + {HashColorName("LightPink2"), RGB(238, 162, 173)}, + {HashColorName("LightPink3"), RGB(205, 140, 149)}, + {HashColorName("LightPink4"), RGB(139, 95, 101)}, + {HashColorName("PaleVioletRed1"), RGB(255, 130, 171)}, + {HashColorName("PaleVioletRed2"), RGB(238, 121, 159)}, + {HashColorName("PaleVioletRed3"), RGB(205, 104, 137)}, + {HashColorName("PaleVioletRed4"), RGB(139, 71, 93)}, + {HashColorName("maroon1"), RGB(255, 52, 179)}, + {HashColorName("maroon2"), RGB(238, 48, 167)}, + {HashColorName("maroon3"), RGB(205, 41, 144)}, + {HashColorName("maroon4"), RGB(139, 28, 98)}, + {HashColorName("VioletRed1"), RGB(255, 62, 150)}, + {HashColorName("VioletRed2"), RGB(238, 58, 140)}, + {HashColorName("VioletRed3"), RGB(205, 50, 120)}, + {HashColorName("VioletRed4"), RGB(139, 34, 82)}, + {HashColorName("magenta1"), RGB(255, 0, 255)}, + {HashColorName("magenta2"), RGB(238, 0, 238)}, + {HashColorName("magenta3"), RGB(205, 0, 205)}, + {HashColorName("magenta4"), RGB(139, 0, 139)}, + {HashColorName("orchid1"), RGB(255, 131, 250)}, + {HashColorName("orchid2"), RGB(238, 122, 233)}, + {HashColorName("orchid3"), RGB(205, 105, 201)}, + {HashColorName("orchid4"), RGB(139, 71, 137)}, + {HashColorName("plum1"), RGB(255, 187, 255)}, + {HashColorName("plum2"), RGB(238, 174, 238)}, + {HashColorName("plum3"), RGB(205, 150, 205)}, + {HashColorName("plum4"), RGB(139, 102, 139)}, + {HashColorName("MediumOrchid1"), RGB(224, 102, 255)}, + {HashColorName("MediumOrchid2"), RGB(209, 95, 238)}, + {HashColorName("MediumOrchid3"), RGB(180, 82, 205)}, + {HashColorName("MediumOrchid4"), RGB(122, 55, 139)}, + {HashColorName("DarkOrchid1"), RGB(191, 62, 255)}, + {HashColorName("DarkOrchid2"), RGB(178, 58, 238)}, + {HashColorName("DarkOrchid3"), RGB(154, 50, 205)}, + {HashColorName("DarkOrchid4"), RGB(104, 34, 139)}, + {HashColorName("purple1"), RGB(155, 48, 255)}, + {HashColorName("purple2"), RGB(145, 44, 238)}, + {HashColorName("purple3"), RGB(125, 38, 205)}, + {HashColorName("purple4"), RGB(85, 26, 139)}, + {HashColorName("MediumPurple1"), RGB(171, 130, 255)}, + {HashColorName("MediumPurple2"), RGB(159, 121, 238)}, + {HashColorName("MediumPurple3"), RGB(137, 104, 205)}, + {HashColorName("MediumPurple4"), RGB(93, 71, 139)}, + {HashColorName("thistle1"), RGB(255, 225, 255)}, + {HashColorName("thistle2"), RGB(238, 210, 238)}, + {HashColorName("thistle3"), RGB(205, 181, 205)}, + {HashColorName("thistle4"), RGB(139, 123, 139)}, + {HashColorName("gray0"), RGB(0, 0, 0)}, + {HashColorName("grey0"), RGB(0, 0, 0)}, + {HashColorName("gray1"), RGB(3, 3, 3)}, + {HashColorName("grey1"), RGB(3, 3, 3)}, + {HashColorName("gray2"), RGB(5, 5, 5)}, + {HashColorName("grey2"), RGB(5, 5, 5)}, + {HashColorName("gray3"), RGB(8, 8, 8)}, + {HashColorName("grey3"), RGB(8, 8, 8)}, + {HashColorName("gray4"), RGB(10, 10, 10)}, + {HashColorName("grey4"), RGB(10, 10, 10)}, + {HashColorName("gray5"), RGB(13, 13, 13)}, + {HashColorName("grey5"), RGB(13, 13, 13)}, + {HashColorName("gray6"), RGB(15, 15, 15)}, + {HashColorName("grey6"), RGB(15, 15, 15)}, + {HashColorName("gray7"), RGB(18, 18, 18)}, + {HashColorName("grey7"), RGB(18, 18, 18)}, + {HashColorName("gray8"), RGB(20, 20, 20)}, + {HashColorName("grey8"), RGB(20, 20, 20)}, + {HashColorName("gray9"), RGB(23, 23, 23)}, + {HashColorName("grey9"), RGB(23, 23, 23)}, + {HashColorName("gray10"), RGB(26, 26, 26)}, + {HashColorName("grey10"), RGB(26, 26, 26)}, + {HashColorName("gray11"), RGB(28, 28, 28)}, + {HashColorName("grey11"), RGB(28, 28, 28)}, + {HashColorName("gray12"), RGB(31, 31, 31)}, + {HashColorName("grey12"), RGB(31, 31, 31)}, + {HashColorName("gray13"), RGB(33, 33, 33)}, + {HashColorName("grey13"), RGB(33, 33, 33)}, + {HashColorName("gray14"), RGB(36, 36, 36)}, + {HashColorName("grey14"), RGB(36, 36, 36)}, + {HashColorName("gray15"), RGB(38, 38, 38)}, + {HashColorName("grey15"), RGB(38, 38, 38)}, + {HashColorName("gray16"), RGB(41, 41, 41)}, + {HashColorName("grey16"), RGB(41, 41, 41)}, + {HashColorName("gray17"), RGB(43, 43, 43)}, + {HashColorName("grey17"), RGB(43, 43, 43)}, + {HashColorName("gray18"), RGB(46, 46, 46)}, + {HashColorName("grey18"), RGB(46, 46, 46)}, + {HashColorName("gray19"), RGB(48, 48, 48)}, + {HashColorName("grey19"), RGB(48, 48, 48)}, + {HashColorName("gray20"), RGB(51, 51, 51)}, + {HashColorName("grey20"), RGB(51, 51, 51)}, + {HashColorName("gray21"), RGB(54, 54, 54)}, + {HashColorName("grey21"), RGB(54, 54, 54)}, + {HashColorName("gray22"), RGB(56, 56, 56)}, + {HashColorName("grey22"), RGB(56, 56, 56)}, + {HashColorName("gray23"), RGB(59, 59, 59)}, + {HashColorName("grey23"), RGB(59, 59, 59)}, + {HashColorName("gray24"), RGB(61, 61, 61)}, + {HashColorName("grey24"), RGB(61, 61, 61)}, + {HashColorName("gray25"), RGB(64, 64, 64)}, + {HashColorName("grey25"), RGB(64, 64, 64)}, + {HashColorName("gray26"), RGB(66, 66, 66)}, + {HashColorName("grey26"), RGB(66, 66, 66)}, + {HashColorName("gray27"), RGB(69, 69, 69)}, + {HashColorName("grey27"), RGB(69, 69, 69)}, + {HashColorName("gray28"), RGB(71, 71, 71)}, + {HashColorName("grey28"), RGB(71, 71, 71)}, + {HashColorName("gray29"), RGB(74, 74, 74)}, + {HashColorName("grey29"), RGB(74, 74, 74)}, + {HashColorName("gray30"), RGB(77, 77, 77)}, + {HashColorName("grey30"), RGB(77, 77, 77)}, + {HashColorName("gray31"), RGB(79, 79, 79)}, + {HashColorName("grey31"), RGB(79, 79, 79)}, + {HashColorName("gray32"), RGB(82, 82, 82)}, + {HashColorName("grey32"), RGB(82, 82, 82)}, + {HashColorName("gray33"), RGB(84, 84, 84)}, + {HashColorName("grey33"), RGB(84, 84, 84)}, + {HashColorName("gray34"), RGB(87, 87, 87)}, + {HashColorName("grey34"), RGB(87, 87, 87)}, + {HashColorName("gray35"), RGB(89, 89, 89)}, + {HashColorName("grey35"), RGB(89, 89, 89)}, + {HashColorName("gray36"), RGB(92, 92, 92)}, + {HashColorName("grey36"), RGB(92, 92, 92)}, + {HashColorName("gray37"), RGB(94, 94, 94)}, + {HashColorName("grey37"), RGB(94, 94, 94)}, + {HashColorName("gray38"), RGB(97, 97, 97)}, + {HashColorName("grey38"), RGB(97, 97, 97)}, + {HashColorName("gray39"), RGB(99, 99, 99)}, + {HashColorName("grey39"), RGB(99, 99, 99)}, + {HashColorName("gray40"), RGB(102, 102, 102)}, + {HashColorName("grey40"), RGB(102, 102, 102)}, + {HashColorName("gray41"), RGB(105, 105, 105)}, + {HashColorName("grey41"), RGB(105, 105, 105)}, + {HashColorName("gray42"), RGB(107, 107, 107)}, + {HashColorName("grey42"), RGB(107, 107, 107)}, + {HashColorName("gray43"), RGB(110, 110, 110)}, + {HashColorName("grey43"), RGB(110, 110, 110)}, + {HashColorName("gray44"), RGB(112, 112, 112)}, + {HashColorName("grey44"), RGB(112, 112, 112)}, + {HashColorName("gray45"), RGB(115, 115, 115)}, + {HashColorName("grey45"), RGB(115, 115, 115)}, + {HashColorName("gray46"), RGB(117, 117, 117)}, + {HashColorName("grey46"), RGB(117, 117, 117)}, + {HashColorName("gray47"), RGB(120, 120, 120)}, + {HashColorName("grey47"), RGB(120, 120, 120)}, + {HashColorName("gray48"), RGB(122, 122, 122)}, + {HashColorName("grey48"), RGB(122, 122, 122)}, + {HashColorName("gray49"), RGB(125, 125, 125)}, + {HashColorName("grey49"), RGB(125, 125, 125)}, + {HashColorName("gray50"), RGB(127, 127, 127)}, + {HashColorName("grey50"), RGB(127, 127, 127)}, + {HashColorName("gray51"), RGB(130, 130, 130)}, + {HashColorName("grey51"), RGB(130, 130, 130)}, + {HashColorName("gray52"), RGB(133, 133, 133)}, + {HashColorName("grey52"), RGB(133, 133, 133)}, + {HashColorName("gray53"), RGB(135, 135, 135)}, + {HashColorName("grey53"), RGB(135, 135, 135)}, + {HashColorName("gray54"), RGB(138, 138, 138)}, + {HashColorName("grey54"), RGB(138, 138, 138)}, + {HashColorName("gray55"), RGB(140, 140, 140)}, + {HashColorName("grey55"), RGB(140, 140, 140)}, + {HashColorName("gray56"), RGB(143, 143, 143)}, + {HashColorName("grey56"), RGB(143, 143, 143)}, + {HashColorName("gray57"), RGB(145, 145, 145)}, + {HashColorName("grey57"), RGB(145, 145, 145)}, + {HashColorName("gray58"), RGB(148, 148, 148)}, + {HashColorName("grey58"), RGB(148, 148, 148)}, + {HashColorName("gray59"), RGB(150, 150, 150)}, + {HashColorName("grey59"), RGB(150, 150, 150)}, + {HashColorName("gray60"), RGB(153, 153, 153)}, + {HashColorName("grey60"), RGB(153, 153, 153)}, + {HashColorName("gray61"), RGB(156, 156, 156)}, + {HashColorName("grey61"), RGB(156, 156, 156)}, + {HashColorName("gray62"), RGB(158, 158, 158)}, + {HashColorName("grey62"), RGB(158, 158, 158)}, + {HashColorName("gray63"), RGB(161, 161, 161)}, + {HashColorName("grey63"), RGB(161, 161, 161)}, + {HashColorName("gray64"), RGB(163, 163, 163)}, + {HashColorName("grey64"), RGB(163, 163, 163)}, + {HashColorName("gray65"), RGB(166, 166, 166)}, + {HashColorName("grey65"), RGB(166, 166, 166)}, + {HashColorName("gray66"), RGB(168, 168, 168)}, + {HashColorName("grey66"), RGB(168, 168, 168)}, + {HashColorName("gray67"), RGB(171, 171, 171)}, + {HashColorName("grey67"), RGB(171, 171, 171)}, + {HashColorName("gray68"), RGB(173, 173, 173)}, + {HashColorName("grey68"), RGB(173, 173, 173)}, + {HashColorName("gray69"), RGB(176, 176, 176)}, + {HashColorName("grey69"), RGB(176, 176, 176)}, + {HashColorName("gray70"), RGB(179, 179, 179)}, + {HashColorName("grey70"), RGB(179, 179, 179)}, + {HashColorName("gray71"), RGB(181, 181, 181)}, + {HashColorName("grey71"), RGB(181, 181, 181)}, + {HashColorName("gray72"), RGB(184, 184, 184)}, + {HashColorName("grey72"), RGB(184, 184, 184)}, + {HashColorName("gray73"), RGB(186, 186, 186)}, + {HashColorName("grey73"), RGB(186, 186, 186)}, + {HashColorName("gray74"), RGB(189, 189, 189)}, + {HashColorName("grey74"), RGB(189, 189, 189)}, + {HashColorName("gray75"), RGB(191, 191, 191)}, + {HashColorName("grey75"), RGB(191, 191, 191)}, + {HashColorName("gray76"), RGB(194, 194, 194)}, + {HashColorName("grey76"), RGB(194, 194, 194)}, + {HashColorName("gray77"), RGB(196, 196, 196)}, + {HashColorName("grey77"), RGB(196, 196, 196)}, + {HashColorName("gray78"), RGB(199, 199, 199)}, + {HashColorName("grey78"), RGB(199, 199, 199)}, + {HashColorName("gray79"), RGB(201, 201, 201)}, + {HashColorName("grey79"), RGB(201, 201, 201)}, + {HashColorName("gray80"), RGB(204, 204, 204)}, + {HashColorName("grey80"), RGB(204, 204, 204)}, + {HashColorName("gray81"), RGB(207, 207, 207)}, + {HashColorName("grey81"), RGB(207, 207, 207)}, + {HashColorName("gray82"), RGB(209, 209, 209)}, + {HashColorName("grey82"), RGB(209, 209, 209)}, + {HashColorName("gray83"), RGB(212, 212, 212)}, + {HashColorName("grey83"), RGB(212, 212, 212)}, + {HashColorName("gray84"), RGB(214, 214, 214)}, + {HashColorName("grey84"), RGB(214, 214, 214)}, + {HashColorName("gray85"), RGB(217, 217, 217)}, + {HashColorName("grey85"), RGB(217, 217, 217)}, + {HashColorName("gray86"), RGB(219, 219, 219)}, + {HashColorName("grey86"), RGB(219, 219, 219)}, + {HashColorName("gray87"), RGB(222, 222, 222)}, + {HashColorName("grey87"), RGB(222, 222, 222)}, + {HashColorName("gray88"), RGB(224, 224, 224)}, + {HashColorName("grey88"), RGB(224, 224, 224)}, + {HashColorName("gray89"), RGB(227, 227, 227)}, + {HashColorName("grey89"), RGB(227, 227, 227)}, + {HashColorName("gray90"), RGB(229, 229, 229)}, + {HashColorName("grey90"), RGB(229, 229, 229)}, + {HashColorName("gray91"), RGB(232, 232, 232)}, + {HashColorName("grey91"), RGB(232, 232, 232)}, + {HashColorName("gray92"), RGB(235, 235, 235)}, + {HashColorName("grey92"), RGB(235, 235, 235)}, + {HashColorName("gray93"), RGB(237, 237, 237)}, + {HashColorName("grey93"), RGB(237, 237, 237)}, + {HashColorName("gray94"), RGB(240, 240, 240)}, + {HashColorName("grey94"), RGB(240, 240, 240)}, + {HashColorName("gray95"), RGB(242, 242, 242)}, + {HashColorName("grey95"), RGB(242, 242, 242)}, + {HashColorName("gray96"), RGB(245, 245, 245)}, + {HashColorName("grey96"), RGB(245, 245, 245)}, + {HashColorName("gray97"), RGB(247, 247, 247)}, + {HashColorName("grey97"), RGB(247, 247, 247)}, + {HashColorName("gray98"), RGB(250, 250, 250)}, + {HashColorName("grey98"), RGB(250, 250, 250)}, + {HashColorName("gray99"), RGB(252, 252, 252)}, + {HashColorName("grey99"), RGB(252, 252, 252)}, + {HashColorName("gray100"), RGB(255, 255, 255)}, + {HashColorName("grey100"), RGB(255, 255, 255)}, + {HashColorName("dark grey"), RGB(169, 169, 169)}, + {HashColorName("DarkGrey"), RGB(169, 169, 169)}, + {HashColorName("dark gray"), RGB(169, 169, 169)}, + {HashColorName("DarkGray"), RGB(169, 169, 169)}, + {HashColorName("dark blue"), RGB(0, 0, 139)}, + {HashColorName("DarkBlue"), RGB(0, 0, 139)}, + {HashColorName("dark cyan"), RGB(0, 139, 139)}, + {HashColorName("DarkCyan"), RGB(0, 139, 139)}, + {HashColorName("dark magenta"), RGB(139, 0, 139)}, + {HashColorName("DarkMagenta"), RGB(139, 0, 139)}, + {HashColorName("dark red"), RGB(139, 0, 0)}, + {HashColorName("DarkRed"), RGB(139, 0, 0)}, + {HashColorName("light green"), RGB(144, 238, 144)}, + {HashColorName("LightGreen"), RGB(144, 238, 144)}, +}; + +///////////////////////////////////////////////////////////////////////// + +bool LookupX11Color(std::string_view name, COLORREF& outColor) +{ +#ifdef _DEBUG + // diagnostics: ensure that there are no hash collisions! + std::call_once(g_hashCollisionCheckFlag, []() { + // check each pair of hashes for uniqueness + for (auto i = std::begin(g_X11Colors); i != std::end(g_X11Colors); i++) + { + for (auto j = i + 1; j != std::end(g_X11Colors); j++) + { + assert(i->hash != j->hash && "Hash collision detected!"); + } + } }); +#endif // _DEBUG + + // compute hash of input name (case-insensitive) + const auto hash = HashColorName(name); + + const auto it = std::find_if(std::begin(g_X11Colors), std::end(g_X11Colors), + [hash](const X11ColorEntry& entry) + { return entry.hash == hash; }); + + if (it == std::end(g_X11Colors)) + return false; + + outColor = it->rgb; + return true; +} diff --git a/src/plugins/pictview/x11-colormap.h b/src/plugins/pictview/x11-colormap.h new file mode 100644 index 000000000..0f7aa6e3c --- /dev/null +++ b/src/plugins/pictview/x11-colormap.h @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2023 Open Salamander Authors +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include + +/// converts X11 color name to COLORREF +bool LookupX11Color(std::string_view name, COLORREF& outColor); diff --git a/src/plugins/pictview/xpm-lite.cpp b/src/plugins/pictview/xpm-lite.cpp new file mode 100644 index 000000000..ebe07e0ee --- /dev/null +++ b/src/plugins/pictview/xpm-lite.cpp @@ -0,0 +1,531 @@ +// SPDX-FileCopyrightText: 2023 Open Salamander Authors +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "precomp.h" +#include "xpm-lite.h" +#include "x11-colormap.h" + +#include +#include +#include + +#undef new +#undef min + +using namespace xpm; + +// --------------------------------------------------------------------------- +// Local helpers +// --------------------------------------------------------------------------- + +inline bool isspace(char c) +{ + return std::isspace(static_cast(c)) != 0; +} + +inline bool isxdigit(char c) +{ + return std::isxdigit(static_cast(c)) != 0; +} + +/// Advance past the current line (including CR, LF, or CRLF). +static const char* NextLine(const char* p) +{ + while (*p && *p != '\n' && *p != '\r') + ++p; + if (*p == '\r' && *(p + 1) == '\n') + p += 2; + else if (*p == '\r' || *p == '\n') + ++p; + return p; +} + +/// Skip sequences of CR/LF (blank lines). +static const char* SkipBlankLines(const char* p) +{ + while (*p == '\r' || *p == '\n') + ++p; + return p; +} + +/// Return the numeric value of a single hex digit. +static uint8_t HexVal(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return 10 + c - 'a'; + if (c >= 'A' && c <= 'F') + return 10 + c - 'A'; + return 0; +} + +/// Parse a hex color string. +static bool ParseHexColor(std::string_view str, COLORREF& color) +{ + if (str.length() == 0 || str[0] != '#') + return false; + str.remove_prefix(1); // skip '#' + + // Count hex digits length + size_t len = 0; + for (const auto c : str) + { + if (!isxdigit(c)) + break; + ++len; + } + + uint8_t r, g, b; + + if (len == 6) + { + // #RRGGBB + r = (HexVal(str[0]) << 4) | HexVal(str[1]); + g = (HexVal(str[2]) << 4) | HexVal(str[3]); + b = (HexVal(str[4]) << 4) | HexVal(str[5]); + } + else if (len == 3) + { + // #RGB – expand each nibble to a full byte + r = (HexVal(str[0]) << 4) | HexVal(str[0]); + g = (HexVal(str[1]) << 4) | HexVal(str[1]); + b = (HexVal(str[2]) << 4) | HexVal(str[2]); + } + else if (len == 9) + { + // #RRRGGGBBB – use upper 8 bits of each 12-bit component + r = (HexVal(str[0]) << 4) | HexVal(str[1]); + g = (HexVal(str[3]) << 4) | HexVal(str[4]); + b = (HexVal(str[6]) << 4) | HexVal(str[7]); + } + else if (len == 12) + { + // #RRRRGGGGBBBB – use upper 8 bits of each 16-bit component + r = (HexVal(str[0]) << 4) | HexVal(str[1]); + g = (HexVal(str[4]) << 4) | HexVal(str[5]); + b = (HexVal(str[8]) << 4) | HexVal(str[9]); + } + else + { + return false; + } + + color = RGB(r, g, b); + return true; +} + +/// Extract the color-value substring that follows a " c " key inside a color +/// definition line. The value extends until the next visual-type key +/// (" m ", " s ", " g ", " g4 ") or end of string. +static std::string_view ExtractColorValue(std::string_view line, size_t start) +{ + size_t end = start; + while (end < line.size()) + { + char ch = line[end]; + if (ch == ' ' || ch == '\t') + { + const size_t next = end + 1; + if (next < line.size()) + { + const char k = line[next]; + const size_t afterKey = next + 1; + + // Single-char keys: m, s, c + if ((k == 'm' || k == 's' || k == 'c') && + afterKey < line.size() && + (line[afterKey] == ' ' || line[afterKey] == '\t')) + break; + + // 'g' or 'g4' + if (k == 'g') + { + if (afterKey < line.size() && line[afterKey] == '4') + { + const size_t afterG4 = afterKey + 1; + if (afterG4 < line.size() && + (line[afterG4] == ' ' || line[afterG4] == '\t')) + break; + } + else if (afterKey < line.size() && + (line[afterKey] == ' ' || line[afterKey] == '\t')) + break; + } + } + } + ++end; + } + + std::string_view value = line.substr(start, end - start); + + // Trim trailing whitespace + while (!value.empty() && isspace(value.back())) + value.remove_suffix(1); + return value; +} + +/// Parse a color specifier string (hex, "None", or X11 name). +static bool ParseColorSpec(std::string_view spec, COLORREF& color) +{ + if (spec.size() == 0) + return false; + + // Hex color + if (spec[0] == '#') + return ParseHexColor(spec, color); + + // Transparent (temporary white color used) + static constexpr const char* TRANSPARENT_COLOR = "None"; + const auto TRANSPARENT_COLOR_LEN = std::string::traits_type::length(TRANSPARENT_COLOR); + if (spec.size() == TRANSPARENT_COLOR_LEN && _strnicmp(spec.data(), TRANSPARENT_COLOR, TRANSPARENT_COLOR_LEN) == 0) + { + spec = "White"; + } + + // Convert X11 named color to COLORREF + return LookupX11Color(spec, color); +} + +/// Locate the position of a " c " (color) key inside a line string. +/// Tries common whitespace combinations around the key. +static size_t FindColorKey(std::string_view line) +{ + static const char* patterns[] = {" c ", "\tc ", " c\t", "\tc\t"}; + for (const auto pat : patterns) + { + const auto pos = line.find(pat); + if (pos != std::string::npos) + return pos; + } + return std::string::npos; +} + +/// Parse one color-definition line (the text between quotes for XPM3, +/// or the plain-text line for XPM2) and return the character key and +/// resolved Color. +static bool ParseColorDef(std::string_view line, uint32_t cpp, + std::string& outKey, COLORREF& outColor) +{ + if (line.size() < cpp) + return false; + + outKey.assign(line.data(), cpp); + + const auto tail = line.substr(cpp); + const size_t cpos = FindColorKey(tail); + + if (cpos != std::string::npos) + { + size_t specStart = cpp + cpos + 3; // skip past " c " + while (specStart < line.size() && + isspace(line[specStart])) + ++specStart; + + const auto colorValue = ExtractColorValue(line, specStart); + if (!colorValue.empty()) + return ParseColorSpec(colorValue, outColor); + } + + return true; +} + +// --------------------------------------------------------------------------- +// Image public interface +// --------------------------------------------------------------------------- + +std::expected, Code> Image::Open(const char* file) +{ + auto img = std::unique_ptr(new Image()); + const auto code = img->DoOpen(file); + if (code != Code::Success) + return std::unexpected(code); + return img; +} + +// --------------------------------------------------------------------------- +// DoOpen – phase 1: read file, detect version, parse header values +// --------------------------------------------------------------------------- + +Code Image::DoOpen(const char* file) +{ + // Open and read the entire file into memory + FILE* f = nullptr; + if (fopen_s(&f, file, "rb") != 0 || !f) + return Code::OpenFailed; + + fseek(f, 0, SEEK_END); + const long size = ftell(f); + fseek(f, 0, SEEK_SET); + + if (size <= 0) + { + fclose(f); + return Code::OpenFailed; + } + + m_fileSize = static_cast(size); + + try + { + m_data.resize(m_fileSize + 1); + } + catch (...) + { + fclose(f); + return Code::NoMemory; + } + + const size_t bytesRead = fread(m_data.data(), 1, m_fileSize, f); + fclose(f); + + if (bytesRead != m_fileSize) + return Code::OpenFailed; + + // This is expected to be a text file, make it a zero-terminated string. + m_data[m_fileSize] = '\0'; + + // --- Detect format and parse the header (values) line --- + + const char* ptr = m_data.data(); + + // Skip leading whitespace + while (*ptr && isspace(*ptr)) + ++ptr; + + static constexpr const char* XPM2_HEADER = "! XPM2"; + static constexpr const char* XPM3_HEADER = "/* XPM */"; + constexpr size_t XPM2_HEADER_LEN = std::string::traits_type::length(XPM2_HEADER); + constexpr size_t XPM3_HEADER_LEN = std::string::traits_type::length(XPM3_HEADER); + + // NOTE: XPM version 1 is not supported! + + if (m_fileSize > XPM2_HEADER_LEN && strncmp(ptr, XPM2_HEADER, XPM2_HEADER_LEN) == 0) + { + // ---- XPM version 2 ---- + m_format = Format::Version2; + + // Skip past the header identifier line + ptr = NextLine(ptr); + ptr = SkipBlankLines(ptr); + + // Values line: + if (sscanf_s(ptr, "%d %d %d %d", &m_width, &m_height, &m_numColors, &m_charsPerPixel) != 4) + return Code::InvalidFormat; + + if (m_width == 0 || m_height == 0 || m_numColors == 0 || m_charsPerPixel == 0) + return Code::InvalidFormat; + + ptr = NextLine(ptr); + m_parsePos = ptr; // colour definitions start here + return Code::Success; + } + else if (m_fileSize > XPM3_HEADER_LEN && strncmp(ptr, XPM3_HEADER, XPM3_HEADER_LEN) == 0) + { + // ---- XPM version 3 ---- + const char* brace = strchr(ptr, '{'); + if (!brace) + return Code::InvalidFormat; + + m_format = Format::Version3; + + ptr = brace + 1; + + // Find the first quoted string – this is the header / values string + while (*ptr && *ptr != '"') + ++ptr; + if (!*ptr) + return Code::InvalidFormat; + ++ptr; // skip opening quote + + if (sscanf_s(ptr, "%d %d %d %d", &m_width, &m_height, &m_numColors, &m_charsPerPixel) != 4) + return Code::InvalidFormat; + + if (m_width == 0 || m_height == 0 || m_numColors == 0 || m_charsPerPixel == 0) + return Code::InvalidFormat; + + // Advance past the closing quote of the header string + ptr = strchr(ptr, '"'); + if (!ptr) + return Code::InvalidFormat; + ++ptr; + + m_parsePos = ptr; // colour definition strings follow + return Code::Success; + } + + return Code::InvalidFormat; // unrecognized format +} + +// Check if image size may cause overflow +bool Image::CheckImageSize() const +{ + return !(m_height != 0 && m_width > UINT_MAX / m_height); +} + +// --------------------------------------------------------------------------- +// Decode – phase 2: parse colour table and pixel data +// --------------------------------------------------------------------------- + +Code Image::Decode(std::function appender) +{ + if (m_format == Format::Unknown || !m_parsePos) + return Code::DecodeError; + + if (!CheckImageSize()) + return Code::NoMemory; + + const char* ptr = m_parsePos; + + // ===================================================================== + // Phase 2a – Parse the colour table + // ===================================================================== + + std::unordered_map colorMap; + try + { + colorMap.reserve(m_numColors); + } + catch (...) + { + return Code::NoMemory; + } + + if (m_format == Format::Version2) + { + // XPM2: each colour definition is a plain-text line + for (uint32_t i = 0; i < m_numColors;) + { + ptr = SkipBlankLines(ptr); + if (!*ptr) + return Code::DecodeError; + + // Skip comments + while (*ptr == '!') + { + ptr = NextLine(ptr); + } + + const char* lineStart = ptr; + const char* lineEnd = ptr; + while (*lineEnd && *lineEnd != '\n' && *lineEnd != '\r') + ++lineEnd; + std::string_view line(lineStart, lineEnd); + + // Skip empty lines (shouldn't happen after SkipBlankLines, but be safe) + if (line.size() == 0) + { + ptr = NextLine(lineStart); + continue; + } + + std::string key; + COLORREF color{}; + if (!ParseColorDef(line, m_charsPerPixel, key, color)) + return Code::DecodeError; + + colorMap[std::move(key)] = color; + ++i; + + ptr = NextLine(lineStart); + } + } + else // Version3 + { + // XPM3: each colour definition is a quoted string + for (uint32_t i = 0; i < m_numColors; ++i) + { + // Find opening quote + while (*ptr && *ptr != '"') + ++ptr; + if (!*ptr) + return Code::DecodeError; + ++ptr; // skip opening quote + + const char* lineStart = ptr; + const char* lineEnd = strchr(ptr, '"'); + if (!lineEnd) + return Code::DecodeError; + std::string_view line(lineStart, lineEnd); + + std::string key; + COLORREF color{}; + if (!ParseColorDef(line, m_charsPerPixel, key, color)) + return Code::DecodeError; + + colorMap[std::move(key)] = color; + + ptr = lineEnd + 1; // past closing quote + } + } + + // Ensure that all colors were read + if (m_numColors != colorMap.size()) + return Code::DecodeError; + + // ===================================================================== + // Phase 2b – Decode pixel data + // ===================================================================== + + for (uint32_t y = 0; y < m_height; ++y) + { + const char* rowStart = nullptr; + + if (m_format == Format::Version2) + { + // Plain-text lines – skip blank / comment lines (starting with '!') + for (;;) + { + ptr = SkipBlankLines(ptr); + if (!*ptr) + return Code::DecodeError; + + if (*ptr == '!') + { + ptr = NextLine(ptr); + continue; + } + break; + } + rowStart = ptr; + } + else // Version3 + { + // Find the next quoted string + while (*ptr && *ptr != '"') + ++ptr; + if (!*ptr) + return Code::DecodeError; + ++ptr; // skip opening quote + rowStart = ptr; + } + + // Decode all pixels in this row + const char* px = rowStart; + for (uint32_t x = 0; x < m_width; ++x, px += m_charsPerPixel) + { + const std::string key(px, m_charsPerPixel); + auto it = colorMap.find(key); + if (it == std::end(colorMap)) + return Code::DecodeError; + + // append the next pixel to the consumer + if (!appender(it->second)) + return Code::Cancelled; + } + + // Advance past this row + if (m_format == Format::Version2) + { + ptr = NextLine(rowStart); + } + else + { + // Skip past the closing quote + const char* endQuote = strchr(rowStart, '"'); + ptr = endQuote ? endQuote + 1 : px; + } + } + + return Code::Success; +} diff --git a/src/plugins/pictview/xpm-lite.h b/src/plugins/pictview/xpm-lite.h new file mode 100644 index 000000000..3372a4940 --- /dev/null +++ b/src/plugins/pictview/xpm-lite.h @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2023 Open Salamander Authors +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace xpm { + +enum class Format +{ + Unknown = 0, + Version2, + Version3 +}; + +enum class Code +{ + Success = 0, + OpenFailed, + InvalidFormat, + NoMemory, + DecodeError, + Cancelled +}; + +class Image +{ + Image() = default; + +public: + ~Image() = default; + + static std::expected, Code> Open(const char* file); + + Code Decode(std::function appender); + + Format GetFormat() const + { + return m_format; + } + + size_t GetFileSize() const + { + return m_fileSize; + } + + uint32_t GetWidth() const + { + return m_width; + } + + uint32_t GetHeight() const + { + return m_height; + } + + bool CheckImageSize() const; + +private: + Code DoOpen(const char* file); + + Format m_format{Format::Unknown}; + std::vector m_data; + size_t m_fileSize{}; + uint32_t m_width{}; + uint32_t m_height{}; + uint32_t m_numColors{}; + uint32_t m_charsPerPixel{}; + const char* m_parsePos{}; +}; + +} // namespace xpm diff --git a/src/plugins/pictview/xpm.cpp b/src/plugins/pictview/xpm.cpp new file mode 100644 index 000000000..7fd7f5daa --- /dev/null +++ b/src/plugins/pictview/xpm.cpp @@ -0,0 +1,125 @@ +// SPDX-FileCopyrightText: 2023 Open Salamander Authors +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "precomp.h" +#include "xpm.h" +#include + +PVCODE ImageXpm::Open(const char* filename, PVImageInfo& pvii) +{ + auto result = xpm::Image::Open(filename); + if (!result) + { + switch (result.error()) + { + case xpm::Code::InvalidFormat: + return PVC_UNKNOWN_FILE_STRUCT; + case xpm::Code::NoMemory: + return PVC_OUT_OF_MEMORY; + case xpm::Code::OpenFailed: + default: + return PVC_CANNOT_OPEN_FILE; + } + } + + // Image loaded successfully, fill out image info struct + m_image = std::move(result.value()); + + pvii.cbSize = sizeof(pvii); + pvii.Format = PVF_BMP; + pvii.Width = m_image->GetWidth(); + pvii.Height = m_image->GetHeight(); + pvii.NumOfImages = 1; + pvii.FileSize = static_cast(m_image->GetFileSize()); + pvii.Colors = PV_COLOR_TC24; + pvii.ColorModel = PVCM_RGB; + pvii.Compression = PVCS_NO_COMPRESSION; + + strcpy_s(pvii.Info1, "X PixMap"); + if (m_image->GetFormat() == xpm::Format::Version2) + { + strcat_s(pvii.Info1, " v2"); + } + else if (m_image->GetFormat() == xpm::Format::Version3) + { + strcat_s(pvii.Info1, " v3"); + } + + return PVC_OK; +} + +PVCODE ImageXpm::Read(HBITMAP& bmp, TProgressProc Progress, void* AppSpecific) +{ + if (!m_image->CheckImageSize()) + return PVC_OUT_OF_MEMORY; + + std::vector bgraData; + try + { + const size_t pixelCount = m_image->GetHeight() * m_image->GetWidth(); + bgraData.reserve(pixelCount * 4); + } + catch (...) + { + return PVC_OUT_OF_MEMORY; + } + + int progressRound = 0; + auto appender = [&bgraData, &progressRound, Progress, AppSpecific](COLORREF color) + { + // Ensure data can be appended to the preallocated buffer + assert(bgraData.size() + 4 <= bgraData.capacity()); + + // Convert decoded RGBA pixels to BGRA byte order for Windows DIB + bgraData.push_back(GetBValue(color)); // blue + bgraData.push_back(GetGValue(color)); // green + bgraData.push_back(GetRValue(color)); // red + bgraData.push_back(0xFF); // ignore alpha for now + + // Report progress + if (Progress && (progressRound++ > 1000)) + { + progressRound = 0; + const auto progressPercentage = static_cast(bgraData.size() * 100 / bgraData.capacity()); + if (Progress(progressPercentage, AppSpecific)) + return false; + } + + return true; + }; + + const auto result = m_image->Decode(appender); + switch (result) + { + case xpm::Code::Success: + break; + case xpm::Code::InvalidFormat: + return PVC_UNKNOWN_FILE_STRUCT; + case xpm::Code::NoMemory: + return PVC_OUT_OF_MEMORY; + case xpm::Code::Cancelled: + return PVC_CANCELED; + case xpm::Code::DecodeError: + default: + return PVC_READING_ERROR; + } + + // Create the DIB bitmap + BITMAPINFO bmi = {0}; + bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); + bmi.bmiHeader.biWidth = static_cast(m_image->GetWidth()); + bmi.bmiHeader.biHeight = -static_cast(m_image->GetHeight()); // top-down DIB + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; // 32-bit BGRA + bmi.bmiHeader.biCompression = BI_RGB; + + HDC hdc = GetDC(nullptr); + if (!hdc) + return PVC_GDI_ERROR; + bmp = CreateDIBitmap(hdc, &bmi.bmiHeader, CBM_INIT, bgraData.data(), &bmi, DIB_RGB_COLORS); + ReleaseDC(nullptr, hdc); + if (!bmp) + return PVC_GDI_ERROR; + + return PVC_OK; +} diff --git a/src/plugins/pictview/xpm.h b/src/plugins/pictview/xpm.h new file mode 100644 index 000000000..5de5a44d4 --- /dev/null +++ b/src/plugins/pictview/xpm.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2023 Open Salamander Authors +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "lib/pvw32dll.h" +#include "xpm-lite.h" + +class ImageXpm +{ +public: + ImageXpm() = default; + ~ImageXpm() = default; + + PVCODE Open(const char* filename, PVImageInfo& pvii); + PVCODE Read(HBITMAP& bmp, TProgressProc Progress, void* AppSpecific); + +private: + std::unique_ptr m_image; +};