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

Add macOS custom clipboard text handling code and convert BGRA to RGBA before setting clipboard image

This commit is contained in:
a740g 2024-03-27 23:51:02 +05:30
parent c43b67c987
commit b5de183921
5 changed files with 164 additions and 101 deletions

View file

@ -17,6 +17,7 @@
#pragma once
#include <cmath>
#include <stdint.h>
#include <stdio.h>
@ -41,16 +42,40 @@
// This is returned to the caller if something goes wrong while loading the image
#define INVALID_IMAGE_HANDLE -1
// 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)))
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 constexpr 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

@ -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

@ -18,6 +18,10 @@
#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()
@ -31,9 +35,45 @@ static std::string g_InternalClipboard;
/// @brief Gets text (if present) in the OS clipboard.
/// @return A qbs string.
qbs *func__clipboard() {
#ifdef 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);
}
#else
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);
@ -47,14 +87,32 @@ void sub__clipboard(const qbs *qbsText) {
g_InternalClipboard.assign(reinterpret_cast<const char *>(qbsText->chr), qbsText->len);
if (qbsText->len) {
#ifdef 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);
}
#else
clip::set_text(g_InternalClipboard);
#endif
}
}
static constexpr inline int clipboard_scale_5bits_to_8bits(const int v) { return (v << 3) | (v >> 2); }
static constexpr inline int clipboard_scale_6bits_to_8bits(const int v) { return (v << 2) | (v >> 4); }
/// @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() {
@ -92,7 +150,7 @@ int32_t func__clipboardimage() {
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,
*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;
@ -107,7 +165,7 @@ int32_t func__clipboardimage() {
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,
*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;
@ -118,7 +176,7 @@ int32_t func__clipboardimage() {
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,
*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;
@ -132,7 +190,7 @@ int32_t func__clipboardimage() {
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,
*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;
@ -145,9 +203,9 @@ int32_t func__clipboardimage() {
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(clipboard_scale_5bits_to_8bits((c & spec.red_mask) >> spec.red_shift),
clipboard_scale_6bits_to_8bits((c & spec.green_mask) >> spec.green_shift),
clipboard_scale_5bits_to_8bits((c & spec.blue_mask) >> spec.blue_shift), 255u);
*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;
}
@ -186,18 +244,19 @@ void sub__clipboardimage(int32_t src) {
}
// End of validation
// We'll set this up like QB64's 32bpp BGRA to avoid conversions at our end
// 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 = 0xff0000;
spec.green_mask = 0xff00;
spec.blue_mask = 0xff;
spec.red_mask = 0x000000ff;
spec.green_mask = 0x0000ff00;
spec.blue_mask = 0x00ff0000;
spec.alpha_mask = 0xff000000;
spec.red_shift = 16;
spec.red_shift = 0;
spec.green_shift = 8;
spec.blue_shift = 0;
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) {
@ -211,9 +270,9 @@ void sub__clipboardimage(int32_t src) {
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);
std::vector<uint32_t> pixels(spec.width * spec.height); // this will hold our converted BGRA pixel data
uint8_t fc, bc, *c = srcImg.offset; // set to the first codepoint
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
@ -239,7 +298,7 @@ void sub__clipboardimage(int32_t src) {
// 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] = (*builtinFont ? srcImg.pal[fc] : srcImg.pal[bc]);
pixels[spec.width * dy + dx] = image_swap_red_blue(*builtinFont ? srcImg.pal[fc] : srcImg.pal[bc]);
++builtinFont;
}
}
@ -247,36 +306,38 @@ void sub__clipboardimage(int32_t src) {
++c; // move to the next codepoint
}
}
IMAGE_DEBUG_PRINT("Setting clipboard image (rendered)");
clip::image clipImg(pixels.data(), spec);
clip::set_image(clipImg);
} 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) {
// BGRA pixels
IMAGE_DEBUG_PRINT("Setting clipboard image (raw)");
// BGRA32 pixels
IMAGE_DEBUG_PRINT("Converting BGRA32 image to RGBA32");
clip::image clipImg(srcImg.offset32, spec);
clip::set_image(clipImg);
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 BGRA indexed surface to BGRA32");
IMAGE_DEBUG_PRINT("Converting BGRA32 indexed image to RGBA32");
std::vector<uint32_t> pixels(spec.width * spec.height); // this will hold our converted BGRA pixel data
auto p = srcImg.offset;
for (size_t i = 0; i < pixels.size(); i++) {
pixels[i] = srcImg.pal[*p];
pixels[i] = image_swap_red_blue(srcImg.pal[*p]);
++p;
}
IMAGE_DEBUG_PRINT("Setting clipboard image (converted)");
clip::image clipImg(pixels.data(), spec);
clip::set_image(clipImg);
}
}
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

@ -369,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
@ -405,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
@ -425,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);
}
}
@ -481,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
@ -512,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;
@ -528,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)