diff --git a/ElectronNET.API/Clipboard.cs b/ElectronNET.API/Clipboard.cs index 8281594..c49dc95 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 @@ namespace ElectronNET.API 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 0000000..0bcd4e3 --- /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 0000000..b965ab0 --- /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 0000000..b54a9b7 --- /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 2e408d1..095cdaf 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(); - // } + private Image _image; + private bool _isTemplateImage = false; - // public static NativeImage CreateFromBuffer(byte[] buffer) - // { - // throw new NotImplementedException(); - // } + public static NativeImage CreateEmpty() + { + return new NativeImage(); + } - // public static NativeImage CreateFromBuffer(byte[] buffer, CreateFromBufferOptions options) - // { - // throw new NotImplementedException(); - // } + public static NativeImage CreateFromBuffer(byte[] buffer) + { + return new NativeImage(buffer); + } - // public static NativeImage CreateFromDataURL(string dataURL) - // { - // throw new NotImplementedException(); - // } + public static NativeImage CreateFromBuffer(byte[] buffer, CreateFromBufferOptions options) + { + return new NativeImage(buffer); + } - // public static NativeImage CreateFromPath(string path) - // { - // throw new NotImplementedException(); - // } + 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); - // public void AddRepresentation(AddRepresentationOptions options) - // { - // throw new NotImplementedException(); - // } + 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 + }; - // public NativeImage Crop(Rectangle rect) - // { - // throw new NotImplementedException(); - // } + return new NativeImage(binData, imageFormat); + } - // public int GetAspectRatio() - // { - // throw new NotImplementedException(); - // } + public static NativeImage CreateFromPath(string path) + { + return new NativeImage(path); + } - // public byte[] GetBitmap() - // { - // throw new NotImplementedException(); - // } + public NativeImage() + { + } - // public byte[] GetBitmap(BitmapOptions options) - // { - // throw new NotImplementedException(); - // } + public NativeImage(Bitmap bitmap) + { + _image = bitmap; + } + public NativeImage(string path) + { + _image = Image.FromFile(path); + } - // public byte[] GetNativeHandle() - // { - // throw new NotImplementedException(); - // } - // public Size GetSize() - // { - // throw new NotImplementedException(); - // } + public NativeImage(byte[] buffer) + { + var ms = new MemoryStream(buffer); + _image = Image.FromStream(ms); + } - // public bool IsEmpty() - // { - // throw new NotImplementedException(); - // } + public NativeImage(byte[] buffer, ImageFormat imageFormat) + { + _image = BytesToImage(buffer, imageFormat); + } - // public bool IsTemplateImage() - // { - // throw new NotImplementedException(); - // } + public Image GetBitmap() + { + return _image; + } - // public NativeImage Resize(ResizeOptions options) - // { - // throw new NotImplementedException(); - // } + 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); - // public void SetTemplateImage(bool option) - // { - // throw new NotImplementedException(); - // } + 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 byte[] ToBitmap(ToBitmapOptions options) - // { - // throw new NotImplementedException(); - // } + public double GetAspectRatio => _image.Width / _image.Height; - // public string ToDataURL(ToDataURLOptions options) - // { - // throw new NotImplementedException(); - // } - // public byte[] ToJPEG(int quality) - // { - // throw new NotImplementedException(); - // } + public byte[] GetBitmap(BitmapOptions options) + { + return ImageToBytes(_image.RawFormat); + } - // public byte[] ToPNG(ToPNGOptions options) - // { - // throw new NotImplementedException(); - // } + 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 0000000..1cc376f --- /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 0000000..e2ba845 --- /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 0000000..09c2b79 --- /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 0000000..580d4d5 --- /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 610e072..1775188 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 f0f92c1..0f73416 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 27f8d9e..631de9f 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 @@ namespace ElectronNET.WebApp.Controllers 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 4e9d8ec..6a17255 100644 --- a/ElectronNET.WebApp/Controllers/MenusController.cs +++ b/ElectronNET.WebApp/Controllers/MenusController.cs @@ -112,13 +112,13 @@ namespace ElectronNET.WebApp.Controllers 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 77e3098..94e274a 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 96afc4b..4ffa4a1 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