1
1
Fork 0
mirror of https://github.com/QB64-Phoenix-Edition/QB64pe.git synced 2024-05-10 08:30:12 +00:00

Compare commits

...

13 commits

Author SHA1 Message Date
github-actions[bot] e56a807348 Automatic update of ./internal/source 2024-04-06 11:10:57 +00:00
Samuel Gomes d26c7790f4
Merge pull request #475 from a740g/clipboard-fixes
Clipboard fixes
2024-04-06 16:21:58 +05:30
Samuel Gomes 1bb83a3134
Merge branch 'main' into clipboard-fixes 2024-03-30 23:31:32 +05:30
Samuel Gomes cde0ec533d Remove duplicate DEP_SCREENIMAGE=y 2024-03-30 01:35:41 +05:30
Samuel Gomes 20f1871966 Add clipboard test and add clip license 2024-03-29 23:21:10 +05:30
Samuel Gomes 38b21669c5 Remove constexpr from image_calculate_rgb_distance 2024-03-28 07:01:37 +05:30
Samuel Gomes f2276e79eb Add custom clipboard text handling code for Windows 2024-03-28 06:26:19 +05:30
a740g 332ecd3336 Add libpng-dev as dependency on Linux 2024-03-28 02:57:56 +05:30
a740g b5de183921 Add macOS custom clipboard text handling code and convert BGRA to RGBA before setting clipboard image 2024-03-27 23:51:02 +05:30
a740g c43b67c987 Simplify text clipboard implementation 2024-03-27 10:30:14 +05:30
a740g f5a32ef466 Implement internal clipboard support 2024-03-27 10:00:49 +05:30
a740g 6bcb68b74c Fix macOS make rule 2024-03-27 09:08:34 +05:30
Samuel Gomes 47e3ec40f7 Add cross-platform clipboard support 2024-03-26 23:34:54 +05:30
30 changed files with 50096 additions and 46785 deletions

View file

@ -46,7 +46,7 @@ jobs:
- name: Install dependencies
if: ${{ matrix.prefix == 'lnx' }}
run: sudo apt update && sudo apt install build-essential x11-utils mesa-common-dev libglu1-mesa-dev libasound2-dev zlib1g-dev pulseaudio dbus-x11 libportaudio2 libcurl4-openssl-dev
run: sudo apt update && sudo apt install build-essential x11-utils mesa-common-dev libglu1-mesa-dev libasound2-dev libpng-dev pulseaudio dbus-x11 libportaudio2 libcurl4-openssl-dev
# Pulseaudio puts a dummy ALSA device in place, which allows us to do
# audio testing on Linux

View file

@ -145,17 +145,17 @@ CXXFLAGS += -fno-strict-aliasing
CXXFLAGS += -Wno-conversion-null
ifeq ($(OS),lnx)
CXXLIBS += -lGL -lGLU -lX11 -lpthread -ldl -lrt
CXXLIBS += -lGL -lGLU -lX11 -lpthread -ldl -lrt -lxcb
CXXFLAGS += -DFREEGLUT_STATIC
endif
ifeq ($(OS),win)
CXXLIBS += -static-libgcc -static-libstdc++ -lcomdlg32 -lole32
CXXLIBS += -static-libgcc -static-libstdc++ -lcomdlg32 -lole32 -lshlwapi -lwindowscodecs
CXXFLAGS += -DGLEW_STATIC -DFREEGLUT_STATIC
endif
ifeq ($(OS),osx)
CXXLIBS += -framework OpenGL -framework IOKit -framework GLUT -framework Cocoa -framework ApplicationServices
CXXLIBS += -framework OpenGL -framework IOKit -framework GLUT -framework Cocoa -framework ApplicationServices -framework CoreFoundation
# OSX doesn't strip using objcopy, so we're using `-s` instead
ifneq ($(STRIP_SYMBOLS),n)
@ -204,6 +204,7 @@ include $(PATH_INTERNAL_C)/parts/video/image/build.mk
include $(PATH_INTERNAL_C)/parts/gui/build.mk
include $(PATH_INTERNAL_C)/parts/network/http/build.mk
include $(PATH_INTERNAL_C)/parts/compression/build.mk
include $(PATH_INTERNAL_C)/parts/os/clipboard/build.mk
.PHONY: all clean
@ -267,6 +268,8 @@ endif
ifneq ($(filter y,$(DEP_SCREENIMAGE)),)
CXXFLAGS += -DDEPENDENCY_SCREENIMAGE
QBLIB_NAME := $(addsuffix 1,$(QBLIB_NAME))
LICENSE_IN_USE += clip
else
CXXFLAGS += -DDEPENDENCY_NO_SCREENIMAGE
QBLIB_NAME := $(addsuffix 0,$(QBLIB_NAME))
@ -287,9 +290,7 @@ ifneq ($(filter y,$(DEP_DEVICEINPUT)),)
ifeq ($(OS),win)
CXXLIBS += -lwinmm -lxinput -ldinput8 -ldxguid -lwbemuuid -lole32 -loleaut32
endif
ifeq ($(OS),osx)
CXXLIBS += -framework CoreFoundation -framework IOKit
endif
QBLIB_NAME := $(addsuffix 1,$(QBLIB_NAME))
LICENSE_IN_USE += libstem_gamepad
@ -308,7 +309,7 @@ ifneq ($(filter y,$(DEP_AUDIO_MINIAUDIO)),)
CXXLIBS += -lwinmm -lksguid -ldxguid -lole32
endif
ifeq ($(OS),osx)
CXXLIBS += -lpthread -lm -framework CoreFoundation -framework CoreAudio -framework CoreMIDI -framework AudioUnit -framework AudioToolbox
CXXLIBS += -lpthread -lm -framework CoreAudio -framework CoreMIDI -framework AudioUnit -framework AudioToolbox
endif
QBLIB_NAME := $(addsuffix 1,$(QBLIB_NAME))

View file

@ -188,15 +188,6 @@ extern "C" int QB64_Resizable() { return ScreenResize; }
int32 sub_gl_called = 0;
extern "C" int qb64_custom_event(int event, int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, void *p1, void *p2);
#ifdef QB64_WINDOWS
extern "C" LRESULT qb64_os_event_windows(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, int *qb64_os_event_info);
#endif
#if defined(QB64_LINUX) && defined(QB64_GUI)
extern "C" void qb64_os_event_linux(XEvent *event, Display *display, int *qb64_os_event_info);
#endif
#define QB64_EVENT_CLOSE 1
#define QB64_EVENT_KEY 2
#define QB64_EVENT_RELATIVE_MOUSE_MOVEMENT 3
@ -23910,448 +23901,6 @@ int32 func__exit() {
return x;
}
#if defined(QB64_LINUX) && defined(QB64_GUI)
// X11 clipboard interface for Linux
// SDL_SysWMinfo syswminfo;
Atom targets, utf8string, compoundtext, clipboard;
int x11filter(XEvent *x11event) {
static int i;
static char *cp;
static XSelectionRequestEvent *x11request;
static XSelectionEvent x11selectionevent;
static Atom mytargets[] = {XA_STRING, utf8string, compoundtext};
if (x11event->type == SelectionRequest) {
x11request = &x11event->xselectionrequest;
x11selectionevent.type = SelectionNotify;
x11selectionevent.serial = x11event->xany.send_event;
x11selectionevent.send_event = True;
x11selectionevent.display = X11_display;
x11selectionevent.requestor = x11request->requestor;
x11selectionevent.selection = x11request->selection;
x11selectionevent.target = None;
x11selectionevent.property = x11request->property;
x11selectionevent.time = x11request->time;
if (x11request->target == targets) {
XChangeProperty(X11_display, x11request->requestor, x11request->property, XA_ATOM, 32, PropModeReplace, (unsigned char *)mytargets, 3);
} else {
if (x11request->target == compoundtext || x11request->target == utf8string || x11request->target == XA_STRING) {
cp = XFetchBytes(X11_display, &i);
XChangeProperty(X11_display, x11request->requestor, x11request->property, x11request->target, 8, PropModeReplace, (unsigned char *)cp, i);
XFree(cp);
} else {
x11selectionevent.property = None;
}
}
XSendEvent(x11request->display, x11request->requestor, 0, NoEventMask, (XEvent *)&x11selectionevent);
XSync(X11_display, False);
}
return 1;
}
void setupx11clipboard() {
static int32 setup = 0;
if (!setup) {
setup = 1;
// SDL_GetWMInfo(&syswminfo);
// SDL_EventState(SDL_SYSWMEVENT,SDL_ENABLE);
// SDL_SetEventFilter(x11filter);
x11_lock();
targets = XInternAtom(X11_display, "TARGETS", False);
utf8string = XInternAtom(X11_display, "UTF8_STRING", False);
compoundtext = XInternAtom(X11_display, "COMPOUND_TEXT", False);
clipboard = XInternAtom(X11_display, "CLIPBOARD", False);
x11_unlock();
}
}
void x11clipboardcopy(const char *text) {
setupx11clipboard();
x11_lock();
XStoreBytes(X11_display, text, strlen(text) + 1);
XSetSelectionOwner(X11_display, clipboard, X11_window, CurrentTime);
x11_unlock();
return;
}
char *x11clipboardpaste() {
static int32 i;
static char *cp;
static unsigned char *cp2;
static Window x11selectionowner;
static XEvent x11event;
static unsigned long data_items, bytes_remaining, ignore;
static int format;
static Atom type;
cp = NULL;
cp2 = NULL;
setupx11clipboard();
// syswminfo.info.x11.lock_func();
x11_lock();
x11selectionowner = XGetSelectionOwner(X11_display, clipboard);
if (x11selectionowner != None) {
// The XGetSelectionOwner() function returns the window ID associated with the window
if (x11selectionowner == X11_window) { // we are the provider, so just return buffered content
x11_unlock();
int bytes;
cp = XFetchBytes(X11_display, &bytes);
return cp;
}
XConvertSelection(X11_display, clipboard, utf8string, clipboard, X11_window, CurrentTime);
XFlush(X11_display);
bool gotReply = false;
int timeoutMs = 10000; // 10sec
do {
XEvent event;
gotReply = XCheckTypedWindowEvent(X11_display, X11_window, SelectionNotify, &event);
if (gotReply) {
if (event.xselection.property == clipboard) {
XGetWindowProperty(X11_display, X11_window, clipboard, 0, 0, False, AnyPropertyType, &type, &format, &data_items, &bytes_remaining, &cp2);
if (cp2) {
XFree(cp2);
cp2 = NULL;
}
if (bytes_remaining) {
if (XGetWindowProperty(X11_display, X11_window, clipboard, 0, bytes_remaining, False, AnyPropertyType, &type, &format, &data_items,
&ignore, &cp2) == Success) {
cp = strdup((char *)cp2);
XFree(cp2);
XDeleteProperty(X11_display, X11_window, clipboard);
x11_unlock();
return cp;
}
}
x11_unlock();
return NULL;
} else {
x11_unlock();
return NULL;
}
}
Sleep(1);
timeoutMs -= 1;
} while (timeoutMs > 0);
} // x11selectionowner!=None
x11_unlock();
return NULL;
}
#elif defined(QB64_LINUX)
void x11clipboardcopy(const char *text) {}
char *x11clipboardpaste() { return NULL; }
#endif
qbs *internal_clipboard = NULL; // used only if clipboard services unavailable
int32 linux_clipboard_init = 0;
void sub__clipboard(qbs *text) {
#ifdef QB64_WINDOWS
static uint8 *textz;
static HGLOBAL h;
if (OpenClipboard(NULL)) {
EmptyClipboard();
h = GlobalAlloc(GMEM_MOVEABLE, text->len + 1);
if (h) {
textz = (uint8 *)GlobalLock(h);
if (textz) {
memcpy(textz, text->chr, text->len);
textz[text->len] = 0;
GlobalUnlock(h);
SetClipboardData(CF_TEXT, h);
}
}
CloseClipboard();
}
return;
#endif
#ifdef QB64_MACOSX
PasteboardRef clipboard;
if (PasteboardCreate(kPasteboardClipboard, &clipboard) != noErr) {
return;
}
if (PasteboardClear(clipboard) != noErr) {
CFRelease(clipboard);
return;
}
CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, text->chr, text->len, kCFAllocatorNull);
if (data == NULL) {
CFRelease(clipboard);
return;
}
OSStatus err;
err = PasteboardPutItemFlavor(clipboard, NULL, kUTTypeUTF8PlainText, data, 0);
CFRelease(clipboard);
CFRelease(data);
return;
#endif
#if defined(QB64_LINUX)
static qbs *textz = NULL;
if (!textz)
textz = qbs_new(0, 0);
qbs_set(textz, qbs_add(text, qbs_new_txt_len("\0", 1)));
x11clipboardcopy((char *)textz->chr);
return;
#endif
if (internal_clipboard == NULL)
internal_clipboard = qbs_new(0, 0);
qbs_set(internal_clipboard, text);
}
#ifdef DEPENDENCY_SCREENIMAGE
void sub__clipboardimage(int32 src) {
# ifdef QB64_WINDOWS
if (is_error_pending())
return;
static int32 i, i2, ii, w, h;
static uint32 *o, *o2;
static int32 x, y, n, c, i3, c2;
// validation
i = src;
if (i >= 0) { // validate i
validatepage(i);
i = page[i];
} else {
i = -i;
if (i >= nextimg) {
error(258);
return;
}
if (!img[i].valid) {
error(258);
return;
}
}
if (img[i].text) {
error(5);
return;
}
// end of validation
w = img[i].width;
h = img[i].height;
// source[http://support.microsoft.com/kb/318876]
HDC hdc;
BITMAPV5HEADER bi;
HBITMAP hBitmap;
void *lpBits;
ZeroMemory(&bi, sizeof(BITMAPV5HEADER));
bi.bV5Size = sizeof(BITMAPV5HEADER);
bi.bV5Width = w;
bi.bV5Height = h;
bi.bV5Planes = 1;
bi.bV5BitCount = 32;
bi.bV5Compression = BI_RGB;
hdc = GetDC(NULL);
// Create the DIB section with an alpha channel.
hBitmap = CreateDIBSection(hdc, (BITMAPINFO *)&bi, DIB_RGB_COLORS, (void **)&lpBits, NULL, (DWORD)0);
// Transfer the source image to a new 32-bit image to avoid incompatible formats)
i2 = func__newimage(w, h, 32, 1);
sub__putimage(NULL, NULL, NULL, NULL, -i, i2, NULL, NULL, NULL, NULL, 8 + 32);
o = img[-i2].offset32;
o2 = (uint32 *)lpBits;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
c = o[(h - 1 - y) * w + x];
o2[y * w + x] = c;
}
}
sub__freeimage(i2, 1);
// Create copy of hBitmap to send to the clipboard
HBITMAP bitmapCopy;
HDC hdc2, hdc3;
bitmapCopy = CreateCompatibleBitmap(hdc, w, h);
hdc2 = CreateCompatibleDC(hdc);
hdc3 = CreateCompatibleDC(hdc);
SelectObject(hdc2, bitmapCopy);
SelectObject(hdc3, hBitmap);
BitBlt(hdc2, 0, 0, w, h, hdc3, 0, 0, SRCCOPY);
ReleaseDC(NULL, hdc);
ReleaseDC(NULL, hdc2);
ReleaseDC(NULL, hdc3);
// Send bitmapCopy to the clipboard
if (OpenClipboard(NULL)) {
EmptyClipboard();
SetClipboardData(CF_BITMAP, bitmapCopy);
CloseClipboard();
}
DeleteObject(hBitmap);
DeleteObject(bitmapCopy);
# endif
}
#endif
#ifdef DEPENDENCY_SCREENIMAGE
int32 func__clipboardimage() {
# ifdef QB64_WINDOWS
if (is_error_pending())
return -1;
static HBITMAP bitmap;
static BITMAP bitmapInfo;
static HDC hdc;
static int32 w, h;
if (OpenClipboard(NULL)) {
if (IsClipboardFormatAvailable(CF_BITMAP) == 0) {
CloseClipboard();
return -1;
}
bitmap = (HBITMAP)GetClipboardData(CF_BITMAP);
CloseClipboard();
GetObject(bitmap, sizeof(BITMAP), &bitmapInfo);
h = bitmapInfo.bmHeight;
w = bitmapInfo.bmWidth;
static BITMAPFILEHEADER bmfHeader;
static BITMAPINFOHEADER bi;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = w;
bi.biHeight = -h;
bi.biPlanes = 1;
bi.biBitCount = 32;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
static int32 i, i2;
i2 = func__dest();
i = func__newimage(w, h, 32, 1);
sub__dest(i);
hdc = GetDC(NULL);
GetDIBits(hdc, bitmap, 0, h, write_page->offset, (BITMAPINFO *)&bi, DIB_RGB_COLORS);
sub__setalpha(255, NULL, NULL, NULL, 0); // required as some images come
// with alpha 0 from the clipboard
sub__dest(i2);
ReleaseDC(NULL, hdc);
DeleteObject(bitmap);
return i;
} else
return -1;
# endif
return -1;
}
#endif
qbs *func__clipboard() {
#ifdef QB64_WINDOWS
static qbs *text;
static uint8 *textz;
static HGLOBAL h;
if (OpenClipboard(NULL)) {
if (IsClipboardFormatAvailable(CF_TEXT)) {
h = GetClipboardData(CF_TEXT);
if (h) {
textz = (uint8 *)GlobalLock(h);
if (textz) {
text = qbs_new(strlen((char *)textz), 1);
memcpy(text->chr, textz, text->len);
GlobalUnlock(h);
CloseClipboard();
return text;
}
}
}
CloseClipboard();
}
text = qbs_new(0, 1);
return text;
#endif
#ifdef QB64_MACOSX
static qbs *text;
OSStatus err = noErr;
ItemCount itemCount;
PasteboardSyncFlags syncFlags;
static PasteboardRef inPasteboard = NULL;
PasteboardCreate(kPasteboardClipboard, &inPasteboard);
char *data;
data = "";
syncFlags = PasteboardSynchronize(inPasteboard);
err = badPasteboardSyncErr;
err = PasteboardGetItemCount(inPasteboard, &itemCount);
if ((err) != noErr)
goto CantGetPasteboardItemCount;
for (int itemIndex = 1; itemIndex <= itemCount; itemIndex++) {
PasteboardItemID itemID;
CFDataRef flavorData;
err = PasteboardGetItemIdentifier(inPasteboard, itemIndex, &itemID);
if ((err) != noErr)
goto CantGetPasteboardItemIdentifier;
err = PasteboardCopyItemFlavorData(inPasteboard, itemID, CFSTR("public.utf8-plain-text"), &flavorData);
if ((err) != noErr)
goto CantGetPasteboardItemCount;
data = (char *)CFDataGetBytePtr(flavorData);
uint32 size;
size = CFDataGetLength(flavorData);
text = qbs_new(size, 1);
memcpy(text->chr, data, text->len);
// CFRelease (flavorData);
// CFRelease (flavorTypeArray);
// CFRelease(inPasteboard);
return text;
CantGetPasteboardItemIdentifier:;
}
CantGetPasteboardItemCount:
text = qbs_new(0, 1);
return text;
return NULL;
#endif
#if defined(QB64_LINUX)
qbs *text;
char *cp = x11clipboardpaste();
cp = x11clipboardpaste();
if (!cp) {
text = qbs_new(0, 1);
} else {
text = qbs_new(strlen(cp), 1);
memcpy(text->chr, cp, text->len);
free(cp);
}
return text;
#endif
if (internal_clipboard == NULL)
internal_clipboard = qbs_new(0, 0);
return internal_clipboard;
}
int32 display_called = 0;
void display_now() {
if (autodisplay) {
@ -32397,8 +31946,6 @@ extern "C" void qb64_os_event_linux(XEvent *event, Display *display, int *qb64_o
X11_display = display;
X11_window = event->xexpose.window;
}
x11filter(event); // handles clipboard request events from other applications
}
if (*qb64_os_event_info == OS_EVENT_POST_PROCESSING) {

View file

@ -0,0 +1,15 @@
//----------------------------------------------------------------------------------------------------------------------
// QB64-PE cross-platform clipboard support
// Powered by clip (https://github.com/dacap/clip)
//----------------------------------------------------------------------------------------------------------------------
#pragma once
#include <stdint.h>
struct qbs;
qbs *func__clipboard();
void sub__clipboard(const qbs *qbsText);
int32_t func__clipboardimage();
void sub__clipboardimage(int32_t src);

View file

@ -1,4 +1,4 @@
//----------------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------
// ___ ___ __ _ _ ___ ___ ___ _ _ _
// / _ \| _ ) / /| | || _ \ __| |_ _|_ __ __ _ __ _ ___ | | (_) |__ _ _ __ _ _ _ _ _
// | (_) | _ \/ _ \_ _| _/ _| | || ' \/ _` / _` / -_) | |__| | '_ \ '_/ _` | '_| || |
@ -17,6 +17,7 @@
#pragma once
#include <cmath>
#include <stdint.h>
#include <stdio.h>
@ -38,16 +39,42 @@
# define IMAGE_DEBUG_CHECK(_exp_) // Don't do anything in release builds
#endif
// The byte ordering here are straight from libqb.cpp. So, if libqb.cpp is wrong, then we are wrong! ;)
#define IMAGE_GET_BGRA_RED(c) ((uint8_t)((uint32_t)(c) >> 16 & 0xFFu))
#define IMAGE_GET_BGRA_GREEN(c) ((uint8_t)((uint32_t)(c) >> 8 & 0xFFu))
#define IMAGE_GET_BGRA_BLUE(c) ((uint8_t)((uint32_t)(c) & 0xFFu))
#define IMAGE_GET_BGRA_ALPHA(c) ((uint8_t)((uint32_t)(c) >> 24))
#define IMAGE_GET_BGRA_BGR(c) ((uint32_t)(c) & 0xFFFFFFu)
#define IMAGE_MAKE_BGRA(r, g, b, a) \
((uint32_t)((uint8_t)(b) | ((uint32_t)((uint8_t)(g)) << 8) | ((uint32_t)((uint8_t)(r)) << 16) | ((uint32_t)((uint8_t)(a)) << 24)))
// This is returned to the caller if something goes wrong while loading the image
#define INVALID_IMAGE_HANDLE -1
struct qbs;
int32_t func__loadimage(qbs *qbsFileName, int32_t bpp, qbs *qbsRequirements, int32_t passed);
void sub__saveimage(qbs *qbsFileName, int32_t imageHandle, qbs *qbsRequirements, int32_t passed);
static inline constexpr uint8_t image_get_bgra_red(const uint32_t c) { return (uint8_t)((c >> 16) & 0xFFu); }
static inline constexpr uint8_t image_get_bgra_green(const uint32_t c) { return (uint8_t)((c >> 8) & 0xFFu); }
static inline constexpr uint8_t image_get_bgra_blue(const uint32_t c) { return (uint8_t)(c & 0xFFu); }
static inline constexpr uint8_t image_get_bgra_alpha(const uint32_t c) { return (uint8_t)(c >> 24); }
static inline constexpr uint32_t image_get_bgra_bgr(const uint32_t c) { return (uint32_t)(c & 0xFFFFFFu); }
static inline constexpr uint32_t image_make_bgra(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a) {
return (uint32_t)(b) | ((uint32_t)(g) << 8) | ((uint32_t)(r) << 16) | ((uint32_t)(a) << 24);
}
static inline constexpr int image_scale_5bits_to_8bits(const int v) { return (v << 3) | (v >> 2); }
static inline constexpr int image_scale_6bits_to_8bits(const int v) { return (v << 2) | (v >> 4); }
static inline constexpr uint32_t image_swap_red_blue(const uint32_t clr) {
return ((clr & 0xFF00FF00u) | ((clr & 0x00FF0000u) >> 16) | ((clr & 0x000000FFu) << 16));
}
static inline constexpr uint8_t image_clamp_color_component(const int n) { return n < 0 ? 0 : n > 255 ? 255 : n; }
static inline float image_calculate_rgb_distance(const uint8_t r1, const uint8_t g1, const uint8_t b1, const uint8_t r2, const uint8_t g2, const uint8_t b2) {
auto delta_r = (float)r2 - (float)r1;
auto delta_g = (float)g2 - (float)g1;
auto delta_b = (float)b2 - (float)b1;
return sqrtf(delta_r * delta_r + delta_g * delta_g + delta_b * delta_b);
}

View file

@ -1047,9 +1047,7 @@ void FGAPIENTRY glutMainLoopEvent( void )
#endif
// QB64-PE: custom code begin
int qb64_os_event_info = 0;
qb64_os_event_info = 1;
int qb64_os_event_info = 1;
qb64_os_event_linux(&event, fgDisplay.Display, &qb64_os_event_info);
if (qb64_os_event_info == 3)
return;

View file

@ -229,10 +229,10 @@ uint32_t func__guiColorChooserDialog(qbs *qbsTitle, uint32_t nDefaultRGB, int32_
nDefaultRGB = 0;
// Break the color into RGB components
uint8_t lRGB[3] = {IMAGE_GET_BGRA_RED(nDefaultRGB), IMAGE_GET_BGRA_GREEN(nDefaultRGB), IMAGE_GET_BGRA_BLUE(nDefaultRGB)};
uint8_t lRGB[3] = {image_get_bgra_red(nDefaultRGB), image_get_bgra_green(nDefaultRGB), image_get_bgra_blue(nDefaultRGB)};
// On cancel, return 0 (i.e. no color, no alpha, nothing). Else, return color with alpha set to 255
return !tinyfd_colorChooser(aTitle.c_str(), nullptr, lRGB, lRGB) ? 0 : IMAGE_MAKE_BGRA(lRGB[0], lRGB[1], lRGB[2], 0xFF);
return !tinyfd_colorChooser(aTitle.c_str(), nullptr, lRGB, lRGB) ? 0 : image_make_bgra(lRGB[0], lRGB[1], lRGB[2], 0xFF);
}
/// @brief Shows the system file open dialog box

View file

@ -0,0 +1,56 @@
# clip Setup:
# Download the latest release from https://github.com/dacap/clip
# Copy all source files except clip_none.cpp to internal/c/parts/os/clipboard/clip
# Compile the source using -DCLIP_ENABLE_IMAGE=1, -DHAVE_XCB_XLIB_H (Linux) and DHAVE_PNG_H (Linux)
CLIP_DEFS := -DCLIP_ENABLE_IMAGE=1
CLIP_SRCS := \
clip.cpp \
image.cpp
ifeq ($(OS),lnx)
CLIP_SRCS += clip_x11.cpp
CLIP_DEFS += -DHAVE_XCB_XLIB_H -DHAVE_PNG_H
CXXLIBS += -lpng
endif
ifeq ($(OS),win)
CLIP_SRCS += clip_win.cpp
endif
ifeq ($(OS),osx)
CLIP_OSX_SRCS := clip_osx.mm
endif
CLIPBOARD_SRCS := clipboard.cpp
CLIP_OBJS := $(patsubst %.cpp,$(PATH_INTERNAL_C)/parts/os/clipboard/clip/%.o,$(CLIP_SRCS))
ifeq ($(OS),osx)
CLIP_OBJS += $(patsubst %.mm,$(PATH_INTERNAL_C)/parts/os/clipboard/clip/%.o,$(CLIP_OSX_SRCS))
endif
CLIPBOARD_OBJS := $(patsubst %.cpp,$(PATH_INTERNAL_C)/parts/os/clipboard/%.o,$(CLIPBOARD_SRCS))
$(PATH_INTERNAL_C)/parts/os/clipboard/clip/%.o: $(PATH_INTERNAL_C)/parts/os/clipboard/clip/%.cpp
$(CXX) -O2 $(CXXFLAGS) $(CLIP_DEFS) -w $< -c -o $@
ifeq ($(OS),osx)
$(PATH_INTERNAL_C)/parts/os/clipboard/clip/%.o: $(PATH_INTERNAL_C)/parts/os/clipboard/clip/%.mm
$(CXX) -O2 $(CXXFLAGS) $(CLIP_DEFS) -w $< -c -o $@
endif
$(PATH_INTERNAL_C)/parts/os/clipboard/%.o: $(PATH_INTERNAL_C)/parts/os/clipboard/%.cpp
$(CXX) -O2 $(CXXFLAGS) $(CLIP_DEFS) -Wall -Wextra $< -c -o $@
CLIPBOARD_LIB := $(PATH_INTERNAL_C)/parts/os/clipboard/clipboard.a
$(CLIPBOARD_LIB): $(CLIP_OBJS) $(CLIPBOARD_OBJS)
$(AR) rcs $@ $(CLIP_OBJS) $(CLIPBOARD_OBJS)
EXE_LIBS += $(CLIPBOARD_LIB)
CLEAN_LIST += $(CLIPBOARD_LIB) $(CLIP_OBJS) $(CLIPBOARD_OBJS)

View file

@ -0,0 +1,184 @@
// Clip Library
// Copyright (c) 2015-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
#include "clip_lock_impl.h"
#include <vector>
#include <stdexcept>
namespace clip {
namespace {
void default_error_handler(ErrorCode code) {
static const char* err[] = {
"Cannot lock clipboard",
"Image format is not supported"
};
throw std::runtime_error(err[static_cast<int>(code)]);
}
} // anonymous namespace
error_handler g_error_handler = default_error_handler;
lock::lock(void* native_window_handle)
: p(new impl(native_window_handle)) {
}
lock::~lock() = default;
bool lock::locked() const {
return p->locked();
}
bool lock::clear() {
return p->clear();
}
bool lock::is_convertible(format f) const {
return p->is_convertible(f);
}
bool lock::set_data(format f, const char* buf, size_t length) {
return p->set_data(f, buf, length);
}
bool lock::get_data(format f, char* buf, size_t len) const {
return p->get_data(f, buf, len);
}
size_t lock::get_data_length(format f) const {
return p->get_data_length(f);
}
#if CLIP_ENABLE_IMAGE
bool lock::set_image(const image& img) {
return p->set_image(img);
}
bool lock::get_image(image& img) const {
return p->get_image(img);
}
bool lock::get_image_spec(image_spec& spec) const {
return p->get_image_spec(spec);
}
#endif // CLIP_ENABLE_IMAGE
format empty_format() { return 0; }
format text_format() { return 1; }
#if CLIP_ENABLE_IMAGE
format image_format() { return 2; }
#endif
bool has(format f) {
lock l;
if (l.locked())
return l.is_convertible(f);
else
return false;
}
bool clear() {
lock l;
if (l.locked())
return l.clear();
else
return false;
}
bool set_text(const std::string& value) {
lock l;
if (l.locked()) {
l.clear();
return l.set_data(text_format(), value.c_str(), value.size());
}
else
return false;
}
bool get_text(std::string& value) {
lock l;
if (!l.locked())
return false;
format f = text_format();
if (!l.is_convertible(f))
return false;
size_t len = l.get_data_length(f);
if (len > 0) {
std::vector<char> buf(len);
l.get_data(f, &buf[0], len);
value = &buf[0];
return true;
}
else {
value.clear();
return true;
}
}
#if CLIP_ENABLE_IMAGE
bool set_image(const image& img) {
lock l;
if (l.locked()) {
l.clear();
return l.set_image(img);
}
else
return false;
}
bool get_image(image& img) {
lock l;
if (!l.locked())
return false;
format f = image_format();
if (!l.is_convertible(f))
return false;
return l.get_image(img);
}
bool get_image_spec(image_spec& spec) {
lock l;
if (!l.locked())
return false;
format f = image_format();
if (!l.is_convertible(f))
return false;
return l.get_image_spec(spec);
}
#endif // CLIP_ENABLE_IMAGE
void set_error_handler(error_handler handler) {
g_error_handler = handler;
}
error_handler get_error_handler() {
return g_error_handler;
}
#ifdef HAVE_XCB_XLIB_H
static int g_x11_timeout = 1000;
void set_x11_wait_timeout(int msecs) { g_x11_timeout = msecs; }
int get_x11_wait_timeout() { return g_x11_timeout; }
#else
void set_x11_wait_timeout(int) { }
int get_x11_wait_timeout() { return 1000; }
#endif
} // namespace clip

View file

@ -0,0 +1,190 @@
// 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_H_INCLUDED
#define CLIP_H_INCLUDED
#pragma once
#include <cassert>
#include <memory>
#include <string>
namespace clip {
// ======================================================================
// Low-level API to lock the clipboard/pasteboard and modify it
// ======================================================================
// Clipboard format identifier.
typedef size_t format;
class image;
struct image_spec;
class lock {
public:
// You can give your current HWND as the "native_window_handle."
// Windows clipboard functions use this handle to open/close
// (lock/unlock) the clipboard. From the MSDN documentation we
// need this handler so SetClipboardData() doesn't fail after a
// EmptyClipboard() call. Anyway it looks to work just fine if we
// call OpenClipboard() with a null HWND.
lock(void* native_window_handle = nullptr);
~lock();
// Returns true if we've locked the clipboard successfully in
// lock() constructor.
bool locked() const;
// Clears the clipboard content. If you don't clear the content,
// previous clipboard content (in unknown formats) could persist
// after the unlock.
bool clear();
// Returns true if the clipboard can be converted to the given
// format.
bool is_convertible(format f) const;
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
// For images
bool set_image(const image& image);
bool get_image(image& image) const;
bool get_image_spec(image_spec& spec) const;
#endif // CLIP_ENABLE_IMAGE
private:
class impl;
std::unique_ptr<impl> p;
};
format register_format(const std::string& name);
// This format is when the clipboard has no content.
format empty_format();
// When the clipboard has UTF8 text.
format text_format();
#if CLIP_ENABLE_IMAGE
// When the clipboard has an image.
format image_format();
#endif
// Returns true if the clipboard has content of the given type.
bool has(format f);
// Clears the clipboard content.
bool clear();
// ======================================================================
// Error handling
// ======================================================================
enum class ErrorCode {
CannotLock,
#if CLIP_ENABLE_IMAGE
ImageNotSupported,
#endif
};
typedef void (*error_handler)(ErrorCode code);
void set_error_handler(error_handler f);
error_handler get_error_handler();
// ======================================================================
// Text
// ======================================================================
// High-level API to put/get UTF8 text in/from the clipboard. These
// functions returns false in case of error.
bool set_text(const std::string& value);
bool get_text(std::string& value);
// ======================================================================
// Image
// ======================================================================
#if CLIP_ENABLE_IMAGE
struct image_spec {
unsigned long width = 0;
unsigned long height = 0;
unsigned long bits_per_pixel = 0;
unsigned long bytes_per_row = 0;
unsigned long red_mask = 0;
unsigned long green_mask = 0;
unsigned long blue_mask = 0;
unsigned long alpha_mask = 0;
unsigned long red_shift = 0;
unsigned long green_shift = 0;
unsigned long blue_shift = 0;
unsigned long alpha_shift = 0;
unsigned long required_data_size() const;
};
// The image data must contain straight RGB values
// (non-premultiplied by alpha). The image retrieved from the
// clipboard will be non-premultiplied too. Basically you will be
// always dealing with straight alpha images.
//
// Details: Windows expects premultiplied images on its clipboard
// content, so the library code make the proper conversion
// automatically. macOS handles straight alpha directly, so there is
// no conversion at all. Linux/X11 images are transferred in
// image/png format which are specified in straight alpha.
class image {
public:
image();
image(const image_spec& spec);
image(const void* data, const image_spec& spec);
image(const image& image);
image(image&& image);
~image();
image& operator=(const image& image);
image& operator=(image&& image);
char* data() const { return m_data; }
const image_spec& spec() const { return m_spec; }
bool is_valid() const { return m_data != nullptr; }
void reset();
private:
void copy_image(const image& image);
void move_image(image&& image);
bool m_own_data;
char* m_data;
image_spec m_spec;
};
// High-level API to set/get an image in/from the clipboard. These
// functions returns false in case of error.
bool set_image(const image& img);
bool get_image(image& img);
bool get_image_spec(image_spec& spec);
#endif // CLIP_ENABLE_IMAGE
// ======================================================================
// Platform-specific
// ======================================================================
// Only for X11: Sets the time (in milliseconds) that we must wait
// for the selection/clipboard owner to receive the content. This
// value is 1000 (one second) by default.
void set_x11_wait_timeout(int msecs);
int get_x11_wait_timeout();
} // namespace clip
#endif // CLIP_H_INCLUDED

View file

@ -0,0 +1,80 @@
// 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.
#ifndef CLIP_COMMON_H_INCLUDED
#define CLIP_COMMON_H_INCLUDED
#pragma once
namespace clip {
namespace details {
#if CLIP_ENABLE_IMAGE
inline void divide_rgb_by_alpha(image& img,
bool hasAlphaGreaterThanZero = false) {
const image_spec& spec = img.spec();
bool hasValidPremultipliedAlpha = true;
for (unsigned long y=0; y<spec.height; ++y) {
const uint32_t* dst = (uint32_t*)(img.data()+y*spec.bytes_per_row);
for (unsigned long x=0; x<spec.width; ++x, ++dst) {
const uint32_t c = *dst;
const int r = ((c & spec.red_mask ) >> spec.red_shift );
const int g = ((c & spec.green_mask) >> spec.green_shift);
const int b = ((c & spec.blue_mask ) >> spec.blue_shift );
const int a = ((c & spec.alpha_mask) >> spec.alpha_shift);
if (a > 0)
hasAlphaGreaterThanZero = true;
if (r > a || g > a || b > a)
hasValidPremultipliedAlpha = false;
}
}
for (unsigned long y=0; y<spec.height; ++y) {
uint32_t* dst = (uint32_t*)(img.data()+y*spec.bytes_per_row);
for (unsigned long x=0; x<spec.width; ++x, ++dst) {
const uint32_t c = *dst;
int r = ((c & spec.red_mask ) >> 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);
// If all alpha values = 0, we make the image opaque.
if (!hasAlphaGreaterThanZero) {
a = 255;
// We cannot change the image spec (e.g. spec.alpha_mask=0) to
// make the image opaque, because the "spec" of the image is
// read-only. The image spec used by the client is the one
// returned by get_image_spec().
}
// If there is alpha information and it's pre-multiplied alpha
else if (hasValidPremultipliedAlpha) {
if (a > 0) {
// Convert it to straight alpha
r = r * 255 / a;
g = g * 255 / a;
b = b * 255 / a;
}
}
*dst =
(r << spec.red_shift ) |
(g << spec.green_shift) |
(b << spec.blue_shift ) |
(a << spec.alpha_shift);
}
}
}
#endif // CLIP_ENABLE_IMAGE
} // namespace details
} // namespace clip
#endif // CLIP_H_INCLUDED

View file

@ -0,0 +1,33 @@
// Clip Library
// Copyright (c) 2015-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef CLIP_LOCK_IMPL_H_INCLUDED
#define CLIP_LOCK_IMPL_H_INCLUDED
namespace clip {
class lock::impl {
public:
impl(void* native_window_handle);
~impl();
bool locked() const { return m_locked; }
bool clear();
bool is_convertible(format f) const;
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;
bool set_image(const image& image);
bool get_image(image& image) const;
bool get_image_spec(image_spec& spec) const;
private:
bool m_locked;
};
} // namespace clip
#endif

View file

@ -0,0 +1,371 @@
// Clip Library
// Copyright (c) 2015-2023 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
#include "clip_common.h"
#include "clip_lock_impl.h"
#include <cassert>
#include <vector>
#include <map>
#include <CoreFoundation/CoreFoundation.h>
#include <Foundation/Foundation.h>
#include <AppKit/AppKit.h>
namespace clip {
namespace {
format g_last_format = 100;
std::map<std::string, format> g_name_to_format;
std::map<format, std::string> g_format_to_name;
#if CLIP_ENABLE_IMAGE
bool get_image_from_clipboard(image* output_img,
image_spec* output_spec)
{
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSString* result = [pasteboard availableTypeFromArray:
[NSArray arrayWithObjects:NSPasteboardTypeTIFF,NSPasteboardTypePNG,nil]];
if (!result)
return false;
NSData* data = [pasteboard dataForType:result];
if (!data)
return false;
NSBitmapImageRep* bitmap = [NSBitmapImageRep imageRepWithData:data];
if ((bitmap.bitmapFormat & NSBitmapFormatFloatingPointSamples) ||
(bitmap.planar)) {
error_handler e = get_error_handler();
if (e)
e(ErrorCode::ImageNotSupported);
return false;
}
image_spec spec;
spec.width = bitmap.pixelsWide;
spec.height = bitmap.pixelsHigh;
spec.bits_per_pixel = bitmap.bitsPerPixel;
spec.bytes_per_row = bitmap.bytesPerRow;
// We need three samples for Red/Green/Blue
if (bitmap.samplesPerPixel >= 3) {
// Here we are guessing the bits per sample (generally 8, not
// sure how many bits per sample macOS uses for 16bpp
// NSBitmapFormat or if this format is even used).
int bits_per_sample = (bitmap.bitsPerPixel == 16 ? 5: 8);
int bits_shift = 0;
// With alpha
if (bitmap.alpha) {
if (bitmap.bitmapFormat & NSBitmapFormatAlphaFirst) {
spec.alpha_shift = 0;
bits_shift += bits_per_sample;
}
else {
spec.alpha_shift = 3*bits_per_sample;
}
}
unsigned long* masks = &spec.red_mask;
unsigned long* shifts = &spec.red_shift;
// Red/green/blue shifts
for (unsigned long* shift=shifts; shift<shifts+3; ++shift) {
*shift = bits_shift;
bits_shift += bits_per_sample;
}
// With alpha
if (bitmap.alpha) {
if (bitmap.bitmapFormat & NSBitmapFormatSixteenBitBigEndian ||
bitmap.bitmapFormat & NSBitmapFormatThirtyTwoBitBigEndian) {
std::swap(spec.red_shift, spec.alpha_shift);
std::swap(spec.green_shift, spec.blue_shift);
}
}
// Without alpha
else {
if (bitmap.bitmapFormat & NSBitmapFormatSixteenBitBigEndian ||
bitmap.bitmapFormat & NSBitmapFormatThirtyTwoBitBigEndian) {
std::swap(spec.red_shift, spec.blue_shift);
}
}
// Calculate all masks
for (unsigned long* shift=shifts, *mask=masks; shift<shifts+4; ++shift, ++mask)
*mask = ((1ul<<bits_per_sample)-1ul) << (*shift);
// Without alpha
if (!bitmap.alpha)
spec.alpha_mask = 0;
}
if (output_spec) {
*output_spec = spec;
}
if (output_img) {
unsigned long size = spec.bytes_per_row*spec.height;
image img(spec);
std::copy(bitmap.bitmapData,
bitmap.bitmapData+size, img.data());
// Convert premultiplied data to unpremultiplied if needed.
if (bitmap.alpha &&
bitmap.samplesPerPixel >= 3 &&
!(bitmap.bitmapFormat & NSBitmapFormatAlphaNonpremultiplied)) {
details::divide_rgb_by_alpha(
img,
true); // hasAlphaGreaterThanZero=true because we have valid alpha information
}
std::swap(*output_img, img);
}
return true;
}
#endif // CLIP_ENABLE_IMAGE
}
lock::impl::impl(void*) : m_locked(true) {
}
lock::impl::~impl() {
}
bool lock::impl::clear() {
@autoreleasepool {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
[pasteboard clearContents];
return true;
}
}
bool lock::impl::is_convertible(format f) const {
@autoreleasepool {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSString* result = nil;
if (f == text_format()) {
result = [pasteboard availableTypeFromArray:[NSArray arrayWithObject:NSPasteboardTypeString]];
}
#if CLIP_ENABLE_IMAGE
else if (f == image_format()) {
result = [pasteboard availableTypeFromArray:
[NSArray arrayWithObjects:NSPasteboardTypeTIFF,NSPasteboardTypePNG,nil]];
}
#endif // CLIP_ENABLE_IMAGE
else {
auto it = g_format_to_name.find(f);
if (it != g_format_to_name.end()) {
const std::string& name = it->second;
NSString* string = [[NSString alloc] initWithBytesNoCopy:(void*)name.c_str()
length:name.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
result = [pasteboard availableTypeFromArray:[NSArray arrayWithObject:string]];
}
}
return (result ? true: false);
}
}
bool lock::impl::set_data(format f, const char* buf, size_t len) {
@autoreleasepool {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
if (f == text_format()) {
NSString* string = [[NSString alloc] initWithBytesNoCopy:(void*)buf
length:len
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
[pasteboard setString:string forType:NSPasteboardTypeString];
return true;
}
else {
auto it = g_format_to_name.find(f);
if (it != g_format_to_name.end()) {
const std::string& formatName = it->second;
NSString* typeString = [[NSString alloc]
initWithBytesNoCopy:(void*)formatName.c_str()
length:formatName.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
NSData* data = [NSData dataWithBytesNoCopy:(void*)buf
length:len
freeWhenDone:NO];
if ([pasteboard setData:data forType:typeString])
return true;
}
}
return false;
}
}
bool lock::impl::get_data(format f, char* buf, size_t len) const {
@autoreleasepool {
assert(buf);
if (!buf || !is_convertible(f))
return false;
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
if (f == text_format()) {
NSString* string = [pasteboard stringForType:NSPasteboardTypeString];
int reqsize = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]+1;
assert(reqsize <= len);
if (reqsize > len) {
// Buffer is too small
return false;
}
if (reqsize == 0)
return true;
memcpy(buf, [string UTF8String], reqsize);
return true;
}
auto it = g_format_to_name.find(f);
if (it == g_format_to_name.end())
return false;
const std::string& formatName = it->second;
NSString* typeString =
[[NSString alloc] initWithBytesNoCopy:(void*)formatName.c_str()
length:formatName.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
NSData* data = [pasteboard dataForType:typeString];
if (!data)
return false;
[data getBytes:buf length:len];
return true;
}
}
size_t lock::impl::get_data_length(format f) const {
@autoreleasepool {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
if (f == text_format()) {
NSString* string = [pasteboard stringForType:NSPasteboardTypeString];
return [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]+1;
}
auto it = g_format_to_name.find(f);
if (it == g_format_to_name.end())
return 0;
const std::string& formatName = it->second;
NSString* typeString =
[[NSString alloc] initWithBytesNoCopy:(void*)formatName.c_str()
length:formatName.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
NSData* data = [pasteboard dataForType:typeString];
if (!data)
return 0;
return data.length;
}
}
#if CLIP_ENABLE_IMAGE
bool lock::impl::set_image(const image& image) {
@autoreleasepool {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
const image_spec& spec = image.spec();
NSBitmapFormat bitmapFormat = 0;
int samples_per_pixel = 0;
if (spec.alpha_mask) {
samples_per_pixel = 4;
if (spec.alpha_shift == 0)
bitmapFormat |= NSBitmapFormatAlphaFirst;
bitmapFormat |= NSBitmapFormatAlphaNonpremultiplied;
}
else if (spec.red_mask || spec.green_mask || spec.blue_mask) {
samples_per_pixel = 3;
}
else {
samples_per_pixel = 1;
}
if (spec.bits_per_pixel == 32)
bitmapFormat |= NSBitmapFormatThirtyTwoBitLittleEndian;
else if (spec.bits_per_pixel == 16)
bitmapFormat |= NSBitmapFormatSixteenBitLittleEndian;
std::vector<unsigned char*> planes(1);
planes[0] = (unsigned char*)image.data();
NSBitmapImageRep* bitmap =
[[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:&planes[0]
pixelsWide:spec.width
pixelsHigh:spec.height
bitsPerSample:spec.bits_per_pixel / samples_per_pixel
samplesPerPixel:samples_per_pixel
hasAlpha:(spec.alpha_mask ? YES: NO)
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bitmapFormat:bitmapFormat
bytesPerRow:spec.bytes_per_row
bitsPerPixel:spec.bits_per_pixel];
if (!bitmap)
return false;
NSData* data = bitmap.TIFFRepresentation;
if (!data)
return false;
if ([pasteboard setData:data forType:NSPasteboardTypeTIFF])
return true;
return false;
}
}
bool lock::impl::get_image(image& img) const {
return get_image_from_clipboard(&img, nullptr);
}
bool lock::impl::get_image_spec(image_spec& spec) const {
return get_image_from_clipboard(nullptr, &spec);
}
#endif // CLIP_ENABLE_IMAGE
format register_format(const std::string& name) {
// Check if the format is already registered
auto it = g_name_to_format.find(name);
if (it != g_name_to_format.end())
return it->second;
format new_format = g_last_format++;
g_name_to_format[name] = new_format;
g_format_to_name[new_format] = name;
return new_format;
}
} // namespace clip

View file

@ -0,0 +1,659 @@
// Clip Library
// Copyright (C) 2015-2020 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
#include "clip_common.h"
#include "clip_lock_impl.h"
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <windows.h>
#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 {
// Data type used as header for custom formats to indicate the exact
// size of the user custom data. This is necessary because it looks
// like GlobalSize() might not return the exact size, but a greater
// value.
typedef uint64_t CustomSizeT;
unsigned long get_shift_from_mask(unsigned long mask) {
unsigned long shift = 0;
for (shift=0; shift<sizeof(unsigned long)*8; ++shift)
if (mask & (1 << shift))
return shift;
return shift;
}
class Hglobal {
public:
Hglobal() : m_handle(nullptr) {
}
explicit Hglobal(HGLOBAL handle) : m_handle(handle) {
}
explicit Hglobal(size_t len) : m_handle(GlobalAlloc(GHND, len)) {
}
~Hglobal() {
if (m_handle)
GlobalFree(m_handle);
}
void release() {
m_handle = nullptr;
}
operator HGLOBAL() {
return m_handle;
}
private:
HGLOBAL m_handle;
};
#if CLIP_ENABLE_IMAGE
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() {
// Use DIBV5 only for 32 bpp uncompressed bitmaps and when all
// masks are valid.
if (IsClipboardFormatAvailable(CF_DIBV5)) {
b5 = (BITMAPV5HEADER*)GetClipboardData(CF_DIBV5);
if (b5 &&
b5->bV5BitCount == 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;
}
}
unsigned long* masks = &spec.red_mask;
unsigned long* shifts = &spec.red_shift;
for (unsigned long* shift=shifts, *mask=masks; shift<shifts+4; ++shift, ++mask) {
if (*mask)
*shift = get_shift_from_mask(*mask);
}
}
};
#endif // CLIP_ENABLE_IMAGE
}
lock::impl::impl(void* hwnd) : m_locked(false) {
for (int i=0; i<5; ++i) {
if (OpenClipboard((HWND)hwnd)) {
m_locked = true;
break;
}
Sleep(20);
}
if (!m_locked) {
error_handler e = get_error_handler();
if (e)
e(ErrorCode::CannotLock);
}
}
lock::impl::~impl() {
if (m_locked)
CloseClipboard();
}
bool lock::impl::clear() {
return (EmptyClipboard() ? true: false);
}
bool lock::impl::is_convertible(format f) const {
if (f == text_format()) {
return
(IsClipboardFormatAvailable(CF_TEXT) ||
IsClipboardFormatAvailable(CF_UNICODETEXT) ||
IsClipboardFormatAvailable(CF_OEMTEXT));
}
#if CLIP_ENABLE_IMAGE
else if (f == image_format()) {
return (IsClipboardFormatAvailable(CF_DIB) ? true: false);
}
#endif // CLIP_ENABLE_IMAGE
else if (IsClipboardFormatAvailable(f))
return true;
else
return false;
}
bool lock::impl::set_data(format f, const char* buf, size_t len) {
bool result = false;
if (f == text_format()) {
if (len > 0) {
int reqsize = MultiByteToWideChar(CP_UTF8, 0, buf, len, NULL, 0);
if (reqsize > 0) {
++reqsize;
Hglobal hglobal(sizeof(WCHAR)*reqsize);
LPWSTR lpstr = static_cast<LPWSTR>(GlobalLock(hglobal));
MultiByteToWideChar(CP_UTF8, 0, buf, len, lpstr, reqsize);
GlobalUnlock(hglobal);
result = (SetClipboardData(CF_UNICODETEXT, hglobal)) ? true: false;
if (result)
hglobal.release();
}
}
}
else {
Hglobal hglobal(len+sizeof(CustomSizeT));
if (hglobal) {
auto dst = (uint8_t*)GlobalLock(hglobal);
if (dst) {
*((CustomSizeT*)dst) = len;
memcpy(dst+sizeof(CustomSizeT), buf, len);
GlobalUnlock(hglobal);
result = (SetClipboardData(f, hglobal) ? true: false);
if (result)
hglobal.release();
}
}
}
return result;
}
bool lock::impl::get_data(format f, char* buf, size_t len) const {
assert(buf);
if (!buf || !is_convertible(f))
return false;
bool result = false;
if (f == text_format()) {
if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
HGLOBAL hglobal = GetClipboardData(CF_UNICODETEXT);
if (hglobal) {
LPWSTR lpstr = static_cast<LPWSTR>(GlobalLock(hglobal));
if (lpstr) {
size_t reqsize =
WideCharToMultiByte(CP_UTF8, 0, lpstr, -1,
nullptr, 0, nullptr, nullptr);
assert(reqsize <= len);
if (reqsize <= len) {
WideCharToMultiByte(CP_UTF8, 0, lpstr, -1,
buf, reqsize, nullptr, nullptr);
result = true;
}
GlobalUnlock(hglobal);
}
}
}
else if (IsClipboardFormatAvailable(CF_TEXT)) {
HGLOBAL hglobal = GetClipboardData(CF_TEXT);
if (hglobal) {
LPSTR lpstr = static_cast<LPSTR>(GlobalLock(hglobal));
if (lpstr) {
// TODO check length
memcpy(buf, lpstr, len);
result = true;
GlobalUnlock(hglobal);
}
}
}
}
else {
if (IsClipboardFormatAvailable(f)) {
HGLOBAL hglobal = GetClipboardData(f);
if (hglobal) {
const SIZE_T total_size = GlobalSize(hglobal);
auto ptr = (const uint8_t*)GlobalLock(hglobal);
if (ptr) {
CustomSizeT reqsize = *((CustomSizeT*)ptr);
// If the registered length of data in the first CustomSizeT
// number of bytes of the hglobal data is greater than the
// GlobalSize(hglobal), something is wrong, it should not
// happen.
assert(reqsize <= total_size);
if (reqsize > total_size)
reqsize = total_size - sizeof(CustomSizeT);
if (reqsize <= len) {
memcpy(buf, ptr+sizeof(CustomSizeT), reqsize);
result = true;
}
GlobalUnlock(hglobal);
}
}
}
}
return result;
}
size_t lock::impl::get_data_length(format f) const {
size_t len = 0;
if (f == text_format()) {
if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
HGLOBAL hglobal = GetClipboardData(CF_UNICODETEXT);
if (hglobal) {
LPWSTR lpstr = static_cast<LPWSTR>(GlobalLock(hglobal));
if (lpstr) {
len =
WideCharToMultiByte(CP_UTF8, 0, lpstr, -1,
nullptr, 0, nullptr, nullptr);
GlobalUnlock(hglobal);
}
}
}
else if (IsClipboardFormatAvailable(CF_TEXT)) {
HGLOBAL hglobal = GetClipboardData(CF_TEXT);
if (hglobal) {
LPSTR lpstr = (LPSTR)GlobalLock(hglobal);
if (lpstr) {
len = strlen(lpstr) + 1;
GlobalUnlock(hglobal);
}
}
}
}
else if (f != empty_format()) {
if (IsClipboardFormatAvailable(f)) {
HGLOBAL hglobal = GetClipboardData(f);
if (hglobal) {
const SIZE_T total_size = GlobalSize(hglobal);
auto ptr = (const uint8_t*)GlobalLock(hglobal);
if (ptr) {
len = *((CustomSizeT*)ptr);
assert(len <= total_size);
if (len > total_size)
len = total_size - sizeof(CustomSizeT);
GlobalUnlock(hglobal);
}
}
}
}
return len;
}
#if CLIP_ENABLE_IMAGE
bool lock::impl::set_image(const image& image) {
const image_spec& spec = image.spec();
// Add the PNG clipboard format for images with alpha channel
// (useful to communicate with some Windows programs that only use
// alpha data from PNG clipboard format)
if (spec.bits_per_pixel == 32 &&
spec.alpha_mask) {
UINT png_format = RegisterClipboardFormatA("PNG");
if (png_format) {
Hglobal png_handle(win::write_png(image));
if (png_handle)
SetClipboardData(png_format, png_handle);
}
}
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 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.width; ++x, ++src_x, ++dst_x) {
uint32_t c = *src_x;
int r = ((c & spec.red_mask ) >> 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);
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<uint32_t> palette(colors);
for (int c=0; c<colors; ++c) {
palette[c] =
(bi.bi->bmiColors[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<spec.width; ++x, ++src, dst+=3) {
int idx = *src;
if (idx < 0)
idx = 0;
else if (idx >= colors)
idx = colors-1;
*((uint32_t*)dst) = palette[idx];
}
}
break;
}
}
std::swap(output_img, img);
return true;
}
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;
}
}
BitmapInfo bi;
if (!bi.is_valid())
return false;
bi.fill_spec(spec);
return true;
}
#endif // CLIP_ENABLE_IMAGE
format register_format(const std::string& name) {
int reqsize = 1+MultiByteToWideChar(CP_UTF8, 0,
name.c_str(), name.size(), NULL, 0);
std::vector<WCHAR> buf(reqsize);
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), name.size(),
&buf[0], reqsize);
// From MSDN, registered clipboard formats are identified by values
// in the range 0xC000 through 0xFFFF.
return (format)RegisterClipboardFormatW(&buf[0]);
}
} // namespace clip

View file

@ -0,0 +1,286 @@
// Clip Library
// Copyright (c) 2020-2022 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
#include <algorithm>
#include <vector>
#include <shlwapi.h>
#include <wincodec.h>
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 T>
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
//////////////////////////////////////////////////////////////////////
// Encode the image as PNG format
bool write_png_on_stream(const image& image,
IStream* stream) {
const image_spec& spec = image.spec();
comptr<IWICBitmapEncoder> 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<IWICBitmapFrameEncode> frame;
comptr<IPropertyBag2> 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<uint32_t> 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.height; ++y) {
auto src_line_start = src;
for (int x=0; x<spec.width; ++x) {
uint32_t c = *src;
*dst = ((((c & spec.red_mask ) >> 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<IStream> 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;
#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<SHCreateMemStreamPtr>(GetProcAddress(shlwapiDll, (LPCSTR)12));
if (!SHCreateMemStream)
return false;
#endif
comptr<IStream> stream(SHCreateMemStream(buf, len));
if (!stream)
return false;
comptr<IWICBitmapDecoder> 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<IWICBitmapFrameDecode> 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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,230 @@
// Clip Library
// Copyright (c) 2018-2021 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
#include <algorithm>
#include <vector>
#include "png.h"
namespace clip {
namespace x11 {
//////////////////////////////////////////////////////////////////////
// Functions to convert clip::image into png data to store it in the
// clipboard.
void write_data_fn(png_structp png, png_bytep buf, png_size_t len) {
std::vector<uint8_t>& output = *(std::vector<uint8_t>*)png_get_io_ptr(png);
const size_t i = output.size();
output.resize(i+len);
std::copy(buf, buf+len, output.begin()+i);
}
bool write_png(const image& image,
std::vector<uint8_t>& output) {
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
nullptr, nullptr, nullptr);
if (!png)
return false;
png_infop info = png_create_info_struct(png);
if (!info) {
png_destroy_write_struct(&png, nullptr);
return false;
}
if (setjmp(png_jmpbuf(png))) {
png_destroy_write_struct(&png, &info);
return false;
}
png_set_write_fn(png,
(png_voidp)&output,
write_data_fn,
nullptr); // No need for a flush function
const image_spec& spec = image.spec();
int color_type = (spec.alpha_mask ?
PNG_COLOR_TYPE_RGB_ALPHA:
PNG_COLOR_TYPE_RGB);
png_set_IHDR(png, info,
spec.width, spec.height, 8, color_type,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_write_info(png, info);
png_set_packing(png);
png_bytep row =
(png_bytep)png_malloc(png, png_get_rowbytes(png, info));
for (png_uint_32 y=0; y<spec.height; ++y) {
const uint32_t* src =
(const uint32_t*)(((const uint8_t*)image.data())
+ y*spec.bytes_per_row);
uint8_t* dst = row;
unsigned int x, c;
for (x=0; x<spec.width; x++) {
c = *(src++);
*(dst++) = (c & spec.red_mask ) >> spec.red_shift;
*(dst++) = (c & spec.green_mask) >> spec.green_shift;
*(dst++) = (c & spec.blue_mask ) >> spec.blue_shift;
if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
*(dst++) = (c & spec.alpha_mask) >> spec.alpha_shift;
}
png_write_rows(png, &row, 1);
}
png_free(png, row);
png_write_end(png, info);
png_destroy_write_struct(&png, &info);
return true;
}
//////////////////////////////////////////////////////////////////////
// Functions to convert png data stored in the clipboard to a
// clip::image.
struct read_png_io {
const uint8_t* buf;
size_t len;
size_t pos;
};
void read_data_fn(png_structp png, png_bytep buf, png_size_t len) {
read_png_io& io = *(read_png_io*)png_get_io_ptr(png);
if (io.pos < io.len) {
size_t n = std::min(len, io.len-io.pos);
if (n > 0) {
std::copy(io.buf+io.pos,
io.buf+io.pos+n,
buf);
io.pos += n;
}
}
}
bool read_png(const uint8_t* buf,
const size_t len,
image* output_image,
image_spec* output_spec) {
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
nullptr, nullptr, nullptr);
if (!png)
return false;
png_infop info = png_create_info_struct(png);
if (!info) {
png_destroy_read_struct(&png, nullptr, nullptr);
return false;
}
if (setjmp(png_jmpbuf(png))) {
png_destroy_read_struct(&png, &info, nullptr);
return false;
}
read_png_io io = { buf, len, 0 };
png_set_read_fn(png, (png_voidp)&io, read_data_fn);
png_read_info(png, info);
png_uint_32 width, height;
int bit_depth, color_type, interlace_type;
png_get_IHDR(png, info, &width, &height,
&bit_depth, &color_type,
&interlace_type,
nullptr, nullptr);
image_spec spec;
spec.width = width;
spec.height = height;
spec.bits_per_pixel = 32;
// Don't use png_get_rowbytes(png, info) here because this is the
// bytes_per_row of the output clip::image (the png file could
// contain 24bpp but we want to return a 32bpp anyway with alpha=255
// in that case).
spec.bytes_per_row = 4*width;
spec.red_mask = 0x000000ff;
spec.green_mask = 0x0000ff00;
spec.blue_mask = 0x00ff0000;
spec.red_shift = 0;
spec.green_shift = 8;
spec.blue_shift = 16;
if ((color_type & PNG_COLOR_MASK_ALPHA) == PNG_COLOR_MASK_ALPHA) {
spec.alpha_mask = 0xff000000;
spec.alpha_shift = 24;
}
else {
spec.alpha_mask = 0;
spec.alpha_shift = 0;
}
if (output_spec)
*output_spec = spec;
if (output_image &&
width > 0 &&
height > 0) {
image img(spec);
// We want RGB 24-bit or RGBA 32-bit as a result
png_set_strip_16(png); // Down to 8-bit (TODO we might support 16-bit values)
png_set_packing(png); // Use one byte if color depth < 8-bit
png_set_expand_gray_1_2_4_to_8(png);
png_set_palette_to_rgb(png);
png_set_gray_to_rgb(png);
png_set_tRNS_to_alpha(png);
int number_passes = png_set_interlace_handling(png);
png_read_update_info(png, info);
const int src_bytes_per_row = png_get_rowbytes(png, info);
png_bytepp rows = (png_bytepp)png_malloc(png, sizeof(png_bytep)*height);
png_uint_32 y;
for (y=0; y<height; ++y)
rows[y] = (png_bytep)png_malloc(png, src_bytes_per_row);
for (int pass=0; pass<number_passes; ++pass)
for (y=0; y<height; ++y)
png_read_rows(png, rows+y, nullptr, 1);
for (y=0; y<height; ++y) {
const uint8_t* src = rows[y];
uint32_t* dst = (uint32_t*)(img.data() + y*spec.bytes_per_row);
unsigned int x, r, g, b, a = 0;
for (x=0; x<width; x++) {
r = *(src++);
g = *(src++);
b = *(src++);
if (spec.alpha_mask)
a = *(src++);
*(dst++) =
(r << spec.red_shift) |
(g << spec.green_shift) |
(b << spec.blue_shift) |
(a << spec.alpha_shift);
}
png_free(png, rows[y]);
}
png_free(png, rows);
std::swap(*output_image, img);
}
png_destroy_read_struct(&png, &info, nullptr);
return true;
}
} // namespace x11
} // namespace clip

View file

@ -0,0 +1,99 @@
// Clip Library
// Copyright (c) 2015-2022 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.h"
namespace clip {
unsigned long image_spec::required_data_size() const
{
unsigned long n = (bytes_per_row * height);
// For 24bpp we add some extra space to access the last pixel (3
// bytes) as an uint32_t
if (bits_per_pixel == 24) {
if ((n % 4) > 0)
n += 4 - (n % 4);
else
++n;
}
return n;
}
image::image()
: m_own_data(false),
m_data(nullptr)
{
}
image::image(const image_spec& spec)
: m_own_data(true),
m_data(new char[spec.required_data_size()]),
m_spec(spec) {
}
image::image(const void* data, const image_spec& spec)
: m_own_data(false),
m_data((char*)data),
m_spec(spec) {
}
image::image(const image& image)
: m_own_data(false),
m_data(nullptr),
m_spec(image.m_spec) {
copy_image(image);
}
image::image(image&& image)
: m_own_data(false),
m_data(nullptr) {
move_image(std::move(image));
}
image::~image() {
reset();
}
image& image::operator=(const image& image) {
copy_image(image);
return *this;
}
image& image::operator=(image&& image) {
move_image(std::move(image));
return *this;
}
void image::reset() {
if (m_own_data) {
delete[] m_data;
m_own_data = false;
m_data = nullptr;
}
}
void image::copy_image(const image& image) {
reset();
m_spec = image.spec();
std::size_t n = m_spec.required_data_size();
m_own_data = true;
m_data = new char[n];
std::copy(image.data(),
image.data()+n,
m_data);
}
void image::move_image(image&& image) {
std::swap(m_own_data, image.m_own_data);
std::swap(m_data, image.m_data);
std::swap(m_spec, image.m_spec);
}
} // namespace clip

View file

@ -0,0 +1,392 @@
//----------------------------------------------------------------------------------------------------------------------
// QB64-PE cross-platform clipboard support
// Powered by clip (https://github.com/dacap/clip)
//----------------------------------------------------------------------------------------------------------------------
#include "libqb-common.h"
// We need 'qbs' and 'image' structs stuff from here. Stop using this when image and friends are refactored
#include "../../../libqb.h"
// 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"
#include "clipboard.h"
#include "error_handle.h"
#define IMAGE_DEBUG 0
#include "image.h"
#include "qbs.h"
#include <vector>
#ifdef QB64_MACOSX
# include <ApplicationServices/ApplicationServices.h>
#endif
extern const img_struct *img; // used by sub__clipboardimage()
extern const img_struct *write_page; // used by func__clipboardimage()
extern const int32_t *page; // used by sub__clipboardimage()
extern const int32_t nextimg; // used by sub__clipboardimage()
extern const uint8_t charset8x8[256][8][8]; // used by sub__clipboardimage()
extern const uint8_t charset8x16[256][16][8]; // used by sub__clipboardimage()
// This is used as a fallback internal clipboard should any text clipboard functions below fail
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)
// We'll use our own clipboard get code on macOS since our requirements are different than what clip supports
PasteboardRef clipboard = nullptr;
OSStatus err = PasteboardCreate(kPasteboardClipboard, &clipboard);
if (err == noErr) {
PasteboardSynchronize(clipboard);
ItemCount itemCount = 0;
err = PasteboardGetItemCount(clipboard, &itemCount);
if (err == noErr) {
for (ItemCount itemIndex = 1; itemIndex <= itemCount; itemIndex++) {
PasteboardItemID itemID = nullptr;
err = PasteboardGetItemIdentifier(clipboard, itemIndex, &itemID);
if (err != noErr)
continue;
CFDataRef flavorData = nullptr;
err = PasteboardCopyItemFlavorData(clipboard, itemID, CFSTR("public.utf8-plain-text"), &flavorData);
if (err == noErr) {
g_InternalClipboard.assign(reinterpret_cast<const char *>(CFDataGetBytePtr(flavorData)), CFDataGetLength(flavorData));
CFRelease(flavorData);
break;
}
}
}
CFRelease(clipboard);
}
#elif defined(QB64_WINDOWS)
// 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 (IsClipboardFormatAvailable(CF_TEXT)) {
HANDLE hClipboardData = GetClipboardData(CF_TEXT);
if (hClipboardData) {
auto pchData = reinterpret_cast<const char *>(GlobalLock(hClipboardData));
if (pchData) {
g_InternalClipboard.assign(pchData, strlen(pchData));
GlobalUnlock(hClipboardData);
}
}
}
CloseClipboard();
}
#else
// clip works like we want on Linux
if (clip::has(clip::text_format()))
clip::get_text(g_InternalClipboard);
#endif
auto qbsText = qbs_new(g_InternalClipboard.length(), 1);
if (qbsText->len)
memcpy(qbsText->chr, g_InternalClipboard.data(), qbsText->len);
return qbsText;
}
/// @brief Sets text to the OS clipboard.
/// @param qbsText A qbs string.
void sub__clipboard(const qbs *qbsText) {
g_InternalClipboard.assign(reinterpret_cast<const char *>(qbsText->chr), qbsText->len);
if (qbsText->len) {
#if defined(QB64_MACOSX)
// 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);
CFRelease(data);
}
}
CFRelease(clipboard);
}
#elif defined(QB64_WINDOWS)
// 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 (hClipboardData) {
auto pchData = reinterpret_cast<uint8_t *>(GlobalLock(hClipboardData));
if (pchData) {
memcpy(pchData, qbsText->chr, qbsText->len);
pchData[qbsText->len] = '\0'; // null terminate
GlobalUnlock(hClipboardData);
SetClipboardData(CF_TEXT, hClipboardData);
}
GlobalFree(hClipboardData);
}
}
CloseClipboard();
}
#else
// clip works like we want on Linux
clip::set_text(g_InternalClipboard);
#endif
}
}
/// @brief Retuns an image handle of an image from the clipboard (if present).
/// @return A valid image handle. Returns -1 if clipboard format is not supported or if there is nothing.
int32_t func__clipboardimage() {
int32_t qb64Img = INVALID_IMAGE_HANDLE; // assume failure
if (is_error_pending())
return qb64Img;
if (clip::has(clip::image_format())) {
clip::image clipImg;
IMAGE_DEBUG_PRINT("Clipboard image found");
if (clip::get_image(clipImg)) {
auto spec = clipImg.spec();
IMAGE_DEBUG_PRINT("Image (%lu x %lu) @ %lubpp", spec.width, spec.height, spec.bits_per_pixel);
if (spec.width && spec.height) {
auto oldDest = func__dest();
// We only support 32bpp images. Images in other formats are converted to 32bpp BGRA
qb64Img = func__newimage(spec.width, spec.height, 32, 1);
if (qb64Img < INVALID_IMAGE_HANDLE) {
sub__dest(qb64Img);
auto dst = write_page->offset32;
IMAGE_DEBUG_PRINT("Converting and copying image");
// Convert and copy the image based on the bpp
switch (spec.bits_per_pixel) {
case 64:
for (uint32_t y = 0; y < spec.height; y++) {
auto src = reinterpret_cast<const uint64_t *>(clipImg.data() + spec.bytes_per_row * y);
for (uint32_t x = 0; x < spec.width; x++, src++) {
auto c = *src;
*dst = image_make_bgra((c & spec.red_mask) >> spec.red_shift >> 8, (c & spec.green_mask) >> spec.green_shift >> 8,
(c & spec.blue_mask) >> spec.blue_shift >> 8, (c & spec.alpha_mask) >> spec.alpha_shift >> 8);
++dst;
}
}
break;
case 32:
// The alpha mask can be zero (which means that the image is just RGB)
if (spec.alpha_mask) {
for (uint32_t y = 0; y < spec.height; y++) {
auto src = reinterpret_cast<const uint32_t *>(clipImg.data() + spec.bytes_per_row * y);
for (uint32_t x = 0; x < spec.width; x++, src++) {
auto c = *src;
*dst = image_make_bgra((c & spec.red_mask) >> spec.red_shift, (c & spec.green_mask) >> spec.green_shift,
(c & spec.blue_mask) >> spec.blue_shift, (c & spec.alpha_mask) >> spec.alpha_shift);
++dst;
}
}
} else {
for (uint32_t y = 0; y < spec.height; y++) {
auto src = reinterpret_cast<const uint32_t *>(clipImg.data() + spec.bytes_per_row * y);
for (uint32_t x = 0; x < spec.width; x++, src++) {
auto c = *src;
*dst = image_make_bgra((c & spec.red_mask) >> spec.red_shift, (c & spec.green_mask) >> spec.green_shift,
(c & spec.blue_mask) >> spec.blue_shift, 255u);
++dst;
}
}
}
break;
case 24:
for (uint32_t y = 0; y < spec.height; y++) {
auto src = reinterpret_cast<const uint8_t *>(clipImg.data() + spec.bytes_per_row * y);
for (uint32_t x = 0; x < spec.width; x++, src += 3) {
auto c = *reinterpret_cast<const uint32_t *>(src);
*dst = image_make_bgra((c & spec.red_mask) >> spec.red_shift, (c & spec.green_mask) >> spec.green_shift,
(c & spec.blue_mask) >> spec.blue_shift, 255u);
++dst;
}
}
break;
case 16:
for (uint32_t y = 0; y < spec.height; y++) {
auto src = reinterpret_cast<const uint16_t *>(clipImg.data() + spec.bytes_per_row * y);
for (uint32_t x = 0; x < spec.width; x++, src++) {
auto c = *src;
*dst = image_make_bgra(image_scale_5bits_to_8bits((c & spec.red_mask) >> spec.red_shift),
image_scale_6bits_to_8bits((c & spec.green_mask) >> spec.green_shift),
image_scale_5bits_to_8bits((c & spec.blue_mask) >> spec.blue_shift), 255u);
++dst;
}
}
}
}
sub__dest(oldDest);
}
}
}
return qb64Img;
}
/// @brief Set the clipboard image using a QB64 image handle.
/// @param src A valid QB64 image handle.
void sub__clipboardimage(int32_t src) {
if (is_error_pending())
return;
// Validation
if (src >= 0) {
validatepage(src);
src = page[src];
} else {
src = -src;
if (src >= nextimg) {
error(QB_ERROR_INVALID_HANDLE);
return;
}
if (!img[src].valid) {
error(QB_ERROR_INVALID_HANDLE);
return;
}
}
// End of validation
// Even though we have color mask and shift support, clip needs the RGBA order :(
clip::image_spec spec;
spec.bits_per_pixel = 32;
spec.red_mask = 0x000000ff;
spec.green_mask = 0x0000ff00;
spec.blue_mask = 0x00ff0000;
spec.alpha_mask = 0xff000000;
spec.red_shift = 0;
spec.green_shift = 8;
spec.blue_shift = 16;
spec.alpha_shift = 24;
std::vector<uint32_t> pixels; // this will hold our converted BGRA32 pixel data
auto srcImg = img[src];
if (srcImg.text) {
IMAGE_DEBUG_PRINT("Rendering text surface to BGRA32");
uint32_t const fontWidth = 8;
uint32_t fontHeight = 16;
if (srcImg.font == 8 || srcImg.font == 14)
fontHeight = srcImg.font;
spec.width = fontWidth * srcImg.width;
spec.height = fontHeight * srcImg.height;
spec.bytes_per_row = spec.width * sizeof(uint32_t);
pixels.resize(spec.width * spec.height);
uint8_t fc, bc, *c = srcImg.offset; // set to the first codepoint
uint8_t const *builtinFont = nullptr;
// Render all text to the raw pixel array
for (uint32_t y = 0; y < spec.height; y += fontHeight) {
for (uint32_t x = 0; x < spec.width; x += fontWidth) {
switch (fontHeight) {
case 8:
builtinFont = &charset8x8[*c][0][0];
break;
case 14:
builtinFont = &charset8x16[*c][1][0];
break;
default: // 16
builtinFont = &charset8x16[*c][0][0];
}
++c; // move to the attribute
fc = *c & 0x0F;
bc = ((*c >> 4) & 7) + ((*c >> 7) << 3);
// Inner codepoint rendering loop
for (uint32_t dy = y, py = 0; py < fontHeight; dy++, py++) {
for (uint32_t dx = x, px = 0; px < fontWidth; dx++, px++) {
pixels[spec.width * dy + dx] = image_swap_red_blue(*builtinFont ? srcImg.pal[fc] : srcImg.pal[bc]);
++builtinFont;
}
}
++c; // move to the next codepoint
}
}
} else {
spec.width = srcImg.width;
spec.height = srcImg.height;
spec.bytes_per_row = spec.width * sizeof(uint32_t);
pixels.resize(spec.width * spec.height);
if (srcImg.bits_per_pixel == 32) {
// BGRA32 pixels
IMAGE_DEBUG_PRINT("Converting BGRA32 image to RGBA32");
auto p = srcImg.offset32;
for (size_t i = 0; i < pixels.size(); i++) {
pixels[i] = image_swap_red_blue(*p);
++p;
}
} else {
// Indexed pixels
IMAGE_DEBUG_PRINT("Converting BGRA32 indexed image to RGBA32");
auto p = srcImg.offset;
for (size_t i = 0; i < pixels.size(); i++) {
pixels[i] = image_swap_red_blue(srcImg.pal[*p]);
++p;
}
}
}
IMAGE_DEBUG_PRINT("Setting clipboard image");
// Send the image off to the OS clipboard
clip::image clipImg(pixels.data(), spec);
clip::set_image(clipImg);
}

View file

@ -1013,21 +1013,21 @@ bool FontRenderTextASCII(int32_t fh, const uint8_t *codepoint, int32_t codepoint
/// @param text The message to build the MD5 hash of
/// @return The generated MD5 hash as hexadecimal string
qbs *func__md5(qbs *text) {
{
MD5_CTX ctx;
unsigned char md5[16];
qbs *res;
int i;
MD5_CTX ctx;
unsigned char md5[16];
qbs *res;
int i;
MD5_Init(&ctx);
if (text->len) MD5_Update(&ctx, text->chr, text->len);
MD5_Final(md5,&ctx);
MD5_Init(&ctx);
if (text->len)
MD5_Update(&ctx, text->chr, text->len);
MD5_Final(md5, &ctx);
res = qbs_new(32, 1);
for (i = 0; i < 16; i++) sprintf((char*)&res->chr[i*2], "%02X", md5[i]);
res = qbs_new(32, 1);
for (i = 0; i < 16; i++)
sprintf((char *)&res->chr[i * 2], "%02X", md5[i]);
return res;
}
return res;
}
/// @brief Return the true font height in pixel
@ -1425,21 +1425,21 @@ void sub__UPrintString(int32_t start_x, int32_t start_y, const qbs *text, int32_
}
}
} else {
uint32_t a = IMAGE_GET_BGRA_ALPHA(write_page->color) + 1;
uint32_t a2 = IMAGE_GET_BGRA_ALPHA(write_page->background_color) + 1;
uint32_t z = IMAGE_GET_BGRA_BGR(write_page->color);
uint32_t z2 = IMAGE_GET_BGRA_BGR(write_page->background_color);
uint32_t a = image_get_bgra_alpha(write_page->color) + 1;
uint32_t a2 = image_get_bgra_alpha(write_page->background_color) + 1;
uint32_t z = image_get_bgra_bgr(write_page->color);
uint32_t z2 = image_get_bgra_bgr(write_page->background_color);
switch (write_page->print_mode) {
case 3: {
float alpha1 = IMAGE_GET_BGRA_ALPHA(write_page->color);
float r1 = IMAGE_GET_BGRA_RED(write_page->color);
float g1 = IMAGE_GET_BGRA_GREEN(write_page->color);
float b1 = IMAGE_GET_BGRA_BLUE(write_page->color);
float alpha2 = IMAGE_GET_BGRA_ALPHA(write_page->background_color);
float r2 = IMAGE_GET_BGRA_RED(write_page->background_color);
float g2 = IMAGE_GET_BGRA_GREEN(write_page->background_color);
float b2 = IMAGE_GET_BGRA_BLUE(write_page->background_color);
float alpha1 = image_get_bgra_alpha(write_page->color);
float r1 = image_get_bgra_red(write_page->color);
float g1 = image_get_bgra_green(write_page->color);
float b1 = image_get_bgra_blue(write_page->color);
float alpha2 = image_get_bgra_alpha(write_page->background_color);
float r2 = image_get_bgra_red(write_page->background_color);
float g2 = image_get_bgra_green(write_page->background_color);
float b2 = image_get_bgra_blue(write_page->background_color);
float dr = r2 - r1;
float dg = g2 - g1;
float db = b2 - b1;
@ -1460,7 +1460,7 @@ void sub__UPrintString(int32_t start_x, int32_t start_y, const qbs *text, int32_
float g3 = g1 + dg * d;
float b3 = b1 + db * d;
pset_and_clip(start_x + pen.x, start_y + pen.y,
IMAGE_MAKE_BGRA(qbr_float_to_long(r3), qbr_float_to_long(g3), qbr_float_to_long(b3), qbr_float_to_long(alpha3)));
image_make_bgra(qbr_float_to_long(r3), qbr_float_to_long(g3), qbr_float_to_long(b3), qbr_float_to_long(alpha3)));
}
}
} break;

View file

@ -1,4 +1,4 @@
//----------------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------
// ___ ___ __ _ _ ___ ___ ___ _ _ _
// / _ \| _ ) / /| | || _ \ __| |_ _|_ __ __ _ __ _ ___ | | (_) |__ _ _ __ _ _ _ _ _
// | (_) | _ \/ _ \_ _| _/ _| | || ' \/ _` / _` / -_) | |__| | '_ \ '_/ _` | '_| || |
@ -46,9 +46,6 @@
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb/stb_image_write.h"
// This is returned to the caller if something goes wrong while loading the image
#define INVALID_IMAGE_HANDLE -1
#ifdef QB64_WINDOWS
# define ZERO_VARIABLE(_v_) ZeroMemory(&(_v_), sizeof(_v_))
#else
@ -372,11 +369,6 @@ static uint32_t *image_decode_from_memory(const uint8_t *data, size_t size, int3
return pixels;
}
/// @brief Clamps a color channel to the range 0 - 255
/// @param n The color component
/// @return The clamped value
static inline uint8_t image_clamp_component(int32_t n) { return n < 0 ? 0 : n > 255 ? 255 : n; }
/// @brief This takes in a 32bpp (BGRA) image raw data and spits out an 8bpp raw image along with it's 256 color (BGRA) palette.
/// @param src32 The source raw image data. This must be in BGRA format and not NULL
/// @param w The width of the image in pixels
@ -408,9 +400,9 @@ static uint8_t *image_convert_8bpp(const uint32_t *src32, int32_t w, int32_t h,
for (auto y = 0; y < h; y++) {
for (auto x = 0; x < w; x++) {
int32_t t = bayerMatrix[((y & 3) << 2) + (x & 3)];
int32_t b = image_clamp_component((*src++) + (t << 1));
int32_t g = image_clamp_component((*src++) + (t << 1));
int32_t r = image_clamp_component((*src++) + (t << 1));
int32_t b = image_clamp_color_component((*src++) + (t << 1));
int32_t g = image_clamp_color_component((*src++) + (t << 1));
int32_t r = image_clamp_color_component((*src++) + (t << 1));
++src; // Ignore alpha
// Quantize
@ -428,9 +420,9 @@ static uint8_t *image_convert_8bpp(const uint32_t *src32, int32_t w, int32_t h,
// Generate a uniform CLUT based on the quantized colors
for (auto i = 0; i < 256; i++) {
if (cubes[i].count) {
paletteOut[i] = IMAGE_MAKE_BGRA(cubes[i].r / cubes[i].count, cubes[i].g / cubes[i].count, cubes[i].b / cubes[i].count, 0xFF);
paletteOut[i] = image_make_bgra(cubes[i].r / cubes[i].count, cubes[i].g / cubes[i].count, cubes[i].b / cubes[i].count, 0xFF);
} else {
paletteOut[i] = IMAGE_MAKE_BGRA(0, 0, 0, 0xFF);
paletteOut[i] = image_make_bgra(0, 0, 0, 0xFF);
}
}
@ -484,19 +476,6 @@ static uint8_t *image_extract_8bpp(const uint32_t *src, int32_t w, int32_t h, ui
return pixels;
}
/// @brief Calculates the distance between 2 RBG points in the RGB color cube
/// @param r1 R1
/// @param g1 G1
/// @param b1 B1
/// @param r2 R2
/// @param g2 G2
/// @param b2 B2
/// @return The distance in floating point
static inline float image_calculate_rgb_distance(uint8_t r1, uint8_t g1, uint8_t b1, uint8_t r2, uint8_t g2, uint8_t b2) {
return sqrt(((float(r2) - float(r1)) * (float(r2) - float(r1))) + ((float(g2) - float(g1)) * (float(g2) - float(g1))) +
((float(b2) - float(b1)) * (float(b2) - float(b1))));
}
/// @brief This modifies an *8bpp* image 'src' to use 'dst_pal' instead of 'src_pal'
/// @param src A pointer to the 8bpp image pixel data. This modifies data 'src' points to and cannot be NULL
/// @param w The width of the image in pixels
@ -515,8 +494,8 @@ static void image_remap_palette(uint8_t *src, int32_t w, int32_t h, const uint32
auto oldDist = maxRGBDist;
for (auto y = 0; y < 256; y++) {
auto newDist = image_calculate_rgb_distance(IMAGE_GET_BGRA_RED(src_pal[x]), IMAGE_GET_BGRA_GREEN(src_pal[x]), IMAGE_GET_BGRA_BLUE(src_pal[x]),
IMAGE_GET_BGRA_RED(dst_pal[y]), IMAGE_GET_BGRA_GREEN(dst_pal[y]), IMAGE_GET_BGRA_BLUE(dst_pal[y]));
auto newDist = image_calculate_rgb_distance(image_get_bgra_red(src_pal[x]), image_get_bgra_green(src_pal[x]), image_get_bgra_blue(src_pal[x]),
image_get_bgra_red(dst_pal[y]), image_get_bgra_green(dst_pal[y]), image_get_bgra_blue(dst_pal[y]));
if (oldDist > newDist) {
oldDist = newDist;
@ -531,11 +510,6 @@ static void image_remap_palette(uint8_t *src, int32_t w, int32_t h, const uint32
}
}
/// @brief Helps convert a BGRA color to an RGBA color and back
/// @param clr A BGRA color or an RGBA color
/// @return An RGBA color or a BGRA color
static inline uint32_t image_swap_red_blue(uint32_t clr) { return ((clr & 0xFF00FF00u) | ((clr & 0x00FF0000u) >> 16) | ((clr & 0x000000FFu) << 16)); }
/// @brief This function loads an image into memory and returns valid LONG image handle values that are less than -1
/// @param qbsFileName The filename or memory buffer (see requirements below) of the image
/// @param bpp 32 = 32bpp, 33 = 32bpp (hardware accelerated), 256=8bpp or 257=8bpp (without palette remap)
@ -866,7 +840,7 @@ void sub__saveimage(qbs *qbsFileName, int32_t imageHandle, qbs *qbsRequirements,
// Inner codepoint rendering loop
for (auto dy = y, py = 0; py < fontHeight; dy++, py++) {
for (auto dx = x, px = 0; px < fontWidth; dx++, px++) {
pixels[width * dy + dx] = image_swap_red_blue(*builtinFont ? palette_256[fc] : palette_256[bc]);
pixels[width * dy + dx] = image_swap_red_blue(*builtinFont ? img[imageHandle].pal[fc] : img[imageHandle].pal[bc]);
++builtinFont;
}
}

View file

@ -1,26 +1,27 @@
#include "audio.h"
#include "bitops.h"
#include "clipboard.h"
#include "command.h"
#include "common.h"
#include "compression.h"
#include "command.h"
#include "datetime.h"
#include "environ.h"
#include "error_handle.h"
#include "event.h"
#include "extended_math.h"
#include "filepath.h"
#include "file-fields.h"
#include "filepath.h"
#include "filesystem.h"
#include "font.h"
#include "gui.h"
#include "hexoctbin.h"
#include "image.h"
#include "qbmath.h"
#include "qbs.h"
#include "qbs-mk-cv.h"
#include "shell.h"
#include "error_handle.h"
#include "mem.h"
#include "qbmath.h"
#include "qbs-mk-cv.h"
#include "qbs.h"
#include "rounding.h"
#include "shell.h"
extern int32 func__cinp(int32 toggle,
int32 passed); // Console INP scan code reader
@ -194,10 +195,6 @@ extern void sub__fullscreen(int32 method, int32 passed);
extern void sub__allowfullscreen(int32 method, int32 smooth);
extern int32 func__fullscreen();
extern int32 func__fullscreensmooth();
extern void sub__clipboard(qbs *);
extern qbs *func__clipboard();
extern int32 func__clipboardimage();
extern void sub__clipboardimage(int32 src);
extern int32 func__exit();
extern void revert_input_check();
extern int32 func__openhost(qbs *);

View file

@ -1,4 +1,4 @@
g++ -no-pie -std=gnu++17 -fno-strict-aliasing -Wno-conversion-null -DFREEGLUT_STATIC -I./internal/c/libqb/include -I./internal/c/parts/core/freeglut/include -I./internal/c/parts/core/glew/include -DDEPENDENCY_SOCKETS -DDEPENDENCY_NO_PRINTER -DDEPENDENCY_ICON -DDEPENDENCY_NO_SCREENIMAGE internal/c/qbx.cpp -c -o internal/c/qbx.o
g++ -no-pie -std=gnu++17 -fno-strict-aliasing -Wno-conversion-null -DFREEGLUT_STATIC -I./internal/c/libqb/include -I./internal/c/parts/core/freeglut/include -I./internal/c/parts/core/glew/include -DDEPENDENCY_SOCKETS -DDEPENDENCY_NO_PRINTER -DDEPENDENCY_ICON -DDEPENDENCY_NO_SCREENIMAGE ./internal/c/libqb_make_000101000.o ./internal/c/qbx.o -o "/home/runner/work/QB64pe/QB64pe/qb64pe" ./internal/c/libqb/src/threading.o ./internal/c/libqb/src/buffer.o ./internal/c/libqb/src/bitops.o ./internal/c/libqb/src/command.o ./internal/c/libqb/src/environ.o ./internal/c/libqb/src/file-fields.o ./internal/c/libqb/src/filepath.o ./internal/c/libqb/src/filesystem.o ./internal/c/libqb/src/datetime.o ./internal/c/libqb/src/error_handle.o ./internal/c/libqb/src/gfs.o ./internal/c/libqb/src/qblist.o ./internal/c/libqb/src/hexoctbin.o ./internal/c/libqb/src/mem.o ./internal/c/libqb/src/math.o ./internal/c/libqb/src/rounding.o ./internal/c/libqb/src/shell.o ./internal/c/libqb/src/qbs.o ./internal/c/libqb/src/qbs_str.o ./internal/c/libqb/src/qbs_cmem.o ./internal/c/libqb/src/qbs_mk_cv.o ./internal/c/libqb/src/string_functions.o ./internal/c/libqb/src/http.o ./internal/c/libqb/src/threading-posix.o ./internal/c/libqb/src/glut-main-thread.o ./internal/c/libqb/src/glut-message.o ./internal/c/libqb/src/glut-msg-queue.o ./internal/c/parts/gui/tinyfiledialogs.o ./internal/c/parts/gui/gui.o ./internal/c/parts/video/font/font.o ./internal/c/parts/video/font/freetype/freetype.a ./internal/c/parts/audio/stub_audio.o ./internal/c/parts/compression/miniz.o ./internal/c/parts/compression/compression.o ./internal/c/parts/core/freeglut.a ./internal/c/parts/core/glew/glew.o -lGL -lGLU -lX11 -lpthread -ldl -lrt -lcurl
g++ -no-pie -std=gnu++17 -fno-strict-aliasing -Wno-conversion-null -DFREEGLUT_STATIC -I./internal/c/libqb/include -I./internal/c/parts/core/freeglut/include -I./internal/c/parts/core/glew/include -DDEPENDENCY_SOCKETS -DDEPENDENCY_NO_PRINTER -DDEPENDENCY_ICON -DDEPENDENCY_NO_SCREENIMAGE ./internal/c/libqb_make_000101000.o ./internal/c/qbx.o -o "/home/runner/work/QB64pe/QB64pe/qb64pe" ./internal/c/libqb/src/threading.o ./internal/c/libqb/src/buffer.o ./internal/c/libqb/src/bitops.o ./internal/c/libqb/src/command.o ./internal/c/libqb/src/environ.o ./internal/c/libqb/src/file-fields.o ./internal/c/libqb/src/filepath.o ./internal/c/libqb/src/filesystem.o ./internal/c/libqb/src/datetime.o ./internal/c/libqb/src/error_handle.o ./internal/c/libqb/src/gfs.o ./internal/c/libqb/src/qblist.o ./internal/c/libqb/src/hexoctbin.o ./internal/c/libqb/src/mem.o ./internal/c/libqb/src/math.o ./internal/c/libqb/src/rounding.o ./internal/c/libqb/src/shell.o ./internal/c/libqb/src/qbs.o ./internal/c/libqb/src/qbs_str.o ./internal/c/libqb/src/qbs_cmem.o ./internal/c/libqb/src/qbs_mk_cv.o ./internal/c/libqb/src/string_functions.o ./internal/c/libqb/src/http.o ./internal/c/libqb/src/threading-posix.o ./internal/c/libqb/src/glut-main-thread.o ./internal/c/libqb/src/glut-message.o ./internal/c/libqb/src/glut-msg-queue.o ./internal/c/parts/gui/tinyfiledialogs.o ./internal/c/parts/gui/gui.o ./internal/c/parts/os/clipboard/clipboard.a ./internal/c/parts/video/font/font.o ./internal/c/parts/video/font/freetype/freetype.a ./internal/c/parts/audio/stub_audio.o ./internal/c/parts/compression/miniz.o ./internal/c/parts/compression/compression.o ./internal/c/parts/core/freeglut.a ./internal/c/parts/core/glew/glew.o -lGL -lGLU -lX11 -lpthread -ldl -lrt -lxcb -lpng -lcurl
objcopy --only-keep-debug "/home/runner/work/QB64pe/QB64pe/qb64pe" "./internal/temp/qb64pe.sym"
objcopy --strip-unneeded "/home/runner/work/QB64pe/QB64pe/qb64pe"

File diff suppressed because it is too large Load diff

View file

@ -122,3 +122,11 @@ This is used by libqb to show alerts and also by the common dialog functions and
| Library | License | License file | Location |
| :------ | :-----: | :----------- | :------- |
| tiny file dialogs | ZLIB | license_tinyfiledialogs.txt | internal/c/parts/gui/ |
## Clipboard Image Support
This is used if you make use of the `_CLIPBOARDIMAGE` function or statement.
| Library | License | License file | Location |
| :------ | :-----: | :----------- | :------- |
| Clip Library | MIT | license_clip.txt | internal/c/parts/os/clipboard/clip/ |

23
licenses/license_clip.txt Normal file
View file

@ -0,0 +1,23 @@
--------------------------------------------------------------------------------
License of Clip Library:
Copyright (c) 2015-2024 David Capello
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -81,19 +81,19 @@ if [ "$DISTRO" == "arch" ]; then
pkg_install
elif [ "$DISTRO" == "linuxmint" ] || [ "$DISTRO" == "ubuntu" ] || [ "$DISTRO" == "debian" ] || [ "$DISTRO" == "zorin" ]; then
echo "Debian based distro detected."
pkg_list="build-essential x11-utils mesa-common-dev libglu1-mesa-dev libasound2-dev zlib1g-dev libcurl4-openssl-dev $GET_WGET"
pkg_list="build-essential x11-utils mesa-common-dev libglu1-mesa-dev libasound2-dev libpng-dev libcurl4-openssl-dev $GET_WGET"
installed_packages=`dpkg -l`
installer_command="sudo apt-get -y install "
pkg_install
elif [ "$DISTRO" == "fedora" ] || [ "$DISTRO" == "redhat" ] || [ "$DISTRO" == "centos" ]; then
echo "Fedora/Redhat based distro detected."
pkg_list="gcc-c++ make mesa-libGLU-devel alsa-lib-devel zlib-devel libcurl-devel $GET_WGET"
pkg_list="gcc-c++ make mesa-libGLU-devel alsa-lib-devel libpng-devel libcurl-devel $GET_WGET"
installed_packages=`yum list installed`
installer_command="sudo yum install "
pkg_install
elif [ "$DISTRO" == "voidlinux" ]; then
echo "VoidLinux detected."
pkg_list="gcc make glu-devel zlib-devel alsa-lib-devel libcurl-devel $GET_WGET"
pkg_list="gcc make glu-devel libpng-devel alsa-lib-devel libcurl-devel $GET_WGET"
installed_packages=`xbps-query -l |grep -v libgcc`
installer_command="sudo xbps-install -Sy "
pkg_install
@ -104,7 +104,7 @@ elif [ -z "$DISTRO" ]; then
echo " OpenGL development libraries"
echo " ALSA development libraries"
echo " GNU C++ Compiler (g++)"
echo " zlib"
echo " libpng"
fi
echo "Compiling and installing QB64-PE..."

View file

@ -12574,7 +12574,6 @@ localpath$ = "internal\c\"
IF DEPENDENCY(DEPENDENCY_GL) THEN makedeps$ = makedeps$ + " DEP_GL=y"
IF DEPENDENCY(DEPENDENCY_SCREENIMAGE) THEN makedeps$ = makedeps$ + " DEP_SCREENIMAGE=y"
IF DEPENDENCY(DEPENDENCY_IMAGE_CODEC) THEN makedeps$ = makedeps$ + " DEP_IMAGE_CODEC=y"
IF DEPENDENCY(DEPENDENCY_CONSOLE_ONLY) THEN makedeps$ = makedeps$ + " DEP_CONSOLE_ONLY=y"
IF DEPENDENCY(DEPENDENCY_SOCKETS) THEN makedeps$ = makedeps$ + " DEP_SOCKETS=y"

View file

@ -0,0 +1,24 @@
$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)
_CLIPBOARD$ = TEXT_HELLO
_CLIPBOARD$ = TEXT_WORLD
PRINT "Set: "; TEXT_WORLD
PRINT "Got: "; _CLIPBOARD$
_CLIPBOARD$ = TEXT_ASCII
IF TEXT_ASCII = _CLIPBOARD$ THEN
PRINT "Test passed"
ELSE
PRINT "Test failed"
END IF
SYSTEM

View file

@ -0,0 +1,3 @@
Set: world
Got: world
Test passed