1// Aseprite
2// Copyright (C) 2020-2022 Igara Studio S.A.
3// Copyright (C) 2016-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/util/clipboard.h"
13
14#include "app/i18n/strings.h"
15#include "base/serialization.h"
16#include "clip/clip.h"
17#include "doc/color_scales.h"
18#include "doc/image.h"
19#include "doc/image_impl.h"
20#include "doc/image_io.h"
21#include "doc/mask_io.h"
22#include "doc/palette_io.h"
23#include "doc/tileset_io.h"
24#include "gfx/size.h"
25#include "os/system.h"
26#include "os/window.h"
27#include "ui/alert.h"
28
29#include <sstream>
30#include <vector>
31
32namespace app {
33
34using namespace base::serialization;
35using namespace base::serialization::little_endian;
36
37namespace {
38 clip::format custom_image_format = 0;
39 bool show_clip_errors = true;
40
41 class InhibitClipErrors {
42 bool m_saved;
43 public:
44 InhibitClipErrors() {
45 m_saved = show_clip_errors;
46 show_clip_errors = false;
47 }
48 ~InhibitClipErrors() {
49 show_clip_errors = m_saved;
50 }
51 };
52
53 void* native_window_handle() {
54 return os::instance()->defaultWindow()->nativeHandle();
55 }
56
57 void custom_error_handler(clip::ErrorCode code) {
58 if (!show_clip_errors)
59 return;
60
61 switch (code) {
62 case clip::ErrorCode::CannotLock:
63 ui::Alert::show(Strings::alerts_clipboard_access_locked());
64 break;
65 case clip::ErrorCode::ImageNotSupported:
66 ui::Alert::show(Strings::alerts_clipboard_image_format_not_supported());
67 break;
68 }
69 }
70
71}
72
73void Clipboard::clearNativeContent()
74{
75 clip::lock l(native_window_handle());
76 l.clear();
77}
78
79void Clipboard::registerNativeFormats()
80{
81 clip::set_error_handler(custom_error_handler);
82 custom_image_format = clip::register_format("org.aseprite.Image");
83}
84
85bool Clipboard::hasNativeBitmap() const
86{
87 return clip::has(clip::image_format());
88}
89
90bool Clipboard::setNativeBitmap(const doc::Image* image,
91 const doc::Mask* mask,
92 const doc::Palette* palette,
93 const doc::Tileset* tileset)
94{
95 clip::lock l(native_window_handle());
96 if (!l.locked())
97 return false;
98
99 l.clear();
100
101 if (!image)
102 return false;
103
104 // Set custom clipboard formats
105 if (custom_image_format) {
106 std::stringstream os;
107 write32(os,
108 (image ? 1: 0) |
109 (mask ? 2: 0) |
110 (palette ? 4: 0) |
111 (tileset ? 8: 0));
112 if (image) doc::write_image(os, image);
113 if (mask) doc::write_mask(os, mask);
114 if (palette) doc::write_palette(os, palette);
115 if (tileset) doc::write_tileset(os, tileset);
116
117 if (os.good()) {
118 size_t size = (size_t)os.tellp();
119 if (size > 0) {
120 std::vector<char> data(size);
121 os.seekp(0);
122 os.read(&data[0], size);
123
124 l.set_data(custom_image_format, &data[0], size);
125 }
126 }
127 }
128
129 clip::image_spec spec;
130 spec.width = image->width();
131 spec.height = image->height();
132 spec.bits_per_pixel = 32;
133 spec.bytes_per_row = (image->pixelFormat() == doc::IMAGE_RGB ?
134 image->getRowStrideSize(): 4*spec.width);
135 spec.red_mask = doc::rgba_r_mask;
136 spec.green_mask = doc::rgba_g_mask;
137 spec.blue_mask = doc::rgba_b_mask;
138 spec.alpha_mask = doc::rgba_a_mask;
139 spec.red_shift = doc::rgba_r_shift;
140 spec.green_shift = doc::rgba_g_shift;
141 spec.blue_shift = doc::rgba_b_shift;
142 spec.alpha_shift = doc::rgba_a_shift;
143
144 switch (image->pixelFormat()) {
145 case doc::IMAGE_RGB: {
146 // We use the RGB image data directly
147 clip::image img(image->getPixelAddress(0, 0), spec);
148 l.set_image(img);
149 break;
150 }
151 case doc::IMAGE_GRAYSCALE: {
152 clip::image img(spec);
153 const doc::LockImageBits<doc::GrayscaleTraits> bits(image);
154 auto it = bits.begin();
155 uint32_t* dst = (uint32_t*)img.data();
156 for (int y=0; y<image->height(); ++y) {
157 for (int x=0; x<image->width(); ++x, ++it) {
158 doc::color_t c = *it;
159 *(dst++) = doc::rgba(doc::graya_getv(c),
160 doc::graya_getv(c),
161 doc::graya_getv(c),
162 doc::graya_geta(c));
163 }
164 }
165 l.set_image(img);
166 break;
167 }
168 case doc::IMAGE_INDEXED: {
169 clip::image img(spec);
170 const doc::LockImageBits<doc::IndexedTraits> bits(image);
171 auto it = bits.begin();
172 uint32_t* dst = (uint32_t*)img.data();
173 for (int y=0; y<image->height(); ++y) {
174 for (int x=0; x<image->width(); ++x, ++it) {
175 doc::color_t c = palette->getEntry(*it);
176
177 // Use alpha=0 for mask color
178 if (*it == image->maskColor())
179 c &= doc::rgba_rgb_mask;
180
181 *(dst++) = c;
182 }
183 }
184 l.set_image(img);
185 break;
186 }
187 }
188
189 return true;
190}
191
192bool Clipboard::getNativeBitmap(doc::Image** image,
193 doc::Mask** mask,
194 doc::Palette** palette,
195 doc::Tileset** tileset)
196{
197 *image = nullptr;
198 *mask = nullptr;
199 *palette = nullptr;
200 *tileset = nullptr;
201
202 clip::lock l(native_window_handle());
203 if (!l.locked())
204 return false;
205
206 // Prefer the custom format (to avoid losing mask and palette)
207 if (l.is_convertible(custom_image_format)) {
208 size_t size = l.get_data_length(custom_image_format);
209 if (size > 0) {
210 std::vector<char> buf(size);
211 if (l.get_data(custom_image_format, &buf[0], size)) {
212 std::stringstream is;
213 is.write(&buf[0], size);
214 is.seekp(0);
215
216 int bits = read32(is);
217 if (bits & 1) *image = doc::read_image(is, false);
218 if (bits & 2) *mask = doc::read_mask(is);
219 if (bits & 4) *palette = doc::read_palette(is);
220 if (bits & 8) *tileset = doc::read_tileset(is, nullptr);
221 if (image)
222 return true;
223 }
224 }
225 }
226
227 if (!l.is_convertible(clip::image_format()))
228 return false;
229
230 clip::image img;
231 if (!l.get_image(img))
232 return false;
233
234 const clip::image_spec& spec = img.spec();
235
236 std::unique_ptr<doc::Image> dst(
237 doc::Image::create(doc::IMAGE_RGB,
238 spec.width, spec.height));
239
240 switch (spec.bits_per_pixel) {
241 case 64: {
242 doc::LockImageBits<doc::RgbTraits> bits(dst.get(), doc::Image::WriteLock);
243 auto it = bits.begin();
244 for (unsigned long y=0; y<spec.height; ++y) {
245 const uint64_t* src = (const uint64_t*)(img.data()+spec.bytes_per_row*y);
246 for (unsigned long x=0; x<spec.width; ++x, ++it, ++src) {
247 uint64_t c = *((const uint64_t*)src);
248 *it = doc::rgba(
249 uint8_t((c & spec.red_mask) >> spec.red_shift >> 8),
250 uint8_t((c & spec.green_mask) >> spec.green_shift >> 8),
251 uint8_t((c & spec.blue_mask) >> spec.blue_shift >> 8),
252 uint8_t((c & spec.alpha_mask) >> spec.alpha_shift >> 8));
253 }
254 }
255 break;
256 }
257 case 32: {
258 doc::LockImageBits<doc::RgbTraits> bits(dst.get(), doc::Image::WriteLock);
259 auto it = bits.begin();
260 for (unsigned long y=0; y<spec.height; ++y) {
261 const uint32_t* src = (const uint32_t*)(img.data()+spec.bytes_per_row*y);
262 for (unsigned long x=0; x<spec.width; ++x, ++it, ++src) {
263 const uint32_t c = *((const uint32_t*)src);
264
265 // The alpha mask can be zero (which means that the image is
266 // just RGB).
267 int alpha =
268 (spec.alpha_mask ?
269 uint8_t((c & spec.alpha_mask) >> spec.alpha_shift): 255);
270
271 *it = doc::rgba(
272 uint8_t((c & spec.red_mask ) >> spec.red_shift ),
273 uint8_t((c & spec.green_mask) >> spec.green_shift),
274 uint8_t((c & spec.blue_mask ) >> spec.blue_shift ),
275 alpha);
276 }
277 }
278 break;
279 }
280 case 24: {
281 doc::LockImageBits<doc::RgbTraits> bits(dst.get(), doc::Image::WriteLock);
282 auto it = bits.begin();
283 for (unsigned long y=0; y<spec.height; ++y) {
284 const char* src = (const char*)(img.data()+spec.bytes_per_row*y);
285 for (unsigned long x=0; x<spec.width; ++x, ++it, src+=3) {
286 unsigned long c = *((const unsigned long*)src);
287 *it = doc::rgba(
288 uint8_t((c & spec.red_mask) >> spec.red_shift),
289 uint8_t((c & spec.green_mask) >> spec.green_shift),
290 uint8_t((c & spec.blue_mask) >> spec.blue_shift),
291 255);
292 }
293 }
294 break;
295 }
296 case 16: {
297 doc::LockImageBits<doc::RgbTraits> bits(dst.get(), doc::Image::WriteLock);
298 auto it = bits.begin();
299 for (unsigned long y=0; y<spec.height; ++y) {
300 const uint16_t* src = (const uint16_t*)(img.data()+spec.bytes_per_row*y);
301 for (unsigned long x=0; x<spec.width; ++x, ++it, ++src) {
302 const uint16_t c = *((const uint16_t*)src);
303 *it = doc::rgba(
304 doc::scale_5bits_to_8bits((c & spec.red_mask ) >> spec.red_shift ),
305 doc::scale_6bits_to_8bits((c & spec.green_mask) >> spec.green_shift),
306 doc::scale_5bits_to_8bits((c & spec.blue_mask ) >> spec.blue_shift ),
307 255);
308 }
309 }
310 break;
311 }
312 }
313
314 *image = dst.release();
315 return true;
316}
317
318bool Clipboard::getNativeBitmapSize(gfx::Size* size)
319{
320 // Don't show errors when we are trying to get the size of the image
321 // only. (E.g. don't show "invalid image format error")
322 InhibitClipErrors inhibitErrors;
323
324 clip::image_spec spec;
325 if (clip::get_image_spec(spec)) {
326 size->w = spec.width;
327 size->h = spec.height;
328 return true;
329 }
330 else
331 return false;
332}
333
334} // namespace app
335