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 | |
32 | namespace app { |
33 | |
34 | using namespace base::serialization; |
35 | using namespace base::serialization::little_endian; |
36 | |
37 | namespace { |
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 | |
73 | void Clipboard::clearNativeContent() |
74 | { |
75 | clip::lock l(native_window_handle()); |
76 | l.clear(); |
77 | } |
78 | |
79 | void Clipboard::registerNativeFormats() |
80 | { |
81 | clip::set_error_handler(custom_error_handler); |
82 | custom_image_format = clip::register_format("org.aseprite.Image" ); |
83 | } |
84 | |
85 | bool Clipboard::hasNativeBitmap() const |
86 | { |
87 | return clip::has(clip::image_format()); |
88 | } |
89 | |
90 | bool 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 | |
192 | bool 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 | |
318 | bool 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 | |