using PaintDotNet.Measurement.HistoryMementos; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; namespace PaintDotNet.Measurement.Tools { public class PaintBrushTool : Tool { private bool mouseDown; private Brush brush; private MouseButtons mouseButton; private List savedRects; private PointF lastMouseXY; private PointF lastNorm; private PointF lastDir; private RenderArgs renderArgs; private BitmapLayer bitmapLayer; private Cursor cursorMouseDown; private Cursor cursorMouseUp; private BrushPreviewRenderer previewRenderer; protected override bool SupportsInk { get { return true; } } protected override void OnMouseEnter() { this.previewRenderer.Visible = true; base.OnMouseEnter(); } protected override void OnMouseLeave() { this.previewRenderer.Visible = false; base.OnMouseLeave(); } protected override void OnActivate() { base.OnActivate(); cursorMouseUp = new Cursor(PdnResources.GetResourceStream("Cursors.PaintBrushToolCursor.cur")); cursorMouseDown = new Cursor(PdnResources.GetResourceStream("Cursors.PaintBrushToolCursorMouseDown.cur")); Cursor = cursorMouseUp; this.savedRects = new List(); if (ActiveLayer != null) { bitmapLayer = (BitmapLayer)ActiveLayer; renderArgs = new RenderArgs(bitmapLayer.Surface); } else { bitmapLayer = null; renderArgs = null; } this.previewRenderer = new BrushPreviewRenderer(this.RendererList); this.RendererList.Add(this.previewRenderer, false); mouseDown = false; } protected override void OnDeactivate() { if (mouseDown) { OnStylusUp(new StylusEventArgs(mouseButton, 0, lastMouseXY.X, lastMouseXY.Y, 0)); } this.RendererList.Remove(this.previewRenderer); this.previewRenderer.Dispose(); this.previewRenderer = null; this.savedRects = null; if (renderArgs != null) { renderArgs.Dispose(); renderArgs = null; } bitmapLayer = null; if (cursorMouseUp != null) { cursorMouseUp.Dispose(); cursorMouseUp = null; } if (cursorMouseDown != null) { cursorMouseDown.Dispose(); cursorMouseDown = null; } base.OnDeactivate(); } private float GetWidth(float Pressure) { return Pressure * Pressure * AppEnvironment.PenInfo().Width * 0.5f; } protected override void OnStylusDown(StylusEventArgs e) { base.OnStylusDown(e); if (mouseDown) { return; } ClearSavedMemory(); this.previewRenderer.Visible = false; Cursor = cursorMouseDown; if (((e.Button & MouseButtons.Left) == MouseButtons.Left) || ((e.Button & MouseButtons.Right) == MouseButtons.Right)) { mouseButton = e.Button; if ((mouseButton & MouseButtons.Left) == MouseButtons.Left) { brush = AppEnvironment.CreateBrush(false); } else if ((mouseButton & MouseButtons.Right) == MouseButtons.Right) { brush = AppEnvironment.CreateBrush(true); } lastMouseXY.X = e.Fx; lastMouseXY.Y = e.Fy; mouseDown = true; mouseButton = e.Button; using (PdnRegion clipRegion = Selection.CreateRegion()) { renderArgs.Graphics.SetClip(clipRegion.GetRegionReadOnly(), CombineMode.Replace); } this.OnStylusMove(new StylusEventArgs(e.Button, e.Clicks, unchecked(e.Fx + 0.01f), e.Fy, e.Delta, e.Pressure)); } } private PointF[] MakePolygon(PointF a, PointF b, PointF c, PointF d) { PointF dirA = new PointF(a.X - b.X, a.Y - b.Y); PointF dirB = new PointF(c.X - d.X, c.Y - d.Y); // Swap points as necessary to keep the polygon winding one direction if (dirA.X * dirB.X + dirA.Y * dirB.Y > 0) { return new PointF[] { a, b, d, c }; } else { return new PointF[] { a, b, c, d }; } } protected override void OnMouseMove(MouseEventArgs e) { if (this.mouseDown && e.Button != MouseButtons.None) { // This is done so that if drawing falls behind due to a // large queue of stylus inputs, it won't do any updates // until it's done. This is accomplished by only updating // when a MouseMove is caught Update(); } base.OnMouseMove(e); } protected override void OnStylusMove(StylusEventArgs e) { base.OnStylusMove(e); PointF currMouseXY = new PointF(e.Fx, e.Fy); if (mouseDown && ((e.Button & mouseButton) != MouseButtons.None)) { float pressure = GetWidth(e.Pressure); float length; PointF a = lastMouseXY; PointF b = currMouseXY; PointF dir = new PointF(b.X - a.X, b.Y - a.Y); PointF norm; PointF[] poly; RectangleF dotRect = Utility.RectangleFromCenter(currMouseXY, pressure); if (pressure > 0.5f) { renderArgs.Graphics.PixelOffsetMode = PixelOffsetMode.Half; } else { renderArgs.Graphics.PixelOffsetMode = PixelOffsetMode.None; } // save direction before normalizing lastDir = dir; // normalize length = Utility.Magnitude(dir); dir.X /= length; dir.Y /= length; // compute normal vector, calculate perpendicular offest from stroke for width norm = new PointF(dir.Y, -dir.X); norm.X *= pressure; norm.Y *= pressure; a.X -= dir.X * 0.1666f; a.Y -= dir.Y * 0.1666f; lastNorm = norm; poly = MakePolygon( new PointF(a.X - lastNorm.X, a.Y - lastNorm.Y), new PointF(a.X + lastNorm.X, a.Y + lastNorm.Y), new PointF(b.X + norm.X, b.Y + norm.Y), new PointF(b.X - norm.X, b.Y - norm.Y)); RectangleF saveRect = RectangleF.Union( dotRect, RectangleF.Union( Utility.PointsToRectangle(poly[0], poly[1]), Utility.PointsToRectangle(poly[2], poly[3]))); saveRect.Inflate(2.0f, 2.0f); // account for anti-aliasing saveRect.Intersect(ActiveLayer.Bounds); // drawing outside of the canvas is a no-op, so don't do anything in that case! // also make sure we're within the clip region if (saveRect.Width > 0 && saveRect.Height > 0 && renderArgs.Graphics.IsVisible(saveRect)) { Rectangle saveRectRounded = Utility.RoundRectangle(saveRect); saveRectRounded.Intersect(ActiveLayer.Bounds); if (saveRectRounded.Width > 0 && saveRectRounded.Height > 0) { SaveRegion(null, saveRectRounded); this.savedRects.Add(saveRectRounded); if (AppEnvironment.AntiAliasing()) { renderArgs.Graphics.SmoothingMode = SmoothingMode.AntiAlias; } else { renderArgs.Graphics.SmoothingMode = SmoothingMode.None; } renderArgs.Graphics.CompositingMode = AppEnvironment.GetCompositingMode(); renderArgs.Graphics.FillEllipse(brush, dotRect); // bail out early if the mouse hasn't even moved. If we don't bail out, we'll get a 0-distance move, which will result in a div-by-0 if (lastMouseXY != currMouseXY) { renderArgs.Graphics.FillPolygon(brush, poly, FillMode.Winding); } } bitmapLayer.Invalidate(saveRectRounded); } lastNorm = norm; lastMouseXY = currMouseXY; } else { lastMouseXY = currMouseXY; lastNorm = PointF.Empty; lastDir = PointF.Empty; this.previewRenderer.BrushSize = AppEnvironment.PenInfo().Width / 2.0f; } this.previewRenderer.BrushLocation = currMouseXY; } protected override void OnStylusUp(StylusEventArgs e) { base.OnStylusUp(e); Cursor = cursorMouseUp; if (mouseDown) { this.previewRenderer.Visible = true; mouseDown = false; if (this.savedRects.Count > 0) { PdnRegion saveMeRegion = Utility.RectanglesToRegion(this.savedRects.ToArray()); HistoryMemento ha = new BitmapHistoryMemento(Name, Image, DocumentWorkspace, ActiveLayerIndex, saveMeRegion, this.ScratchSurface); //HistoryStack.PushNewMemento(ha); saveMeRegion.Dispose(); this.savedRects.Clear(); this.ClearSavedMemory(); } this.brush.Dispose(); this.brush = null; } } public PaintBrushTool(IDocumentWorkspace documentWorkspace) : base(documentWorkspace, PdnResources.GetImageResource("Icons.PaintBrushToolIcon.png"), PdnResources.GetString("PaintBrushTool.Name"), PdnResources.GetString("PaintBrushTool.HelpText"), 'b', false, ToolBarConfigItems.Brush | ToolBarConfigItems.Pen | ToolBarConfigItems.Antialiasing | ToolBarConfigItems.AlphaBlending) { // initialize any state information you need mouseDown = false; } } }