| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738 | using PaintDotNet.Measurement.HistoryMementos;using PaintDotNet.SystemLayer;using System;using System.Drawing;using System.Drawing.Drawing2D;using System.Threading;using System.Windows.Forms;namespace PaintDotNet.Measurement.Tools{    public sealed class GradientTool : Tool    {        public static string StaticName        {            get            {                return PdnResources.GetString("GradientTool.Name");            }        }        public static ImageResource StaticImage        {            get            {                return PdnResources.GetImageResource("Icons.GradientToolIcon.png");            }        }        private Cursor toolCursor;        private Cursor toolMouseDownCursor;        private ImageResource toolIcon;        private MoveNubRenderer[] moveNubs;        private MoveNubRenderer startNub;        private MoveNubRenderer endNub;        private MoveNubRenderer mouseNub; // which nub the mouse is manipulating, null for neither        private MouseButtons mouseButton = MouseButtons.None;        private PointF startPoint;        private PointF endPoint;        private string helpTextInitial = PdnResources.GetString("GradientTool.HelpText");        private string helpTextWhileAdjustingFormat = PdnResources.GetString("GradientTool.HelpText.WhileAdjusting.Format");        private string helpTextAdjustable = PdnResources.GetString("GradientTool.HelpText.Adjustable");        private bool shouldMoveBothNubs = false;        private bool shouldConstrain = false;        private bool shouldSwapColors = false;        private bool gradientActive = false; // we are drawing or adjusting a gradient        private CompoundHistoryMemento historyMemento = null;        private void ConstrainPoints(PointF a, ref PointF b)        {            PointF dir = new PointF(b.X - a.X, b.Y - a.Y);            double theta = Math.Atan2(dir.Y, dir.X);            double len = Math.Sqrt(dir.X * dir.X + dir.Y * dir.Y);            theta = Math.Round(12 * theta / Math.PI) * Math.PI / 12;            b = new PointF((float)(a.X + len * Math.Cos(theta)), (float)(a.Y + len * Math.Sin(theta)));        }        protected override void OnPulse()        {            if (this.gradientActive && this.moveNubs != null)            {                for (int i = 0; i < this.moveNubs.Length; ++i)                {                    if (!this.moveNubs[i].Visible)                    {                        continue;                    }                    // Oscillate between 25% and 100% alpha over a period of 2 seconds                    // Alpha value of 100% is sustained for a large duration of this period                    const int period = 10000 * 2000; // 10000 ticks per ms, 2000ms per second                    long tick = (DateTime.Now.Ticks % period) + (i * (period / this.moveNubs.Length)); ;                    double sin = Math.Sin(((double)tick / (double)period) * (2.0 * Math.PI));                    // sin is [-1, +1]                    sin = Math.Min(0.5, sin);                    // sin is [-1, +0.5]                    sin += 1.0;                    // sin is [0, 1.5]                    sin /= 2.0;                    // sin is [0, 0.75]                    sin += 0.25;                    // sin is [0.25, 1]                    int newAlpha = (int)(sin * 255.0);                    int clampedAlpha = Utility.Clamp(newAlpha, 0, 255);                    this.moveNubs[i].Alpha = clampedAlpha;                }            }            base.OnPulse();        }        private bool controlKeyDown = false;        private DateTime controlKeyDownTime = DateTime.MinValue;        private readonly TimeSpan controlKeyDownThreshold = new TimeSpan(0, 0, 0, 0, 400);        protected override void OnKeyDown(KeyEventArgs e)        {            switch (e.KeyCode)            {                case Keys.ControlKey:                    if (!this.controlKeyDown)                    {                        this.controlKeyDown = true;                        this.controlKeyDownTime = DateTime.Now;                    }                    break;                case Keys.ShiftKey:                    bool oldShouldConstrain = this.shouldConstrain;                    this.shouldConstrain = true;                    if (this.gradientActive &&                        this.mouseButton != MouseButtons.None &&                        !oldShouldConstrain)                    {                        RenderGradient();                    }                    break;            }            base.OnKeyDown(e);        }        protected override void OnKeyUp(KeyEventArgs e)        {            switch (e.KeyCode)            {                case Keys.ControlKey:                    TimeSpan heldDuration = (DateTime.Now - this.controlKeyDownTime);                    // If the user taps Ctrl, then we should toggle the visiblity of the moveNubs                    if (heldDuration < this.controlKeyDownThreshold)                    {                        for (int i = 0; i < this.moveNubs.Length; ++i)                        {                            this.moveNubs[i].Visible = this.gradientActive && !this.moveNubs[i].Visible;                        }                    }                    this.controlKeyDown = false;                    break;                case Keys.ShiftKey:                    this.shouldConstrain = false;                    if (this.gradientActive &&                        this.mouseButton != MouseButtons.None)                    {                        RenderGradient();                    }                    break;            }            base.OnKeyUp(e);        }        protected override void OnKeyPress(KeyPressEventArgs e)        {            if (this.gradientActive)            {                switch (e.KeyChar)                {                    case '\r': // Enter                        e.Handled = true;                        CommitGradient();                        break;                    case (char)27: // Escape                        e.Handled = true;                        CommitGradient();                        //HistoryStack.StepBackward();                        break;                }            }            base.OnKeyPress(e);        }        private sealed class RenderContext        {            public Surface surface;            public Rectangle[] rois;            public GradientRenderer renderer;            public void Render(object cpuIndexObj)            {                int cpuIndex = (int)cpuIndexObj;                int start = (this.rois.Length * cpuIndex) / Processor.LogicalCpuCount;                int end = (this.rois.Length * (cpuIndex + 1)) / Processor.LogicalCpuCount;                renderer.Render(this.surface, this.rois, start, end - start);            }        }        private void RenderGradient(Surface surface, PdnRegion clipRegion, CompositingMode compositingMode,            PointF startPointF, ColorBgra startColor, PointF endPointF, ColorBgra endColor)        {            GradientRenderer gr = AppEnvironment.GradientInfo().CreateGradientRenderer();            gr.StartColor = startColor;            gr.EndColor = endColor;            gr.StartPoint = startPointF;            gr.EndPoint = endPointF;            gr.AlphaBlending = (compositingMode == CompositingMode.SourceOver);            gr.BeforeRender();            Rectangle[] oldRois = clipRegion.GetRegionScansReadOnlyInt();            Rectangle[] newRois;            if (oldRois.Length == 1)            {                newRois = new Rectangle[Processor.LogicalCpuCount];                Utility.SplitRectangle(oldRois[0], newRois);            }            else            {                newRois = oldRois;            }            RenderContext rc = new RenderContext();            rc.surface = surface;            rc.rois = newRois;            rc.renderer = gr;            WaitCallback wc = new WaitCallback(rc.Render);            for (int i = 0; i < Processor.LogicalCpuCount; ++i)            {                if (i == Processor.LogicalCpuCount - 1)                {                    wc(BoxedConstants.GetInt32(i));                }                else                {                    PaintDotNet.Threading.ThreadPool.Global.QueueUserWorkItem(wc, BoxedConstants.GetInt32(i));                }            }            PaintDotNet.Threading.ThreadPool.Global.Drain();        }        private void RenderGradient()        {            ColorBgra startColor = AppEnvironment.PrimaryColor();            ColorBgra endColor = AppEnvironment.SecondaryColor();            if (this.shouldSwapColors)            {                if (AppEnvironment.GradientInfo().AlphaOnly)                {                    // In transparency mode, the color values don't matter. We just need to reverse                    // and invert the alpha values.                    byte startAlpha = startColor.A;                    startColor.A = (byte)(255 - endColor.A);                    endColor.A = (byte)(255 - startAlpha);                }                else                {                    Utility.Swap(ref startColor, ref endColor);                }            }            PointF startPointF = this.startPoint;            PointF endPointF = this.endPoint;            if (this.shouldConstrain)            {                if (this.mouseNub == this.startNub)                {                    ConstrainPoints(endPointF, ref startPointF);                }                else                {                    ConstrainPoints(startPointF, ref endPointF);                }            }            RestoreSavedRegion();            Surface surface = ((BitmapLayer)DocumentWorkspace.GetActiveLayer()).Surface;            PdnRegion clipRegion = DocumentWorkspace.GetSelection().CreateRegion();            SaveRegion(clipRegion, clipRegion.GetBoundsInt());            RenderGradient(surface, clipRegion, AppEnvironment.GetCompositingMode(), startPointF, startColor, endPointF, endColor);            using (PdnRegion simplified = Utility.SimplifyAndInflateRegion(clipRegion, Utility.DefaultSimplificationFactor, 0))            {                DocumentWorkspace.GetActiveLayer().Invalidate(simplified);            }            clipRegion.Dispose();            // Set up status bar text            double angle = -180.0 * Math.Atan2(endPointF.Y - startPointF.Y, endPointF.X - startPointF.X) / Math.PI;            MeasurementUnit units = AppWorkspace.GetUnits();            double offsetXPhysical = Document.PixelToPhysicalX(endPointF.X - startPointF.X, units);            double offsetYPhysical = Document.PixelToPhysicalY(endPointF.Y - startPointF.Y, units);            double offsetLengthPhysical = Math.Sqrt(offsetXPhysical * offsetXPhysical + offsetYPhysical * offsetYPhysical);            string numberFormat;            string unitsAbbreviation;            if (units != MeasurementUnit.Pixel)            {                string unitsAbbreviationName = "MeasurementUnit." + units.ToString() + ".Abbreviation";                unitsAbbreviation = PdnResources.GetString(unitsAbbreviationName);                numberFormat = "F2";            }            else            {                unitsAbbreviation = string.Empty;                numberFormat = "F0";            }            string unitsString = PdnResources.GetString("MeasurementUnit." + units.ToString() + ".Plural");            string statusText = string.Format(                this.helpTextWhileAdjustingFormat,                offsetXPhysical.ToString(numberFormat),                unitsAbbreviation,                offsetYPhysical.ToString(numberFormat),                unitsAbbreviation,                offsetLengthPhysical.ToString("F2"),                unitsString,                angle.ToString("F2"));            SetStatus(this.toolIcon, statusText);            // Make sure everything is on screen.            Update();        }        protected override void OnMouseDown(MouseEventArgs e)        {            PointF mousePt = new PointF(e.X, e.Y);            MoveNubRenderer mouseCursorNub = PointToNub(mousePt);            if (this.mouseButton != MouseButtons.None)            {                this.shouldMoveBothNubs = !this.shouldMoveBothNubs;            }            else            {                bool startNewGradient = true;                this.mouseButton = e.Button;                if (!this.gradientActive)                {                    this.shouldSwapColors = (this.mouseButton == MouseButtons.Right);                }                else                {                    this.shouldMoveBothNubs = false;                    // We are already in the process of drawing or adjusting a gradient.                    // Determine if they clicked to drag one of the nubs for adjusting.                    if (mouseCursorNub == null)                    {                        // No. Commit the old gradient and begin a new one.                        CommitGradient();                        startNewGradient = true;                        this.shouldSwapColors = (this.mouseButton == MouseButtons.Right);                    }                    else                    {                        // Yes. Continue adjusting the old gradient.                        Cursor = this.handCursorMouseDown;                        this.mouseNub = mouseCursorNub;                        this.mouseNub.Location = mousePt;                        if (this.mouseNub == this.startNub)                        {                            this.startPoint = mousePt;                        }                        else                        {                            this.endPoint = mousePt;                        }                        if (this.mouseButton == MouseButtons.Right)                        {                            this.shouldSwapColors = !this.shouldSwapColors;                        }                        RenderGradient();                        startNewGradient = false;                    }                }                if (startNewGradient)                {                    // Brand new gradient. Set everything up.                    this.startPoint = mousePt;                    this.startNub.Location = mousePt;                    this.startNub.Visible = true;                    this.endNub.Location = mousePt;                    this.endNub.Visible = true;                    this.endPoint = mousePt;                    this.mouseNub = mouseCursorNub;                    Cursor = this.toolMouseDownCursor;                    this.gradientActive = true;                    ClearSavedRegion();                    RenderGradient();                    this.historyMemento = new CompoundHistoryMemento(StaticName, StaticImage);                    //HistoryStack.PushNewMemento(this.historyMemento); // this makes it so they can push Esc to undo                }            }            base.OnMouseDown(e);        }        private MoveNubRenderer PointToNub(PointF mousePtF)        {            float startDistance = Utility.Distance(mousePtF, this.startNub.Location);            float endDistance = Utility.Distance(mousePtF, this.endNub.Location);            if (this.startNub.Visible &&                startDistance < endDistance &&                this.startNub.IsPointTouching(mousePtF, true))            {                return this.startNub;            }            else if (this.endNub.Visible &&                     this.endNub.IsPointTouching(mousePtF, true))            {                return this.endNub;            }            else            {                return null;            }        }        protected override void OnMouseMove(MouseEventArgs e)        {            PointF mousePtF = new PointF(e.X, e.Y);            MoveNubRenderer mouseCursorNub = PointToNub(mousePtF);            if (this.mouseButton == MouseButtons.None)            {                // No mouse button dragging is being tracked.                this.mouseNub = mouseCursorNub;                if (this.mouseNub == this.startNub || this.mouseNub == this.endNub)                {                    Cursor = this.handCursor;                }                else                {                    Cursor = this.toolCursor;                }            }            else            {                if (this.mouseNub == this.startNub)                {                    // Dragging the start nub                    if (this.shouldConstrain && !this.shouldMoveBothNubs)                    {                        ConstrainPoints(this.endPoint, ref mousePtF);                    }                    this.startNub.Location = mousePtF;                    SizeF delta = new SizeF(                        this.startNub.Location.X - this.startPoint.X,                        this.startNub.Location.Y - this.startPoint.Y);                    this.startPoint = mousePtF;                    if (this.shouldMoveBothNubs)                    {                        this.endNub.Location += delta;                        this.endPoint += delta;                    }                }                else if (this.mouseNub == this.endNub)                {                    // Dragging the ending nub                    if (this.shouldConstrain && !this.shouldMoveBothNubs)                    {                        ConstrainPoints(this.startPoint, ref mousePtF);                    }                    this.endNub.Location = mousePtF;                    SizeF delta = new SizeF(                        this.endNub.Location.X - this.endPoint.X,                        this.endNub.Location.Y - this.endPoint.Y);                    this.endPoint = mousePtF;                    if (this.shouldMoveBothNubs)                    {                        this.startNub.Location += delta;                        this.startPoint += delta;                    }                }                else                {                    // Initial drawing                    if (this.shouldMoveBothNubs)                    {                        SizeF delta = new SizeF(                            this.endNub.Location.X - mousePtF.X,                            this.endNub.Location.Y - mousePtF.Y);                        this.startNub.Location -= delta;                        this.startPoint -= delta;                    }                    else if (this.shouldConstrain)                    {                        ConstrainPoints(this.startPoint, ref mousePtF);                    }                    this.endNub.Location = mousePtF;                    this.endPoint = mousePtF;                }                RenderGradient();            }            base.OnMouseMove(e);        }        private void CommitGradient()        {            if (!this.gradientActive)            {                throw new InvalidOperationException("CommitGradient() called when a gradient was not active");            }            RenderGradient();            using (PdnRegion clipRegion = DocumentWorkspace.GetSelection().CreateRegion())            {                BitmapHistoryMemento bhm = new BitmapHistoryMemento(                    StaticName,                    StaticImage,                    DocumentWorkspace,                    DocumentWorkspace.GetActiveLayerIndex(),                    clipRegion,                    this.ScratchSurface);                this.historyMemento.PushNewAction(bhm);                // We assume this.historyMemento has already been pushed on to the HistoryStack                this.historyMemento = null;            }            this.startNub.Visible = false;            this.endNub.Visible = false;            ClearSavedRegion();            ClearSavedMemory();            this.gradientActive = false;            SetStatus(this.toolIcon, this.helpTextInitial);        }        protected override void OnMouseUp(MouseEventArgs e)        {            PointF mousePt = new PointF(e.X, e.Y);            if (!this.gradientActive)            {                // do nothing            }            else if (e.Button != this.mouseButton)            {                this.shouldMoveBothNubs = !this.shouldMoveBothNubs;            }            else            {                if (this.mouseNub == this.startNub)                {                    // We were adjusting the start nub.                    if (this.shouldConstrain)                    {                        ConstrainPoints(this.endPoint, ref mousePt);                    }                    this.startNub.Location = mousePt;                    this.startPoint = mousePt;                }                else if (this.mouseNub == this.endNub)                {                    // We were adjusting the ending nub.                    if (this.shouldConstrain)                    {                        ConstrainPoints(this.startPoint, ref mousePt);                    }                    this.endNub.Location = mousePt;                    this.endPoint = mousePt;                }                else                {                    // We were drawing a brand new gradient.                    if (this.shouldConstrain)                    {                        ConstrainPoints(this.startPoint, ref mousePt);                    }                    this.endNub.Location = mousePt;                    this.endPoint = mousePt;                }                // In any event, make sure the nubs are visible and other state adjusted accordingly.                this.startNub.Visible = true;                this.endNub.Visible = true;                this.mouseButton = MouseButtons.None;                this.gradientActive = true;                SetStatus(this.toolIcon, this.helpTextAdjustable);            }            base.OnMouseUp(e);        }        private void RenderBecauseOfEvent(object sender, EventArgs e)        {            if (this.gradientActive)            {                RenderGradient();            }        }        protected override void OnActivate()        {            this.toolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.GenericToolCursor.cur"));            this.toolMouseDownCursor = new Cursor(PdnResources.GetResourceStream("Cursors.GenericToolCursorMouseDown.cur"));            this.Cursor = this.toolCursor;            this.toolIcon = this.Image;            this.startNub = new MoveNubRenderer(RendererList);            this.startNub.Visible = false;            this.startNub.Shape = MoveNubShape.Circle;            RendererList.Add(this.startNub, false);            this.endNub = new MoveNubRenderer(RendererList);            this.endNub.Visible = false;            this.endNub.Shape = MoveNubShape.Circle;            RendererList.Add(this.endNub, false);            this.moveNubs =                new MoveNubRenderer[]                {                    this.startNub,                    this.endNub                };            AppEnvironment.PrimaryColorChanged += new EventHandler(RenderBecauseOfEvent);            AppEnvironment.SecondaryColorChanged += new EventHandler(RenderBecauseOfEvent);            AppEnvironment.GradientInfoChanged += new EventHandler(RenderBecauseOfEvent);            AppEnvironment.AlphaBlendingChanged += new EventHandler(RenderBecauseOfEvent);            AppWorkspace.UnitsChanged += new EventHandler(RenderBecauseOfEvent);            base.OnActivate();        }        protected override void OnDeactivate()        {            AppEnvironment.PrimaryColorChanged -= new EventHandler(RenderBecauseOfEvent);            AppEnvironment.SecondaryColorChanged -= new EventHandler(RenderBecauseOfEvent);            AppEnvironment.GradientInfoChanged -= new EventHandler(RenderBecauseOfEvent);            AppEnvironment.AlphaBlendingChanged -= new EventHandler(RenderBecauseOfEvent);            AppWorkspace.UnitsChanged -= new EventHandler(RenderBecauseOfEvent);            if (this.gradientActive)            {                CommitGradient();                this.mouseButton = MouseButtons.None;            }            if (this.startNub != null)            {                RendererList.Remove(this.startNub);                this.startNub.Dispose();                this.startNub = null;            }            if (this.endNub != null)            {                RendererList.Remove(this.endNub);                this.endNub.Dispose();                this.endNub = null;            }            this.moveNubs = null;            if (this.toolCursor != null)            {                this.toolCursor.Dispose();                this.toolCursor = null;            }            if (this.toolMouseDownCursor != null)            {                this.toolMouseDownCursor.Dispose();                this.toolMouseDownCursor = null;            }            base.OnDeactivate();        }        public GradientTool(IDocumentWorkspace documentWorkspace)            : base(documentWorkspace,                   StaticImage,                   StaticName,                   PdnResources.GetString("GradientTool.HelpText"),                   'g',                   false,                   ToolBarConfigItems.Gradient | ToolBarConfigItems.AlphaBlending)        {        }    }}
 |