fix
This commit is contained in:
parent
07c2db22f0
commit
9a6d9fcad7
@ -1,6 +1,6 @@
|
|||||||
cmake_minimum_required(VERSION 2.8)
|
cmake_minimum_required(VERSION 2.8)
|
||||||
project(owo)
|
project(owo)
|
||||||
add_executable(owo owo.cpp)
|
add_executable(owo owo.cpp)
|
||||||
add_subdirectory(clip)
|
add_subdirectory(clip-lib)
|
||||||
target_link_libraries(owo clip)
|
target_link_libraries(owo clip)
|
||||||
|
|
||||||
|
1
clip
1
clip
@ -1 +0,0 @@
|
|||||||
Subproject commit 4e9eeea9293c6f35a1ebac0fd66db7463890f143
|
|
98
clip-lib/CMakeLists.txt
Normal file
98
clip-lib/CMakeLists.txt
Normal 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
5
clip-lib/CONTRIBUTING.md
Normal 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
77
clip-lib/README.md
Normal 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
192
clip-lib/clip.cpp
Normal 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
211
clip-lib/clip.h
Normal 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
82
clip-lib/clip_common.h
Normal 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
40
clip-lib/clip_lock_impl.h
Normal 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
86
clip-lib/clip_none.cpp
Normal 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
35
clip-lib/clip_osx.h
Normal 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
377
clip-lib/clip_osx.mm
Normal 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
407
clip-lib/clip_win.cpp
Normal 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
26
clip-lib/clip_win.h
Normal 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
347
clip-lib/clip_win_bmp.cpp
Normal 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
66
clip-lib/clip_win_bmp.h
Normal 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
472
clip-lib/clip_win_wic.cpp
Normal 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
76
clip-lib/clip_win_wic.h
Normal 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
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
230
clip-lib/clip_x11_png.h
Normal 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
99
clip-lib/image.cpp
Normal 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
|
Loading…
Reference in New Issue
Block a user