// Leave uncommented to always use bilinear rendering. Otherwise nearest neighbor // is used while interacting with the selection via the mouse, for better performance. //#define ALWAYSHIGHQUALITY using PaintDotNet.Measurement.Enum; using PaintDotNet.Measurement.HistoryMementos; using System; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Runtime.Serialization; using System.Windows.Forms; namespace PaintDotNet.Measurement.Tools { public class MoveTool : MoveToolBase { public static string StaticName { get { return PdnResources.GetString("MoveTool.Name"); } } // if this equals false, then Render() will always use NearestNeighbor, regardless of AppEnvironment.ResamplingAlgorithm private bool fullQuality = false; private BitmapLayer activeLayer; private RenderArgs renderArgs; private bool didPaste = false; private MoveToolContext ourContext { get { return (MoveToolContext)this.context; } } [Serializable] private sealed class MoveToolContext : MoveToolBase.Context { [NonSerialized] private MaskedSurface liftedPixels; [NonSerialized] public PersistedObject poLiftedPixels; public Guid poLiftedPixelsGuid; public MaskedSurface LiftedPixels { get { if (this.liftedPixels == null) { if (this.poLiftedPixels != null) { this.liftedPixels = (MaskedSurface)poLiftedPixels.Object; } } return this.liftedPixels; } set { if (value == null) { this.poLiftedPixels = null; this.liftedPixels = null; } else { this.poLiftedPixels = new PersistedObject(value, true); this.poLiftedPixelsGuid = PersistedObjectLocker.Add(this.poLiftedPixels); this.liftedPixels = null; } } } public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue("poLiftedPixelsGuid", this.poLiftedPixelsGuid); } public MoveToolContext(SerializationInfo info, StreamingContext context) : base(info, context) { this.poLiftedPixelsGuid = (Guid)info.GetValue("poLiftedPixelsGuid", typeof(Guid)); this.poLiftedPixels = PersistedObjectLocker.Get(this.poLiftedPixelsGuid); } public MoveToolContext(MoveToolContext cloneMe) : base(cloneMe) { this.poLiftedPixelsGuid = cloneMe.poLiftedPixelsGuid; this.poLiftedPixels = cloneMe.poLiftedPixels; // do not clone this.liftedPixels = cloneMe.liftedPixels; // do not clone } public MoveToolContext() { } protected override void Dispose(bool disposing) { if (disposing) { } base.Dispose(disposing); } public override object Clone() { return new MoveToolContext(this); } } private class ContextHistoryMemento : ToolHistoryMemento { private int layerIndex; private object liftedPixelsRef; // prevent this from being GC'd [Serializable] private class OurContextHistoryMementoData : HistoryMementoData { public MoveToolContext context; public OurContextHistoryMementoData(Context context) { this.context = (MoveToolContext)context.Clone(); } } protected override HistoryMemento OnToolUndo() { MoveTool moveTool = DocumentWorkspace.GetTool() as MoveTool; if (moveTool == null) { throw new InvalidOperationException("Current Tool is not the MoveTool"); } ContextHistoryMemento cha = new ContextHistoryMemento(DocumentWorkspace, moveTool.ourContext, this.Name, this.Image); OurContextHistoryMementoData ohad = (OurContextHistoryMementoData)this.Data; Context newContext = ohad.context; if (moveTool.ActiveLayerIndex != this.layerIndex) { bool oldDOLC = moveTool.deactivateOnLayerChange; moveTool.deactivateOnLayerChange = false; moveTool.ActiveLayerIndex = this.layerIndex; moveTool.deactivateOnLayerChange = oldDOLC; moveTool.activeLayer = (BitmapLayer)moveTool.ActiveLayer; moveTool.renderArgs = new RenderArgs(moveTool.activeLayer.Surface); moveTool.ClearSavedMemory(); } moveTool.context.Dispose(); moveTool.context = newContext; moveTool.DestroyNubs(); if (moveTool.context.lifted) { moveTool.PositionNubs(moveTool.context.currentMode); } return cha; } public ContextHistoryMemento(IDocumentWorkspace documentWorkspace, MoveToolContext context, string name, ImageResource image) : base(documentWorkspace, name, image) { this.Data = new OurContextHistoryMementoData(context); this.layerIndex = this.DocumentWorkspace.GetActiveLayerIndex(); this.liftedPixelsRef = context.poLiftedPixels; } } protected override void OnActivate() { AppEnvironment.ResamplingAlgorithmChanged += AppEnvironment_ResamplingAlgorithmChanged; this.moveToolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.MoveToolCursor.cur")); this.Cursor = this.moveToolCursor; this.context.lifted = false; this.ourContext.LiftedPixels = null; this.context.offset = new Point(0, 0); this.context.liftedBounds = Selection.GetBoundsF(); this.activeLayer = (BitmapLayer)ActiveLayer; if (this.renderArgs != null) { this.renderArgs.Dispose(); this.renderArgs = null; } if (this.activeLayer == null) { this.renderArgs = null; } else { this.renderArgs = new RenderArgs(this.activeLayer.Surface); } this.tracking = false; PositionNubs(this.context.currentMode); #if ALWAYSHIGHQUALITY this.fullQuality = true; #endif base.OnActivate(); } protected override void OnDeactivate() { AppEnvironment.ResamplingAlgorithmChanged -= AppEnvironment_ResamplingAlgorithmChanged; if (this.moveToolCursor != null) { this.moveToolCursor.Dispose(); this.moveToolCursor = null; } if (context.lifted) { Drop(); } this.activeLayer = null; if (this.renderArgs != null) { this.renderArgs.Dispose(); this.renderArgs = null; } this.tracking = false; DestroyNubs(); base.OnDeactivate(); } private void AppEnvironment_ResamplingAlgorithmChanged(object sender, EventArgs e) { if (this.ourContext.LiftedPixels != null) { bool oldHQ = this.fullQuality; this.fullQuality = true; PreRender(); Render(this.context.offset, true); Update(); this.fullQuality = oldHQ; } } protected override void Drop() { RestoreSavedRegion(); PdnRegion regionCopy = Selection.CreateRegion(); using (PdnRegion simplifiedRegion = Utility.SimplifyAndInflateRegion(regionCopy, Utility.DefaultSimplificationFactor, 2)) { HistoryMemento bitmapAction2 = new BitmapHistoryMemento(Name, Image, DocumentWorkspace, ActiveLayerIndex, simplifiedRegion); bool oldHQ = this.fullQuality; this.fullQuality = true; Render(this.context.offset, true); this.fullQuality = oldHQ; this.currentHistoryMementos.Add(bitmapAction2); activeLayer.Invalidate(simplifiedRegion); Update(); } regionCopy.Dispose(); regionCopy = null; ContextHistoryMemento cha = new ContextHistoryMemento(this.DocumentWorkspace, this.ourContext, this.Name, this.Image); this.currentHistoryMementos.Add(cha); string name; ImageResource image; if (didPaste) { name = EnumLocalizer.EnumValueToLocalizedName(typeof(CommonAction), CommonAction.Paste); image = PdnResources.GetImageResource("Icons.MenuEditPasteIcon.png"); } else { name = this.Name; image = this.Image; } didPaste = false; SelectionHistoryMemento sha = new SelectionHistoryMemento(this.Name, this.Image, this.DocumentWorkspace); this.currentHistoryMementos.Add(sha); this.context.Dispose(); this.context = new MoveToolContext(); this.FlushHistoryMementos(PdnResources.GetString("MoveTool.HistoryMemento.DropPixels")); } protected override void OnSelectionChanging() { base.OnSelectionChanging(); if (!dontDrop) { if (context.lifted) { Drop(); } if (tracking) { tracking = false; } } } protected override void OnSelectionChanged() { if (!context.lifted) { DestroyNubs(); PositionNubs(this.context.currentMode); } base.OnSelectionChanged(); } /// /// Provided as a special entry point so that Paste can work well. /// /// What you want to paste. /// Where you want to paste it. public void PasteMouseDown(SurfaceForClipboard sfc, Point offset) { if (this.context.lifted) { Drop(); } MaskedSurface pixels = sfc.MaskedSurface; PdnGraphicsPath pastePath = pixels.CreatePath(); PdnRegion pasteRegion = new PdnRegion(pastePath); PdnRegion simplifiedPasteRegion = Utility.SimplifyAndInflateRegion(pasteRegion); HistoryMemento bitmapAction = new BitmapHistoryMemento(Name, Image, DocumentWorkspace, ActiveLayerIndex, simplifiedPasteRegion); // SLOW (110ms) this.currentHistoryMementos.Add(bitmapAction); PushContextHistoryMemento(); this.context.seriesGuid = Guid.NewGuid(); this.context.currentMode = Mode.Translate; this.context.startEdge = Edge.None; this.context.startAngle = 0.0f; this.ourContext.LiftedPixels = pixels; this.context.lifted = true; this.context.liftTransform = new Matrix(); this.context.liftTransform.Reset(); this.context.deltaTransform = new Matrix(); this.context.deltaTransform.Reset(); this.context.offset = new Point(0, 0); bool oldDD = this.dontDrop; this.dontDrop = true; SelectionHistoryMemento sha = new SelectionHistoryMemento(null, null, DocumentWorkspace); this.currentHistoryMementos.Add(sha); Selection.PerformChanging(); Selection.Reset(); Selection.SetContinuation(pastePath, CombineMode.Replace, true); pastePath = null; Selection.CommitContinuation(); Selection.PerformChanged(); PushContextHistoryMemento(); this.context.liftedBounds = Selection.GetBoundsF(false); this.context.startBounds = this.context.liftedBounds; this.context.baseTransform = new Matrix(); this.context.baseTransform.Reset(); this.tracking = true; this.dontDrop = oldDD; this.didPaste = true; this.tracking = true; DestroyNubs(); PositionNubs(this.context.currentMode); // we use the value 70,000 to simulate mouse input because that's guaranteed to be out of bounds of where // the mouse can actually be -- PDN is limited to 65536 x 65536 images by design MouseEventArgs mea1 = new MouseEventArgs(MouseButtons.Left, 0, 70000, 70000, 0); MouseEventArgs mea2 = new MouseEventArgs(MouseButtons.Left, 0, 70000 + offset.X, 70000 + offset.Y, 0); this.context.startMouseXY = new Point(70000, 70000); OnMouseDown(mea1); OnMouseMove(mea2); // SLOW (200ms) OnMouseUp(mea2); } protected override void OnLift(MouseEventArgs e) { PdnGraphicsPath liftPath = Selection.CreatePath(); PdnRegion liftRegion = Selection.CreateRegion(); this.ourContext.LiftedPixels = new MaskedSurface(activeLayer.Surface, liftPath); HistoryMemento bitmapAction = new BitmapHistoryMemento( Name, Image, DocumentWorkspace, ActiveLayerIndex, this.ourContext.poLiftedPixelsGuid); this.currentHistoryMementos.Add(bitmapAction); // If the user is holding down the control key, we want to *copy* the pixels // and not "lift and erase" if ((ModifierKeys & Keys.Control) == Keys.None) { ColorBgra fill = AppEnvironment.SecondaryColor(); fill.A = 0; UnaryPixelOp op = new UnaryPixelOps.Constant(fill); op.Apply(this.renderArgs.Surface, liftRegion); } liftRegion.Dispose(); liftRegion = null; liftPath.Dispose(); liftPath = null; } protected override void PushContextHistoryMemento() { ContextHistoryMemento cha = new ContextHistoryMemento(this.DocumentWorkspace, this.ourContext, null, null); this.currentHistoryMementos.Add(cha); } protected override void Render(Point newOffset, bool useNewOffset) { Render(newOffset, useNewOffset, true); } protected void Render(Point newOffset, bool useNewOffset, bool saveRegion) { Rectangle saveBounds = Selection.GetBounds(); PdnRegion selectedRegion = Selection.CreateRegion(); PdnRegion simplifiedRegion = Utility.SimplifyAndInflateRegion(selectedRegion); if (saveRegion) { SaveRegion(simplifiedRegion, saveBounds); } WaitCursorChanger wcc = null; if (this.fullQuality && AppEnvironment.ResamplingAlgorithm() == ResamplingAlgorithm.Bilinear) { wcc = new WaitCursorChanger(DocumentWorkspace.GetThis()); } this.ourContext.LiftedPixels.Draw( this.renderArgs.Surface, this.context.deltaTransform, this.fullQuality ? AppEnvironment.ResamplingAlgorithm() : ResamplingAlgorithm.NearestNeighbor); if (wcc != null) { wcc.Dispose(); wcc = null; } activeLayer.Invalidate(simplifiedRegion); PositionNubs(this.context.currentMode); simplifiedRegion.Dispose(); selectedRegion.Dispose(); } protected override void PreRender() { RestoreSavedRegion(); } protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); if (!tracking) { return; } this.fullQuality = true; OnMouseMove(e); #if !ALWAYSHIGHQUALITY this.fullQuality = false; #endif this.rotateNub.Visible = false; tracking = false; PositionNubs(this.context.currentMode); string resourceName; switch (this.context.currentMode) { default: throw new InvalidEnumArgumentException(); case Mode.Rotate: resourceName = "MoveTool.HistoryMemento.Rotate"; break; case Mode.Scale: resourceName = "MoveTool.HistoryMemento.Scale"; break; case Mode.Translate: resourceName = "MoveTool.HistoryMemento.Translate"; break; } this.context.startAngle += this.angleDelta; if (this.context.liftTransform == null) { this.context.liftTransform = new Matrix(); } this.context.liftTransform.Reset(); this.context.liftTransform.Multiply(this.context.deltaTransform, MatrixOrder.Append); string actionName = PdnResources.GetString(resourceName); FlushHistoryMementos(actionName); } private void FlushHistoryMementos(string name) { if (this.currentHistoryMementos.Count > 0) { CompoundHistoryMemento cha = new CompoundHistoryMemento(null, null, this.currentHistoryMementos.ToArray()); string haName; ImageResource image; if (this.didPaste) { haName = PdnResources.GetString("CommonAction.Paste"); image = PdnResources.GetImageResource("Icons.MenuEditPasteIcon.png"); this.didPaste = false; } else { if (name == null) { haName = this.Name; } else { haName = name; } image = this.Image; } CompoundToolHistoryMemento ctha = new CompoundToolHistoryMemento(cha, this.DocumentWorkspace, haName, image); ctha.SeriesGuid = context.seriesGuid; //HistoryStack.PushNewMemento(ctha); this.currentHistoryMementos.Clear(); } } public MoveTool(IDocumentWorkspace documentWorkspace) : base(documentWorkspace, PdnResources.GetImageResource("Icons.MoveToolIcon.png"), MoveTool.StaticName, PdnResources.GetString("MoveTool.HelpText"), // "Click and drag to move a selected region", 'm', false, ToolBarConfigItems.Resampling) { this.context = new MoveToolContext(); this.enableOutline = false; } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { DestroyNubs(); if (this.renderArgs != null) { this.renderArgs.Dispose(); this.renderArgs = null; } if (this.context != null) { this.context.Dispose(); this.context = null; } } } protected override void OnExecutingHistoryMemento(ExecutingHistoryMementoEventArgs e) { this.dontDrop = true; RestoreSavedRegion(); ClearSavedMemory(); if (e.MayAlterSuspendTool) { e.SuspendTool = false; } } protected override void OnExecutedHistoryMemento(ExecutedHistoryMementoEventArgs e) { if (context.lifted) { bool oldHQ = this.fullQuality; this.fullQuality = false; Render(context.offset, true); ClearSavedMemory(); this.fullQuality = oldHQ; } else { DestroyNubs(); PositionNubs(this.context.currentMode); } this.dontDrop = false; } protected override void OnFinishedHistoryStepGroup() { if (context.lifted) { bool oldHQ = this.fullQuality; this.fullQuality = true; Render(context.offset, true, false); this.fullQuality = oldHQ; } base.OnFinishedHistoryStepGroup(); } } }