diff --git a/clip_win.cpp b/clip_win.cpp index c44d3f6..058ea43 100644 --- a/clip_win.cpp +++ b/clip_win.cpp @@ -53,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 @@ -79,7 +78,7 @@ class AnonymousTokenImpersonator { const bool m_must_revert; }; -} +} // anonymous namespace lock::impl::impl(void* hwnd) : m_locked(false) { for (int i=0; i<5; ++i) { @@ -117,13 +116,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) || + win::wic_image_format_available(nullptr) != nullptr); } #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) { @@ -351,35 +349,35 @@ 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); + // 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, 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); + 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 33e0cbb..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 @@ -178,53 +222,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; + // 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) { + 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) - return false; +// 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) +{ + coinit com; comptr decoder; - HRESULT hr = CoCreateInstance(CLSID_WICPngDecoder2, - nullptr, CLSCTX_INPROC_SERVER, + HRESULT hr = CoCreateInstance(decoder_clsid1, nullptr, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&decoder)); - if (FAILED(hr)) { - hr = CoCreateInstance(CLSID_WICPngDecoder1, - nullptr, CLSCTX_INPROC_SERVER, + if (FAILED(hr) && decoder_clsid2 != GUID_NULL) { + hr = CoCreateInstance(decoder_clsid2, nullptr, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&decoder)); - if (FAILED(hr)) - return false; } + 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 +329,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 +342,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) = (*src < numcolors ? colors[*src] : 0); + } + 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 +421,52 @@ 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, GUID_NULL, + 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, GUID_NULL, + 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, GUID_NULL, + buf, len, output_image, output_spec); +} + } // namespace win } // namespace clip diff --git a/clip_win_wic.h b/clip_win_wic.h index fd0905a..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 @@ -38,6 +45,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