diff --git a/src/plugins/pictview/PVOverrider.cpp b/src/plugins/pictview/PVOverrider.cpp new file mode 100644 index 000000000..e01ff6636 --- /dev/null +++ b/src/plugins/pictview/PVOverrider.cpp @@ -0,0 +1,481 @@ +// 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; + }; + + // utilities + Handle* _ConvertHandle(LPPVHandle hPVImage); + LPPVHandle _GetHandleForLocalCall(Handle* handle); + + PVCODE WINAPI 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 WINAPI _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 WINAPI ReadImage(LPPVHandle Img, HDC PaintDC, RECT* pDRect, TProgressProc Progress, void* AppSpecific, int ImageIndex) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = _ConvertHandle(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 WINAPI DrawImage(LPPVHandle Img, HDC PaintDC, int X, int Y, LPRECT rect) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = _ConvertHandle(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 WINAPI CloseImage(LPPVHandle Img) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = _ConvertHandle(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 WINAPI SetBkHandle(LPPVHandle Img, COLORREF BkColor) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = _ConvertHandle(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 WINAPI GetHandles2(LPPVHandle Img, LPPVImageHandles* pHandles) + { + if (!Img) + return PVC_INVALID_HANDLE; + 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 WINAPI GetImageInfo(LPPVHandle Img, LPPVImageInfo pImgInfo, int cbSize, int ImageIndex) + { + if (!Img) + return PVC_INVALID_HANDLE; + 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 WINAPI SetStretchParameters(LPPVHandle Img, DWORD Width, DWORD Height, DWORD Mode) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = _ConvertHandle(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 WINAPI LoadFromClipboard(LPPVHandle* Img, LPPVImageInfo pImgInfo, int 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 WINAPI SaveImage(LPPVHandle Img, const char* OutFName, LPPVSaveImageInfo pSii, TProgressProc Progress, void* AppSpecific, int ImageIndex) + { + if (!Img) + return PVC_INVALID_HANDLE; + 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 WINAPI ChangeImage(LPPVHandle Img, DWORD Flags) + { + if (!Img) + return PVC_INVALID_HANDLE; + 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 WINAPI ReadImageSequence(LPPVHandle Img, LPPVImageSequence* ppSeq) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = _ConvertHandle(Img); + if (handle->type != HandleType::PictView) + return PVC_INVALID_HANDLE; + // call the original function + return origPVW32DLL.PVReadImageSequence(std::get(handle->data), ppSeq); + } + + PVCODE WINAPI CropImage(LPPVHandle Img, int Left, int Top, int Width, int Height) + { + if (!Img) + return PVC_INVALID_HANDLE; + auto* handle = _ConvertHandle(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 = _ConvertHandle(Img); + if (handle->type != HandleType::PictView) + return false; + // call the original function + return origPVW32DLL.GetRGBAtCursor(_GetHandleForLocalCall(handle), 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 = _ConvertHandle(PVHandle); + if (handle->type != HandleType::PictView) + return PVC_INVALID_HANDLE; + // call the original function + return origPVW32DLL.CalculateHistogram(_GetHandleForLocalCall(handle), 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 = _ConvertHandle(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(_GetHandleForLocalCall(handle), 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 = _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 + +////////////////////////////////////////////////////////////////////////// + +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..ffa90c054 --- /dev/null +++ b/src/plugins/pictview/heif.cpp @@ -0,0 +1,177 @@ +// SPDX-FileCopyrightText: 2023 Open Salamander Authors +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "precomp.h" +#include "heif.h" +#include +#include + +struct HeifProgressData +{ + TProgressProc progressProc; + void* appSpecific; + int cancelFlag{false}; + int maxProgress{0}; +}; + +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); + + // 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 = 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; + + // decode the image + 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; + + // 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; +} + +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 new file mode 100644 index 000000000..c4db2290c --- /dev/null +++ b/src/plugins/pictview/heif.h @@ -0,0 +1,26 @@ +// 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: + 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{}; +}; 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..0978fcb11 100644 --- a/src/plugins/pictview/vcxproj/pictview.vcxproj +++ b/src/plugins/pictview/vcxproj/pictview.vcxproj @@ -100,6 +100,11 @@ + + true + true + true + stdcpplatest @@ -131,6 +136,7 @@ + @@ -151,6 +157,7 @@ + @@ -167,6 +174,7 @@ + @@ -201,6 +209,7 @@ + @@ -215,6 +224,7 @@ + @@ -225,6 +235,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..c906ff863 --- /dev/null +++ b/src/plugins/pictview/webp.cpp @@ -0,0 +1,135 @@ +// 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 = static_cast(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; + + // 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..d41ea3799 --- /dev/null +++ b/src/plugins/pictview/webp.h @@ -0,0 +1,25 @@ +// 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: + // 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"