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