@@ -192,6 +192,95 @@ fn set_clipboard_text(s: &str) {
192
192
}
193
193
}
194
194
195
+ /// Set the clipboard image.
196
+ fn set_clipboard_image ( image : & egui:: ColorImage ) {
197
+ if let Some ( window) = web_sys:: window ( ) {
198
+ if !window. is_secure_context ( ) {
199
+ log:: error!(
200
+ "Clipboard is not available because we are not in a secure context. \
201
+ See https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts"
202
+ ) ;
203
+ return ;
204
+ }
205
+
206
+ let png_bytes = to_image ( image) . and_then ( |image| to_png_bytes ( & image) ) ;
207
+ let png_bytes = match png_bytes {
208
+ Ok ( png_bytes) => png_bytes,
209
+ Err ( err) => {
210
+ log:: error!( "Failed to encode image to png: {err}" ) ;
211
+ return ;
212
+ }
213
+ } ;
214
+
215
+ let mime = "image/png" ;
216
+
217
+ let item = match create_clipboard_item ( mime, & png_bytes) {
218
+ Ok ( item) => item,
219
+ Err ( err) => {
220
+ log:: error!( "Failed to copy image: {}" , string_from_js_value( & err) ) ;
221
+ return ;
222
+ }
223
+ } ;
224
+ let items = js_sys:: Array :: of1 ( & item) ;
225
+ let promise = window. navigator ( ) . clipboard ( ) . write ( & items) ;
226
+ let future = wasm_bindgen_futures:: JsFuture :: from ( promise) ;
227
+ let future = async move {
228
+ if let Err ( err) = future. await {
229
+ log:: error!(
230
+ "Copy/cut image action failed: {}" ,
231
+ string_from_js_value( & err)
232
+ ) ;
233
+ }
234
+ } ;
235
+ wasm_bindgen_futures:: spawn_local ( future) ;
236
+ }
237
+ }
238
+
239
+ fn to_image ( image : & egui:: ColorImage ) -> Result < image:: RgbaImage , String > {
240
+ profiling:: function_scope!( ) ;
241
+ image:: RgbaImage :: from_raw (
242
+ image. width ( ) as _ ,
243
+ image. height ( ) as _ ,
244
+ bytemuck:: cast_slice ( & image. pixels ) . to_vec ( ) ,
245
+ )
246
+ . ok_or_else ( || "Invalid IconData" . to_owned ( ) )
247
+ }
248
+
249
+ fn to_png_bytes ( image : & image:: RgbaImage ) -> Result < Vec < u8 > , String > {
250
+ profiling:: function_scope!( ) ;
251
+ let mut png_bytes: Vec < u8 > = Vec :: new ( ) ;
252
+ image
253
+ . write_to (
254
+ & mut std:: io:: Cursor :: new ( & mut png_bytes) ,
255
+ image:: ImageFormat :: Png ,
256
+ )
257
+ . map_err ( |err| err. to_string ( ) ) ?;
258
+ Ok ( png_bytes)
259
+ }
260
+
261
+ fn create_clipboard_item ( mime : & str , bytes : & [ u8 ] ) -> Result < web_sys:: ClipboardItem , JsValue > {
262
+ let array = js_sys:: Uint8Array :: from ( bytes) ;
263
+ let blob_parts = js_sys:: Array :: new ( ) ;
264
+ blob_parts. push ( & array) ;
265
+
266
+ let options = web_sys:: BlobPropertyBag :: new ( ) ;
267
+ options. set_type ( mime) ;
268
+
269
+ let blob = web_sys:: Blob :: new_with_u8_array_sequence_and_options ( & blob_parts, & options) ?;
270
+
271
+ let items = js_sys:: Object :: new ( ) ;
272
+
273
+ // SAFETY: I hope so
274
+ #[ allow( unsafe_code, unused_unsafe) ] // Weird false positive
275
+ unsafe {
276
+ js_sys:: Reflect :: set ( & items, & JsValue :: from_str ( mime) , & blob) ?
277
+ } ;
278
+
279
+ let clipboard_item = web_sys:: ClipboardItem :: new_with_record_from_str_to_blob_promise ( & items) ?;
280
+
281
+ Ok ( clipboard_item)
282
+ }
283
+
195
284
fn cursor_web_name ( cursor : egui:: CursorIcon ) -> & ' static str {
196
285
match cursor {
197
286
egui:: CursorIcon :: Alias => "alias" ,
0 commit comments