diff --git a/AIStudio.Wpf.DiagramApp/ViewModels/MainWindowViewModel.cs b/AIStudio.Wpf.DiagramApp/ViewModels/MainWindowViewModel.cs index 42a1b20..f7af5ac 100644 --- a/AIStudio.Wpf.DiagramApp/ViewModels/MainWindowViewModel.cs +++ b/AIStudio.Wpf.DiagramApp/ViewModels/MainWindowViewModel.cs @@ -91,16 +91,16 @@ namespace AIStudio.Wpf.DiagramApp.ViewModels } } - private Models.ColorType _colorObject; + private Models.ColorType _colorType; public Models.ColorType ColorType { get { - return _colorObject; + return _colorType; } set { - SetProperty(ref _colorObject, value); + SetProperty(ref _colorType, value); } } @@ -715,21 +715,128 @@ namespace AIStudio.Wpf.DiagramApp.ViewModels List result = new List(); for (var i = 0; i < count; i++) { - //var colors = ColorGallery.GetGradient(ColorGallery.StandardThemeColors[i], 10); - //for (var j = 9; j >= 0; j--) - //{ - // result.AddTo(colors[j]); - //} + var colors = GetGradient(ColorGallery.StandardThemeColors[i], 10); + for (var j = 9; j >= 0; j--) + { + result.Add(colors[j]); + } } { - //var colors = ColorGallery.GetGradient(Colors.Black, 10); - //for (var j = 9; j >= 0; j--) - //{ - // result.AddTo(colors[j]); - //} + var colors = GetGradient(Colors.Black, 10); + for (var j = 9; j >= 0; j--) + { + result.Add(colors[j]); + } } return result.ToArray(); } + + #endregion + + #region Gradient Generation + + /// + /// Returns brightness of the given color from 0..1 + /// + /// Color + /// Brightness of the given color from 0..1 + private static double GetBrightness(Color color) + { + var summ = (double)color.R + color.G + color.B; + return summ / (255.0 * 3.0); + } + + // Makes the given color lighter + private static Color Lighter(Color color, double power) + { + var totalAvailability = (255.0 * 3.0) - color.R + color.G + color.B; + double redAvailability; + double greenAvailability; + double blueAvailability; + double needToBeAdded; + + if (color.R + color.G + color.B == 0) + { + redAvailability = 1.0 / 3.0; + greenAvailability = 1.0 / 3.0; + blueAvailability = 1.0 / 3.0; + needToBeAdded = power * 255.0 * 3.0; + } + else + { + redAvailability = (255.0 - color.R) / totalAvailability; + greenAvailability = (255.0 - color.G) / totalAvailability; + blueAvailability = (255.0 - color.B) / totalAvailability; + needToBeAdded = ((double)color.R + color.G + color.B) * (power - 1); + } + + var result = Color.FromRgb( + (byte)(color.R + (byte)(redAvailability * needToBeAdded)), + (byte)(color.G + (byte)(greenAvailability * needToBeAdded)), + (byte)(color.B + (byte)(blueAvailability * needToBeAdded))); + + return result; + } + + // Makes the given color darker + private static Color Darker(Color color, double power) + { + var totalAvailability = (double)color.R + color.G + color.B; + var redAvailability = color.R / totalAvailability; + var greenAvailability = color.G / totalAvailability; + var blueAvailability = color.B / totalAvailability; + + var needToBeAdded = (double)color.R + color.G + color.B; + needToBeAdded = needToBeAdded - (needToBeAdded * power); + + var result = Color.FromRgb( + (byte)(color.R - (byte)(redAvailability * needToBeAdded)), + (byte)(color.G - (byte)(greenAvailability * needToBeAdded)), + (byte)(color.B - (byte)(blueAvailability * needToBeAdded))); + + return result; + } + + // Makes a new color from the given with new brightness + private static Color Rebright(Color color, double newBrightness) + { + var currentBrightness = GetBrightness(color); + var power = DoubleUtil.AreClose(currentBrightness, 0.0) == false + ? newBrightness / currentBrightness + : 1.0 + newBrightness; + + // TODO: round power to make nice numbers + // ... + + if (power > 1.0) + { + return Lighter(color, power); + } + + return Darker(color, power); + } + + /// + /// Makes gradient colors from lighter to darker + /// + /// Base color + /// Count of items in the gradient + /// Colors from lighter to darker + public static Color[] GetGradient(Color color, int count) + { + const double lowBrightness = 0.15; + const double highBrightness = 0.85; + var result = new Color[count]; + + for (var i = 0; i < count; i++) + { + var brightness = lowBrightness + (i * (highBrightness - lowBrightness) / count); + result[count - i - 1] = Rebright(color, brightness); + } + + return result; + } + #endregion } } diff --git a/AIStudio.Wpf.DiagramDesigner.Additionals/AIStudio.Wpf.DiagramDesigner.Additionals.csproj b/AIStudio.Wpf.DiagramDesigner.Additionals/AIStudio.Wpf.DiagramDesigner.Additionals.csproj index d86ce72..8ef1a62 100644 --- a/AIStudio.Wpf.DiagramDesigner.Additionals/AIStudio.Wpf.DiagramDesigner.Additionals.csproj +++ b/AIStudio.Wpf.DiagramDesigner.Additionals/AIStudio.Wpf.DiagramDesigner.Additionals.csproj @@ -18,7 +18,7 @@ - + diff --git a/AIStudio.Wpf.DiagramDesigner.Additionals/Controls/Barcode.xaml.cs b/AIStudio.Wpf.DiagramDesigner.Additionals/Controls/Barcode.xaml.cs index e6da408..1ee457f 100644 --- a/AIStudio.Wpf.DiagramDesigner.Additionals/Controls/Barcode.xaml.cs +++ b/AIStudio.Wpf.DiagramDesigner.Additionals/Controls/Barcode.xaml.cs @@ -116,6 +116,7 @@ namespace AIStudio.Wpf.DiagramDesigner.Additionals.Controls Margin = 0 } }; + writer.Renderer = new GeometryRenderer(); var image = writer.Write(Text ?? "AIStudio画板"); imageBarcodeEncoderGeometry.Data = image; #else diff --git a/AIStudio.Wpf.DiagramDesigner.Additionals/Controls/GeometryRenderer.cs b/AIStudio.Wpf.DiagramDesigner.Additionals/Controls/GeometryRenderer.cs new file mode 100644 index 0000000..0d3ea2d --- /dev/null +++ b/AIStudio.Wpf.DiagramDesigner.Additionals/Controls/GeometryRenderer.cs @@ -0,0 +1,310 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; +using ZXing.Common; +using ZXing.Rendering; +using ZXing; +using System.Windows; + +namespace AIStudio.Wpf.DiagramDesigner.Additionals.Controls +{ + /// + /// Renders a barcode into a geometry + /// Autor: Rob Fonseca-Ensor + /// + public class GeometryRenderer : IBarcodeRenderer + { + /// + /// Renders the specified matrix. + /// + /// The matrix. + /// The format. + /// The content. + /// + public Geometry Render(BitMatrix matrix, BarcodeFormat format, string content) + { + return Render(matrix, format, content, null); + } + + /// + /// Renders the specified matrix. + /// + /// The matrix. + /// The format. + /// The content. + /// The options. + /// + public Geometry Render(BitMatrix matrix, BarcodeFormat format, string content, EncodingOptions options) + { + var edges = new HashSet(); + var edgeMap = new Dictionary>(); + var cols = matrix.Width; + var rows = matrix.Height; + + for (int c = 0; c <= cols; c++) + { + for (int r = 0; r <= rows; r++) + { + var cell = GetCell(c, r, matrix); + var westCell = GetCell(c - 1, r, matrix); + var northCell = GetCell(c, r - 1, matrix); + if (northCell != cell) + { + AddEdge(new Edge(c, r, c + 1, r), edges, edgeMap); + } + + if (westCell != cell) + { + AddEdge(new Edge(c, r, c, r + 1), edges, edgeMap); + } + } + } + + var cycles = new List>(); + + while (edges.Count > 0) + { + var edge = edges.First(); + RemoveEdge(edge, edges, edgeMap); + + if (IsEdgeLeftHand(matrix, edge)) + { + edge = edge.Reversed(); + } + + var currentCycle = new List { edge.From, edge.To }; + while (edge.To != currentCycle[0]) + { + var moves = from direction in Turns(edge.From - edge.To) + let nextCoordinate = direction + edge.To + from e in EdgesFrom(edge.To, edgeMap) + where e.To == nextCoordinate || e.From == nextCoordinate + select e; + + var nextEdge = moves.First(); + RemoveEdge(nextEdge, edges, edgeMap); + edge = nextEdge.To != edge.To ? nextEdge : nextEdge.Reversed(); + currentCycle.Add(edge.To); + } + cycles.Add(currentCycle); + } + + return new PathGeometry(cycles.Select(x => new PathFigure(x.First().ToPoint(1), x.Skip(1).Select(y => new LineSegment(y.ToPoint(1), true)), true))); + + } + + private static bool IsEdgeLeftHand(BitMatrix b, Edge edge) + { + var cell = GetCell(edge.From.Col, edge.From.Row, b); + return (edge.From.Row < edge.To.Row && cell) || (!(edge.From.Col < edge.To.Col && cell)); + } + + private static IEnumerable EdgesFrom(Coordinate c, Dictionary> edgeMap) + { + return edgeMap.ContainsKey(c) ? edgeMap[c] : Enumerable.Empty(); + } + + private static IEnumerable Turns(Coordinate currentDirection) + { + int index = Array.IndexOf(Coordinate.Directions, currentDirection); + return Coordinate.Directions.Skip(index + 1).Concat(Coordinate.Directions.Take(index)); + } + + private static bool GetCell(int c, int r, BitMatrix matrix) + { + if (r < 0 || r >= matrix.Height) + { + return false; + } + if (c < 0 || c >= matrix.Width) + { + return false; + } + return matrix[c, r]; + } + + private static void RemoveEdge(Edge e, HashSet edges, Dictionary> edgeMap) + { + edges.Remove(e); + edgeMap[e.From].Remove(e); + edgeMap[e.To].Remove(e); + } + + private static void AddEdge(Edge e, HashSet edges, Dictionary> edgeMap) + { + edges.Add(e); + AddCoordinate(e.From, e, edgeMap); + AddCoordinate(e.To, e, edgeMap); + } + + private static void AddCoordinate(Coordinate c, Edge e, Dictionary> edgeMap) + { + List list; + if (!edgeMap.TryGetValue(c, out list)) + { + edgeMap[c] = list = new List(); + } + list.Add(e); + } + + + private struct Coordinate + { + public readonly int Row, Col; + + public Coordinate(int col, int row) + { + Col = col; + Row = row; + } + + public bool Equals(Coordinate other) + { + return other.Row == Row && other.Col == Col; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (obj.GetType() != typeof(Coordinate)) + return false; + return Equals((Coordinate)obj); + } + + public override int GetHashCode() + { + unchecked + { + return (Row * 397) ^ Col; + } + } + + public override string ToString() + { + var s = ""; + if (this == North) + s = " n"; + if (this == West) + s = " w"; + if (this == South) + s = " s"; + if (this == East) + s = " e"; + return String.Format("({0}, {1}{2})", Col, Row, s); + } + + public static bool operator ==(Coordinate left, Coordinate right) + { + return left.Equals(right); + } + + public static bool operator !=(Coordinate left, Coordinate right) + { + return !left.Equals(right); + } + + public static Coordinate operator +(Coordinate c1, Coordinate c2) + { + return new Coordinate(c1.Col + c2.Col, c1.Row + c2.Row); + } + + public static Coordinate operator -(Coordinate c1, Coordinate c2) + { + return new Coordinate(c1.Col - c2.Col, c1.Row - c2.Row); + } + + public Point ToPoint(double scale) + { + return new Point(Col * scale, Row * scale); + } + + private static readonly Coordinate West = new Coordinate(-1, 0); + private static readonly Coordinate South = new Coordinate(0, 1); + private static readonly Coordinate East = new Coordinate(1, 0); + private static readonly Coordinate North = new Coordinate(0, -1); + + public static readonly Coordinate[] Directions = new[] + { + West, + South, + East, + North, + }; + } + + private struct Edge + { + public readonly Coordinate From, To; + + public Edge(Coordinate from, Coordinate to) + { + From = from; + To = to; + } + + public Edge(int fromCol, int fromRow, int toCol, int toRow) + : this(new Coordinate(fromCol, fromRow), new Coordinate(toCol, toRow)) + { + + } + + + public bool Equals(Edge other) + { + return other.From.Equals(From) && other.To.Equals(To); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (obj.GetType() != typeof(Edge)) + return false; + return Equals((Edge)obj); + } + + public override int GetHashCode() + { + unchecked + { + return (From.GetHashCode() * 397) ^ To.GetHashCode(); + } + } + + public override string ToString() + { + char angle = ' '; + if (From.Col == To.Col) + { + angle = '|'; + } + if (From.Row == To.Row) + { + angle = '-'; + } + + + return string.Format("[{0} {2} {1}]", From, To, angle); + } + + public static bool operator ==(Edge left, Edge right) + { + return left.Equals(right); + } + + public static bool operator !=(Edge left, Edge right) + { + return !left.Equals(right); + } + + public Edge Reversed() + { + return new Edge(To, From); + } + } + } +} diff --git a/AIStudio.Wpf.DiagramDesigner/Controls/DesignerCanvas.cs b/AIStudio.Wpf.DiagramDesigner/Controls/DesignerCanvas.cs index 1f51f22..02c891f 100644 --- a/AIStudio.Wpf.DiagramDesigner/Controls/DesignerCanvas.cs +++ b/AIStudio.Wpf.DiagramDesigner/Controls/DesignerCanvas.cs @@ -279,6 +279,7 @@ namespace AIStudio.Wpf.DiagramDesigner { StreamResourceInfo sri = Application.GetResourceStream(new Uri("pack://application:,,,/AIStudio.Wpf.DiagramDesigner;component/Images/FormatPainter.cur", UriKind.RelativeOrAbsolute)); this.Cursor = new Cursor(sri.Stream); + foreach (SelectableDesignerItemViewModelBase item in _viewModel.Items) { item.IsHitTestVisible = false; diff --git a/AIStudio.Wpf.DiagramDesigner/Helpers/DoubleUtil.cs b/AIStudio.Wpf.DiagramDesigner/Helpers/DoubleUtil.cs new file mode 100644 index 0000000..661f5f7 --- /dev/null +++ b/AIStudio.Wpf.DiagramDesigner/Helpers/DoubleUtil.cs @@ -0,0 +1,64 @@ +namespace AIStudio.Wpf.DiagramDesigner +{ + using System; + + public static class DoubleUtil + { + // Const values come from sdk\inc\crt\float.h +#pragma warning disable SA1310 // Field names must not contain underscore + // ReSharper disable once InconsistentNaming + private const double DBL_EPSILON = 2.2204460492503131e-016; /* smallest such that 1.0+DBL_EPSILON != 1.0 */ +#pragma warning restore SA1310 // Field names must not contain underscore + + /// + /// AreClose - Returns whether or not two doubles are "close". That is, whether or + /// not they are within epsilon of each other. Note that this epsilon is proportional + /// to the numbers themselves to that AreClose survives scalar multiplication. + /// There are plenty of ways for this to return false even for numbers which + /// are theoretically identical, so no code calling this should fail to work if this + /// returns false. This is important enough to repeat: + /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be + /// used for optimizations *only*. + /// + /// + /// bool - the result of the AreClose comparision. + /// + /// The first double to compare. + /// The second double to compare. + public static bool AreClose(double value1, double value2) + { + // in case they are Infinities (then epsilon check does not work) + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (value1 == value2) + { + return true; + } + + // This computes (|value1-value2| / (|value1| + |value2| + 10.0)) < DBL_EPSILON + var eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DBL_EPSILON; + var delta = value1 - value2; + return (-eps < delta) && (eps > delta); + } + + /// + /// GreaterThan - Returns whether or not the first double is greater than the second double. + /// That is, whether or not the first is strictly greater than *and* not within epsilon of + /// the other number. Note that this epsilon is proportional to the numbers themselves + /// to that AreClose survives scalar multiplication. Note, + /// There are plenty of ways for this to return false even for numbers which + /// are theoretically identical, so no code calling this should fail to work if this + /// returns false. This is important enough to repeat: + /// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be + /// used for optimizations *only*. + /// + /// + /// bool - the result of the GreaterThan comparision. + /// + /// The first double to compare. + /// The second double to compare. + public static bool GreaterThan(double value1, double value2) + { + return (value1 > value2) && !AreClose(value1, value2); + } + } +} \ No newline at end of file diff --git a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/SelectableViewModelBase.cs b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/SelectableViewModelBase.cs index c9997be..d9febc2 100644 --- a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/SelectableViewModelBase.cs +++ b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/SelectableViewModelBase.cs @@ -192,10 +192,6 @@ namespace AIStudio.Wpf.DiagramDesigner { get { - if (IsHitTestVisible == false) - { - return false; - } return _isSelected; } set