This commit is contained in:
Alfie King 2024-06-20 15:11:55 +01:00
parent 07c2db22f0
commit 9a6d9fcad7
22 changed files with 4051 additions and 3 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 2.8)
project(owo)
add_executable(owo owo.cpp)
add_subdirectory(clip)
add_subdirectory(clip-lib)
target_link_libraries(owo clip)

1
clip

@ -1 +0,0 @@
Subproject commit 4e9eeea9293c6f35a1ebac0fd66db7463890f143

98
clip-lib/CMakeLists.txt Normal file
View File

@ -0,0 +1,98 @@
# Clip Library
# Copyright (c) 2015-2024 David Capello
cmake_minimum_required(VERSION 3.5)
project(clip LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
# Use libc++ explicitly so we can compile for
# CMAKE_OSX_DEPLOYMENT_TARGET=10.7 or 10.8
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
endif()
option(CLIP_ENABLE_IMAGE "Compile with support to copy/paste images" on)
if(WIN32)
option(CLIP_ENABLE_LIST_FORMATS "Compile with support to list clipboard formats" off)
endif()
option(CLIP_EXAMPLES "Compile clip examples" on)
option(CLIP_TESTS "Compile clip tests" on)
if(UNIX AND NOT APPLE)
option(CLIP_X11_WITH_PNG "Compile with libpng to support copy/paste image in png format" on)
endif()
add_library(clip clip.cpp)
if(CLIP_ENABLE_IMAGE)
target_sources(clip PRIVATE image.cpp)
target_compile_definitions(clip PUBLIC -DCLIP_ENABLE_IMAGE=1)
endif()
if(CLIP_ENABLE_LIST_FORMATS)
target_compile_definitions(clip PUBLIC -DCLIP_ENABLE_LIST_FORMATS=1)
endif()
if(WIN32)
option(CLIP_SUPPORT_WINXP "Enable Windows XP support" OFF)
target_sources(clip PRIVATE clip_win.cpp)
if(CLIP_ENABLE_IMAGE)
target_sources(clip PRIVATE clip_win_bmp.cpp clip_win_wic.cpp)
target_link_libraries(clip shlwapi)
endif()
if(MSVC)
target_compile_definitions(clip PRIVATE -D_SCL_SECURE_NO_WARNINGS)
endif()
if (CLIP_SUPPORT_WINXP)
target_compile_definitions(clip PRIVATE -DCLIP_SUPPORT_WINXP)
endif()
# MinGW requires the windowscodecs just because CLSIDs are defined
# in the windowscodecs.a file instead of the wincodec.h file (?!)
if(MINGW)
find_library(CLIP_WINDOWSCODECS_LIBRARY windowscodecs)
if(CLIP_WINDOWSCODECS_LIBRARY)
target_link_libraries(clip ${CLIP_WINDOWSCODECS_LIBRARY})
endif()
endif()
elseif(APPLE)
target_compile_options(clip PRIVATE -fobjc-arc)
find_library(COCOA_LIBRARY Cocoa)
if(COCOA_LIBRARY)
target_sources(clip PRIVATE clip_osx.mm)
target_link_libraries(clip ${COCOA_LIBRARY})
else()
target_sources(clip PRIVATE clip_none.cpp)
endif()
elseif(UNIX)
include(CheckIncludeFiles)
check_include_files(xcb/xcb.h HAVE_XCB_XLIB_H)
if(HAVE_XCB_XLIB_H)
target_compile_definitions(clip PRIVATE -DHAVE_XCB_XLIB_H)
target_link_libraries(clip xcb pthread)
if(CLIP_ENABLE_IMAGE AND CLIP_X11_WITH_PNG)
check_include_files(png.h HAVE_PNG_H)
if(CLIP_X11_PNG_LIBRARY)
set(PNG_LIBRARY ${CLIP_X11_PNG_LIBRARY})
else()
find_library(PNG_LIBRARY png)
endif()
if(HAVE_PNG_H AND PNG_LIBRARY)
target_compile_definitions(clip PRIVATE -DHAVE_PNG_H)
endif()
target_link_libraries(clip ${PNG_LIBRARY})
endif()
target_sources(clip PRIVATE clip_x11.cpp)
else()
target_sources(clip PRIVATE clip_none.cpp)
endif()
else()
target_sources(clip PRIVATE clip_none.cpp)
endif()

5
clip-lib/CONTRIBUTING.md Normal file
View File

@ -0,0 +1,5 @@
By submitting a pull request, you represent that you have the right to
license your contribution to the Clip project owners and the community,
agree by submitting the patch that your contributions are licensed under
the [Clip license](https://raw.githubusercontent.com/dacap/clip/main/LICENSE.txt),
and agree to future changes to the licensing.

77
clip-lib/README.md Normal file
View File

@ -0,0 +1,77 @@
# Clip Library
*Copyright (c) 2015-2024 David Capello*
[![build](https://github.com/dacap/clip/workflows/build/badge.svg)](https://github.com/dacap/clip/actions?query=workflow%3Abuild)
[![MIT Licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.txt)
Library to copy/retrieve content to/from the clipboard/pasteboard.
## Features
Available features on Windows, macOS, and Linux (X11):
* Copy/paste UTF-8 text.
* Copy/paste user-defined data.
* Copy/paste RGB/RGBA images. This library use non-premultiplied alpha RGB values.
## Example
```cpp
#include "clip.h"
#include <iostream>
int main() {
clip::set_text("Hello World");
std::string value;
clip::get_text(value);
std::cout << value << "\n";
}
```
## User-defined clipboard formats
```cpp
#include "clip.h"
int main() {
clip::format my_format =
clip::register_format("com.appname.FormatName");
int value = 32;
clip::lock l;
l.clear();
l.set_data(clip::text_format(), "Alternative text for value 32");
l.set_data(my_format, &value, sizeof(int));
}
```
## Platform specific details
* If two versions of your application (32-bit and 64-bit) can run at
at the same time, remember to avoid storing data types that could
change depending on the platform (e.g. `size_t`) in your custom
format data.
* **Windows**:
- [Limited number of clipboard formats on Windows](http://blogs.msdn.com/b/oldnewthing/archive/2015/03/19/10601208.aspx)
* **Linux**:
- To be able to copy/paste on Linux you need `libx11-dev`/`libX11-devel` package.
- To copy/paste images you will need `libpng-dev`/`libpng-devel` package.
## Compilation Flags
* `CLIP_ENABLE_IMAGE`: Enables the support to
[copy](examples/put_image.cpp)/[paste](examples/show_image.cpp) images.
* `CLIP_ENABLE_LIST_FORMATS` (only for Windows): Enables the
`clip::lock::list_formats()` API function and the
[list_clip_formats](examples/list_clip_formats.cpp) example.
* `CLIP_EXAMPLES`: Compile [examples](examples/).
* `CLIP_TESTS`: Compile [tests](tests/).
* `CLIP_X11_WITH_PNG` (only for Linux/X11): Enables support to
copy/paste images using the `libpng` library on Linux.
## Who is using this library?
[Check the wiki](https://github.com/dacap/clip/wiki#who-is-using-clip)
to know what projects are using the `clip` library.

192
clip-lib/clip.cpp Normal file
View File

@ -0,0 +1,192 @@
// Clip Library
// Copyright (c) 2015-2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip.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
#if CLIP_ENABLE_LIST_FORMATS
std::vector<format_info> lock::list_formats() const {
return p->list_formats();
}
#endif // CLIP_ENABLE_LIST_FORMATS
format empty_format() { return 0; }
format text_format() { return 1; }
#if CLIP_ENABLE_IMAGE
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

211
clip-lib/clip.h Normal file
View File

@ -0,0 +1,211 @@
// 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>
#include <vector>
namespace clip {
// ======================================================================
// Low-level API to lock the clipboard/pasteboard and modify it
// ======================================================================
// Clipboard format identifier.
typedef size_t format;
#if CLIP_ENABLE_IMAGE
class image;
struct image_spec;
#endif // CLIP_ENABLE_IMAGE
#if CLIP_ENABLE_LIST_FORMATS
struct format_info {
format id = 0;
std::string name;
format_info(const format id,
const std::string& name)
: id(id),
name(name) {
}
};
#endif // CLIP_ENABLE_LIST_FORMATS
class lock {
public:
// 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
#if CLIP_ENABLE_LIST_FORMATS
// Returns the list of available formats (by name) in the
// clipboard.
std::vector<format_info> list_formats() const;
#endif // CLIP_ENABLE_LIST_FORMATS
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

82
clip-lib/clip_common.h Normal file
View File

@ -0,0 +1,82 @@
// 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
#include "clip.h"
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

40
clip-lib/clip_lock_impl.h Normal file
View File

@ -0,0 +1,40 @@
// 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_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;
#if CLIP_ENABLE_IMAGE
bool set_image(const image& image);
bool get_image(image& image) const;
bool get_image_spec(image_spec& spec) const;
#endif // CLIP_ENABLE_IMAGE
#if CLIP_ENABLE_LIST_FORMATS
std::vector<format_info> list_formats() const;
#endif // CLIP_ENABLE_LIST_FORMATS
private:
bool m_locked;
};
} // namespace clip
#endif

86
clip-lib/clip_none.cpp Normal file
View File

@ -0,0 +1,86 @@
// 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 <cassert>
#include <map>
#include <vector>
namespace clip {
typedef std::vector<char> Buffer;
typedef std::map<format, Buffer> Map;
static format g_last_format = 100; // TODO create an enum with common formats
static Map g_data;
lock::impl::impl(void* native_handle) : m_locked(true) {
}
lock::impl::~impl() {
}
bool lock::impl::clear() {
g_data.clear();
return true;
}
bool lock::impl::is_convertible(format f) const {
return (g_data.find(f) != g_data.end());
}
bool lock::impl::set_data(format f, const char* buf, size_t len) {
Buffer& dst = g_data[f];
dst.resize(len);
if (buf && len > 0)
std::copy(buf, buf+len, dst.begin());
if (f == text_format() &&
len > 0 && dst.back() != 0) {
dst.push_back(0);
}
return true;
}
bool lock::impl::get_data(format f, char* buf, size_t len) const {
assert(buf);
if (!buf || !is_convertible(f))
return false;
const Buffer& src = g_data[f];
std::copy(src.begin(), src.end(), buf);
return true;
}
size_t lock::impl::get_data_length(format f) const {
if (is_convertible(f))
return g_data[f].size();
else
return 0;
}
bool lock::impl::set_image(const image& image) {
return false; // TODO
}
bool lock::impl::get_image(image& image) const {
return false; // TODO
}
bool lock::impl::get_image_spec(image_spec& spec) const {
return false; // TODO
}
format register_format(const std::string& name) {
return g_last_format++;
}
} // namespace clip

35
clip-lib/clip_osx.h Normal file
View File

@ -0,0 +1,35 @@
// Clip Library
// Copyright (c) 2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef CLIP_OSX_H_INCLUDED
#define CLIP_OSX_H_INCLUDED
#pragma once
#ifdef __OBJC__
#include <Cocoa/Cocoa.h>
namespace clip {
class image;
struct image_spec;
namespace osx {
#if CLIP_ENABLE_IMAGE
bool get_image_from_clipboard(NSPasteboard* pasteboard,
image* output_img,
image_spec* output_spec);
#endif // CLIP_ENABLE_IMAGE
} // namespace osx
} // namespace clip
#endif
#endif

377
clip-lib/clip_osx.mm Normal file
View File

@ -0,0 +1,377 @@
// 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;
}
namespace osx {
#if CLIP_ENABLE_IMAGE
bool get_image_from_clipboard(NSPasteboard* pasteboard,
image* output_img,
image_spec* output_spec)
{
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
} // namespace osx
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 {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
return osx::get_image_from_clipboard(pasteboard, &img, nullptr);
}
bool lock::impl::get_image_spec(image_spec& spec) const {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
return osx::get_image_from_clipboard(pasteboard, 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

407
clip-lib/clip_win.cpp Normal file
View File

@ -0,0 +1,407 @@
// Clip Library
// Copyright (C) 2015-2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip_win.h"
#include "clip.h"
#include "clip_lock_impl.h"
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
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;
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;
};
// From: https://issues.chromium.org/issues/40080988#comment8
//
// "Adds impersonation of the anonymous token around calls to the
// CloseClipboard() system call. On Windows 8+ the win32k driver
// captures the access token of the caller and makes it available to
// other users on the desktop through the system call
// GetClipboardAccessToken(). This introduces a risk of privilege
// escalation in sandboxed processes. By performing the
// impersonation then whenever Chrome writes data to the clipboard
// only the anonymous token is available."
//
class AnonymousTokenImpersonator {
public:
AnonymousTokenImpersonator()
: m_must_revert(ImpersonateAnonymousToken(GetCurrentThread()))
{}
~AnonymousTokenImpersonator() {
if (m_must_revert)
RevertToSelf();
}
private:
const bool m_must_revert;
};
} // anonymous namespace
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) {
AnonymousTokenImpersonator guard;
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) ||
win::wic_image_format_available(nullptr) != nullptr);
}
#endif // CLIP_ENABLE_IMAGE
else
return IsClipboardFormatAvailable(f);
}
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_LIST_FORMATS
std::vector<format_info> lock::impl::list_formats() const {
static const char* standard_formats[CF_MAX] = {
"", "CF_TEXT", "CF_BITMAP", "CF_METAFILEPICT",
"CF_SYLK", "CF_DIF", "CF_TIFF", "CF_OEMTEXT",
"CF_DIB", "CF_PALETTE", "CF_PENDATA", "CF_RIFF",
"CF_WAVE", "CF_UNICODETEXT", "CF_ENHMETAFILE", "CF_HDROP",
"CF_LOCALE", "CF_DIBV5"
};
std::vector<format_info> formats;
std::vector<char> format_name(512);
formats.reserve(CountClipboardFormats());
UINT format_id = EnumClipboardFormats(0);
while (format_id != 0) {
if (format_id >= CF_TEXT && format_id < CF_MAX) {
// Standard clipboard format
formats.emplace_back(format_id, standard_formats[format_id]);
}
// Get user-defined format name
else {
int size = GetClipboardFormatNameA(
format_id,
format_name.data(),
format_name.size());
formats.emplace_back(format_id, std::string(format_name.data(), size));
}
format_id = EnumClipboardFormats(format_id);
}
return formats;
}
#endif // CLIP_ENABLE_LIST_FORMATS
#if CLIP_ENABLE_IMAGE
bool lock::impl::set_image(const image& image) {
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);
}
}
Hglobal hmem(clip::win::create_dibv5(image));
if (!hmem)
return false;
SetClipboardData(CF_DIBV5, hmem);
return true;
}
bool lock::impl::get_image(image& output_img) const {
// Tries to get the first image format that can be read using WIC
// ("PNG", "JPG", "GIF", etc).
UINT cbformat;
if (auto read_img = win::wic_image_format_available(&cbformat)) {
HANDLE handle = GetClipboardData(cbformat);
if (handle) {
size_t size = GlobalSize(handle);
uint8_t* data = (uint8_t*)GlobalLock(handle);
bool result = read_img(data, size, &output_img, nullptr);
GlobalUnlock(handle);
if (result)
return true;
}
}
// If we couldn't find any, we try to use the regular DIB format.
win::BitmapInfo bi;
return bi.to_image(output_img);
}
bool lock::impl::get_image_spec(image_spec& spec) const {
UINT cbformat;
if (auto read_img = win::wic_image_format_available(&cbformat)) {
HANDLE handle = GetClipboardData(cbformat);
if (handle) {
size_t size = GlobalSize(handle);
uint8_t* data = (uint8_t*)GlobalLock(handle);
bool result = read_img(data, size, nullptr, &spec);
GlobalUnlock(handle);
if (result)
return true;
}
}
win::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

26
clip-lib/clip_win.h Normal file
View File

@ -0,0 +1,26 @@
// Clip Library
// Copyright (c) 2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef CLIP_WIN_H_INCLUDED
#define CLIP_WIN_H_INCLUDED
#pragma once
#include <windows.h>
#ifndef LCS_WINDOWS_COLOR_SPACE
#define LCS_WINDOWS_COLOR_SPACE 'Win '
#endif
#ifndef CF_DIBV5
#define CF_DIBV5 17
#endif
#if CLIP_ENABLE_IMAGE
#include "clip_win_bmp.h"
#include "clip_win_wic.h"
#endif
#endif // CLIP_WIN_H_INCLUDED

347
clip-lib/clip_win_bmp.cpp Normal file
View File

@ -0,0 +1,347 @@
// Clip Library
// Copyright (c) 2015-2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip_win_bmp.h"
#include "clip.h"
#include "clip_common.h"
#include <vector>
namespace clip {
namespace win {
namespace {
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;
}
} // anonymous namespace
BitmapInfo::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 (load_from(b5))
return;
}
if (IsClipboardFormatAvailable(CF_DIB)) {
bi = (BITMAPINFO*)GetClipboardData(CF_DIB);
load_from(bi);
}
}
bool BitmapInfo::load_from(BITMAPV5HEADER* b5) {
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 true;
}
return false;
}
bool BitmapInfo::load_from(BITMAPINFO* bi) {
if (!bi)
return false;
width = bi->bmiHeader.biWidth;
height = bi->bmiHeader.biHeight;
bit_count = bi->bmiHeader.biBitCount;
compression = bi->bmiHeader.biCompression;
if (compression == BI_BITFIELDS) {
red_mask = *((uint32_t*)&bi->bmiColors[0]);
green_mask = *((uint32_t*)&bi->bmiColors[1]);
blue_mask = *((uint32_t*)&bi->bmiColors[2]);
if (bit_count == 32)
alpha_mask = 0xff000000;
return true;
}
if (compression == BI_RGB) {
switch (bit_count) {
case 32:
red_mask = 0xff0000;
green_mask = 0xff00;
blue_mask = 0xff;
alpha_mask = 0xff000000;
break;
case 24:
case 8: // We return 8bpp images as 24bpp
red_mask = 0xff0000;
green_mask = 0xff00;
blue_mask = 0xff;
break;
case 16:
red_mask = 0x7c00;
green_mask = 0x03e0;
blue_mask = 0x001f;
break;
}
return true;
}
return false;
}
BitmapInfo::BitmapInfo(BITMAPV5HEADER* pb5) {
if (load_from(pb5))
b5 = pb5;
}
BitmapInfo::BitmapInfo(BITMAPINFO* pbi) {
if (load_from(pbi))
bi = pbi;
}
void BitmapInfo::fill_spec(image_spec& spec) const {
spec.width = width;
spec.height = (height >= 0 ? height: -height);
// We convert indexed to 24bpp RGB images to match the OS X behavior
spec.bits_per_pixel = bit_count;
if (spec.bits_per_pixel <= 8)
spec.bits_per_pixel = 24;
spec.bytes_per_row = width*((spec.bits_per_pixel+7)/8);
spec.red_mask = red_mask;
spec.green_mask = green_mask;
spec.blue_mask = blue_mask;
spec.alpha_mask = alpha_mask;
switch (spec.bits_per_pixel) {
case 24: {
// We need one extra byte to avoid a crash updating the last
// pixel on last row using:
//
// *((uint32_t*)ptr) = pixel24bpp;
//
++spec.bytes_per_row;
// Align each row to 32bpp
int padding = (4-(spec.bytes_per_row&3))&3;
spec.bytes_per_row += padding;
break;
}
case 16: {
int padding = (4-(spec.bytes_per_row&3))&3;
spec.bytes_per_row += padding;
break;
}
}
unsigned long* masks = &spec.red_mask;
unsigned long* shifts = &spec.red_shift;
for (unsigned long* shift=shifts, *mask=masks; shift<shifts+4; ++shift, ++mask) {
if (*mask)
*shift = get_shift_from_mask(*mask);
}
}
bool BitmapInfo::to_image(image& output_img) const {
if (!is_valid()) {
// There is no valid image. Maybe because there is no image at all
// in the clipboard when using the BitmapInfo default
// constructor. No need to report this as an error, just return
// false.
return false;
}
image_spec spec;
fill_spec(spec);
image img(spec);
switch (bit_count) {
case 32:
case 24:
case 16: {
const uint8_t* src = nullptr;
if (compression == BI_RGB ||
compression == BI_BITFIELDS) {
if (b5)
src = ((uint8_t*)b5) + b5->bV5Size;
else
src = ((uint8_t*)bi) + bi->bmiHeader.biSize;
if (compression == BI_BITFIELDS)
src += sizeof(RGBQUAD)*3;
}
if (src) {
const int src_bytes_per_row = spec.width*((bit_count+7)/8);
const int padding = (4-(src_bytes_per_row&3))&3;
for (long y=spec.height-1; y>=0; --y, src+=src_bytes_per_row+padding) {
char* dst = img.data()+y*spec.bytes_per_row;
std::copy(src, src+src_bytes_per_row, dst);
}
}
// Windows uses premultiplied RGB values, and we use straight
// alpha. So we have to divide all RGB values by its alpha.
if (bit_count == 32 && spec.alpha_mask) {
details::divide_rgb_by_alpha(img);
}
break;
}
case 8: {
assert(bi);
const int colors = (bi->bmiHeader.biClrUsed > 0 ? bi->bmiHeader.biClrUsed: 256);
std::vector<uint32_t> palette(colors);
for (int c=0; c<colors; ++c) {
palette[c] =
(bi->bmiColors[c].rgbRed << spec.red_shift) |
(bi->bmiColors[c].rgbGreen << spec.green_shift) |
(bi->bmiColors[c].rgbBlue << spec.blue_shift);
}
const uint8_t* src = (((uint8_t*)bi) + bi->bmiHeader.biSize + sizeof(RGBQUAD)*colors);
const int padding = (4-(spec.width&3))&3;
for (long y=spec.height-1; y>=0; --y, src+=padding) {
char* dst = img.data()+y*spec.bytes_per_row;
for (unsigned long x=0; x<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;
}
HGLOBAL create_dibv5(const image& image) {
const image_spec& spec = image.spec();
image_spec out_spec = spec;
int palette_colors = 0;
int padding = 0;
switch (spec.bits_per_pixel) {
case 24: padding = (4-((spec.width*3)&3))&3; break;
case 16: padding = ((4-((spec.width*2)&3))&3)/2; break;
case 8: padding = (4-(spec.width&3))&3; break;
}
out_spec.bytes_per_row += padding;
// Create the BITMAPV5HEADER structure
HGLOBAL hmem =
GlobalAlloc(
GHND,
sizeof(BITMAPV5HEADER)
+ palette_colors*sizeof(RGBQUAD)
+ out_spec.bytes_per_row*out_spec.height);
if (!hmem)
return nullptr;
out_spec.red_mask = 0x00ff0000;
out_spec.green_mask = 0xff00;
out_spec.blue_mask = 0xff;
out_spec.alpha_mask = 0xff000000;
out_spec.red_shift = 16;
out_spec.green_shift = 8;
out_spec.blue_shift = 0;
out_spec.alpha_shift = 24;
BITMAPV5HEADER* bi = (BITMAPV5HEADER*)GlobalLock(hmem);
bi->bV5Size = sizeof(BITMAPV5HEADER);
bi->bV5Width = out_spec.width;
bi->bV5Height = out_spec.height;
bi->bV5Planes = 1;
bi->bV5BitCount = (WORD)out_spec.bits_per_pixel;
bi->bV5Compression = BI_RGB;
bi->bV5SizeImage = out_spec.bytes_per_row*spec.height;
bi->bV5RedMask = out_spec.red_mask;
bi->bV5GreenMask = out_spec.green_mask;
bi->bV5BlueMask = out_spec.blue_mask;
bi->bV5AlphaMask = out_spec.alpha_mask;
bi->bV5CSType = LCS_WINDOWS_COLOR_SPACE;
bi->bV5Intent = LCS_GM_GRAPHICS;
bi->bV5ClrUsed = 0;
switch (spec.bits_per_pixel) {
case 32: {
const char* src = image.data();
char* dst = (((char*)bi)+bi->bV5Size) + (out_spec.height-1)*out_spec.bytes_per_row;
for (long y=spec.height-1; y>=0; --y) {
const uint32_t* src_x = (const uint32_t*)src;
uint32_t* dst_x = (uint32_t*)dst;
for (unsigned long x=0; x<spec.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:
GlobalUnlock(hmem);
GlobalFree(hmem);
error_handler e = get_error_handler();
if (e)
e(ErrorCode::ImageNotSupported);
return nullptr;
}
GlobalUnlock(hmem);
return hmem;
}
} // namespace win
} // namespace clip

66
clip-lib/clip_win_bmp.h Normal file
View File

@ -0,0 +1,66 @@
// Clip Library
// Copyright (c) 2015-2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef CLIP_WIN_BMP_H_INCLUDED
#define CLIP_WIN_BMP_H_INCLUDED
#pragma once
#if !CLIP_ENABLE_IMAGE
#error This file can be include only when CLIP_ENABLE_IMAGE is defined
#endif
#include <cstdint>
#include <windows.h>
namespace clip {
class image;
struct image_spec;
namespace win {
struct BitmapInfo {
BITMAPV5HEADER* b5 = nullptr;
BITMAPINFO* bi = nullptr;
int width = 0;
int height = 0;
uint16_t bit_count = 0;
uint32_t compression = 0;
uint32_t red_mask = 0;
uint32_t green_mask = 0;
uint32_t blue_mask = 0;
uint32_t alpha_mask = 0;
BitmapInfo();
explicit BitmapInfo(BITMAPV5HEADER* pb5);
explicit BitmapInfo(BITMAPINFO* pbi);
bool is_valid() const {
return (b5 || bi);
}
void fill_spec(image_spec& spec) const;
// Fills the output_img with the data provided by this
// BitmapInfo. Returns true if it was able to fill the output image
// or false otherwise.
bool to_image(image& output_img) const;
private:
bool load_from(BITMAPV5HEADER* b5);
bool load_from(BITMAPINFO* bi);
};
// Returns a handle to the HGLOBAL memory reserved to create a DIBV5
// based on the image passed by parameter. Returns null if it cannot
// create the handle.
HGLOBAL create_dibv5(const image& image);
} // namespace win
} // namespace clip
#endif // CLIP_WIN_BMP_H_INCLUDED

472
clip-lib/clip_win_wic.cpp Normal file
View File

@ -0,0 +1,472 @@
// Clip Library
// Copyright (c) 2020-2024 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "clip_win_wic.h"
#include "clip.h"
#include <algorithm>
#include <vector>
#include <shlwapi.h>
#include <wincodec.h>
namespace clip {
namespace win {
namespace {
// Successful calls to CoInitialize() (S_OK or S_FALSE) must match
// the calls to CoUninitialize().
// From: https://docs.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-couninitialize#remarks
struct coinit {
HRESULT hr;
coinit() {
hr = CoInitialize(nullptr);
}
~coinit() {
if (hr == S_OK || hr == S_FALSE)
CoUninitialize();
}
};
template<class 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
struct WicImageFormat {
const char* names[3]; // Alternative names of this format
UINT ids[3]; // Clipboard format ID for each name of this format
ReadWicImageFormatFunc read; // Function used to decode data in this format
};
WicImageFormat wic_image_formats[] = {
{ { "PNG", "image/png", nullptr }, { 0, 0, 0 }, read_png },
{ { "JPG", "image/jpeg", "JPEG" }, { 0, 0, 0 }, read_jpg },
{ { "BMP", "image/bmp", nullptr }, { 0, 0, 0 }, read_bmp },
{ { "GIF", "image/gif", nullptr }, { 0, 0, 0 }, read_gif }
};
} // anonymous namespace
ReadWicImageFormatFunc wic_image_format_available(UINT* output_cbformat) {
for (auto& fmt : wic_image_formats) {
for (int i=0; i<3; ++i) {
const char* name = fmt.names[i];
if (!name)
break;
// Although RegisterClipboardFormatA() already returns the same
// value for the same "name" (even for different apps), we
// prefer to cache the value to avoid calling
// RegisterClipboardFormatA() several times (as internally that
// function must do some kind of hash map name -> ID
// conversion).
UINT cbformat = fmt.ids[i];
if (cbformat == 0)
fmt.ids[i] = cbformat = RegisterClipboardFormatA(name);
if (cbformat && IsClipboardFormatAvailable(cbformat)) {
if (output_cbformat)
*output_cbformat = cbformat;
return fmt.read;
}
}
}
return nullptr;
}
//////////////////////////////////////////////////////////////////////
// Encode the image as PNG format
bool write_png_on_stream(const image& image,
IStream* stream) {
const image_spec& spec = image.spec();
comptr<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;
}
IStream* create_stream(const BYTE* pInit, UINT cbInit)
{
#ifdef CLIP_SUPPORT_WINXP
// Pull SHCreateMemStream from shlwapi.dll by ordinal 12
// for Windows XP support
// From: https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-shcreatememstream#remarks
typedef IStream*(WINAPI * SHCreateMemStreamPtr)(const BYTE* pInit,
UINT cbInit);
hmodule shlwapiDll(L"shlwapi.dll");
if (!shlwapiDll)
return false;
auto SHCreateMemStream = reinterpret_cast<SHCreateMemStreamPtr>(
GetProcAddress(shlwapiDll, (LPCSTR)12));
if (!SHCreateMemStream)
return false;
#endif
return SHCreateMemStream(pInit, cbInit);
}
image_spec spec_from_pixelformat(const WICPixelFormatGUID& pixelFormat, unsigned long w, unsigned long h)
{
image_spec spec;
spec.width = w;
spec.height = h;
if (pixelFormat == GUID_WICPixelFormat32bppBGRA ||
pixelFormat == GUID_WICPixelFormat32bppBGR) {
spec.bits_per_pixel = 32;
spec.red_mask = 0xff0000;
spec.green_mask = 0xff00;
spec.blue_mask = 0xff;
spec.alpha_mask = 0xff000000;
spec.red_shift = 16;
spec.green_shift = 8;
spec.blue_shift = 0;
spec.alpha_shift = 24;
// Reset mask and shift for BGR pixel format.
if (pixelFormat == GUID_WICPixelFormat32bppBGR) {
spec.alpha_mask = 0;
spec.alpha_shift = 0;
}
}
else if (pixelFormat == GUID_WICPixelFormat24bppBGR ||
pixelFormat == GUID_WICPixelFormat8bppIndexed) {
spec.bits_per_pixel = 24;
spec.red_mask = 0xff0000;
spec.green_mask = 0xff00;
spec.blue_mask = 0xff;
spec.alpha_mask = 0;
spec.red_shift = 16;
spec.green_shift = 8;
spec.blue_shift = 0;
spec.alpha_shift = 0;
}
spec.bytes_per_row = ((w*spec.bits_per_pixel+31) / 32) * 4;
return spec;
}
// Tries to decode the input buf of size len using the specified
// decoders. If output_image is not null, the decoded image is
// returned there, if output_spec is not null then the image
// specifications are set there.
bool decode(const GUID decoder_clsid1,
const GUID decoder_clsid2,
const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec)
{
coinit com;
comptr<IWICBitmapDecoder> decoder;
HRESULT hr = CoCreateInstance(decoder_clsid1, nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&decoder));
if (FAILED(hr) && decoder_clsid2 != GUID_NULL) {
hr = CoCreateInstance(decoder_clsid2, nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&decoder));
}
if (FAILED(hr))
return false;
// Can decoder be nullptr if hr is S_OK/successful? We've received
// some crash reports that might indicate this.
if (!decoder)
return false;
comptr<IStream> stream(create_stream(buf, len));
if (!stream)
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 these pixel formats
// TODO add support for more pixel formats
if (pixelFormat != GUID_WICPixelFormat32bppBGRA &&
pixelFormat != GUID_WICPixelFormat32bppBGR &&
pixelFormat != GUID_WICPixelFormat24bppBGR &&
pixelFormat != GUID_WICPixelFormat8bppIndexed)
return false;
UINT width = 0, height = 0;
hr = frame->GetSize(&width, &height);
if (FAILED(hr))
return false;
image_spec spec = spec_from_pixelformat(pixelFormat, width, height);
if (output_spec)
*output_spec = spec;
image img;
if (output_image) {
if (pixelFormat == GUID_WICPixelFormat8bppIndexed) {
std::vector<BYTE> pixels(spec.width * spec.height);
hr = frame->CopyPixels(nullptr, // Entire bitmap
spec.width,
spec.width * spec.height,
pixels.data());
if (FAILED(hr))
return false;
comptr<IWICImagingFactory> factory;
HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory,
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&factory));
if (FAILED(hr))
return false;
comptr<IWICPalette> palette;
hr = factory->CreatePalette(&palette);
if (FAILED(hr))
return false;
hr = frame->CopyPalette(palette.get());
if (FAILED(hr))
return false;
UINT numcolors;
hr = palette->GetColorCount(&numcolors);
if (FAILED(hr))
return false;
UINT actualNumcolors;
std::vector<WICColor> colors(numcolors);
hr = palette->GetColors(numcolors, colors.data(), &actualNumcolors);
if (FAILED(hr))
return false;
BOOL hasAlpha = false;
palette->HasAlpha(&hasAlpha);
if (hasAlpha) {
spec = spec_from_pixelformat(GUID_WICPixelFormat32bppBGRA, width, height);
}
img = image(spec);
char* dst = img.data();
BYTE* src = pixels.data();
for (int y = 0; y < spec.height; ++y) {
char* dst_x = dst;
for (int x = 0; x < spec.width; ++x, dst_x+=spec.bits_per_pixel/8, ++src) {
*((uint32_t*)dst_x) = (*src < numcolors ? colors[*src] : 0);
}
dst += spec.bytes_per_row;
}
}
else {
img = image(spec);
hr = frame->CopyPixels(nullptr, // Entire bitmap
spec.bytes_per_row,
spec.bytes_per_row * spec.height,
(BYTE*)img.data());
if (FAILED(hr))
return false;
}
std::swap(*output_image, img);
}
return true;
}
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from PNG format
bool read_png(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec) {
return decode(CLSID_WICPngDecoder2, CLSID_WICPngDecoder1,
buf, len, output_image, output_spec);
}
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from JPEG format
bool read_jpg(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec)
{
return decode(CLSID_WICJpegDecoder, GUID_NULL,
buf, len, output_image, output_spec);
}
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from GIF format
bool read_gif(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec)
{
return decode(CLSID_WICGifDecoder, GUID_NULL,
buf, len, output_image, output_spec);
}
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from BMP format
bool read_bmp(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec)
{
return decode(CLSID_WICBmpDecoder, GUID_NULL,
buf, len, output_image, output_spec);
}
} // namespace win
} // namespace clip

76
clip-lib/clip_win_wic.h Normal file
View File

@ -0,0 +1,76 @@
// 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_WIN_WIC_H_INCLUDED
#define CLIP_WIN_WIC_H_INCLUDED
#pragma once
#if !CLIP_ENABLE_IMAGE
#error This file can be include only when CLIP_ENABLE_IMAGE is defined
#endif
#include <cstdint>
#include <windows.h>
namespace clip {
class image;
struct image_spec;
namespace win {
typedef bool (*ReadWicImageFormatFunc)(const uint8_t*,
const UINT,
clip::image*,
clip::image_spec*);
ReadWicImageFormatFunc wic_image_format_available(UINT* output_cbformat);
//////////////////////////////////////////////////////////////////////
// Encode the image as PNG format
bool write_png_on_stream(const image& image, IStream* stream);
HGLOBAL write_png(const image& image);
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from PNG format
bool read_png(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec);
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from JPEG format
bool read_jpg(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec);
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from GIF format
bool read_gif(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec);
//////////////////////////////////////////////////////////////////////
// Decode the clipboard data from BMP format
bool read_bmp(const uint8_t* buf,
const UINT len,
image* output_image,
image_spec* output_spec);
} // namespace win
} // namespace clip
#endif // CLIP_WIN_WIC_H_INCLUDED

1123
clip-lib/clip_x11.cpp Normal file

File diff suppressed because it is too large Load Diff

230
clip-lib/clip_x11_png.h Normal file
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

99
clip-lib/image.cpp Normal file
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

@ -2,7 +2,7 @@
#include <regex>
#include <string>
#include <fstream>
#include "clip/clip.h"
#include "clip-lib/clip.h"
using namespace std;
string replacements[6][2] = {