// Clip Library // Copyright (c) 2020-2024 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. #include "clip_win_wic.h" #include "clip.h" #include #include #include #include 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 struct coinit { HRESULT hr; coinit() { hr = CoInitialize(nullptr); } ~coinit() { if (hr == S_OK || hr == S_FALSE) CoUninitialize(); } }; template class comptr { public: comptr() { } explicit comptr(T* ptr) : m_ptr(ptr) { } comptr(const comptr&) = delete; comptr& operator=(const comptr&) = delete; ~comptr() { reset(); } T** operator&() { return &m_ptr; } T* operator->() { return m_ptr; } bool operator!() const { return !m_ptr; } T* get() { return m_ptr; } void reset() { if (m_ptr) { m_ptr->Release(); m_ptr = nullptr; } } private: T* m_ptr = nullptr; }; #ifdef CLIP_SUPPORT_WINXP class hmodule { public: hmodule(LPCWSTR name) : m_ptr(LoadLibraryW(name)) { } hmodule(const hmodule&) = delete; hmodule& operator=(const hmodule&) = delete; ~hmodule() { if (m_ptr) FreeLibrary(m_ptr); } operator HMODULE() { return m_ptr; } bool operator!() const { return !m_ptr; } private: HMODULE m_ptr = nullptr; }; #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 bool write_png_on_stream(const image& image, IStream* stream) { const image_spec& spec = image.spec(); comptr encoder; HRESULT hr = CoCreateInstance(CLSID_WICPngEncoder, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&encoder)); if (FAILED(hr)) return false; hr = encoder->Initialize(stream, WICBitmapEncoderNoCache); if (FAILED(hr)) return false; comptr frame; comptr options; hr = encoder->CreateNewFrame(&frame, &options); if (FAILED(hr)) return false; hr = frame->Initialize(options.get()); if (FAILED(hr)) return false; // PNG encoder (and decoder) only supports GUID_WICPixelFormat32bppBGRA for 32bpp. // See: https://docs.microsoft.com/en-us/windows/win32/wic/-wic-codec-native-pixel-formats#png-native-codec WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppBGRA; hr = frame->SetPixelFormat(&pixelFormat); if (FAILED(hr)) return false; hr = frame->SetSize(spec.width, spec.height); if (FAILED(hr)) return false; std::vector buf; uint8_t* ptr = (uint8_t*)image.data(); int bytes_per_row = spec.bytes_per_row; // Convert to GUID_WICPixelFormat32bppBGRA if needed if (spec.red_mask != 0xff0000 || spec.green_mask != 0xff00 || spec.blue_mask != 0xff || spec.alpha_mask != 0xff000000) { buf.resize(spec.width * spec.height); uint32_t* dst = (uint32_t*)&buf[0]; uint32_t* src = (uint32_t*)image.data(); for (int y=0; y> spec.red_shift ) << 16) | (((c & spec.green_mask) >> spec.green_shift) << 8) | (((c & spec.blue_mask ) >> spec.blue_shift ) ) | (((c & spec.alpha_mask) >> spec.alpha_shift) << 24)); ++dst; ++src; } src = (uint32_t*)(((uint8_t*)src_line_start) + spec.bytes_per_row); } ptr = (uint8_t*)&buf[0]; bytes_per_row = 4 * spec.width; } hr = frame->WritePixels(spec.height, bytes_per_row, bytes_per_row * spec.height, (BYTE*)ptr); if (FAILED(hr)) return false; hr = frame->Commit(); if (FAILED(hr)) return false; hr = encoder->Commit(); if (FAILED(hr)) return false; return true; } HGLOBAL write_png(const image& image) { coinit com; comptr stream; HRESULT hr = CreateStreamOnHGlobal(nullptr, false, &stream); if (FAILED(hr)) return nullptr; bool result = write_png_on_stream(image, stream.get()); HGLOBAL handle; hr = GetHGlobalFromStream(stream.get(), &handle); if (result) return handle; GlobalFree(handle); return nullptr; } 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); hmodule shlwapiDll(L"shlwapi.dll"); if (!shlwapiDll) return false; auto SHCreateMemStream = reinterpret_cast( GetProcAddress(shlwapiDll, (LPCSTR)12)); if (!SHCreateMemStream) return false; #endif return SHCreateMemStream(pInit, cbInit); } 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; } // 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(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 (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; comptr frame; hr = decoder->GetFrame(0, &frame); if (FAILED(hr)) return false; WICPixelFormatGUID pixelFormat; hr = frame->GetPixelFormat(&pixelFormat); if (FAILED(hr)) return false; // Only support these pixel formats // TODO add support for more pixel formats if (pixelFormat != GUID_WICPixelFormat32bppBGRA && pixelFormat != GUID_WICPixelFormat32bppBGR && pixelFormat != GUID_WICPixelFormat24bppBGR && pixelFormat != GUID_WICPixelFormat8bppIndexed) return false; UINT width = 0, height = 0; hr = frame->GetSize(&width, &height); if (FAILED(hr)) return false; image_spec spec = spec_from_pixelformat(pixelFormat, width, height); if (output_spec) *output_spec = spec; image img; if (output_image) { 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); } 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