From a849bd1ef8379c3288ee85b328d266d256a99e24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Capello?= Date: Fri, 26 Apr 2024 18:13:18 -0300 Subject: [PATCH 1/6] Add support to decode JPEG, GIF and BMP formats, and more pixel formats --- clip_win_wic.cpp | 228 +++++++++++++++++++++++++++++++++++++---------- clip_win_wic.h | 25 ++++++ 2 files changed, 207 insertions(+), 46 deletions(-) diff --git a/clip_win_wic.cpp b/clip_win_wic.cpp index 33e0cbb..9d49fd5 100644 --- a/clip_win_wic.cpp +++ b/clip_win_wic.cpp @@ -178,53 +178,99 @@ HGLOBAL write_png(const image& image) { return nullptr; } -////////////////////////////////////////////////////////////////////// -// Decode the clipboard data from PNG format - -bool read_png(const uint8_t* buf, - const UINT len, - image* output_image, - image_spec* output_spec) { - coinit com; - +IStream* create_stream(const BYTE* pInit, UINT cbInit) +{ #ifdef CLIP_SUPPORT_WINXP // Pull SHCreateMemStream from shlwapi.dll by ordinal 12 // for Windows XP support // From: https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-shcreatememstream#remarks - typedef IStream* (WINAPI* SHCreateMemStreamPtr)(const BYTE* pInit, UINT cbInit); + typedef IStream*(WINAPI * SHCreateMemStreamPtr)(const BYTE* pInit, + UINT cbInit); hmodule shlwapiDll(L"shlwapi.dll"); if (!shlwapiDll) return false; - auto SHCreateMemStream = - reinterpret_cast(GetProcAddress(shlwapiDll, (LPCSTR)12)); + auto SHCreateMemStream = reinterpret_cast( + GetProcAddress(shlwapiDll, (LPCSTR)12)); if (!SHCreateMemStream) return false; #endif + return SHCreateMemStream(pInit, cbInit); +} - comptr stream(SHCreateMemStream(buf, len)); +image_spec spec_from_pixelformat(const WICPixelFormatGUID& pixelFormat, unsigned long w, unsigned long h) +{ + image_spec spec; + spec.width = w; + spec.height = h; + if (pixelFormat == GUID_WICPixelFormat32bppBGRA || + pixelFormat == GUID_WICPixelFormat32bppBGR) { + spec.bits_per_pixel = 32; + spec.red_mask = 0xff0000; + spec.green_mask = 0xff00; + spec.blue_mask = 0xff; + spec.alpha_mask = 0xff000000; + spec.red_shift = 16; + spec.green_shift = 8; + spec.blue_shift = 0; + spec.alpha_shift = 24; + } + else if (pixelFormat == GUID_WICPixelFormat24bppBGR || + pixelFormat == GUID_WICPixelFormat8bppIndexed) { + spec.bits_per_pixel = 24; + spec.red_mask = 0xff0000; + spec.green_mask = 0xff00; + spec.blue_mask = 0xff; + spec.alpha_mask = 0; + spec.red_shift = 16; + spec.green_shift = 8; + spec.blue_shift = 0; + spec.alpha_shift = 0; + } + spec.bytes_per_row = ((w*spec.bits_per_pixel+31) / 32) * 4; + return spec; +} - if (!stream) +// Tries to decode the input buf of size len using the first decoder available from the +// ones specified in decoderCLSIDs vector. If output_image is not null, the decoded +// image is returned there, if output_spec is not null then the image specifications +// are set there. +bool decode(std::vector decoderCLSIDs, + const uint8_t* buf, + const UINT len, + image* output_image, + image_spec* output_spec) +{ + if (decoderCLSIDs.empty()) return false; + coinit com; + comptr decoder; - HRESULT hr = CoCreateInstance(CLSID_WICPngDecoder2, - nullptr, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&decoder)); - if (FAILED(hr)) { - hr = CoCreateInstance(CLSID_WICPngDecoder1, - nullptr, CLSCTX_INPROC_SERVER, + + HRESULT hr = E_FAIL; + for (GUID decoderCLSID : decoderCLSIDs) { + hr = CoCreateInstance(decoderCLSID, nullptr, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&decoder)); - if (FAILED(hr)) - return false; + if (SUCCEEDED(hr)) + break; } + if (FAILED(hr)) + return false; + // Can decoder be nullptr if hr is S_OK/successful? We've received // some crash reports that might indicate this. if (!decoder) return false; + comptr stream(create_stream(buf, len)); + + if (!stream) + return false; + hr = decoder->Initialize(stream.get(), WICDecodeMetadataCacheOnDemand); if (FAILED(hr)) return false; @@ -239,9 +285,12 @@ bool read_png(const uint8_t* buf, if (FAILED(hr)) return false; - // Only support this pixel format + // Only support these pixel formats // TODO add support for more pixel formats - if (pixelFormat != GUID_WICPixelFormat32bppBGRA) + if (pixelFormat != GUID_WICPixelFormat32bppBGRA && + pixelFormat != GUID_WICPixelFormat32bppBGR && + pixelFormat != GUID_WICPixelFormat24bppBGR && + pixelFormat != GUID_WICPixelFormat8bppIndexed) return false; UINT width = 0, height = 0; @@ -249,34 +298,78 @@ bool read_png(const uint8_t* buf, if (FAILED(hr)) return false; - image_spec spec; - spec.width = width; - spec.height = height; - spec.bits_per_pixel = 32; - spec.bytes_per_row = 4 * width; - spec.red_mask = 0xff0000; - spec.green_mask = 0xff00; - spec.blue_mask = 0xff; - spec.alpha_mask = 0xff000000; - spec.red_shift = 16; - spec.green_shift = 8; - spec.blue_shift = 0; - spec.alpha_shift = 24; + image_spec spec = spec_from_pixelformat(pixelFormat, width, height); if (output_spec) *output_spec = spec; + image img; if (output_image) { - image img(spec); - - hr = frame->CopyPixels( - nullptr, // Entire bitmap - spec.bytes_per_row, - spec.bytes_per_row * spec.height, - (BYTE*)img.data()); - if (FAILED(hr)) { - return false; + if (pixelFormat == GUID_WICPixelFormat8bppIndexed) { + std::vector pixels(spec.width * spec.height); + hr = frame->CopyPixels(nullptr, // Entire bitmap + spec.width, + spec.width * spec.height, + pixels.data()); + + if (FAILED(hr)) + return false; + + comptr factory; + HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, + nullptr, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&factory)); + if (FAILED(hr)) + return false; + + comptr palette; + hr = factory->CreatePalette(&palette); + if (FAILED(hr)) + return false; + + hr = frame->CopyPalette(palette.get()); + if (FAILED(hr)) + return false; + + UINT numcolors; + hr = palette->GetColorCount(&numcolors); + if (FAILED(hr)) + return false; + + UINT actualNumcolors; + std::vector colors(numcolors); + hr = palette->GetColors(numcolors, colors.data(), &actualNumcolors); + if (FAILED(hr)) + return false; + + BOOL hasAlpha = false; + palette->HasAlpha(&hasAlpha); + if (hasAlpha) { + spec = spec_from_pixelformat(GUID_WICPixelFormat32bppBGRA, width, height); + } + + img = image(spec); + char* dst = img.data(); + BYTE* src = pixels.data(); + for (int y = 0; y < spec.height; ++y) { + char* dst_x = dst; + for (int x = 0; x < spec.width; ++x, dst_x+=spec.bits_per_pixel/8, ++src) { + *((uint32_t*)dst_x) = colors[*src]; + } + dst += spec.bytes_per_row; + } } + else { + img = image(spec); + hr = frame->CopyPixels(nullptr, // Entire bitmap + spec.bytes_per_row, + spec.bytes_per_row * spec.height, + (BYTE*)img.data()); + if (FAILED(hr)) + return false; + } + std::swap(*output_image, img); } @@ -284,5 +377,48 @@ bool read_png(const uint8_t* buf, return true; } +////////////////////////////////////////////////////////////////////// +// Decode the clipboard data from PNG format + +bool read_png(const uint8_t* buf, + const UINT len, + image* output_image, + image_spec* output_spec) { + return decode({CLSID_WICPngDecoder2, CLSID_WICPngDecoder1}, buf, len, output_image, output_spec); +} + +////////////////////////////////////////////////////////////////////// +// Decode the clipboard data from JPEG format + +bool read_jpg(const uint8_t* buf, + const UINT len, + image* output_image, + image_spec* output_spec) +{ + return decode({CLSID_WICJpegDecoder}, buf, len, output_image, output_spec); +} + +////////////////////////////////////////////////////////////////////// +// Decode the clipboard data from GIF format + +bool read_gif(const uint8_t* buf, + const UINT len, + image* output_image, + image_spec* output_spec) +{ + return decode({CLSID_WICGifDecoder}, buf, len, output_image, output_spec); +} + +////////////////////////////////////////////////////////////////////// +// Decode the clipboard data from BMP format + +bool read_bmp(const uint8_t* buf, + const UINT len, + image* output_image, + image_spec* output_spec) +{ + return decode({ CLSID_WICBmpDecoder }, buf, len, output_image, output_spec); +} + } // namespace win } // namespace clip diff --git a/clip_win_wic.h b/clip_win_wic.h index fd0905a..2a99add 100644 --- a/clip_win_wic.h +++ b/clip_win_wic.h @@ -38,6 +38,31 @@ bool read_png(const uint8_t* buf, image* output_image, image_spec* output_spec); +////////////////////////////////////////////////////////////////////// +// Decode the clipboard data from JPEG format + +bool read_jpg(const uint8_t* buf, + const UINT len, + image* output_image, + image_spec* output_spec); + +////////////////////////////////////////////////////////////////////// +// Decode the clipboard data from GIF format + +bool read_gif(const uint8_t* buf, + const UINT len, + image* output_image, + image_spec* output_spec); + +////////////////////////////////////////////////////////////////////// +// Decode the clipboard data from BMP format + +bool read_bmp(const uint8_t* buf, + const UINT len, + image* output_image, + image_spec* output_spec); + + } // namespace win } // namespace clip From f86406033b4fc0f34fa7a8fc294555c69e294ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Capello?= Date: Fri, 3 May 2024 10:43:49 -0300 Subject: [PATCH 2/6] [win] Add support to get images from the clipboard registered as: "JPG", "JPEG", "image/jpeg", "image/png", "BMP", "image/bmp", "GIF", and "image/gif" --- clip_win.cpp | 137 +++++++++++++++++++++++++++++++++++++---------- clip_win_wic.cpp | 2 +- 2 files changed, 111 insertions(+), 28 deletions(-) diff --git a/clip_win.cpp b/clip_win.cpp index 09cdfa5..20bd303 100644 --- a/clip_win.cpp +++ b/clip_win.cpp @@ -12,6 +12,9 @@ #include #include #include +#if CLIP_ENABLE_IMAGE +#include +#endif #include #include @@ -79,6 +82,83 @@ class AnonymousTokenImpersonator { const bool m_must_revert; }; +#if CLIP_ENABLE_IMAGE +class ImageFormat { + using ReadDataFunc = std::function; + +public: + static bool anyAvailable() { + for (auto fmt : m_formats) { + if (fmt->isAvailable()) { + return true; + } + } + return false; + } + + // Returns all the formats that were succesfuly registered + static const std::vector& formats() { + return m_formats; + } + + ImageFormat(const std::vector& names, + ReadDataFunc func) : m_readFunc(func) + { + for (auto& name : names) { + UINT cbformat = RegisterClipboardFormatA(name.c_str()); + if (cbformat) { + m_names.push_back(name); + m_ids.push_back(cbformat); + } + } + // Add this format only if we were able to get a valid clipboard format id for it. + if (!m_ids.empty()) + m_formats.push_back(this); + } + + // Checks if this format is currently available on the clipboard, in which case + // returns true and sets *out_format (if it is not null) to the id of the + // available clipboard format. Returns false otherwise. + bool isAvailable(UINT* out_format = nullptr) + { + for (auto id : m_ids) { + if (IsClipboardFormatAvailable(id)) { + if (out_format) + *out_format = id; + return true; + } + } + return false; + } + + bool read(const uint8_t* buf, + const UINT len, + image* output_image, + image_spec* output_spec) + { + return m_readFunc(buf, len, output_image, output_spec); + } + +private: + static std::vector m_formats; // Holds all the supported formats. + + std::vector m_names; // Alternative names of this format + std::vector m_ids; // Clipboard format ID for each name of this format + ReadDataFunc m_readFunc; // Function used to decode data in this format +}; + +std::vector ImageFormat::m_formats; + +ImageFormat Png = { { "PNG", "image/png" } , win::read_png }; +ImageFormat Jpg = { { "JPG", "image/jpeg", "JPEG" }, win::read_jpg }; +ImageFormat Bmp = { { "BMP", "image/bmp" } , win::read_bmp }; +ImageFormat Gif = { { "GIF", "image/gif" } , win::read_gif }; + +#endif + } lock::impl::impl(void* hwnd) : m_locked(false) { @@ -117,13 +197,12 @@ bool lock::impl::is_convertible(format f) const { } #if CLIP_ENABLE_IMAGE else if (f == image_format()) { - return (IsClipboardFormatAvailable(CF_DIB) ? true: false); + return (IsClipboardFormatAvailable(CF_DIB) || + ImageFormat::anyAvailable()); } #endif // CLIP_ENABLE_IMAGE - else if (IsClipboardFormatAvailable(f)) - return true; else - return false; + return IsClipboardFormatAvailable(f); } bool lock::impl::set_data(format f, const char* buf, size_t len) { @@ -311,37 +390,41 @@ bool lock::impl::set_image(const image& image) { } bool lock::impl::get_image(image& output_img) const { - // Get the "PNG" clipboard format (this is useful only for 32bpp - // images with alpha channel, in other case we can use the regular - // DIB format) - UINT png_format = RegisterClipboardFormatA("PNG"); - if (png_format && IsClipboardFormatAvailable(png_format)) { - HANDLE png_handle = GetClipboardData(png_format); - if (png_handle) { - size_t png_size = GlobalSize(png_handle); - uint8_t* png_data = (uint8_t*)GlobalLock(png_handle); - bool result = win::read_png(png_data, png_size, &output_img, nullptr); - GlobalUnlock(png_handle); - if (result) - return true; + // Tries to get the first "custom" ("PNG", "JPG", "GIF", etc) clipboard + // format. + for (auto fmt : ImageFormat::formats()) { + UINT cbformat; + if (fmt->isAvailable(&cbformat)) { + HANDLE handle = GetClipboardData(cbformat); + if (handle) { + size_t size = GlobalSize(handle); + uint8_t* data = (uint8_t*)GlobalLock(handle); + bool result = fmt->read(data, size, &output_img, nullptr); + GlobalUnlock(handle); + if (result) + return true; + } } } + // If we couldn't find any "custom" format, we try to use the regular DIB format) win::BitmapInfo bi; return bi.to_image(output_img); } bool lock::impl::get_image_spec(image_spec& spec) const { - UINT png_format = RegisterClipboardFormatA("PNG"); - if (png_format && IsClipboardFormatAvailable(png_format)) { - HANDLE png_handle = GetClipboardData(png_format); - if (png_handle) { - size_t png_size = GlobalSize(png_handle); - uint8_t* png_data = (uint8_t*)GlobalLock(png_handle); - bool result = win::read_png(png_data, png_size, nullptr, &spec); - GlobalUnlock(png_handle); - if (result) - return true; + for (auto fmt : ImageFormat::formats()) { + UINT cbformat; + if (fmt->isAvailable(&cbformat)) { + HANDLE handle = GetClipboardData(cbformat); + if (handle) { + size_t size = GlobalSize(handle); + uint8_t* data = (uint8_t*)GlobalLock(handle); + bool result = fmt->read(data, size, nullptr, &spec); + GlobalUnlock(handle); + if (result) + return true; + } } } diff --git a/clip_win_wic.cpp b/clip_win_wic.cpp index 9d49fd5..f3241ff 100644 --- a/clip_win_wic.cpp +++ b/clip_win_wic.cpp @@ -417,7 +417,7 @@ bool read_bmp(const uint8_t* buf, image* output_image, image_spec* output_spec) { - return decode({ CLSID_WICBmpDecoder }, buf, len, output_image, output_spec); + return decode({CLSID_WICBmpDecoder}, buf, len, output_image, output_spec); } } // namespace win From a3a90d6d1736ab280909713fba7a15220c27a5c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Capello?= Date: Fri, 3 May 2024 14:45:43 -0300 Subject: [PATCH 3/6] Reset alpha and mask setting for 32bpp BGR images --- clip_win_wic.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clip_win_wic.cpp b/clip_win_wic.cpp index f3241ff..3bcf904 100644 --- a/clip_win_wic.cpp +++ b/clip_win_wic.cpp @@ -215,6 +215,11 @@ image_spec spec_from_pixelformat(const WICPixelFormatGUID& pixelFormat, unsigned spec.green_shift = 8; spec.blue_shift = 0; spec.alpha_shift = 24; + // Reset mask and shift for BGR pixel format. + if (pixelFormat == GUID_WICPixelFormat32bppBGR) { + spec.alpha_mask = 0; + spec.alpha_shift = 0; + } } else if (pixelFormat == GUID_WICPixelFormat24bppBGR || pixelFormat == GUID_WICPixelFormat8bppIndexed) { From dc8a30a767c1826a40332fb9a3a10307aeeef1e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Capello?= Date: Fri, 3 May 2024 14:53:09 -0300 Subject: [PATCH 4/6] [win] Check that the source color references a valid index in the palette when converting 8bpp images This avoids crashing the program when converting a decoded malformed BMP (i.e. references palette indexes that don't exist). --- clip_win_wic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clip_win_wic.cpp b/clip_win_wic.cpp index 3bcf904..8fbe994 100644 --- a/clip_win_wic.cpp +++ b/clip_win_wic.cpp @@ -360,7 +360,7 @@ bool decode(std::vector decoderCLSIDs, for (int y = 0; y < spec.height; ++y) { char* dst_x = dst; for (int x = 0; x < spec.width; ++x, dst_x+=spec.bits_per_pixel/8, ++src) { - *((uint32_t*)dst_x) = colors[*src]; + *((uint32_t*)dst_x) = (*src < numcolors ? colors[*src] : 0); } dst += spec.bytes_per_row; } From b047b278bce01e85c3c414ab9459ca7ba8ad19c5 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 6 May 2024 10:22:45 -0300 Subject: [PATCH 5/6] Minor change in clip::wic::decode() to avoid a std::vector --- clip_win_wic.cpp | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/clip_win_wic.cpp b/clip_win_wic.cpp index 8fbe994..f7d9c71 100644 --- a/clip_win_wic.cpp +++ b/clip_win_wic.cpp @@ -237,32 +237,28 @@ image_spec spec_from_pixelformat(const WICPixelFormatGUID& pixelFormat, unsigned return spec; } -// Tries to decode the input buf of size len using the first decoder available from the -// ones specified in decoderCLSIDs vector. If output_image is not null, the decoded -// image is returned there, if output_spec is not null then the image specifications -// are set there. -bool decode(std::vector decoderCLSIDs, +// Tries to decode the input buf of size len using the specified +// decoders. If output_image is not null, the decoded image is +// returned there, if output_spec is not null then the image +// specifications are set there. +bool decode(const GUID decoder_clsid1, + const GUID decoder_clsid2, const uint8_t* buf, const UINT len, image* output_image, image_spec* output_spec) { - if (decoderCLSIDs.empty()) - return false; - coinit com; comptr decoder; - - HRESULT hr = E_FAIL; - for (GUID decoderCLSID : decoderCLSIDs) { - hr = CoCreateInstance(decoderCLSID, nullptr, + HRESULT hr = CoCreateInstance(decoder_clsid1, nullptr, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&decoder)); + if (FAILED(hr) && decoder_clsid2 != GUID_NULL) { + hr = CoCreateInstance(decoder_clsid2, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&decoder)); - if (SUCCEEDED(hr)) - break; } - if (FAILED(hr)) return false; @@ -272,7 +268,6 @@ bool decode(std::vector decoderCLSIDs, return false; comptr stream(create_stream(buf, len)); - if (!stream) return false; @@ -389,7 +384,8 @@ bool read_png(const uint8_t* buf, const UINT len, image* output_image, image_spec* output_spec) { - return decode({CLSID_WICPngDecoder2, CLSID_WICPngDecoder1}, buf, len, output_image, output_spec); + return decode(CLSID_WICPngDecoder2, CLSID_WICPngDecoder1, + buf, len, output_image, output_spec); } ////////////////////////////////////////////////////////////////////// @@ -400,7 +396,8 @@ bool read_jpg(const uint8_t* buf, image* output_image, image_spec* output_spec) { - return decode({CLSID_WICJpegDecoder}, buf, len, output_image, output_spec); + return decode(CLSID_WICJpegDecoder, GUID_NULL, + buf, len, output_image, output_spec); } ////////////////////////////////////////////////////////////////////// @@ -411,7 +408,8 @@ bool read_gif(const uint8_t* buf, image* output_image, image_spec* output_spec) { - return decode({CLSID_WICGifDecoder}, buf, len, output_image, output_spec); + return decode(CLSID_WICGifDecoder, GUID_NULL, + buf, len, output_image, output_spec); } ////////////////////////////////////////////////////////////////////// @@ -422,7 +420,8 @@ bool read_bmp(const uint8_t* buf, image* output_image, image_spec* output_spec) { - return decode({CLSID_WICBmpDecoder}, buf, len, output_image, output_spec); + return decode(CLSID_WICBmpDecoder, GUID_NULL, + buf, len, output_image, output_spec); } } // namespace win From fa9360479af2de617c20b431f33c8ffc18bbdc15 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 6 May 2024 10:28:28 -0300 Subject: [PATCH 6/6] Convert WIC image formats info w/arrays instead of std::vector+ctors This reduces 30KB the resulting .exe binary code in release mode. --- clip_win.cpp | 135 +++++++++-------------------------------------- clip_win_wic.cpp | 44 +++++++++++++++ clip_win_wic.h | 7 +++ 3 files changed, 76 insertions(+), 110 deletions(-) diff --git a/clip_win.cpp b/clip_win.cpp index 20bd303..a8d847b 100644 --- a/clip_win.cpp +++ b/clip_win.cpp @@ -12,9 +12,6 @@ #include #include #include -#if CLIP_ENABLE_IMAGE -#include -#endif #include #include @@ -56,7 +53,6 @@ class Hglobal { HGLOBAL m_handle; }; - // From: https://issues.chromium.org/issues/40080988#comment8 // // "Adds impersonation of the anonymous token around calls to the @@ -82,84 +78,7 @@ class AnonymousTokenImpersonator { const bool m_must_revert; }; -#if CLIP_ENABLE_IMAGE -class ImageFormat { - using ReadDataFunc = std::function; - -public: - static bool anyAvailable() { - for (auto fmt : m_formats) { - if (fmt->isAvailable()) { - return true; - } - } - return false; - } - - // Returns all the formats that were succesfuly registered - static const std::vector& formats() { - return m_formats; - } - - ImageFormat(const std::vector& names, - ReadDataFunc func) : m_readFunc(func) - { - for (auto& name : names) { - UINT cbformat = RegisterClipboardFormatA(name.c_str()); - if (cbformat) { - m_names.push_back(name); - m_ids.push_back(cbformat); - } - } - // Add this format only if we were able to get a valid clipboard format id for it. - if (!m_ids.empty()) - m_formats.push_back(this); - } - - // Checks if this format is currently available on the clipboard, in which case - // returns true and sets *out_format (if it is not null) to the id of the - // available clipboard format. Returns false otherwise. - bool isAvailable(UINT* out_format = nullptr) - { - for (auto id : m_ids) { - if (IsClipboardFormatAvailable(id)) { - if (out_format) - *out_format = id; - return true; - } - } - return false; - } - - bool read(const uint8_t* buf, - const UINT len, - image* output_image, - image_spec* output_spec) - { - return m_readFunc(buf, len, output_image, output_spec); - } - -private: - static std::vector m_formats; // Holds all the supported formats. - - std::vector m_names; // Alternative names of this format - std::vector m_ids; // Clipboard format ID for each name of this format - ReadDataFunc m_readFunc; // Function used to decode data in this format -}; - -std::vector ImageFormat::m_formats; - -ImageFormat Png = { { "PNG", "image/png" } , win::read_png }; -ImageFormat Jpg = { { "JPG", "image/jpeg", "JPEG" }, win::read_jpg }; -ImageFormat Bmp = { { "BMP", "image/bmp" } , win::read_bmp }; -ImageFormat Gif = { { "GIF", "image/gif" } , win::read_gif }; - -#endif - -} +} // anonymous namespace lock::impl::impl(void* hwnd) : m_locked(false) { for (int i=0; i<5; ++i) { @@ -198,7 +117,7 @@ bool lock::impl::is_convertible(format f) const { #if CLIP_ENABLE_IMAGE else if (f == image_format()) { return (IsClipboardFormatAvailable(CF_DIB) || - ImageFormat::anyAvailable()); + win::wic_image_format_available(nullptr) != nullptr); } #endif // CLIP_ENABLE_IMAGE else @@ -390,41 +309,37 @@ bool lock::impl::set_image(const image& image) { } bool lock::impl::get_image(image& output_img) const { - // Tries to get the first "custom" ("PNG", "JPG", "GIF", etc) clipboard - // format. - for (auto fmt : ImageFormat::formats()) { - UINT cbformat; - if (fmt->isAvailable(&cbformat)) { - HANDLE handle = GetClipboardData(cbformat); - if (handle) { - size_t size = GlobalSize(handle); - uint8_t* data = (uint8_t*)GlobalLock(handle); - bool result = fmt->read(data, size, &output_img, nullptr); - GlobalUnlock(handle); - if (result) - return true; - } + // Tries to get the first image format that can be read using WIC + // ("PNG", "JPG", "GIF", etc). + UINT cbformat; + if (auto read_img = win::wic_image_format_available(&cbformat)) { + HANDLE handle = GetClipboardData(cbformat); + if (handle) { + size_t size = GlobalSize(handle); + uint8_t* data = (uint8_t*)GlobalLock(handle); + bool result = read_img(data, size, &output_img, nullptr); + GlobalUnlock(handle); + if (result) + return true; } } - // If we couldn't find any "custom" format, we try to use the regular DIB format) + // If we couldn't find any, we try to use the regular DIB format. win::BitmapInfo bi; return bi.to_image(output_img); } bool lock::impl::get_image_spec(image_spec& spec) const { - for (auto fmt : ImageFormat::formats()) { - UINT cbformat; - if (fmt->isAvailable(&cbformat)) { - HANDLE handle = GetClipboardData(cbformat); - if (handle) { - size_t size = GlobalSize(handle); - uint8_t* data = (uint8_t*)GlobalLock(handle); - bool result = fmt->read(data, size, nullptr, &spec); - GlobalUnlock(handle); - if (result) - return true; - } + UINT cbformat; + if (auto read_img = win::wic_image_format_available(&cbformat)) { + HANDLE handle = GetClipboardData(cbformat); + if (handle) { + size_t size = GlobalSize(handle); + uint8_t* data = (uint8_t*)GlobalLock(handle); + bool result = read_img(data, size, nullptr, &spec); + GlobalUnlock(handle); + if (result) + return true; } } diff --git a/clip_win_wic.cpp b/clip_win_wic.cpp index f7d9c71..dd37158 100644 --- a/clip_win_wic.cpp +++ b/clip_win_wic.cpp @@ -17,6 +17,8 @@ namespace clip { namespace win { +namespace { + // Successful calls to CoInitialize() (S_OK or S_FALSE) must match // the calls to CoUninitialize(). // From: https://docs.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-couninitialize#remarks @@ -73,6 +75,48 @@ class hmodule { }; #endif +struct WicImageFormat { + const char* names[3]; // Alternative names of this format + UINT ids[3]; // Clipboard format ID for each name of this format + ReadWicImageFormatFunc read; // Function used to decode data in this format +}; + +WicImageFormat wic_image_formats[] = { + { { "PNG", "image/png", nullptr }, { 0, 0, 0 }, read_png }, + { { "JPG", "image/jpeg", "JPEG" }, { 0, 0, 0 }, read_jpg }, + { { "BMP", "image/bmp", nullptr }, { 0, 0, 0 }, read_bmp }, + { { "GIF", "image/gif", nullptr }, { 0, 0, 0 }, read_gif } +}; + +} // anonymous namespace + +ReadWicImageFormatFunc wic_image_format_available(UINT* output_cbformat) { + for (auto& fmt : wic_image_formats) { + for (int i=0; i<3; ++i) { + const char* name = fmt.names[i]; + if (!name) + break; + + // Although RegisterClipboardFormatA() already returns the same + // value for the same "name" (even for different apps), we + // prefer to cache the value to avoid calling + // RegisterClipboardFormatA() several times (as internally that + // function must do some kind of hash map name -> ID + // conversion). + UINT cbformat = fmt.ids[i]; + if (cbformat == 0) + fmt.ids[i] = cbformat = RegisterClipboardFormatA(name); + + if (cbformat && IsClipboardFormatAvailable(cbformat)) { + if (output_cbformat) + *output_cbformat = cbformat; + return fmt.read; + } + } + } + return nullptr; +} + ////////////////////////////////////////////////////////////////////// // Encode the image as PNG format diff --git a/clip_win_wic.h b/clip_win_wic.h index 2a99add..dfef88c 100644 --- a/clip_win_wic.h +++ b/clip_win_wic.h @@ -23,6 +23,13 @@ struct image_spec; namespace win { +typedef bool (*ReadWicImageFormatFunc)(const uint8_t*, + const UINT, + clip::image*, + clip::image_spec*); + +ReadWicImageFormatFunc wic_image_format_available(UINT* output_cbformat); + ////////////////////////////////////////////////////////////////////// // Encode the image as PNG format