| 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 | |