From 7daac2d04ee6b974d50390d4a6bf6fcb4ed0855e Mon Sep 17 00:00:00 2001 From: ThrDev Date: Sun, 26 Apr 2020 19:21:29 -0400 Subject: [PATCH 1/6] Add NativeImage support, clipboard image read/write. --- ElectronNET.API/Clipboard.cs | 40 +- .../Entities/AddRepresentationOptions.cs | 11 + ElectronNET.API/Entities/BitmapOptions.cs | 11 + .../Entities/CreateFromBufferOptions.cs | 9 + ElectronNET.API/Entities/NativeImage.cs | 349 +++++++++++++----- ElectronNET.API/Entities/ResizeOptions.cs | 17 + ElectronNET.API/Entities/ToBitmapOptions.cs | 11 + ElectronNET.API/Entities/ToDataUrlOptions.cs | 11 + ElectronNET.API/Entities/ToPNGOptions.cs | 11 + ElectronNET.Host/api/clipboard.js | 9 + ElectronNET.Host/api/clipboard.ts | 14 +- .../Controllers/ClipboardController.cs | 12 +- .../Controllers/MenusController.cs | 4 +- .../Views/Clipboard/Index.cshtml | 139 ++++++- .../wwwroot/assets/css/demo.css | 4 + 15 files changed, 541 insertions(+), 111 deletions(-) create mode 100644 ElectronNET.API/Entities/AddRepresentationOptions.cs create mode 100644 ElectronNET.API/Entities/BitmapOptions.cs create mode 100644 ElectronNET.API/Entities/CreateFromBufferOptions.cs create mode 100644 ElectronNET.API/Entities/ResizeOptions.cs create mode 100644 ElectronNET.API/Entities/ToBitmapOptions.cs create mode 100644 ElectronNET.API/Entities/ToDataUrlOptions.cs create mode 100644 ElectronNET.API/Entities/ToPNGOptions.cs diff --git a/ElectronNET.API/Clipboard.cs b/ElectronNET.API/Clipboard.cs index 82815947..c49dc958 100644 --- a/ElectronNET.API/Clipboard.cs +++ b/ElectronNET.API/Clipboard.cs @@ -1,4 +1,5 @@ -using ElectronNET.API.Entities; +using System; +using ElectronNET.API.Entities; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; @@ -237,6 +238,43 @@ public void Write(Data data, string type = "") BridgeConnector.Socket.Emit("clipboard-write", JObject.FromObject(data, _jsonSerializer), type); } + /// + /// Reads an image from the clipboard. + /// + /// + /// + public Task ReadImageAsync(string type = "") + { + var taskCompletionSource = new TaskCompletionSource(); + + BridgeConnector.Socket.On("clipboard-readImage-Completed", (image) => + { + BridgeConnector.Socket.Off("clipboard-readImage-Completed"); + + var b64 = image.ToString(); + var bytes = Convert.FromBase64String(b64); + + var nativeImage = NativeImage.CreateFromBuffer(bytes); + + taskCompletionSource.SetResult(nativeImage); + + }); + + BridgeConnector.Socket.Emit("clipboard-readImage", type); + + return taskCompletionSource.Task; + } + + /// + /// Writes an image to the clipboard. + /// + /// + /// + public void WriteImage(NativeImage image, string type = "") + { + BridgeConnector.Socket.Emit("clipboard-writeImage", JsonConvert.SerializeObject(image.GetBytes()), type); + } + private JsonSerializer _jsonSerializer = new JsonSerializer() { ContractResolver = new CamelCasePropertyNamesContractResolver(), diff --git a/ElectronNET.API/Entities/AddRepresentationOptions.cs b/ElectronNET.API/Entities/AddRepresentationOptions.cs new file mode 100644 index 00000000..0bcd4e3d --- /dev/null +++ b/ElectronNET.API/Entities/AddRepresentationOptions.cs @@ -0,0 +1,11 @@ +namespace ElectronNET.API.Entities +{ + public class AddRepresentationOptions + { + public int? Width { get; set; } + public int? Height { get; set; } + public int ScaleFactor { get; set; } + public byte[] Buffer { get; set; } + public string DataUrl { get; set; } + } +} \ No newline at end of file diff --git a/ElectronNET.API/Entities/BitmapOptions.cs b/ElectronNET.API/Entities/BitmapOptions.cs new file mode 100644 index 00000000..b965ab04 --- /dev/null +++ b/ElectronNET.API/Entities/BitmapOptions.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ElectronNET.API.Entities +{ + public class BitmapOptions + { + public double ScaleFactor { get; set; } = 1.0d; + } +} diff --git a/ElectronNET.API/Entities/CreateFromBufferOptions.cs b/ElectronNET.API/Entities/CreateFromBufferOptions.cs new file mode 100644 index 00000000..b54a9b74 --- /dev/null +++ b/ElectronNET.API/Entities/CreateFromBufferOptions.cs @@ -0,0 +1,9 @@ +namespace ElectronNET.API.Entities +{ + public class CreateFromBufferOptions + { + public int Width { get; set; } + public int Height { get; set; } + public int ScaleFactor { get; set; } + } +} \ No newline at end of file diff --git a/ElectronNET.API/Entities/NativeImage.cs b/ElectronNET.API/Entities/NativeImage.cs index 2e408d19..095cdaf2 100644 --- a/ElectronNET.API/Entities/NativeImage.cs +++ b/ElectronNET.API/Entities/NativeImage.cs @@ -1,109 +1,256 @@ -namespace ElectronNET.API.Entities +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace ElectronNET.API.Entities { - // TODO: Need some of real code :) /// /// /// public class NativeImage { - // public static NativeImage CreateEmpty() - // { - // throw new NotImplementedException(); - // } - - // public static NativeImage CreateFromBuffer(byte[] buffer) - // { - // throw new NotImplementedException(); - // } - - // public static NativeImage CreateFromBuffer(byte[] buffer, CreateFromBufferOptions options) - // { - // throw new NotImplementedException(); - // } - - // public static NativeImage CreateFromDataURL(string dataURL) - // { - // throw new NotImplementedException(); - // } - - // public static NativeImage CreateFromPath(string path) - // { - // throw new NotImplementedException(); - // } - - // public void AddRepresentation(AddRepresentationOptions options) - // { - // throw new NotImplementedException(); - // } - - // public NativeImage Crop(Rectangle rect) - // { - // throw new NotImplementedException(); - // } - - // public int GetAspectRatio() - // { - // throw new NotImplementedException(); - // } - - // public byte[] GetBitmap() - // { - // throw new NotImplementedException(); - // } - - // public byte[] GetBitmap(BitmapOptions options) - // { - // throw new NotImplementedException(); - // } - - // public byte[] GetNativeHandle() - // { - // throw new NotImplementedException(); - // } - - // public Size GetSize() - // { - // throw new NotImplementedException(); - // } - - // public bool IsEmpty() - // { - // throw new NotImplementedException(); - // } - - // public bool IsTemplateImage() - // { - // throw new NotImplementedException(); - // } - - // public NativeImage Resize(ResizeOptions options) - // { - // throw new NotImplementedException(); - // } - - // public void SetTemplateImage(bool option) - // { - // throw new NotImplementedException(); - // } - - // public byte[] ToBitmap(ToBitmapOptions options) - // { - // throw new NotImplementedException(); - // } - - // public string ToDataURL(ToDataURLOptions options) - // { - // throw new NotImplementedException(); - // } - - // public byte[] ToJPEG(int quality) - // { - // throw new NotImplementedException(); - // } - - // public byte[] ToPNG(ToPNGOptions options) - // { - // throw new NotImplementedException(); - // } + private Image _image; + private bool _isTemplateImage = false; + + public static NativeImage CreateEmpty() + { + return new NativeImage(); + } + + public static NativeImage CreateFromBuffer(byte[] buffer) + { + return new NativeImage(buffer); + } + + public static NativeImage CreateFromBuffer(byte[] buffer, CreateFromBufferOptions options) + { + return new NativeImage(buffer); + } + + public static NativeImage CreateFromDataURL(string dataUrl) + { + var parsedDataUrl = Regex.Match(dataUrl, @"data:image/(?.+?),(?.+)"); + var actualData = parsedDataUrl.Groups["data"].Value; + var type = parsedDataUrl.Groups["type"].Value; + var binData = Convert.FromBase64String(actualData); + + var imageFormat = type switch + { + "jpeg" => ImageFormat.Jpeg, + "jpg" => ImageFormat.Jpeg, + "png" => ImageFormat.Png, + "gif" => ImageFormat.Gif, + "x-icon" => ImageFormat.Icon, + "bmp" => ImageFormat.Bmp, + _ => ImageFormat.Png + }; + + return new NativeImage(binData, imageFormat); + } + + public static NativeImage CreateFromPath(string path) + { + return new NativeImage(path); + } + + public NativeImage() + { + } + + public NativeImage(Bitmap bitmap) + { + _image = bitmap; + } + public NativeImage(string path) + { + _image = Image.FromFile(path); + } + + + public NativeImage(byte[] buffer) + { + var ms = new MemoryStream(buffer); + _image = Image.FromStream(ms); + } + + public NativeImage(byte[] buffer, ImageFormat imageFormat) + { + _image = BytesToImage(buffer, imageFormat); + } + + public Image GetBitmap() + { + return _image; + } + + public NativeImage Crop(Rectangle rect) + { + using (var g = Graphics.FromImage(_image)) + { + var bmp = new Bitmap(rect.Width, rect.Height); + g.DrawImage(bmp, new System.Drawing.Rectangle(0, 0, _image.Width, _image.Height), new System.Drawing.Rectangle(rect.X, rect.Y, rect.Width, rect.Height), GraphicsUnit.Pixel); + + return new NativeImage(bmp); + } + } + public void AddRepresentation(AddRepresentationOptions options) + { + if (options.Buffer.Length > 0) + { + _image = CreateFromBuffer(options.Buffer).GetBitmap(); + } + else if (!string.IsNullOrEmpty(options.DataUrl)) + { + _image = CreateFromDataURL(options.DataUrl).GetBitmap(); + } + } + + public double GetAspectRatio => _image.Width / _image.Height; + + + public byte[] GetBitmap(BitmapOptions options) + { + return ImageToBytes(_image.RawFormat); + } + + public byte[] GetNativeHandle() + { + return ImageToBytes(_image.RawFormat); + } + + public Size GetSize() + { + return new Size + { + Width = _image.Width, + Height = _image.Height + }; + } + + public bool IsEmpty() + { + return _image == null; + } + + /// + /// Deprecated. Whether the image is a template image. + /// + /// + public bool IsTemplateImage => _isTemplateImage; + + /// + /// Deprecated. Marks the image as a template image. + /// + public void SetTemplateImage(bool option) + { + _isTemplateImage = option; + } + + + public NativeImage Resize(ResizeOptions options) + { + return new NativeImage((Bitmap)Resize(options.Width, options.Height)); + } + + public byte[] ToBitmap(ToBitmapOptions options) + { + return ImageToBytes(_image.RawFormat); + } + + public string ToDataURL(ToDataUrlOptions options) + { + var mimeType = ImageCodecInfo.GetImageEncoders().FirstOrDefault(x => x.FormatID == _image.RawFormat.Guid)?.MimeType; + if (mimeType is null) + { + mimeType = "image/png"; + } + + var bytes = ImageToBytes(_image.RawFormat); + var base64 = Convert.ToBase64String(bytes); + + return $"data:{mimeType};base64,{base64}"; + } + + public byte[] ToJPEG(int quality) + { + return ImageToBytes(ImageFormat.Jpeg, 1.0d, quality); + } + + public byte[] ToPNG(ToPNGOptions options) + { + return ImageToBytes(ImageFormat.Png, options.ScaleFactor); + } + + private byte[] ImageToBytes(ImageFormat imageFormat, double scaleFactor = 1.0d, int quality = 100) + { + using (var ms = new MemoryStream()) + { + Image img = _image; + + if (Math.Abs(scaleFactor - 1.0d) > 0.0) + { + img = Resize(_image.Width, _image.Height, scaleFactor); + } + + ImageCodecInfo encoderCodecInfo = GetEncoder(imageFormat); + Encoder encoder = Encoder.Quality; + + EncoderParameters encoderParameters = new EncoderParameters(1) + { + Param = new [] + { + new EncoderParameter(encoder, quality) + } + }; + + img.Save(ms, encoderCodecInfo, encoderParameters); + + return ms.ToArray(); + } + } + + private Image Resize(int? width, int? height, double scaleFactor = 1.0d) + { + using (var g = Graphics.FromImage(_image)) + { + g.CompositingQuality = CompositingQuality.HighQuality; + + var bmp = new Bitmap((int)Math.Round(width ?? _image.Width * scaleFactor), (int)Math.Round(height ?? _image.Height * scaleFactor)); + g.DrawImage(bmp, + new System.Drawing.Rectangle(0, 0, _image.Width, _image.Height), + new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), + GraphicsUnit.Pixel); + + return bmp; + } + } + + private Image BytesToImage(byte[] bytes, ImageFormat imageFormat) + { + var ms = new MemoryStream(bytes); + return Image.FromStream(ms); + } + + private ImageCodecInfo GetEncoder(ImageFormat format) + { + ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders(); + foreach (ImageCodecInfo codec in codecs) + { + if (codec.FormatID == format.Guid) + { + return codec; + } + } + return null; + } + + internal byte[] GetBytes() + { + return ImageToBytes(_image.RawFormat); + } } } diff --git a/ElectronNET.API/Entities/ResizeOptions.cs b/ElectronNET.API/Entities/ResizeOptions.cs new file mode 100644 index 00000000..1cc376f6 --- /dev/null +++ b/ElectronNET.API/Entities/ResizeOptions.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ElectronNET.API.Entities +{ + public class ResizeOptions + { + public int? Width { get; set; } + public int? Height { get; set; } + + /// + /// good, better, or best. Default is "best"; + /// + public string Quality { get; set; } = "best"; + } +} diff --git a/ElectronNET.API/Entities/ToBitmapOptions.cs b/ElectronNET.API/Entities/ToBitmapOptions.cs new file mode 100644 index 00000000..e2ba8451 --- /dev/null +++ b/ElectronNET.API/Entities/ToBitmapOptions.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ElectronNET.API.Entities +{ + public class ToBitmapOptions + { + public double ScaleFactor { get; set; } = 1.0d; + } +} diff --git a/ElectronNET.API/Entities/ToDataUrlOptions.cs b/ElectronNET.API/Entities/ToDataUrlOptions.cs new file mode 100644 index 00000000..09c2b794 --- /dev/null +++ b/ElectronNET.API/Entities/ToDataUrlOptions.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ElectronNET.API.Entities +{ + public class ToDataUrlOptions + { + public double ScaleFactor { get; set; } = 1.0d; + } +} diff --git a/ElectronNET.API/Entities/ToPNGOptions.cs b/ElectronNET.API/Entities/ToPNGOptions.cs new file mode 100644 index 00000000..580d4d57 --- /dev/null +++ b/ElectronNET.API/Entities/ToPNGOptions.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ElectronNET.API.Entities +{ + public class ToPNGOptions + { + public double ScaleFactor { get; set; } = 1.0d; + } +} diff --git a/ElectronNET.Host/api/clipboard.js b/ElectronNET.Host/api/clipboard.js index 610e072e..1775188d 100644 --- a/ElectronNET.Host/api/clipboard.js +++ b/ElectronNET.Host/api/clipboard.js @@ -48,5 +48,14 @@ module.exports = (socket) => { socket.on('clipboard-write', (data, type) => { electron_1.clipboard.write(data, type); }); + socket.on('clipboard-readImage', (type) => { + const image = electron_1.clipboard.readImage(type); + electronSocket.emit('clipboard-readImage-Completed', image.getNativeHandle().buffer); + }); + socket.on('clipboard-writeImage', (data, type) => { + const buff = Buffer.from(JSON.parse(data), 'base64'); + const ni = electron_1.nativeImage.createFromBuffer(buff); + electron_1.clipboard.writeImage(ni, type); + }); }; //# sourceMappingURL=clipboard.js.map \ No newline at end of file diff --git a/ElectronNET.Host/api/clipboard.ts b/ElectronNET.Host/api/clipboard.ts index f0f92c10..0f734164 100644 --- a/ElectronNET.Host/api/clipboard.ts +++ b/ElectronNET.Host/api/clipboard.ts @@ -1,4 +1,4 @@ -import { clipboard } from 'electron'; +import { clipboard, nativeImage } from 'electron'; let electronSocket; export = (socket: SocketIO.Socket) => { @@ -60,4 +60,16 @@ export = (socket: SocketIO.Socket) => { socket.on('clipboard-write', (data, type) => { clipboard.write(data, type); }); + + socket.on('clipboard-readImage', (type) => { + var image = clipboard.readImage(type); + var b64 = image.getNativeHandle().buffer.toString('base64'); + electronSocket.emit('clipboard-readImage-Completed', b64); + }); + + socket.on('clipboard-writeImage', (data, type) => { + var buff = Buffer.from(JSON.parse(data), 'base64'); + const ni = nativeImage.createFromBuffer(buff); + clipboard.writeImage(ni, type); + }); }; diff --git a/ElectronNET.WebApp/Controllers/ClipboardController.cs b/ElectronNET.WebApp/Controllers/ClipboardController.cs index 27f8d9e1..631de9f3 100644 --- a/ElectronNET.WebApp/Controllers/ClipboardController.cs +++ b/ElectronNET.WebApp/Controllers/ClipboardController.cs @@ -1,6 +1,10 @@ -using Microsoft.AspNetCore.Mvc; +using System; +using System.Drawing; +using System.IO; +using Microsoft.AspNetCore.Mvc; using ElectronNET.API; using System.Linq; +using ElectronNET.API.Entities; namespace ElectronNET.WebApp.Controllers { @@ -23,6 +27,12 @@ public IActionResult Index() var mainWindow = Electron.WindowManager.BrowserWindows.First(); Electron.IpcMain.Send(mainWindow, "paste-from", pasteText); }); + + Electron.IpcMain.On("copy-image-to", (test) => + { + var nativeImage = NativeImage.CreateFromDataURL(test.ToString()); + Electron.Clipboard.WriteImage(nativeImage); + }); } return View(); diff --git a/ElectronNET.WebApp/Controllers/MenusController.cs b/ElectronNET.WebApp/Controllers/MenusController.cs index 4e9d8ec0..6a17255c 100644 --- a/ElectronNET.WebApp/Controllers/MenusController.cs +++ b/ElectronNET.WebApp/Controllers/MenusController.cs @@ -112,13 +112,13 @@ private void CreateContextMenu() new MenuItem { Label = "Electron.NET", Type = MenuType.checkbox, Checked = true } }; - var mainWindow = Electron.WindowManager.BrowserWindows.First(); + /*var mainWindow = Electron.WindowManager.BrowserWindows.First(); Electron.Menu.SetContextMenu(mainWindow, menu); Electron.IpcMain.On("show-context-menu", (args) => { Electron.Menu.ContextMenuPopup(mainWindow); - }); + });*/ } } } \ No newline at end of file diff --git a/ElectronNET.WebApp/Views/Clipboard/Index.cshtml b/ElectronNET.WebApp/Views/Clipboard/Index.cshtml index 77e3098d..94e274a7 100644 --- a/ElectronNET.WebApp/Views/Clipboard/Index.cshtml +++ b/ElectronNET.WebApp/Views/Clipboard/Index.cshtml @@ -8,7 +8,7 @@

The Electron.Clipboard provides methods to perform copy and paste operations.

This module also has methods for copying text as markup (HTML) to the clipboard.

- +

You find the sample source code in Controllers\ClipboardController.cs.

@@ -90,24 +90,153 @@ pasteBtn.addEventListener('click', () => { - diff --git a/ElectronNET.WebApp/wwwroot/assets/css/demo.css b/ElectronNET.WebApp/wwwroot/assets/css/demo.css index 96afc4bd..4ffa4a1a 100644 --- a/ElectronNET.WebApp/wwwroot/assets/css/demo.css +++ b/ElectronNET.WebApp/wwwroot/assets/css/demo.css @@ -203,3 +203,7 @@ font-weight: 600; } +/* Clipboard paste image ------------------ */ +.demo-image-box { + padding-left: 15px; +} \ No newline at end of file From 23015a9f3d43082c087cb780d5fb9aeadd17cd16 Mon Sep 17 00:00:00 2001 From: ThrDev Date: Sun, 26 Apr 2020 19:26:45 -0400 Subject: [PATCH 2/6] Add System.Drawing.Common reference. --- ElectronNET.API/ElectronNET.API.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/ElectronNET.API/ElectronNET.API.csproj b/ElectronNET.API/ElectronNET.API.csproj index 5d546318..083d7bce 100644 --- a/ElectronNET.API/ElectronNET.API.csproj +++ b/ElectronNET.API/ElectronNET.API.csproj @@ -41,6 +41,7 @@ This package contains the API to access the "native" electron API. runtime; build; native; contentfiles; analyzers + From e295558258aad9ad4f5e0491a1fc37d5059822d2 Mon Sep 17 00:00:00 2001 From: ThrDev Date: Sun, 26 Apr 2020 19:36:16 -0400 Subject: [PATCH 3/6] Fix MenusController.cs --- ElectronNET.WebApp/Controllers/MenusController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ElectronNET.WebApp/Controllers/MenusController.cs b/ElectronNET.WebApp/Controllers/MenusController.cs index 6a17255c..4e9d8ec0 100644 --- a/ElectronNET.WebApp/Controllers/MenusController.cs +++ b/ElectronNET.WebApp/Controllers/MenusController.cs @@ -112,13 +112,13 @@ private void CreateContextMenu() new MenuItem { Label = "Electron.NET", Type = MenuType.checkbox, Checked = true } }; - /*var mainWindow = Electron.WindowManager.BrowserWindows.First(); + var mainWindow = Electron.WindowManager.BrowserWindows.First(); Electron.Menu.SetContextMenu(mainWindow, menu); Electron.IpcMain.On("show-context-menu", (args) => { Electron.Menu.ContextMenuPopup(mainWindow); - });*/ + }); } } } \ No newline at end of file From 9b270755d041debb34362eca1b2c75e82b48bf19 Mon Sep 17 00:00:00 2001 From: ThrDev Date: Wed, 6 May 2020 19:05:21 -0400 Subject: [PATCH 4/6] Add full NativeImage support for Electron.NET --- ElectronNET.API/Clipboard.cs | 8 +- .../Entities/AddRepresentationOptions.cs | 2 +- ElectronNET.API/Entities/BitmapOptions.cs | 2 +- .../Entities/CreateFromBitmapOptions.cs | 13 + .../Entities/CreateFromBufferOptions.cs | 6 +- ElectronNET.API/Entities/NativeImage.cs | 380 +++++++++++++----- .../Entities/NativeImageJsonConverter.cs | 34 ++ ElectronNET.API/Entities/ToBitmapOptions.cs | 2 +- ElectronNET.API/Entities/ToDataUrlOptions.cs | 2 +- ElectronNET.API/Entities/ToPNGOptions.cs | 2 +- ElectronNET.Host/api/clipboard.js | 13 +- ElectronNET.Host/api/clipboard.ts | 14 +- .../Controllers/ClipboardController.cs | 8 + .../Views/Clipboard/Index.cshtml | 57 ++- 14 files changed, 430 insertions(+), 113 deletions(-) create mode 100644 ElectronNET.API/Entities/CreateFromBitmapOptions.cs create mode 100644 ElectronNET.API/Entities/NativeImageJsonConverter.cs diff --git a/ElectronNET.API/Clipboard.cs b/ElectronNET.API/Clipboard.cs index c49dc958..a43a86bc 100644 --- a/ElectronNET.API/Clipboard.cs +++ b/ElectronNET.API/Clipboard.cs @@ -4,6 +4,7 @@ using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using System.Threading.Tasks; +using Console = System.Console; namespace ElectronNET.API { @@ -251,10 +252,7 @@ public Task ReadImageAsync(string type = "") { BridgeConnector.Socket.Off("clipboard-readImage-Completed"); - var b64 = image.ToString(); - var bytes = Convert.FromBase64String(b64); - - var nativeImage = NativeImage.CreateFromBuffer(bytes); + var nativeImage = ((JObject)image).ToObject(); taskCompletionSource.SetResult(nativeImage); @@ -272,7 +270,7 @@ public Task ReadImageAsync(string type = "") /// public void WriteImage(NativeImage image, string type = "") { - BridgeConnector.Socket.Emit("clipboard-writeImage", JsonConvert.SerializeObject(image.GetBytes()), type); + BridgeConnector.Socket.Emit("clipboard-writeImage", JsonConvert.SerializeObject(image), type); } private JsonSerializer _jsonSerializer = new JsonSerializer() diff --git a/ElectronNET.API/Entities/AddRepresentationOptions.cs b/ElectronNET.API/Entities/AddRepresentationOptions.cs index 0bcd4e3d..c5d5bb7f 100644 --- a/ElectronNET.API/Entities/AddRepresentationOptions.cs +++ b/ElectronNET.API/Entities/AddRepresentationOptions.cs @@ -4,7 +4,7 @@ public class AddRepresentationOptions { public int? Width { get; set; } public int? Height { get; set; } - public int ScaleFactor { get; set; } + public float ScaleFactor { get; set; } = 1.0f; public byte[] Buffer { get; set; } public string DataUrl { get; set; } } diff --git a/ElectronNET.API/Entities/BitmapOptions.cs b/ElectronNET.API/Entities/BitmapOptions.cs index b965ab04..de5945d6 100644 --- a/ElectronNET.API/Entities/BitmapOptions.cs +++ b/ElectronNET.API/Entities/BitmapOptions.cs @@ -6,6 +6,6 @@ namespace ElectronNET.API.Entities { public class BitmapOptions { - public double ScaleFactor { get; set; } = 1.0d; + public float ScaleFactor { get; set; } = 1.0f; } } diff --git a/ElectronNET.API/Entities/CreateFromBitmapOptions.cs b/ElectronNET.API/Entities/CreateFromBitmapOptions.cs new file mode 100644 index 00000000..6b0424ae --- /dev/null +++ b/ElectronNET.API/Entities/CreateFromBitmapOptions.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ElectronNET.API.Entities +{ + public class CreateFromBitmapOptions + { + public int? Width { get; set; } + public int? Height { get; set; } + public float ScaleFactor { get; set; } = 1.0f; + } +} diff --git a/ElectronNET.API/Entities/CreateFromBufferOptions.cs b/ElectronNET.API/Entities/CreateFromBufferOptions.cs index b54a9b74..d50b6367 100644 --- a/ElectronNET.API/Entities/CreateFromBufferOptions.cs +++ b/ElectronNET.API/Entities/CreateFromBufferOptions.cs @@ -2,8 +2,8 @@ namespace ElectronNET.API.Entities { public class CreateFromBufferOptions { - public int Width { get; set; } - public int Height { get; set; } - public int ScaleFactor { get; set; } + public int? Width { get; set; } + public int? Height { get; set; } + public float ScaleFactor { get; set; } = 1.0f; } } \ No newline at end of file diff --git a/ElectronNET.API/Entities/NativeImage.cs b/ElectronNET.API/Entities/NativeImage.cs index 095cdaf2..d6b4f362 100644 --- a/ElectronNET.API/Entities/NativeImage.cs +++ b/ElectronNET.API/Entities/NativeImage.cs @@ -1,139 +1,265 @@ using System; +using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Text.RegularExpressions; +using Newtonsoft.Json; namespace ElectronNET.API.Entities { /// - /// + /// Native Image handler for Electron.NET /// + [JsonConverter(typeof(NativeImageJsonConverter))] public class NativeImage { - private Image _image; - private bool _isTemplateImage = false; + private readonly Dictionary _images = new Dictionary(); + private bool _isTemplateImage; + private static readonly Dictionary ScaleFactorPairs = new Dictionary + { + {"@2x", 2.0f}, {"@3x", 3.0f}, {"@1x", 1.0f}, {"@4x", 4.0f}, + {"@5x", 5.0f}, {"@1.25x", 1.25f}, {"@1.33x", 1.33f}, {"@1.4x", 1.4f}, + {"@1.5x", 1.5f}, {"@1.8x", 1.8f}, {"@2.5x", 2.5f} + }; + + private static float? ExtractDpiFromFilePath(string filePath) + { + var withoutExtension = Path.GetFileNameWithoutExtension(filePath); + return ScaleFactorPairs + .Where(p => withoutExtension.EndsWith(p.Key)) + .Select(p => p.Value) + .FirstOrDefault(); + } + private static Image BytesToImage(byte[] bytes) + { + var ms = new MemoryStream(bytes); + return Image.FromStream(ms); + } + + /// + /// Creates an empty NativeImage + /// public static NativeImage CreateEmpty() { return new NativeImage(); } - public static NativeImage CreateFromBuffer(byte[] buffer) + /// + /// + /// + public static NativeImage CreateFromBitmap(Bitmap bitmap, CreateFromBitmapOptions options = null) { - return new NativeImage(buffer); + if (options is null) + { + options = new CreateFromBitmapOptions(); + } + + return new NativeImage(bitmap, options.ScaleFactor); } - public static NativeImage CreateFromBuffer(byte[] buffer, CreateFromBufferOptions options) + /// + /// Creates a NativeImage from a byte array. + /// + public static NativeImage CreateFromBuffer(byte[] buffer, CreateFromBufferOptions options = null) { - return new NativeImage(buffer); + if (options is null) + { + options = new CreateFromBufferOptions(); + } + + var ms = new MemoryStream(buffer); + var image = Image.FromStream(ms); + + return new NativeImage(image, options.ScaleFactor); } + /// + /// Creates a NativeImage from a base64 encoded data URL. + /// + /// A data URL with a base64 encoded image. public static NativeImage CreateFromDataURL(string dataUrl) { + var images = new Dictionary(); var parsedDataUrl = Regex.Match(dataUrl, @"data:image/(?.+?),(?.+)"); var actualData = parsedDataUrl.Groups["data"].Value; - var type = parsedDataUrl.Groups["type"].Value; var binData = Convert.FromBase64String(actualData); - var imageFormat = type switch - { - "jpeg" => ImageFormat.Jpeg, - "jpg" => ImageFormat.Jpeg, - "png" => ImageFormat.Png, - "gif" => ImageFormat.Gif, - "x-icon" => ImageFormat.Icon, - "bmp" => ImageFormat.Bmp, - _ => ImageFormat.Png - }; - - return new NativeImage(binData, imageFormat); + var image = BytesToImage(binData); + + images.Add(1.0f, image); + + return new NativeImage(images); } + /// + /// Creates a NativeImage from an image on the disk. + /// + /// The path of the image public static NativeImage CreateFromPath(string path) { - return new NativeImage(path); + var images = new Dictionary(); + if (Regex.IsMatch(path, "(@.+?x)")) + { + var dpi = ExtractDpiFromFilePath(path); + if (dpi == null) + { + throw new Exception($"Invalid scaling factor for '{path}'."); + } + + images[dpi.Value] = Image.FromFile(path); + } + else + { + var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); + var extension = Path.GetExtension(path); + // Load as 1x dpi + images[1.0f] = Image.FromFile(path); + + foreach (var scale in ScaleFactorPairs) + { + var fileName = $"{fileNameWithoutExtension}{scale}.{extension}"; + if (File.Exists(fileName)) + { + var dpi = ExtractDpiFromFilePath(fileName); + if (dpi != null) + { + images[dpi.Value] = Image.FromFile(fileName); + } + } + } + } + + return new NativeImage(images); } + /// + /// Creates an empty NativeImage + /// public NativeImage() { } - public NativeImage(Bitmap bitmap) - { - _image = bitmap; - } - public NativeImage(string path) + /// + /// Creates a NativeImage from a bitmap and scale factor + /// + public NativeImage(Image bitmap, float scaleFactor = 1.0f) { - _image = Image.FromFile(path); + _images.Add(scaleFactor, bitmap); } - - public NativeImage(byte[] buffer) + /// + /// Creates a NativeImage from a dictionary of scales and images. + /// + public NativeImage(Dictionary imageDictionary) { - var ms = new MemoryStream(buffer); - _image = Image.FromStream(ms); + _images = imageDictionary; } - public NativeImage(byte[] buffer, ImageFormat imageFormat) + /// + /// Crops the image specified by the input rectangle and computes scale factor + /// + public NativeImage Crop(Rectangle rect) { - _image = BytesToImage(buffer, imageFormat); - } + var images = new Dictionary(); + foreach (var image in _images) + { + images.Add(image.Key, Crop(rect.X, rect.Y, rect.Width, rect.Height, image.Key)); + } - public Image GetBitmap() - { - return _image; + return new NativeImage(images); } - public NativeImage Crop(Rectangle rect) + /// + /// Resizes the image and computes scale factor + /// + public NativeImage Resize(ResizeOptions options) { - using (var g = Graphics.FromImage(_image)) + var images = new Dictionary(); + foreach (var image in _images) { - var bmp = new Bitmap(rect.Width, rect.Height); - g.DrawImage(bmp, new System.Drawing.Rectangle(0, 0, _image.Width, _image.Height), new System.Drawing.Rectangle(rect.X, rect.Y, rect.Width, rect.Height), GraphicsUnit.Pixel); - - return new NativeImage(bmp); + images.Add(image.Key, Resize(options.Width, options.Height, image.Key)); } + + return new NativeImage(images); } + + /// + /// Add an image representation for a specific scale factor. + /// + /// public void AddRepresentation(AddRepresentationOptions options) { if (options.Buffer.Length > 0) { - _image = CreateFromBuffer(options.Buffer).GetBitmap(); + _images[options.ScaleFactor] = + CreateFromBuffer(options.Buffer, new CreateFromBufferOptions {ScaleFactor = options.ScaleFactor}) + .GetScale(options.ScaleFactor); } else if (!string.IsNullOrEmpty(options.DataUrl)) { - _image = CreateFromDataURL(options.DataUrl).GetBitmap(); + _images[options.ScaleFactor] = CreateFromDataURL(options.DataUrl).GetScale(options.ScaleFactor); } } - public double GetAspectRatio => _image.Width / _image.Height; + /// + /// Gets the aspect ratio for the image based on scale factor + /// + /// Optional + public float GetAspectRatio(float scaleFactor = 1.0f) + { + var image = GetScale(scaleFactor); + if (image != null) + { + return image.Width / image.Height; + } + return 0f; + } + /// + /// Returns a byte array that contains the image's raw bitmap pixel data. + /// public byte[] GetBitmap(BitmapOptions options) { - return ImageToBytes(_image.RawFormat); + return ToBitmap(new ToBitmapOptions{ ScaleFactor = options.ScaleFactor }); } + /// + /// Returns a byte array that contains the image's raw bitmap pixel data. + /// public byte[] GetNativeHandle() { - return ImageToBytes(_image.RawFormat); + return ToBitmap(new ToBitmapOptions()); } - public Size GetSize() + /// + /// Gets the size of the specified image based on scale factor + /// + public Size GetSize(float scaleFactor = 1.0f) { - return new Size + if (_images.ContainsKey(scaleFactor)) { - Width = _image.Width, - Height = _image.Height - }; + var image = _images[scaleFactor]; + return new Size + { + Width = image.Width, + Height = image.Height + }; + } + + return null; } + /// + /// Checks to see if the NativeImage instance is empty. + /// public bool IsEmpty() { - return _image == null; + return _images.Count <= 0; } /// @@ -150,78 +276,102 @@ public void SetTemplateImage(bool option) _isTemplateImage = option; } - - public NativeImage Resize(ResizeOptions options) - { - return new NativeImage((Bitmap)Resize(options.Width, options.Height)); - } - + /// + /// Outputs a bitmap based on the scale factor + /// public byte[] ToBitmap(ToBitmapOptions options) { - return ImageToBytes(_image.RawFormat); + return ImageToBytes(ImageFormat.Bmp, options.ScaleFactor); } + /// + /// Outputs a data URL based on the scale factor + /// public string ToDataURL(ToDataUrlOptions options) { - var mimeType = ImageCodecInfo.GetImageEncoders().FirstOrDefault(x => x.FormatID == _image.RawFormat.Guid)?.MimeType; + if (!_images.ContainsKey(options.ScaleFactor)) + { + return null; + } + + var image = _images[options.ScaleFactor]; + var mimeType = ImageCodecInfo.GetImageEncoders().FirstOrDefault(x => x.FormatID == image.RawFormat.Guid)?.MimeType; if (mimeType is null) { mimeType = "image/png"; } - var bytes = ImageToBytes(_image.RawFormat); + var bytes = ImageToBytes(image.RawFormat, options.ScaleFactor); var base64 = Convert.ToBase64String(bytes); return $"data:{mimeType};base64,{base64}"; } + /// + /// Outputs a JPEG for the default scale factor + /// public byte[] ToJPEG(int quality) { - return ImageToBytes(ImageFormat.Jpeg, 1.0d, quality); + return ImageToBytes(ImageFormat.Jpeg, 1.0f, quality); } + /// + /// Outputs a PNG for the specified scale factor + /// public byte[] ToPNG(ToPNGOptions options) { return ImageToBytes(ImageFormat.Png, options.ScaleFactor); } - - private byte[] ImageToBytes(ImageFormat imageFormat, double scaleFactor = 1.0d, int quality = 100) - { - using (var ms = new MemoryStream()) - { - Image img = _image; - if (Math.Abs(scaleFactor - 1.0d) > 0.0) - { - img = Resize(_image.Width, _image.Height, scaleFactor); - } + private byte[] ImageToBytes(ImageFormat imageFormat = null, float scaleFactor = 1.0f, int quality = 100) + { + using var ms = new MemoryStream(); - ImageCodecInfo encoderCodecInfo = GetEncoder(imageFormat); - Encoder encoder = Encoder.Quality; + if (_images.ContainsKey(scaleFactor)) + { + var image = _images[scaleFactor]; + var encoderCodecInfo = GetEncoder(imageFormat ?? image.RawFormat); + var encoder = Encoder.Quality; - EncoderParameters encoderParameters = new EncoderParameters(1) + var encoderParameters = new EncoderParameters(1) { - Param = new [] + Param = new[] { new EncoderParameter(encoder, quality) - } + } }; - img.Save(ms, encoderCodecInfo, encoderParameters); - + image.Save(ms, encoderCodecInfo, encoderParameters); + return ms.ToArray(); } + + return null; } - private Image Resize(int? width, int? height, double scaleFactor = 1.0d) + private Image Resize(int? width, int? height, float scaleFactor = 1.0f) { - using (var g = Graphics.FromImage(_image)) + if (!_images.ContainsKey(scaleFactor) || (width is null && height is null)) + { + return null; + } + + var image = _images[scaleFactor]; + using (var g = Graphics.FromImage(image)) { g.CompositingQuality = CompositingQuality.HighQuality; - var bmp = new Bitmap((int)Math.Round(width ?? _image.Width * scaleFactor), (int)Math.Round(height ?? _image.Height * scaleFactor)); + var aspect = GetAspectRatio(scaleFactor); + + width ??= Convert.ToInt32(image.Width * aspect); + height ??= Convert.ToInt32(image.Height * aspect); + + width = Convert.ToInt32(width * scaleFactor); + height = Convert.ToInt32(height * scaleFactor); + + var bmp = new Bitmap(width.Value, height.Value); g.DrawImage(bmp, - new System.Drawing.Rectangle(0, 0, _image.Width, _image.Height), + new System.Drawing.Rectangle(0, 0, image.Width, image.Height), new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), GraphicsUnit.Pixel); @@ -229,15 +379,40 @@ private Image Resize(int? width, int? height, double scaleFactor = 1.0d) } } - private Image BytesToImage(byte[] bytes, ImageFormat imageFormat) + private Image Crop(int? x, int? y, int? width, int? height, float scaleFactor = 1.0f) { - var ms = new MemoryStream(bytes); - return Image.FromStream(ms); + if (!_images.ContainsKey(scaleFactor)) + { + return null; + } + + var image = _images[scaleFactor]; + using (var g = Graphics.FromImage(image)) + { + g.CompositingQuality = CompositingQuality.HighQuality; + + x ??= 0; + y ??= 0; + + x = Convert.ToInt32(x * scaleFactor); + y = Convert.ToInt32(y * scaleFactor); + + width ??= image.Width; + height ??= image.Height; + + width = Convert.ToInt32(width * scaleFactor); + height = Convert.ToInt32(height * scaleFactor); + + var bmp = new Bitmap(width.Value, height.Value); + g.DrawImage(bmp, new System.Drawing.Rectangle(0, 0, image.Width, image.Height), new System.Drawing.Rectangle(x.Value, y.Value, width.Value, height.Value), GraphicsUnit.Pixel); + + return bmp; + } } private ImageCodecInfo GetEncoder(ImageFormat format) { - ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders(); + var codecs = ImageCodecInfo.GetImageDecoders(); foreach (ImageCodecInfo codec in codecs) { if (codec.FormatID == format.Guid) @@ -248,9 +423,32 @@ private ImageCodecInfo GetEncoder(ImageFormat format) return null; } - internal byte[] GetBytes() + internal Dictionary GetAllScaledImages() + { + var dict = new Dictionary(); + try + { + foreach (var (scale, image) in _images) + { + dict.Add(scale, Convert.ToBase64String(ImageToBytes(null, scale))); + } + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + + return dict; + } + + internal Image GetScale(float scaleFactor) { - return ImageToBytes(_image.RawFormat); + if (_images.ContainsKey(scaleFactor)) + { + return _images[scaleFactor]; + } + + return null; } } } diff --git a/ElectronNET.API/Entities/NativeImageJsonConverter.cs b/ElectronNET.API/Entities/NativeImageJsonConverter.cs new file mode 100644 index 00000000..a82c38ec --- /dev/null +++ b/ElectronNET.API/Entities/NativeImageJsonConverter.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using Newtonsoft.Json; + +namespace ElectronNET.API.Entities +{ + internal class NativeImageJsonConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value is NativeImage nativeImage) + { + var scaledImages = nativeImage.GetAllScaledImages(); + serializer.Serialize(writer, scaledImages); + } + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var dict = serializer.Deserialize>(reader); + var newDictionary = new Dictionary(); + foreach (var item in dict) + { + var bytes = Convert.FromBase64String(item.Value); + newDictionary.Add(item.Key, Image.FromStream(new MemoryStream(bytes))); + } + return new NativeImage(newDictionary); + } + + public override bool CanConvert(Type objectType) => objectType == typeof(NativeImage); + } +} diff --git a/ElectronNET.API/Entities/ToBitmapOptions.cs b/ElectronNET.API/Entities/ToBitmapOptions.cs index e2ba8451..4be12b3b 100644 --- a/ElectronNET.API/Entities/ToBitmapOptions.cs +++ b/ElectronNET.API/Entities/ToBitmapOptions.cs @@ -6,6 +6,6 @@ namespace ElectronNET.API.Entities { public class ToBitmapOptions { - public double ScaleFactor { get; set; } = 1.0d; + public float ScaleFactor { get; set; } = 1.0f; } } diff --git a/ElectronNET.API/Entities/ToDataUrlOptions.cs b/ElectronNET.API/Entities/ToDataUrlOptions.cs index 09c2b794..bb597fe5 100644 --- a/ElectronNET.API/Entities/ToDataUrlOptions.cs +++ b/ElectronNET.API/Entities/ToDataUrlOptions.cs @@ -6,6 +6,6 @@ namespace ElectronNET.API.Entities { public class ToDataUrlOptions { - public double ScaleFactor { get; set; } = 1.0d; + public float ScaleFactor { get; set; } = 1.0f; } } diff --git a/ElectronNET.API/Entities/ToPNGOptions.cs b/ElectronNET.API/Entities/ToPNGOptions.cs index 580d4d57..40b02b9c 100644 --- a/ElectronNET.API/Entities/ToPNGOptions.cs +++ b/ElectronNET.API/Entities/ToPNGOptions.cs @@ -6,6 +6,6 @@ namespace ElectronNET.API.Entities { public class ToPNGOptions { - public double ScaleFactor { get; set; } = 1.0d; + public float ScaleFactor { get; set; } = 1.0f; } } diff --git a/ElectronNET.Host/api/clipboard.js b/ElectronNET.Host/api/clipboard.js index 1775188d..f32c6c7d 100644 --- a/ElectronNET.Host/api/clipboard.js +++ b/ElectronNET.Host/api/clipboard.js @@ -50,11 +50,18 @@ module.exports = (socket) => { }); socket.on('clipboard-readImage', (type) => { const image = electron_1.clipboard.readImage(type); - electronSocket.emit('clipboard-readImage-Completed', image.getNativeHandle().buffer); + electronSocket.emit('clipboard-readImage-Completed', { 1: image.toPNG().toString('base64') }); }); socket.on('clipboard-writeImage', (data, type) => { - const buff = Buffer.from(JSON.parse(data), 'base64'); - const ni = electron_1.nativeImage.createFromBuffer(buff); + var data = JSON.parse(data); + const ni = electron_1.nativeImage.createEmpty(); + for (var i in data) { + var scaleFactor = i; + var bytes = data[i]; + var buff = Buffer.from(bytes, 'base64'); + ni.addRepresentation({ scaleFactor: scaleFactor, buffer: buff }); + } + electron_1.clipboard.writeImage(ni, type); }); }; diff --git a/ElectronNET.Host/api/clipboard.ts b/ElectronNET.Host/api/clipboard.ts index 0f734164..0cbca39f 100644 --- a/ElectronNET.Host/api/clipboard.ts +++ b/ElectronNET.Host/api/clipboard.ts @@ -63,13 +63,19 @@ export = (socket: SocketIO.Socket) => { socket.on('clipboard-readImage', (type) => { var image = clipboard.readImage(type); - var b64 = image.getNativeHandle().buffer.toString('base64'); - electronSocket.emit('clipboard-readImage-Completed', b64); + electronSocket.emit('clipboard-readImage-Completed', { 1: image.toPNG().toString('base64') }); }); socket.on('clipboard-writeImage', (data, type) => { - var buff = Buffer.from(JSON.parse(data), 'base64'); - const ni = nativeImage.createFromBuffer(buff); + var data = JSON.parse(data); + const ni = nativeImage.createEmpty(); + for (var i in data) { + var scaleFactor = i; + var bytes = data[i]; + var buff = Buffer.from(bytes, 'base64'); + ni.addRepresentation({ scaleFactor: +scaleFactor, buffer: buff }); + } + clipboard.writeImage(ni, type); }); }; diff --git a/ElectronNET.WebApp/Controllers/ClipboardController.cs b/ElectronNET.WebApp/Controllers/ClipboardController.cs index 631de9f3..a290236a 100644 --- a/ElectronNET.WebApp/Controllers/ClipboardController.cs +++ b/ElectronNET.WebApp/Controllers/ClipboardController.cs @@ -5,6 +5,7 @@ using ElectronNET.API; using System.Linq; using ElectronNET.API.Entities; +using Newtonsoft.Json; namespace ElectronNET.WebApp.Controllers { @@ -33,6 +34,13 @@ public IActionResult Index() var nativeImage = NativeImage.CreateFromDataURL(test.ToString()); Electron.Clipboard.WriteImage(nativeImage); }); + + Electron.IpcMain.On("paste-image-to", async test => + { + var nativeImage = await Electron.Clipboard.ReadImageAsync(); + var mainWindow = Electron.WindowManager.BrowserWindows.First(); + Electron.IpcMain.Send(mainWindow, "paste-image-from", JsonConvert.SerializeObject(nativeImage)); + }); } return View(); diff --git a/ElectronNET.WebApp/Views/Clipboard/Index.cshtml b/ElectronNET.WebApp/Views/Clipboard/Index.cshtml index 94e274a7..99264763 100644 --- a/ElectronNET.WebApp/Views/Clipboard/Index.cshtml +++ b/ElectronNET.WebApp/Views/Clipboard/Index.cshtml @@ -101,7 +101,6 @@ pasteBtn.addEventListener('click', () => {
-

In this example we copy an image to the Clipboard. After clicking 'Copy' use the text area to paste (CMD + V or CTRL + V) the image from the clipboard.

Main Process (C#)
@@ -129,9 +128,45 @@ copyBtn.addEventListener('click', () => { +
+
+ +
+
+ + +
+

In this example we paste an image from the Clipboard. After clicking 'Paste', if your clipboard contained an image, the image will show up next to the paste button.

+
Main Process (C#)
+
Electron.IpcMain.On("paste-image-to", async test =>
+{
+    var nativeImage = await Electron.Clipboard.ReadImageAsync();
+    var mainWindow = Electron.WindowManager.BrowserWindows.First();
+    Electron.IpcMain.Send(mainWindow, "paste-image-from", JsonConvert.SerializeObject(nativeImage));
+});
+
+

ProTip

+ Electron.js Support in Electron.NET. +

The clipboard module is built into Electron.js (therefore you can use this in the renderer processes).

+
const { nativeImage, clipboard } = require('electron');
+
+const pasteBtn = document.getElementById('paste-to-image');
+
+copyBtn.addEventListener('click', () => {
+    const image = clipboard.readImage();
+    document.getElementById('image-holder').src = image.toDataURL();
+})
+
+
+
+
+