From f7542269f569f17ecc983ebe59c9ab9b29921a16 Mon Sep 17 00:00:00 2001 From: Samuel Gomes Date: Wed, 25 Sep 2024 17:44:58 +0530 Subject: [PATCH] Fix #541 and update clip library --- Makefile | 2 +- internal/c/parts/os/clipboard/build.mk | 5 +- internal/c/parts/os/clipboard/clip/clip.cpp | 10 +- internal/c/parts/os/clipboard/clip/clip.h | 21 + .../c/parts/os/clipboard/clip/clip_common.h | 2 + .../parts/os/clipboard/clip/clip_lock_impl.h | 9 +- internal/c/parts/os/clipboard/clip/clip_osx.h | 35 ++ .../c/parts/os/clipboard/clip/clip_osx.mm | 16 +- .../c/parts/os/clipboard/clip/clip_win.cpp | 440 ++++------------ internal/c/parts/os/clipboard/clip/clip_win.h | 26 + .../parts/os/clipboard/clip/clip_win_bmp.cpp | 347 +++++++++++++ .../c/parts/os/clipboard/clip/clip_win_bmp.h | 66 +++ .../parts/os/clipboard/clip/clip_win_wic.cpp | 472 ++++++++++++++++++ .../c/parts/os/clipboard/clip/clip_win_wic.h | 292 ++--------- internal/c/parts/os/clipboard/clipboard.cpp | 69 +-- .../clipboard/clipboard_test.bas | 35 +- .../clipboard/clipboard_test.output | 6 +- 17 files changed, 1201 insertions(+), 652 deletions(-) create mode 100644 internal/c/parts/os/clipboard/clip/clip_osx.h create mode 100644 internal/c/parts/os/clipboard/clip/clip_win.h create mode 100644 internal/c/parts/os/clipboard/clip/clip_win_bmp.cpp create mode 100644 internal/c/parts/os/clipboard/clip/clip_win_bmp.h create mode 100644 internal/c/parts/os/clipboard/clip/clip_win_wic.cpp diff --git a/Makefile b/Makefile index 050b4a6d2..19a579138 100644 --- a/Makefile +++ b/Makefile @@ -150,7 +150,7 @@ ifeq ($(OS),lnx) endif ifeq ($(OS),win) - CXXLIBS += -static-libgcc -static-libstdc++ -lcomdlg32 -lole32 -lshlwapi -lwindowscodecs + CXXLIBS += -static-libgcc -static-libstdc++ -lcomdlg32 -lole32 -luuid -lshlwapi -lwindowscodecs CXXFLAGS += -DGLEW_STATIC -DFREEGLUT_STATIC endif diff --git a/internal/c/parts/os/clipboard/build.mk b/internal/c/parts/os/clipboard/build.mk index f65d6eaec..26d5502f8 100644 --- a/internal/c/parts/os/clipboard/build.mk +++ b/internal/c/parts/os/clipboard/build.mk @@ -17,7 +17,10 @@ ifeq ($(OS),lnx) endif ifeq ($(OS),win) - CLIP_SRCS += clip_win.cpp + CLIP_SRCS += \ + clip_win.cpp \ + clip_win_bmp.cpp \ + clip_win_wic.cpp endif ifeq ($(OS),osx) diff --git a/internal/c/parts/os/clipboard/clip/clip.cpp b/internal/c/parts/os/clipboard/clip/clip.cpp index 2058dbec9..16ead849e 100644 --- a/internal/c/parts/os/clipboard/clip/clip.cpp +++ b/internal/c/parts/os/clipboard/clip/clip.cpp @@ -1,5 +1,5 @@ // Clip Library -// Copyright (c) 2015-2018 David Capello +// Copyright (c) 2015-2024 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -72,6 +72,14 @@ bool lock::get_image_spec(image_spec& spec) const { #endif // CLIP_ENABLE_IMAGE +#if CLIP_ENABLE_LIST_FORMATS + +std::vector lock::list_formats() const { + return p->list_formats(); +} + +#endif // CLIP_ENABLE_LIST_FORMATS + format empty_format() { return 0; } format text_format() { return 1; } #if CLIP_ENABLE_IMAGE diff --git a/internal/c/parts/os/clipboard/clip/clip.h b/internal/c/parts/os/clipboard/clip/clip.h index 100ab5f82..a6f0913a3 100644 --- a/internal/c/parts/os/clipboard/clip/clip.h +++ b/internal/c/parts/os/clipboard/clip/clip.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace clip { @@ -21,8 +22,22 @@ namespace clip { // Clipboard format identifier. typedef size_t format; +#if CLIP_ENABLE_IMAGE class image; struct image_spec; +#endif // CLIP_ENABLE_IMAGE + +#if CLIP_ENABLE_LIST_FORMATS + struct format_info { + format id = 0; + std::string name; + format_info(const format id, + const std::string& name) + : id(id), + name(name) { + } + }; +#endif // CLIP_ENABLE_LIST_FORMATS class lock { public: @@ -58,6 +73,12 @@ namespace clip { bool get_image_spec(image_spec& spec) const; #endif // CLIP_ENABLE_IMAGE +#if CLIP_ENABLE_LIST_FORMATS + // Returns the list of available formats (by name) in the + // clipboard. + std::vector list_formats() const; +#endif // CLIP_ENABLE_LIST_FORMATS + private: class impl; std::unique_ptr p; diff --git a/internal/c/parts/os/clipboard/clip/clip_common.h b/internal/c/parts/os/clipboard/clip/clip_common.h index 9446bd39a..61fa99259 100644 --- a/internal/c/parts/os/clipboard/clip/clip_common.h +++ b/internal/c/parts/os/clipboard/clip/clip_common.h @@ -8,6 +8,8 @@ #define CLIP_COMMON_H_INCLUDED #pragma once +#include "clip.h" + namespace clip { namespace details { diff --git a/internal/c/parts/os/clipboard/clip/clip_lock_impl.h b/internal/c/parts/os/clipboard/clip/clip_lock_impl.h index 3f08af7f5..0c678e5b8 100644 --- a/internal/c/parts/os/clipboard/clip/clip_lock_impl.h +++ b/internal/c/parts/os/clipboard/clip/clip_lock_impl.h @@ -1,5 +1,5 @@ // Clip Library -// Copyright (c) 2015-2018 David Capello +// Copyright (c) 2015-2024 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -20,9 +20,16 @@ class lock::impl { bool set_data(format f, const char* buf, size_t len); bool get_data(format f, char* buf, size_t len) const; size_t get_data_length(format f) const; + +#if CLIP_ENABLE_IMAGE bool set_image(const image& image); bool get_image(image& image) const; bool get_image_spec(image_spec& spec) const; +#endif // CLIP_ENABLE_IMAGE + +#if CLIP_ENABLE_LIST_FORMATS + std::vector list_formats() const; +#endif // CLIP_ENABLE_LIST_FORMATS private: bool m_locked; diff --git a/internal/c/parts/os/clipboard/clip/clip_osx.h b/internal/c/parts/os/clipboard/clip/clip_osx.h new file mode 100644 index 000000000..73469768c --- /dev/null +++ b/internal/c/parts/os/clipboard/clip/clip_osx.h @@ -0,0 +1,35 @@ +// Clip Library +// Copyright (c) 2024 David Capello +// +// This file is released under the terms of the MIT license. +// Read LICENSE.txt for more information. + +#ifndef CLIP_OSX_H_INCLUDED +#define CLIP_OSX_H_INCLUDED +#pragma once + +#ifdef __OBJC__ + +#include + +namespace clip { + +class image; +struct image_spec; + +namespace osx { + +#if CLIP_ENABLE_IMAGE + +bool get_image_from_clipboard(NSPasteboard* pasteboard, + image* output_img, + image_spec* output_spec); + +#endif // CLIP_ENABLE_IMAGE + +} // namespace osx +} // namespace clip + +#endif + +#endif diff --git a/internal/c/parts/os/clipboard/clip/clip_osx.mm b/internal/c/parts/os/clipboard/clip/clip_osx.mm index 390ef88b4..a86214501 100644 --- a/internal/c/parts/os/clipboard/clip/clip_osx.mm +++ b/internal/c/parts/os/clipboard/clip/clip_osx.mm @@ -24,12 +24,16 @@ std::map g_name_to_format; std::map g_format_to_name; +} + +namespace osx { + #if CLIP_ENABLE_IMAGE - bool get_image_from_clipboard(image* output_img, + bool get_image_from_clipboard(NSPasteboard* pasteboard, + image* output_img, image_spec* output_spec) { - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; NSString* result = [pasteboard availableTypeFromArray: [NSArray arrayWithObjects:NSPasteboardTypeTIFF,NSPasteboardTypePNG,nil]]; @@ -137,7 +141,7 @@ bool get_image_from_clipboard(image* output_img, #endif // CLIP_ENABLE_IMAGE -} +} // namespace osx lock::impl::impl(void*) : m_locked(true) { } @@ -347,11 +351,13 @@ bool get_image_from_clipboard(image* output_img, } bool lock::impl::get_image(image& img) const { - return get_image_from_clipboard(&img, nullptr); + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + return osx::get_image_from_clipboard(pasteboard, &img, nullptr); } bool lock::impl::get_image_spec(image_spec& spec) const { - return get_image_from_clipboard(nullptr, &spec); + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + return osx::get_image_from_clipboard(pasteboard, nullptr, &spec); } #endif // CLIP_ENABLE_IMAGE diff --git a/internal/c/parts/os/clipboard/clip/clip_win.cpp b/internal/c/parts/os/clipboard/clip/clip_win.cpp index 661f76004..058ea43fe 100644 --- a/internal/c/parts/os/clipboard/clip/clip_win.cpp +++ b/internal/c/parts/os/clipboard/clip/clip_win.cpp @@ -1,11 +1,12 @@ // Clip Library -// Copyright (C) 2015-2020 David Capello +// Copyright (C) 2015-2024 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. +#include "clip_win.h" + #include "clip.h" -#include "clip_common.h" #include "clip_lock_impl.h" #include @@ -14,20 +15,6 @@ #include #include -#include - -#if CLIP_ENABLE_IMAGE -#include "clip_win_wic.h" -#endif // CLIP_ENABLE_IMAGE - -#ifndef LCS_WINDOWS_COLOR_SPACE -#define LCS_WINDOWS_COLOR_SPACE 'Win ' -#endif - -#ifndef CF_DIBV5 -#define CF_DIBV5 17 -#endif - namespace clip { namespace { @@ -38,14 +25,6 @@ namespace { // value. typedef uint64_t CustomSizeT; -unsigned long get_shift_from_mask(unsigned long mask) { - unsigned long shift = 0; - for (shift=0; shiftbV5BitCount == 32 && - ((b5->bV5Compression == BI_RGB) || - (b5->bV5Compression == BI_BITFIELDS && - b5->bV5RedMask && b5->bV5GreenMask && - b5->bV5BlueMask && b5->bV5AlphaMask))) { - width = b5->bV5Width; - height = b5->bV5Height; - bit_count = b5->bV5BitCount; - compression = b5->bV5Compression; - if (compression == BI_BITFIELDS) { - red_mask = b5->bV5RedMask; - green_mask = b5->bV5GreenMask; - blue_mask = b5->bV5BlueMask; - alpha_mask = b5->bV5AlphaMask; - } - else { - red_mask = 0xff0000; - green_mask = 0xff00; - blue_mask = 0xff; - alpha_mask = 0xff000000; - } - return; - } - } - - if (IsClipboardFormatAvailable(CF_DIB)) - bi = (BITMAPINFO*)GetClipboardData(CF_DIB); - if (!bi) - return; - - width = bi->bmiHeader.biWidth; - height = bi->bmiHeader.biHeight; - bit_count = bi->bmiHeader.biBitCount; - compression = bi->bmiHeader.biCompression; - - if (compression == BI_BITFIELDS) { - red_mask = *((uint32_t*)&bi->bmiColors[0]); - green_mask = *((uint32_t*)&bi->bmiColors[1]); - blue_mask = *((uint32_t*)&bi->bmiColors[2]); - if (bit_count == 32) - alpha_mask = 0xff000000; - } - else if (compression == BI_RGB) { - switch (bit_count) { - case 32: - red_mask = 0xff0000; - green_mask = 0xff00; - blue_mask = 0xff; - alpha_mask = 0xff000000; - break; - case 24: - case 8: // We return 8bpp images as 24bpp - red_mask = 0xff0000; - green_mask = 0xff00; - blue_mask = 0xff; - break; - case 16: - red_mask = 0x7c00; - green_mask = 0x03e0; - blue_mask = 0x001f; - break; - } - } - } - - bool is_valid() const { - return (b5 || bi); - } - - void fill_spec(image_spec& spec) { - spec.width = width; - spec.height = (height >= 0 ? height: -height); - // We convert indexed to 24bpp RGB images to match the OS X behavior - spec.bits_per_pixel = bit_count; - if (spec.bits_per_pixel <= 8) - spec.bits_per_pixel = 24; - spec.bytes_per_row = width*((spec.bits_per_pixel+7)/8); - spec.red_mask = red_mask; - spec.green_mask = green_mask; - spec.blue_mask = blue_mask; - spec.alpha_mask = alpha_mask; - - switch (spec.bits_per_pixel) { - - case 24: { - // We need one extra byte to avoid a crash updating the last - // pixel on last row using: - // - // *((uint32_t*)ptr) = pixel24bpp; - // - ++spec.bytes_per_row; - - // Align each row to 32bpp - int padding = (4-(spec.bytes_per_row&3))&3; - spec.bytes_per_row += padding; - break; - } - - case 16: { - int padding = (4-(spec.bytes_per_row&3))&3; - spec.bytes_per_row += padding; - break; - } - } +// From: https://issues.chromium.org/issues/40080988#comment8 +// +// "Adds impersonation of the anonymous token around calls to the +// CloseClipboard() system call. On Windows 8+ the win32k driver +// captures the access token of the caller and makes it available to +// other users on the desktop through the system call +// GetClipboardAccessToken(). This introduces a risk of privilege +// escalation in sandboxed processes. By performing the +// impersonation then whenever Chrome writes data to the clipboard +// only the anonymous token is available." +// +class AnonymousTokenImpersonator { +public: + AnonymousTokenImpersonator() + : m_must_revert(ImpersonateAnonymousToken(GetCurrentThread())) + {} - unsigned long* masks = &spec.red_mask; - unsigned long* shifts = &spec.red_shift; - for (unsigned long* shift=shifts, *mask=masks; shift lock::impl::list_formats() const { + static const char* standard_formats[CF_MAX] = { + "", "CF_TEXT", "CF_BITMAP", "CF_METAFILEPICT", + "CF_SYLK", "CF_DIF", "CF_TIFF", "CF_OEMTEXT", + "CF_DIB", "CF_PALETTE", "CF_PENDATA", "CF_RIFF", + "CF_WAVE", "CF_UNICODETEXT", "CF_ENHMETAFILE", "CF_HDROP", + "CF_LOCALE", "CF_DIBV5" + }; + + std::vector formats; + std::vector format_name(512); + + formats.reserve(CountClipboardFormats()); + + UINT format_id = EnumClipboardFormats(0); + while (format_id != 0) { + if (format_id >= CF_TEXT && format_id < CF_MAX) { + // Standard clipboard format + formats.emplace_back(format_id, standard_formats[format_id]); + } + // Get user-defined format name + else { + int size = GetClipboardFormatNameA( + format_id, + format_name.data(), + format_name.size()); + + formats.emplace_back(format_id, std::string(format_name.data(), size)); + } + + format_id = EnumClipboardFormats(format_id); + } + + return formats; +} + +#endif // CLIP_ENABLE_LIST_FORMATS + #if CLIP_ENABLE_IMAGE bool lock::impl::set_image(const image& image) { @@ -432,210 +340,50 @@ bool lock::impl::set_image(const image& image) { } } - image_spec out_spec = spec; - - int palette_colors = 0; - int padding = 0; - switch (spec.bits_per_pixel) { - case 24: padding = (4-((spec.width*3)&3))&3; break; - case 16: padding = ((4-((spec.width*2)&3))&3)/2; break; - case 8: padding = (4-(spec.width&3))&3; break; - } - out_spec.bytes_per_row += padding; - - // Create the BITMAPV5HEADER structure - Hglobal hmem( - GlobalAlloc( - GHND, - sizeof(BITMAPV5HEADER) - + palette_colors*sizeof(RGBQUAD) - + out_spec.bytes_per_row*out_spec.height)); + Hglobal hmem(clip::win::create_dibv5(image)); if (!hmem) return false; - out_spec.red_mask = 0x00ff0000; - out_spec.green_mask = 0xff00; - out_spec.blue_mask = 0xff; - out_spec.alpha_mask = 0xff000000; - out_spec.red_shift = 16; - out_spec.green_shift = 8; - out_spec.blue_shift = 0; - out_spec.alpha_shift = 24; - - BITMAPV5HEADER* bi = (BITMAPV5HEADER*)GlobalLock(hmem); - bi->bV5Size = sizeof(BITMAPV5HEADER); - bi->bV5Width = out_spec.width; - bi->bV5Height = out_spec.height; - bi->bV5Planes = 1; - bi->bV5BitCount = (WORD)out_spec.bits_per_pixel; - bi->bV5Compression = BI_RGB; - bi->bV5SizeImage = out_spec.bytes_per_row*spec.height; - bi->bV5RedMask = out_spec.red_mask; - bi->bV5GreenMask = out_spec.green_mask; - bi->bV5BlueMask = out_spec.blue_mask; - bi->bV5AlphaMask = out_spec.alpha_mask; - bi->bV5CSType = LCS_WINDOWS_COLOR_SPACE; - bi->bV5Intent = LCS_GM_GRAPHICS; - bi->bV5ClrUsed = 0; - - switch (spec.bits_per_pixel) { - case 32: { - const char* src = image.data(); - char* dst = (((char*)bi)+bi->bV5Size) + (out_spec.height-1)*out_spec.bytes_per_row; - for (long y=spec.height-1; y>=0; --y) { - const uint32_t* src_x = (const uint32_t*)src; - uint32_t* dst_x = (uint32_t*)dst; - - for (unsigned long x=0; x> spec.red_shift ); - int g = ((c & spec.green_mask) >> spec.green_shift); - int b = ((c & spec.blue_mask ) >> spec.blue_shift ); - int a = ((c & spec.alpha_mask) >> spec.alpha_shift); - - // Windows requires premultiplied RGBA values - r = r * a / 255; - g = g * a / 255; - b = b * a / 255; - - *dst_x = - (r << out_spec.red_shift ) | - (g << out_spec.green_shift) | - (b << out_spec.blue_shift ) | - (a << out_spec.alpha_shift); - } - - src += spec.bytes_per_row; - dst -= out_spec.bytes_per_row; - } - break; - } - default: - error_handler e = get_error_handler(); - if (e) - e(ErrorCode::ImageNotSupported); - return false; - } - - GlobalUnlock(hmem); SetClipboardData(CF_DIBV5, hmem); return true; } 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; } } - BitmapInfo bi; - if (!bi.is_valid()) { - // There is no image at all in the clipboard, no need to report - // this as an error, just return false. - return false; - } - - image_spec spec; - bi.fill_spec(spec); - image img(spec); - - switch (bi.bit_count) { - - case 32: - case 24: - case 16: { - const uint8_t* src = nullptr; - - if (bi.compression == BI_RGB || - bi.compression == BI_BITFIELDS) { - if (bi.b5) - src = ((uint8_t*)bi.b5) + bi.b5->bV5Size; - else - src = ((uint8_t*)bi.bi) + bi.bi->bmiHeader.biSize; - if (bi.compression == BI_BITFIELDS) - src += sizeof(RGBQUAD)*3; - } - - if (src) { - const int src_bytes_per_row = spec.width*((bi.bit_count+7)/8); - const int padding = (4-(src_bytes_per_row&3))&3; - - for (long y=spec.height-1; y>=0; --y, src+=src_bytes_per_row+padding) { - char* dst = img.data()+y*spec.bytes_per_row; - std::copy(src, src+src_bytes_per_row, dst); - } - } - - // Windows uses premultiplied RGB values, and we use straight - // alpha. So we have to divide all RGB values by its alpha. - if (bi.bit_count == 32 && spec.alpha_mask) { - details::divide_rgb_by_alpha(img); - } - break; - } - - case 8: { - assert(bi.bi); - - const int colors = (bi.bi->bmiHeader.biClrUsed > 0 ? bi.bi->bmiHeader.biClrUsed: 256); - std::vector palette(colors); - for (int c=0; cbmiColors[c].rgbRed << spec.red_shift) | - (bi.bi->bmiColors[c].rgbGreen << spec.green_shift) | - (bi.bi->bmiColors[c].rgbBlue << spec.blue_shift); - } - - const uint8_t* src = (((uint8_t*)bi.bi) + bi.bi->bmiHeader.biSize + sizeof(RGBQUAD)*colors); - const int padding = (4-(spec.width&3))&3; - - for (long y=spec.height-1; y>=0; --y, src+=padding) { - char* dst = img.data()+y*spec.bytes_per_row; - - for (unsigned long x=0; x= colors) - idx = colors-1; - - *((uint32_t*)dst) = palette[idx]; - } - } - break; - } - } - - std::swap(output_img, img); - 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; } } - BitmapInfo bi; + win::BitmapInfo bi; if (!bi.is_valid()) return false; bi.fill_spec(spec); diff --git a/internal/c/parts/os/clipboard/clip/clip_win.h b/internal/c/parts/os/clipboard/clip/clip_win.h new file mode 100644 index 000000000..8efccd83a --- /dev/null +++ b/internal/c/parts/os/clipboard/clip/clip_win.h @@ -0,0 +1,26 @@ +// Clip Library +// Copyright (c) 2024 David Capello +// +// This file is released under the terms of the MIT license. +// Read LICENSE.txt for more information. + +#ifndef CLIP_WIN_H_INCLUDED +#define CLIP_WIN_H_INCLUDED +#pragma once + +#include + +#ifndef LCS_WINDOWS_COLOR_SPACE +#define LCS_WINDOWS_COLOR_SPACE 'Win ' +#endif + +#ifndef CF_DIBV5 +#define CF_DIBV5 17 +#endif + +#if CLIP_ENABLE_IMAGE + #include "clip_win_bmp.h" + #include "clip_win_wic.h" +#endif + +#endif // CLIP_WIN_H_INCLUDED diff --git a/internal/c/parts/os/clipboard/clip/clip_win_bmp.cpp b/internal/c/parts/os/clipboard/clip/clip_win_bmp.cpp new file mode 100644 index 000000000..ffb0d6f51 --- /dev/null +++ b/internal/c/parts/os/clipboard/clip/clip_win_bmp.cpp @@ -0,0 +1,347 @@ +// Clip Library +// Copyright (c) 2015-2024 David Capello +// +// This file is released under the terms of the MIT license. +// Read LICENSE.txt for more information. + +#include "clip_win_bmp.h" + +#include "clip.h" +#include "clip_common.h" + +#include + +namespace clip { +namespace win { + +namespace { + +unsigned long get_shift_from_mask(unsigned long mask) { + unsigned long shift = 0; + for (shift=0; shiftbV5BitCount == 32 && + ((b5->bV5Compression == BI_RGB) || + (b5->bV5Compression == BI_BITFIELDS && + b5->bV5RedMask && b5->bV5GreenMask && + b5->bV5BlueMask && b5->bV5AlphaMask))) { + width = b5->bV5Width; + height = b5->bV5Height; + bit_count = b5->bV5BitCount; + compression = b5->bV5Compression; + if (compression == BI_BITFIELDS) { + red_mask = b5->bV5RedMask; + green_mask = b5->bV5GreenMask; + blue_mask = b5->bV5BlueMask; + alpha_mask = b5->bV5AlphaMask; + } + else { + red_mask = 0xff0000; + green_mask = 0xff00; + blue_mask = 0xff; + alpha_mask = 0xff000000; + } + return true; + } + return false; +} + +bool BitmapInfo::load_from(BITMAPINFO* bi) { + if (!bi) + return false; + + width = bi->bmiHeader.biWidth; + height = bi->bmiHeader.biHeight; + bit_count = bi->bmiHeader.biBitCount; + compression = bi->bmiHeader.biCompression; + + if (compression == BI_BITFIELDS) { + red_mask = *((uint32_t*)&bi->bmiColors[0]); + green_mask = *((uint32_t*)&bi->bmiColors[1]); + blue_mask = *((uint32_t*)&bi->bmiColors[2]); + if (bit_count == 32) + alpha_mask = 0xff000000; + return true; + } + if (compression == BI_RGB) { + switch (bit_count) { + case 32: + red_mask = 0xff0000; + green_mask = 0xff00; + blue_mask = 0xff; + alpha_mask = 0xff000000; + break; + case 24: + case 8: // We return 8bpp images as 24bpp + red_mask = 0xff0000; + green_mask = 0xff00; + blue_mask = 0xff; + break; + case 16: + red_mask = 0x7c00; + green_mask = 0x03e0; + blue_mask = 0x001f; + break; + } + return true; + } + return false; +} + +BitmapInfo::BitmapInfo(BITMAPV5HEADER* pb5) { + if (load_from(pb5)) + b5 = pb5; +} + +BitmapInfo::BitmapInfo(BITMAPINFO* pbi) { + if (load_from(pbi)) + bi = pbi; +} + +void BitmapInfo::fill_spec(image_spec& spec) const { + spec.width = width; + spec.height = (height >= 0 ? height: -height); + // We convert indexed to 24bpp RGB images to match the OS X behavior + spec.bits_per_pixel = bit_count; + if (spec.bits_per_pixel <= 8) + spec.bits_per_pixel = 24; + spec.bytes_per_row = width*((spec.bits_per_pixel+7)/8); + spec.red_mask = red_mask; + spec.green_mask = green_mask; + spec.blue_mask = blue_mask; + spec.alpha_mask = alpha_mask; + + switch (spec.bits_per_pixel) { + + case 24: { + // We need one extra byte to avoid a crash updating the last + // pixel on last row using: + // + // *((uint32_t*)ptr) = pixel24bpp; + // + ++spec.bytes_per_row; + + // Align each row to 32bpp + int padding = (4-(spec.bytes_per_row&3))&3; + spec.bytes_per_row += padding; + break; + } + + case 16: { + int padding = (4-(spec.bytes_per_row&3))&3; + spec.bytes_per_row += padding; + break; + } + } + + unsigned long* masks = &spec.red_mask; + unsigned long* shifts = &spec.red_shift; + for (unsigned long* shift=shifts, *mask=masks; shiftbV5Size; + else + src = ((uint8_t*)bi) + bi->bmiHeader.biSize; + if (compression == BI_BITFIELDS) + src += sizeof(RGBQUAD)*3; + } + + if (src) { + const int src_bytes_per_row = spec.width*((bit_count+7)/8); + const int padding = (4-(src_bytes_per_row&3))&3; + + for (long y=spec.height-1; y>=0; --y, src+=src_bytes_per_row+padding) { + char* dst = img.data()+y*spec.bytes_per_row; + std::copy(src, src+src_bytes_per_row, dst); + } + } + + // Windows uses premultiplied RGB values, and we use straight + // alpha. So we have to divide all RGB values by its alpha. + if (bit_count == 32 && spec.alpha_mask) { + details::divide_rgb_by_alpha(img); + } + break; + } + + case 8: { + assert(bi); + + const int colors = (bi->bmiHeader.biClrUsed > 0 ? bi->bmiHeader.biClrUsed: 256); + std::vector palette(colors); + for (int c=0; cbmiColors[c].rgbRed << spec.red_shift) | + (bi->bmiColors[c].rgbGreen << spec.green_shift) | + (bi->bmiColors[c].rgbBlue << spec.blue_shift); + } + + const uint8_t* src = (((uint8_t*)bi) + bi->bmiHeader.biSize + sizeof(RGBQUAD)*colors); + const int padding = (4-(spec.width&3))&3; + + for (long y=spec.height-1; y>=0; --y, src+=padding) { + char* dst = img.data()+y*spec.bytes_per_row; + + for (unsigned long x=0; x= colors) + idx = colors-1; + + *((uint32_t*)dst) = palette[idx]; + } + } + break; + } + } + + std::swap(output_img, img); + return true; +} + +HGLOBAL create_dibv5(const image& image) { + const image_spec& spec = image.spec(); + image_spec out_spec = spec; + + int palette_colors = 0; + int padding = 0; + switch (spec.bits_per_pixel) { + case 24: padding = (4-((spec.width*3)&3))&3; break; + case 16: padding = ((4-((spec.width*2)&3))&3)/2; break; + case 8: padding = (4-(spec.width&3))&3; break; + } + out_spec.bytes_per_row += padding; + + // Create the BITMAPV5HEADER structure + HGLOBAL hmem = + GlobalAlloc( + GHND, + sizeof(BITMAPV5HEADER) + + palette_colors*sizeof(RGBQUAD) + + out_spec.bytes_per_row*out_spec.height); + if (!hmem) + return nullptr; + + out_spec.red_mask = 0x00ff0000; + out_spec.green_mask = 0xff00; + out_spec.blue_mask = 0xff; + out_spec.alpha_mask = 0xff000000; + out_spec.red_shift = 16; + out_spec.green_shift = 8; + out_spec.blue_shift = 0; + out_spec.alpha_shift = 24; + + BITMAPV5HEADER* bi = (BITMAPV5HEADER*)GlobalLock(hmem); + bi->bV5Size = sizeof(BITMAPV5HEADER); + bi->bV5Width = out_spec.width; + bi->bV5Height = out_spec.height; + bi->bV5Planes = 1; + bi->bV5BitCount = (WORD)out_spec.bits_per_pixel; + bi->bV5Compression = BI_RGB; + bi->bV5SizeImage = out_spec.bytes_per_row*spec.height; + bi->bV5RedMask = out_spec.red_mask; + bi->bV5GreenMask = out_spec.green_mask; + bi->bV5BlueMask = out_spec.blue_mask; + bi->bV5AlphaMask = out_spec.alpha_mask; + bi->bV5CSType = LCS_WINDOWS_COLOR_SPACE; + bi->bV5Intent = LCS_GM_GRAPHICS; + bi->bV5ClrUsed = 0; + + switch (spec.bits_per_pixel) { + case 32: { + const char* src = image.data(); + char* dst = (((char*)bi)+bi->bV5Size) + (out_spec.height-1)*out_spec.bytes_per_row; + for (long y=spec.height-1; y>=0; --y) { + const uint32_t* src_x = (const uint32_t*)src; + uint32_t* dst_x = (uint32_t*)dst; + + for (unsigned long x=0; x> spec.red_shift ); + int g = ((c & spec.green_mask) >> spec.green_shift); + int b = ((c & spec.blue_mask ) >> spec.blue_shift ); + int a = ((c & spec.alpha_mask) >> spec.alpha_shift); + + // Windows requires premultiplied RGBA values + r = r * a / 255; + g = g * a / 255; + b = b * a / 255; + + *dst_x = + (r << out_spec.red_shift ) | + (g << out_spec.green_shift) | + (b << out_spec.blue_shift ) | + (a << out_spec.alpha_shift); + } + + src += spec.bytes_per_row; + dst -= out_spec.bytes_per_row; + } + break; + } + default: + GlobalUnlock(hmem); + GlobalFree(hmem); + + error_handler e = get_error_handler(); + if (e) + e(ErrorCode::ImageNotSupported); + return nullptr; + } + + GlobalUnlock(hmem); + return hmem; +} + +} // namespace win +} // namespace clip diff --git a/internal/c/parts/os/clipboard/clip/clip_win_bmp.h b/internal/c/parts/os/clipboard/clip/clip_win_bmp.h new file mode 100644 index 000000000..5c9c38ba0 --- /dev/null +++ b/internal/c/parts/os/clipboard/clip/clip_win_bmp.h @@ -0,0 +1,66 @@ +// Clip Library +// Copyright (c) 2015-2024 David Capello +// +// This file is released under the terms of the MIT license. +// Read LICENSE.txt for more information. + +#ifndef CLIP_WIN_BMP_H_INCLUDED +#define CLIP_WIN_BMP_H_INCLUDED +#pragma once + +#if !CLIP_ENABLE_IMAGE + #error This file can be include only when CLIP_ENABLE_IMAGE is defined +#endif + +#include + +#include + +namespace clip { + +class image; +struct image_spec; + +namespace win { + +struct BitmapInfo { + BITMAPV5HEADER* b5 = nullptr; + BITMAPINFO* bi = nullptr; + int width = 0; + int height = 0; + uint16_t bit_count = 0; + uint32_t compression = 0; + uint32_t red_mask = 0; + uint32_t green_mask = 0; + uint32_t blue_mask = 0; + uint32_t alpha_mask = 0; + + BitmapInfo(); + explicit BitmapInfo(BITMAPV5HEADER* pb5); + explicit BitmapInfo(BITMAPINFO* pbi); + + bool is_valid() const { + return (b5 || bi); + } + + void fill_spec(image_spec& spec) const; + + // Fills the output_img with the data provided by this + // BitmapInfo. Returns true if it was able to fill the output image + // or false otherwise. + bool to_image(image& output_img) const; + +private: + bool load_from(BITMAPV5HEADER* b5); + bool load_from(BITMAPINFO* bi); +}; + +// Returns a handle to the HGLOBAL memory reserved to create a DIBV5 +// based on the image passed by parameter. Returns null if it cannot +// create the handle. +HGLOBAL create_dibv5(const image& image); + +} // namespace win +} // namespace clip + +#endif // CLIP_WIN_BMP_H_INCLUDED diff --git a/internal/c/parts/os/clipboard/clip/clip_win_wic.cpp b/internal/c/parts/os/clipboard/clip/clip_win_wic.cpp new file mode 100644 index 000000000..dd3715834 --- /dev/null +++ b/internal/c/parts/os/clipboard/clip/clip_win_wic.cpp @@ -0,0 +1,472 @@ +// 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 diff --git a/internal/c/parts/os/clipboard/clip/clip_win_wic.h b/internal/c/parts/os/clipboard/clip/clip_win_wic.h index cca061217..dfef88c7b 100644 --- a/internal/c/parts/os/clipboard/clip/clip_win_wic.h +++ b/internal/c/parts/os/clipboard/clip/clip_win_wic.h @@ -1,180 +1,41 @@ // Clip Library -// Copyright (c) 2020-2022 David Capello +// 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.h" +#ifndef CLIP_WIN_WIC_H_INCLUDED +#define CLIP_WIN_WIC_H_INCLUDED +#pragma once -#include -#include - -#include -#include +#if !CLIP_ENABLE_IMAGE + #error This file can be include only when CLIP_ENABLE_IMAGE is defined +#endif -namespace clip { -namespace win { +#include -// 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(); - } -}; +#include -template -class comptr { -public: - comptr() { } - explicit comptr(T* ptr) : m_ptr(ptr) { } - comptr(const comptr&) = delete; - comptr& operator=(const comptr&) = delete; - ~comptr() { reset(); } +namespace clip { - T** operator&() { return &m_ptr; } - T* operator->() { return m_ptr; } - bool operator!() const { return !m_ptr; } +class image; +struct image_spec; - T* get() { return m_ptr; } - void reset() { - if (m_ptr) { - m_ptr->Release(); - m_ptr = nullptr; - } - } -private: - T* m_ptr = nullptr; -}; +namespace win { -#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); - } +typedef bool (*ReadWicImageFormatFunc)(const uint8_t*, + const UINT, + clip::image*, + clip::image_spec*); - operator HMODULE() { return m_ptr; } - bool operator!() const { return !m_ptr; } -private: - HMODULE m_ptr = nullptr; -}; -#endif +ReadWicImageFormatFunc wic_image_format_available(UINT* output_cbformat); ////////////////////////////////////////////////////////////////////// // 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; +bool write_png_on_stream(const image& image, IStream* stream); - GlobalFree(handle); - return nullptr; -} +HGLOBAL write_png(const image& image); ////////////////////////////////////////////////////////////////////// // Decode the clipboard data from PNG format @@ -182,105 +43,34 @@ HGLOBAL write_png(const image& image) { bool read_png(const uint8_t* buf, const UINT len, image* output_image, - image_spec* output_spec) { - coinit com; + image_spec* output_spec); -#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 - - 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; - } - - // Can decoder be nullptr if hr is S_OK/successful? We've received - // some crash reports that might indicate this. - if (!decoder) - 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; +////////////////////////////////////////////////////////////////////// +// Decode the clipboard data from JPEG format - 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; +bool read_jpg(const uint8_t* buf, + const UINT len, + image* output_image, + image_spec* output_spec); - if (output_spec) - *output_spec = spec; +////////////////////////////////////////////////////////////////////// +// Decode the clipboard data from GIF format - if (output_image) { - image img(spec); +bool read_gif(const uint8_t* buf, + const UINT len, + image* output_image, + image_spec* output_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; - } +////////////////////////////////////////////////////////////////////// +// Decode the clipboard data from BMP format - std::swap(*output_image, img); - } +bool read_bmp(const uint8_t* buf, + const UINT len, + image* output_image, + image_spec* output_spec); - return true; -} } // namespace win } // namespace clip + +#endif // CLIP_WIN_WIC_H_INCLUDED diff --git a/internal/c/parts/os/clipboard/clipboard.cpp b/internal/c/parts/os/clipboard/clipboard.cpp index 78c702502..0864a7cce 100644 --- a/internal/c/parts/os/clipboard/clipboard.cpp +++ b/internal/c/parts/os/clipboard/clipboard.cpp @@ -11,6 +11,9 @@ // Uncomment this to to print debug messages to stderr // #define IMAGE_DEBUG 1 +// Comment the following bypass custom clipboard code in func__clipboard() and sub__clipboard() +#define QB64_USE_CUSTOM_CLIPBOARD_CODE 1 + // This is not strictly needed. But we'll leave it here for VSCode to do it's magic #define CLIP_ENABLE_IMAGE 1 #include "clip/clip.h" @@ -37,7 +40,7 @@ static std::string g_InternalClipboard; /// @brief Gets text (if present) in the OS clipboard. /// @return A qbs string. qbs *func__clipboard() { -#if defined(QB64_MACOSX) +#if defined(QB64_MACOSX) && defined(QB64_USE_CUSTOM_CLIPBOARD_CODE) && QB64_USE_CUSTOM_CLIPBOARD_CODE == 1 // We'll use our own clipboard get code on macOS since our requirements are different than what clip supports PasteboardRef clipboard = nullptr; @@ -69,7 +72,7 @@ qbs *func__clipboard() { CFRelease(clipboard); } -#elif defined(QB64_WINDOWS) +#elif defined(QB64_WINDOWS) && defined(QB64_USE_CUSTOM_CLIPBOARD_CODE) && QB64_USE_CUSTOM_CLIPBOARD_CODE == 1 // We'll need custom code for Windows because clip does automatic UTF-8 conversions that leads to some undesired behavior when copying extended ASCII if (OpenClipboard(NULL)) { @@ -110,58 +113,56 @@ qbs *func__clipboard() { void sub__clipboard(const qbs *qbsText) { g_InternalClipboard.assign(reinterpret_cast(qbsText->chr), qbsText->len); - if (qbsText->len) { -#if defined(QB64_MACOSX) +#if defined(QB64_MACOSX) && defined(QB64_USE_CUSTOM_CLIPBOARD_CODE) && QB64_USE_CUSTOM_CLIPBOARD_CODE == 1 - // We'll use our own clipboard set code on macOS since our requirements are different than what clip supports - PasteboardRef clipboard; - if (PasteboardCreate(kPasteboardClipboard, &clipboard) == noErr) { - if (PasteboardClear(clipboard) == noErr) { - CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, qbsText->chr, qbsText->len, kCFAllocatorNull); + // We'll use our own clipboard set code on macOS since our requirements are different than what clip supports + PasteboardRef clipboard; + if (PasteboardCreate(kPasteboardClipboard, &clipboard) == noErr) { + if (PasteboardClear(clipboard) == noErr) { + CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, qbsText->chr, qbsText->len, kCFAllocatorNull); - if (data) { - PasteboardPutItemFlavor(clipboard, nullptr, kUTTypeUTF8PlainText, data, 0); + if (data) { + PasteboardPutItemFlavor(clipboard, nullptr, kUTTypeUTF8PlainText, data, 0); - CFRelease(data); - } + CFRelease(data); } - - CFRelease(clipboard); } -#elif defined(QB64_WINDOWS) + CFRelease(clipboard); + } - // We'll need custom code for Windows because clip does automatic UTF-8 conversions that leads to some undesired behavior when copying extended ASCII - if (OpenClipboard(NULL)) { - if (EmptyClipboard()) { - HGLOBAL hClipboardData = GlobalAlloc(GMEM_MOVEABLE, qbsText->len + 1); +#elif defined(QB64_WINDOWS) && defined(QB64_USE_CUSTOM_CLIPBOARD_CODE) && QB64_USE_CUSTOM_CLIPBOARD_CODE == 1 - if (hClipboardData) { - auto pchData = reinterpret_cast(GlobalLock(hClipboardData)); + // We'll need custom code for Windows because clip does automatic UTF-8 conversions that leads to some undesired behavior when copying extended ASCII + if (OpenClipboard(NULL)) { + if (EmptyClipboard()) { + HGLOBAL hClipboardData = GlobalAlloc(GMEM_MOVEABLE, qbsText->len + 1); - if (pchData) { - memcpy(pchData, qbsText->chr, qbsText->len); - pchData[qbsText->len] = '\0'; // null terminate + if (hClipboardData) { + auto pchData = reinterpret_cast(GlobalLock(hClipboardData)); - GlobalUnlock(hClipboardData); + if (pchData) { + memcpy(pchData, qbsText->chr, qbsText->len); + pchData[qbsText->len] = '\0'; // null terminate - SetClipboardData(CF_TEXT, hClipboardData); - } + GlobalUnlock(hClipboardData); - GlobalFree(hClipboardData); + SetClipboardData(CF_TEXT, hClipboardData); } - } - CloseClipboard(); + GlobalFree(hClipboardData); + } } + CloseClipboard(); + } + #else - // clip works like we want on Linux - clip::set_text(g_InternalClipboard); + // clip works like we want on Linux + clip::set_text(g_InternalClipboard); #endif - } } /// @brief Returns an image handle of an image from the clipboard (if present). diff --git a/tests/compile_tests/clipboard/clipboard_test.bas b/tests/compile_tests/clipboard/clipboard_test.bas index bfb561cd9..468e7f733 100644 --- a/tests/compile_tests/clipboard/clipboard_test.bas +++ b/tests/compile_tests/clipboard/clipboard_test.bas @@ -2,23 +2,40 @@ $CONSOLE:ONLY OPTION _EXPLICIT -CONST TEXT_HELLO = "Hello" -CONST TEXT_WORLD = "world" -CONST TEXT_ASCII = CHR$(176) + CHR$(177) + CHR$(178) + " " + TEXT_HELLO + " " + TEXT_WORLD + " " + CHR$(178) + CHR$(177) + CHR$(176) +CONST TEXT_FAIL = "Failed" +CONST TEXT_PASS = "Passed" +CONST TEXT_ASCII = CHR$(176) + CHR$(177) + CHR$(178) + " " + TEXT_PASS + " " + CHR$(178) + CHR$(177) + CHR$(176) +PRINT "Clipboard set test: "; -_CLIPBOARD$ = TEXT_HELLO -_CLIPBOARD$ = TEXT_WORLD +_CLIPBOARD$ = TEXT_FAIL +_CLIPBOARD$ = TEXT_PASS -PRINT "Set: "; TEXT_WORLD -PRINT "Got: "; _CLIPBOARD$ +IF TEXT_PASS = _CLIPBOARD$ THEN + PRINT TEXT_PASS +ELSE + PRINT TEXT_FAIL +END IF + +PRINT "Clipboard extended ASCII set test: "; _CLIPBOARD$ = TEXT_ASCII IF TEXT_ASCII = _CLIPBOARD$ THEN - PRINT "Test passed" + PRINT TEXT_PASS +ELSE + PRINT TEXT_FAIL +END IF + +PRINT "Clipboard clear test: "; + +_CLIPBOARD$ = TEXT_FAIL +_CLIPBOARD$ = "" + +IF "" = _CLIPBOARD$ THEN + PRINT TEXT_PASS ELSE - PRINT "Test failed" + PRINT TEXT_FAIL END IF SYSTEM diff --git a/tests/compile_tests/clipboard/clipboard_test.output b/tests/compile_tests/clipboard/clipboard_test.output index fa20c80e6..0d5422bc6 100644 --- a/tests/compile_tests/clipboard/clipboard_test.output +++ b/tests/compile_tests/clipboard/clipboard_test.output @@ -1,3 +1,3 @@ -Set: world -Got: world -Test passed +Clipboard set test: Passed +Clipboard extended ASCII set test: Passed +Clipboard clear test: Passed