using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Platform.Storage; using ChunkyImageLib; using PixiEditor.AvaloniaUI.Helpers; using PixiEditor.AvaloniaUI.Helpers.Constants; using PixiEditor.AvaloniaUI.Helpers.Extensions; using PixiEditor.AvaloniaUI.Models.Clipboard; using PixiEditor.AvaloniaUI.Models.Commands.Attributes.Evaluators; using PixiEditor.AvaloniaUI.Models.Dialogs; using PixiEditor.AvaloniaUI.Models.IO; using PixiEditor.AvaloniaUI.ViewModels.Document; using PixiEditor.ChangeableDocument.Enums; using PixiEditor.DrawingApi.Core.Numerics; using PixiEditor.DrawingApi.Core.Surface.ImageData; using PixiEditor.Numerics; using PixiEditor.Parser; using PixiEditor.Parser.Deprecated; using Bitmap = Avalonia.Media.Imaging.Bitmap; namespace PixiEditor.AvaloniaUI.Models.Controllers; #nullable enable internal static class ClipboardController { public static IClipboard Clipboard { get; private set; } private const string PositionFormat = "PixiEditor.Position"; public static void Initialize(IClipboard clipboard) { Clipboard = clipboard; } public static readonly string TempCopyFilePath = Path.Join( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "PixiEditor", "Copied.png"); /// /// Copies the selection to clipboard in PNG, Bitmap and DIB formats. /// public static async Task CopyToClipboard(DocumentViewModel document) { await Clipboard.ClearAsync(); var surface = document.MaybeExtractSelectedArea(); if (surface.IsT0) return; if (surface.IsT1) { NoticeDialog.Show("SELECTED_AREA_EMPTY", "NOTHING_TO_COPY"); return; } var (actuallySurface, area) = surface.AsT2; DataObject data = new DataObject(); using (ImgData pngData = actuallySurface.DrawingSurface.Snapshot().Encode()) { using MemoryStream pngStream = new MemoryStream(); await pngData.AsStream().CopyToAsync(pngStream); data.Set(ClipboardDataFormats.Png, pngStream.ToArray()); // PNG, supports transparency pngStream.Position = 0; Directory.CreateDirectory(Path.GetDirectoryName(TempCopyFilePath)!); await using FileStream fileStream = new FileStream(TempCopyFilePath, FileMode.Create, FileAccess.Write); await pngStream.CopyToAsync(fileStream); data.SetFileDropList(new [] { TempCopyFilePath }); } WriteableBitmap finalBitmap = actuallySurface.ToWriteableBitmap(); data.Set(ClipboardDataFormats.Bitmap, finalBitmap); // Bitmap, no transparency data.Set(ClipboardDataFormats.Dib, finalBitmap); // DIB format, no transparency if (area.Size != document.SizeBindable && area.Pos != VecI.Zero) { data.SetVecI(PositionFormat, area.Pos); } await Clipboard.SetDataObjectAsync(data); } /// /// Pastes image from clipboard into new layer. /// public static bool TryPaste(DocumentViewModel document, IDataObject data, bool pasteAsNew = false) { List images = GetImage(data); if (images.Count == 0) return false; if (images.Count == 1) { var dataImage = images[0]; var position = dataImage.position; if (document.SizeBindable.X < position.X || document.SizeBindable.Y < position.Y) { position = VecI.Zero; } if (pasteAsNew) { var guid = document.Operations.CreateStructureMember(StructureMemberType.Layer, "New Layer", false); if (guid == null) { return false; } document.Operations.SetSelectedMember(guid.Value); document.Operations.PasteImageWithTransform(dataImage.image, position, guid.Value, false); } else { document.Operations.PasteImageWithTransform(dataImage.image, position); } return true; } document.Operations.PasteImagesAsLayers(images, document.AnimationDataViewModel.ActiveFrameBindable); return true; } /// /// Pastes image from clipboard into new layer. /// public static async Task TryPasteFromClipboard(DocumentViewModel document, bool pasteAsNew = false) { //TODO: maybe if we have access to more formats, we can check them as well var data = await TryGetDataObject(); if (data == null) return false; return TryPaste(document, data, pasteAsNew); } private static async Task TryGetDataObject() { string[] formats = await Clipboard.GetFormatsAsync(); if (formats.Length == 0) return null; string format = formats[0]; var obj = await Clipboard.GetDataAsync(format); if (obj == null) return null; DataObject data = new DataObject(); data.Set(format, obj); return data; } public static async Task> GetImagesFromClipboard() { var dataObj = await TryGetDataObject(); return GetImage(dataObj); } /// /// Gets images from clipboard, supported PNG, Dib and Bitmap. /// public static List GetImage(IDataObject? data) { List surfaces = new(); if (data == null) return surfaces; if (TryExtractSingleImage(data, out var singleImage)) { surfaces.Add(new DataImage(singleImage, data.GetVecI(PositionFormat))); return surfaces; } var paths = data.GetFileDropList().Select(x => x.Path.LocalPath).ToList(); if(paths != null && data.TryGetRawTextPath(out string? textPath)) { paths.Add(textPath); } if (paths == null || paths.Count == 0) { return surfaces; } foreach (string? path in paths) { if (path is null || !Importer.IsSupportedFile(path)) continue; try { Surface imported; if (Path.GetExtension(path) == ".pixi") { using var stream = new FileStream(path, FileMode.Open, FileAccess.Read); try { imported = Surface.Load(PixiParser.Deserialize(path).PreviewImage); } catch (InvalidFileException e) { // Check if it could be a old file if (!e.Message.StartsWith("Header")) { throw; } stream.Position = 0; var document = DeprecatedPixiParser.Deserialize(stream); imported = Surface.Load(document.PreviewImage); } } else { imported = Surface.Load(path); } string filename = Path.GetFullPath(path); surfaces.Add(new DataImage(filename, imported, data.GetVecI(PositionFormat))); } catch { continue; } } return surfaces; } [Evaluator.CanExecute("PixiEditor.Clipboard.HasImageInClipboard")] public static async Task IsImageInClipboard() { var formats = await Clipboard.GetFormatsAsync(); if (formats == null || formats.Length == 0) return false; bool isImage = IsImageFormat(formats); if (!isImage) { string path = await TryFindImageInFiles(formats); return path != string.Empty; } return isImage; } private static async Task TryFindImageInFiles(string[] formats) { foreach (string format in formats) { if (format == DataFormats.Text) { string text = await Clipboard.GetTextAsync(); if (Importer.IsSupportedFile(text)) { return text; } } } return string.Empty; } public static bool IsImage(IDataObject? dataObject) { if (dataObject == null) return false; try { var files = dataObject.GetFileDropList(); if (files != null) { if (IsImageFormat(files.Select(x => x.Path.LocalPath).ToArray())) { return true; } } } catch (COMException) { return false; } return HasData(dataObject, "PNG", ClipboardDataFormats.Dib, ClipboardDataFormats.Bitmap); } private static bool IsImageFormat(string[] files) { foreach (var file in files) { if (Importer.IsSupportedFile(file)) { return true; } } return false; } private static Bitmap FromPNG(IDataObject data) { object obj = data.Get("PNG"); if(obj is byte[] bytes) { using MemoryStream stream = new MemoryStream(bytes); return new Bitmap(stream); } if (obj is MemoryStream memoryStream) { return new Bitmap(memoryStream); } throw new InvalidDataException("PNG data is not in a supported format."); } private static bool HasData(IDataObject dataObject, params string[] formats) => formats.Any(dataObject.Contains); private static bool TryExtractSingleImage(IDataObject data, [NotNullWhen(true)] out Surface? result) { try { Bitmap source; if (data.Contains("PNG")) { source = FromPNG(data); } else if (HasData(data, ClipboardDataFormats.Dib, ClipboardDataFormats.Bitmap)) { var imgs = GetImage(data); if (imgs == null || imgs.Count == 0) { result = null; return false; } result = imgs[0].image; return true; } else { result = null; return false; } if (source.Format.Value.IsSkiaSupported()) { result = SurfaceHelpers.FromBitmap(source); } else { source.ExtractPixels(out IntPtr address); Bitmap newFormat = new Bitmap(PixelFormats.Bgra8888, AlphaFormat.Premul, address, source.PixelSize, source.Dpi, source.PixelSize.Width * 4); result = SurfaceHelpers.FromBitmap(newFormat); } return true; } catch { } result = null; return false; } }