// Clip Library // Copyright (c) 2020 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. #pragma once #include "clip.h" #include #include #include #include namespace clip { namespace win { // 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() : m_ptr(nullptr) { } 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; }; ////////////////////////////////////////////////////////////////////// // Encode the image as PNG format bool write_png_on_stream(const image& img, IStream* stream) { const image_spec& spec = img.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*)img.data(); int bytes_per_row = spec.bytes_per_row; image converted; // Convert to GUID_WICPixelFormat32bppBGRA if needed if (!img.is_bgra8888()) { converted = img.to_bgra8888(); ptr = (uint8_t*)img.data(); 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; } ////////////////////////////////////////////////////////////////////// // 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; comptr stream(SHCreateMemStream(buf, len)); if (!stream) return false; 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, IID_PPV_ARGS(&decoder)); if (FAILED(hr)) 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 this pixel format // TODO add support for more pixel formats if (pixelFormat != GUID_WICPixelFormat32bppBGRA) return false; UINT width = 0, height = 0; hr = frame->GetSize(&width, &height); 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; if (output_spec) *output_spec = spec; 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; } std::swap(*output_image, img); } return true; } } // namespace win } // namespace clip