using DominionBase; using DominionBase.Cards; using DominionBase.Currencies; using DominionBase.Enums; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Xml; namespace Dominion.NET_WPF { public enum CardSize { Text, SmallText, Small, Medium, Full } public class ConstraintTypeConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return value is ConstraintType vConstraintType ? vConstraintType.ToDescription() : value.ToString(); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return null; } } public class ConstraintTypeToolTipConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return value is ConstraintType vConstraintType ? vConstraintType.ToToolTip() : value.ToString(); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return null; } } public class ConstraintConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value == null) return null; if (value is KeyValuePair kvpCost) return $"({kvpCost.Value}) Cost: {Utilities.RenderText(kvpCost.Key.ToString(", "))}"; if (value is KeyValuePair kvpTraits) return $"({kvpTraits.Value}) {kvpTraits.Key.ToDescription()}"; if (value is KeyValuePair kvpSource) return $"({kvpSource.Value}) {kvpSource.Key}"; if (value is KeyValuePair kvpCategories) return $"({kvpCategories.Value}) {kvpCategories.Key}"; return value.ToString(); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return null; } } public static class ExtensionMethods { public static string ToDescription(this Enum en) //ext method { var type = en.GetType(); var memInfo = type.GetMember(en.ToString()); if (memInfo.Length > 0) { var attrs = memInfo[0].GetCustomAttributes( typeof(DescriptionAttribute), false); if (attrs.Length > 0) return ((DescriptionAttribute)attrs[0]).Description; } return en.ToString(); } public static string ToToolTip(this Enum en) //ext method { var type = en.GetType(); var memInfo = type.GetMember(en.ToString()); if (memInfo.Length > 0) { var attrs = memInfo[0].GetCustomAttributes( typeof(ToolTipAttribute), false); if (attrs.Length > 0) return ((ToolTipAttribute)attrs[0]).Text; } return en.ToString(); } } internal static class CustomCommands { public static RoutedCommand CardViewer = new RoutedCommand(); public static RoutedCommand CardPreview = new RoutedCommand(); public static RoutedCommand NewGame = new RoutedCommand(); public static RoutedCommand SaveGame = new RoutedCommand(); public static RoutedCommand LoadGame = new RoutedCommand(); } public enum SupplyVisibility { Plain, Gainable, Selectable, NotClickable } public class DisplayObjects { private readonly Func _selector; //private readonly EditionUsage _edition = EditionUsage.Errata2019Priority; private readonly EditionUsage _edition = EditionUsage.SecondPriority; private ObservableCollection _cards; private ObservableCollection> _trait; private Dictionary _traitDict; private ObservableCollection> _costs; private Dictionary _costsDict; private ObservableCollection> _sources; private Dictionary _sourcesDict; private ObservableCollection> _categoriesContains; private Dictionary _categoriesContainsDict; public DisplayObjects() { } public DisplayObjects(Func selector, EditionUsage edition) { _selector = selector; _edition = edition; } public ObservableCollection Cards { get { if (_cards == null) { var cards = new List(CardCollection.GetAllCards(_selector ?? (c => true), _edition)); cards.Sort((c1, c2) => c1.Name.CompareTo(c2.Name)); _cards = new ObservableCollection(cards); } return _cards; } } public ObservableCollection> Trait { get { return _trait ?? (_trait = new ObservableCollection>(TraitsDict.OrderBy(kvp => (int)kvp.Key))); } } private Dictionary TraitsDict { get { if (_traitDict == null) { _traitDict = new Dictionary(); foreach (var card in Cards) { foreach (Traits trait in Enum.GetValues(typeof(Traits))) { if (trait == Traits.Basic || trait == Traits.None || trait == Traits.Randomizer) continue; if (card.Traits.HasFlag(trait)) { if (!_traitDict.ContainsKey(trait)) _traitDict[trait] = 0; _traitDict[trait]++; } } } } return _traitDict; } } public ObservableCollection> Costs { get { return _costs ?? (_costs = new ObservableCollection>(CostsDict.OrderBy(kvp => kvp.Key))); } } private Dictionary CostsDict { get { if (_costsDict == null) { _costsDict = new Dictionary(); foreach (var card in Cards) { if (!card.HasCost) continue; if (!_costsDict.ContainsKey(card.BaseCost)) _costsDict[card.BaseCost] = 0; _costsDict[card.BaseCost]++; } } return _costsDict; } } public ObservableCollection> Sources { get { return _sources ?? (_sources = new ObservableCollection>(SourcesDict.OrderBy(kvp => (int)kvp.Key))); } } private Dictionary SourcesDict { get { if (_sourcesDict == null) { _sourcesDict = new Dictionary(); foreach (var card in Cards) { foreach (Source source in Enum.GetValues(typeof(Source))) { if (source == Source.All) continue; if (card.Source == source) { if (!_sourcesDict.ContainsKey(source)) _sourcesDict[source] = 0; _sourcesDict[source]++; } } } } return _sourcesDict; } } public ObservableCollection> CategoriesContains { get { return _categoriesContains ?? (_categoriesContains = new ObservableCollection>( CategoriesContainsDict.OrderBy(kvp => (int)kvp.Key))); } } private Dictionary CategoriesContainsDict { get { if (_categoriesContainsDict == null) { _categoriesContainsDict = new Dictionary(); foreach (var card in Cards) { foreach (Categories category in Enum.GetValues(typeof(Categories))) { if (category == Categories.Unknown || category == Categories.Card || category == Categories.Prize) continue; if (card.Category.HasFlag(category)) { if (!_categoriesContainsDict.ContainsKey(category)) _categoriesContainsDict[category] = 0; _categoriesContainsDict[category]++; } } } } return _categoriesContainsDict; } } } public enum RenderSize { Tiny, Small, Medium, Large, ExtraLarge } public class Utilities { private static readonly Regex TagMatch = new Regex(@"<(?[a-z]+)(/>|>(?[^<]*?)>)", RegexOptions.Singleline); public static string RenderText(string text) { var sb = new StringBuilder(); foreach (var run in ((TextBlock)RenderText(text, RenderSize.Small, true)[0]).Inlines.OfType()) sb.Append(run.Text); return sb.ToString(); } private static Run CreateRun(string text, Brush foreground) { var r = new Run(text); if (foreground != null) r.Foreground = foreground; return r; } public static List RenderText(string text, RenderSize renderSize, bool textOnly, Brush textColor = null) { var elements = new List(); var textBlockTemp = new TextBlock(); var inlines = textBlockTemp.Inlines; var match = TagMatch.Match(text); while (match.Success) { var preText = text.Substring(0, match.Index); var postText = text.Substring(match.Index + match.Length); switch (match.Groups["tag"].Value) { case "b": // Bold if (match.Index > 0) inlines.Add(CreateRun(preText, textColor)); var rB = new Run(match.Groups["text"].Value) { FontWeight = FontWeights.Bold, FontSize = 14 }; if (textColor != null) rB.Foreground = textColor; inlines.Add(rB); text = postText; break; case "i": // Italics if (match.Index > 0) inlines.Add(CreateRun(preText, textColor)); var rI = new Run(match.Groups["text"].Value) { FontStyle = FontStyles.Italic, FontSize = 11 }; if (textColor != null) rI.Foreground = textColor; inlines.Add(rI); text = postText; break; case "u": // Underline if (match.Index > 0) inlines.Add(CreateRun(preText, textColor)); var rU = new Run(match.Groups["text"].Value) { TextDecorations = TextDecorations.Underline, FontSize = 11 }; if (textColor != null) rU.Foreground = textColor; inlines.Add(rU); text = postText; break; case "h": // Header if (match.Index > 0) inlines.Add(CreateRun(preText, textColor)); var rH = new Run(match.Groups["text"].Value); switch (renderSize) { case RenderSize.ExtraLarge: rH.FontSize = 72; break; default: rH.FontSize = 36; break; } rH.FontWeight = FontWeights.Bold; if (textColor != null) rH.Foreground = textColor; inlines.Add(rH); text = postText; break; case "br": // Line break if (match.Index > 0) inlines.Add(CreateRun(preText, textColor)); inlines.Add(new LineBreak()); text = postText; break; case "sk": // Shortcut Key if (match.Index > 0) inlines.Add(CreateRun(preText, textColor)); var rSK = new Run(match.Groups["text"].Value) { TextDecorations = TextDecorations.Underline, Foreground = Brushes.Crimson }; inlines.Add(rSK); text = postText; break; case "sm": if (match.Index > 0) inlines.Add(CreateRun(preText, textColor)); var rS = new Run(match.Groups["text"].Value) { FontSize = 11 }; if (textColor != null) rS.Foreground = textColor; inlines.Add(rS); text = postText; break; case "nbsp": // Non-breaking space text = $"{preText}{Convert.ToChar(160)}{postText}"; break; case "vp": if (textOnly) text = $"{preText}{match.Groups["text"].Value}‡{postText}"; else { if (!string.IsNullOrEmpty(preText)) inlines.Add(CreateRun(preText, textColor)); var rVp = new Run(match.Groups["text"].Value); rVp.Foreground = Brushes.Black; inlines.Add(rVp); text = postText; var vpUiContainer = new InlineUIContainer { BaselineAlignment = BaselineAlignment.Center }; var vp = new Image(); var ir = Caching.ImageRepository.Acquire(); vp.Source = ir.GetBitmapImage("vp", string.Empty); switch (renderSize) { case RenderSize.Tiny: vp.Height = 14; vp.Width = 14; break; default: vp.Height = 18; vp.Width = 18; break; } Caching.ImageRepository.Release(); vpUiContainer.Child = vp; inlines.Add(vpUiContainer); } break; case "vplg": if (textOnly) text = $"{preText}{match.Groups["text"].Value}‡{postText}"; else { if (!string.IsNullOrEmpty(preText)) inlines.Add(CreateRun(preText, textColor)); var rVpLg = new Run(match.Groups["text"].Value); switch (renderSize) { case RenderSize.ExtraLarge: rVpLg.FontSize = 72; break; default: rVpLg.FontSize = 36; break; } rVpLg.FontWeight = FontWeights.Bold; rVpLg.Foreground = Brushes.Black; inlines.Add(rVpLg); text = postText; var vpUiContainer = new InlineUIContainer(); vpUiContainer.BaselineAlignment = BaselineAlignment.Center; var vp = new Image(); var ir = Caching.ImageRepository.Acquire(); vp.Source = ir.GetBitmapImage("vplg", string.Empty); Caching.ImageRepository.Release(); switch (renderSize) { case RenderSize.ExtraLarge: vp.Height = 96; vp.Width = 96; break; default: vp.Height = 48; vp.Width = 48; break; } vpUiContainer.Child = vp; inlines.Add(vpUiContainer); } break; case "coin": if (textOnly) text = $"{preText}{match.Groups["text"].Value}{Coin.Sign}{postText}"; else { if (!string.IsNullOrEmpty(preText)) inlines.Add(CreateRun(preText, textColor)); text = postText; var coinUiContainer = new InlineUIContainer { BaselineAlignment = BaselineAlignment.Center }; var coinCanvas = new Canvas(); var coin = new Image(); var ir = Caching.ImageRepository.Acquire(); coin.Source = ir.GetBitmapImage("coin", string.Empty); switch (renderSize) { case RenderSize.Tiny: coinCanvas.Height = coin.Height = 14; coinCanvas.Width = coin.Width = 14; break; case RenderSize.Medium: coinCanvas.Height = coin.Height = 24; coinCanvas.Width = coin.Width = 24; break; default: coinCanvas.Height = coin.Height = 18; coinCanvas.Width = coin.Width = 18; break; } Caching.ImageRepository.Release(); coinCanvas.Children.Add(coin); var tbNum = new TextBlock(); var r = new Run(match.Groups["text"].Value) { FontWeight = FontWeights.Bold }; r.Foreground = Brushes.Black; tbNum.Inlines.Add(r); switch (renderSize) { case RenderSize.Tiny: Canvas.SetLeft(tbNum, 8 - 4 * match.Groups["text"].Value.Length); Canvas.SetTop(tbNum, -2); break; case RenderSize.Medium: r.FontSize = 18; Canvas.SetLeft(tbNum, 11 - 5 * match.Groups["text"].Value.Length); Canvas.SetTop(tbNum, 2); break; default: Canvas.SetLeft(tbNum, 10 - 4 * match.Groups["text"].Value.Length); Canvas.SetTop(tbNum, 2); break; } coinCanvas.Children.Add(tbNum); coinUiContainer.Child = coinCanvas; inlines.Add(coinUiContainer); } break; case "coinlg": if (textOnly) text = $"{preText}{match.Groups["text"].Value}{Coin.Sign}{postText}"; else { if (!string.IsNullOrEmpty(preText)) inlines.Add(CreateRun(preText, textColor)); text = postText; var coinUiContainer = new InlineUIContainer { BaselineAlignment = BaselineAlignment.Center }; var coinCanvas = new Canvas(); var coin = new Image(); var ir = Caching.ImageRepository.Acquire(); coin.Source = ir.GetBitmapImage("coinlg", string.Empty); Caching.ImageRepository.Release(); coinCanvas.Children.Add(coin); var tbNum = new TextBlock(); var r = new Run(match.Groups["text"].Value) { FontWeight = FontWeights.Bold }; r.Foreground = Brushes.Black; int blurRadius; switch (renderSize) { case RenderSize.ExtraLarge: r.FontSize = 72; coinCanvas.Height = coin.Height = 96; coinCanvas.Width = coin.Width = 96; Canvas.SetLeft(tbNum, 28); blurRadius = 50; break; default: r.FontSize = 36; coinCanvas.Height = coin.Height = 48; coinCanvas.Width = coin.Width = 48; Canvas.SetLeft(tbNum, 14); blurRadius = 30; break; } tbNum.Inlines.Add(r); tbNum.Effect = Caching.DropShadowRepository.GetDSE(blurRadius, Color.FromRgb(192, 192, 192), 1d); coinCanvas.Children.Add(tbNum); coinUiContainer.Child = coinCanvas; inlines.Add(coinUiContainer); } break; case "potion": if (textOnly) text = $"{preText}{match.Groups["text"].Value}{Potion.Sign}{postText}"; else { if (!string.IsNullOrEmpty(preText)) inlines.Add(CreateRun(preText, textColor)); if (match.Groups["text"].Value != "1") inlines.Add(new Run(match.Groups["text"].Value)); text = postText; var potionUiContainer = new InlineUIContainer { BaselineAlignment = BaselineAlignment.Center }; var potion = new Image(); var ir = Caching.ImageRepository.Acquire(); potion.Source = ir.GetBitmapImage("potion", string.Empty); switch (renderSize) { case RenderSize.Tiny: potion.Height = 14; potion.Width = 14; break; default: potion.Height = 18; potion.Width = 18; break; } Caching.ImageRepository.Release(); potionUiContainer.Child = potion; inlines.Add(potionUiContainer); } break; case "potionlg": if (textOnly) text = $"{preText}{match.Groups["text"].Value}{Potion.Sign}{postText}"; else { if (!string.IsNullOrEmpty(preText)) inlines.Add(CreateRun(preText, textColor)); var rPotionLg = new Run(match.Groups["text"].Value); switch (renderSize) { case RenderSize.ExtraLarge: rPotionLg.FontSize = 72; break; default: rPotionLg.FontSize = 36; break; } rPotionLg.FontWeight = FontWeights.Bold; inlines.Add(rPotionLg); text = postText; var potionUiContainer = new InlineUIContainer { BaselineAlignment = BaselineAlignment.Center }; var potion = new Image(); var ir = Caching.ImageRepository.Acquire(); potion.Source = ir.GetBitmapImage("potionlg", string.Empty); Caching.ImageRepository.Release(); switch (renderSize) { case RenderSize.ExtraLarge: potion.Height = 96; potion.Width = 96; break; default: potion.Height = 48; potion.Width = 48; break; } potionUiContainer.Child = potion; inlines.Add(potionUiContainer); } break; case "debt": if (textOnly) text = $"{preText}{match.Groups["text"].Value}{Debt.Sign}{postText}"; else { if (!string.IsNullOrEmpty(preText)) inlines.Add(CreateRun(preText, textColor)); text = postText; var debtUiContainer = new InlineUIContainer { BaselineAlignment = BaselineAlignment.Center }; var debtCanvas = new Canvas(); var debt = new Image(); var ir = Caching.ImageRepository.Acquire(); debt.Source = ir.GetBitmapImage("debt", string.Empty); switch (renderSize) { case RenderSize.Tiny: debtCanvas.Height = debt.Height = 14; debtCanvas.Width = debt.Width = 14; break; case RenderSize.Medium: debtCanvas.Height = debt.Height = 24; debtCanvas.Width = debt.Width = 24; break; default: debtCanvas.Height = debt.Height = 18; debtCanvas.Width = debt.Width = 18; break; } Caching.ImageRepository.Release(); debtCanvas.Children.Add(debt); var tbNum = new TextBlock(); var r = new Run(match.Groups["text"].Value) { FontWeight = FontWeights.Bold }; tbNum.Foreground = Brushes.White; tbNum.Inlines.Add(r); switch (renderSize) { case RenderSize.Tiny: Canvas.SetLeft(tbNum, 8 - 4 * match.Groups["text"].Value.Length); Canvas.SetTop(tbNum, -2); break; case RenderSize.Medium: r.FontSize = 18; Canvas.SetLeft(tbNum, 11 - 4 * match.Groups["text"].Value.Length); Canvas.SetTop(tbNum, 2); break; default: Canvas.SetLeft(tbNum, 10 - 4 * match.Groups["text"].Value.Length); Canvas.SetTop(tbNum, 2); break; } debtCanvas.Children.Add(tbNum); debtUiContainer.Child = debtCanvas; inlines.Add(debtUiContainer); } break; case "card": if (textOnly) text = $"{preText}{match.Groups["text"].Value} card{postText}"; else { if (!string.IsNullOrEmpty(preText)) inlines.Add(CreateRun(preText, textColor)); inlines.Add(new Run(match.Groups["text"].Value)); inlines.Add(new Run(" ")); text = postText; var cardUiContainer = new InlineUIContainer { BaselineAlignment = BaselineAlignment.Center }; var ir = Caching.ImageRepository.Acquire(); var card = new Image { Source = ir.GetBitmapImage("back", "small") }; switch (renderSize) { case RenderSize.Tiny: card.Height = 14; card.Width = 14; break; case RenderSize.Medium: card.Height = 24; card.Width = 24; break; default: card.Height = 18; card.Width = 18; break; } Caching.ImageRepository.Release(); cardUiContainer.Child = card; inlines.Add(cardUiContainer); } break; case "villager": text = $"{preText}{match.Groups["text"].Value} Villager{postText}"; break; default: text = string.Empty; break; } match = TagMatch.Match(text); } if (!string.IsNullOrEmpty(text)) inlines.Add(CreateRun(text, textColor)); elements.Add(textBlockTemp); return elements; } public static string Ordinal(int number) { switch (number % 100) { case 11: case 12: case 13: return number + "th"; } switch (number % 10) { case 1: return number + "st"; case 2: return number + "nd"; case 3: return number + "rd"; default: return number + "th"; } } public static void Log(string filename, string line) { try { if (!Directory.Exists(Path.GetDirectoryName(filename))) { Directory.CreateDirectory(Path.GetDirectoryName(filename)); } using (var sw = new StreamWriter(filename, true)) { sw.WriteLine(line); } } catch (IOException) { } catch (Exception ex) { MessageBox.Show(ex.Message); } } internal static void LogClear(string filename) { try { if (!Directory.Exists(Path.GetDirectoryName(filename))) { Directory.CreateDirectory(Path.GetDirectoryName(filename)); } if (File.Exists(filename)) File.Delete(filename); } catch (IOException) { } catch (Exception ex) { MessageBox.Show(ex.Message); } } } public class VersionInfo { public bool IsVersionValid; public Version Version = new Version(); public Uri Url; public Uri FileUrl; public DateTime Date; public bool IsNewerThan(Version currentVersion) { if (Version.Major == 0 && Version.Minor == 0 && Version.Build == 0 && Version.Revision == 0) return false; var curVersion = new Version(currentVersion.Major, currentVersion.Minor, currentVersion.Build, Version.Revision); return curVersion < Version; } } public class VersionChecker { public static VersionInfo GetLatestVersion() { var latestVersion = new VersionInfo(); const string xmlUrl = "http://dominion.technowall.net/files/version.xml"; try { var xDoc = new XmlDocument(); xDoc.Load(xmlUrl); var xnHead = xDoc.SelectSingleNode("Dominion.NET"); if (xnHead != null) { var xnVersion = xnHead.SelectSingleNode("version"); if (xnVersion != null) { latestVersion.Version = new Version(xnVersion.InnerText); latestVersion.IsVersionValid = true; } var xnUrl = xnHead.SelectSingleNode("url"); if (xnUrl != null) latestVersion.Url = new Uri(xnUrl.InnerText); var xnFileUrl = xnHead.SelectSingleNode("fileurl"); if (xnFileUrl != null) latestVersion.FileUrl = new Uri(xnFileUrl.InnerText); var xnDate = xnHead.SelectSingleNode("date"); if (xnDate != null) latestVersion.Date = DateTime.Parse(xnDate.InnerText).Date; } } catch (System.Net.WebException) { // We'll just silently ignore these errors } catch (Exception ex) { DominionBase.Utilities.Logging.LogError(ex); } return latestVersion; } } public struct ColorHls { public double H; public double L; public double S; public double A; } public class HlsColor { public static ColorHls RgbToHls(Color rgbColor) { // Initialize result var hlsColor = new ColorHls(); // Convert RGB values to percentages var r = (double)rgbColor.R / 255; var g = (double)rgbColor.G / 255; var b = (double)rgbColor.B / 255; var a = (double)rgbColor.A / 255; // Find min and max RGB values var min = Math.Min(r, Math.Min(g, b)); var max = Math.Max(r, Math.Max(g, b)); var delta = max - min; /* If max and min are equal, that means we are dealing with * a shade of gray. So we set H and S to zero, and L to either * max or min (it doesn't matter which), and then we exit. */ //Special case: Gray if (Math.Abs(max - min) < 0.001) { hlsColor.H = 0; hlsColor.S = 0; hlsColor.L = max; return hlsColor; } /* If we get to this point, we know we don't have a shade of gray. */ // Set L hlsColor.L = (min + max) / 2; // Set S if (hlsColor.L < 0.5) { hlsColor.S = delta / (max + min); } else { hlsColor.S = delta / (2.0 - max - min); } // Set H if (Math.Abs(r - max) < 0.001) hlsColor.H = (g - b) / delta; if (Math.Abs(g - max) < 0.001) hlsColor.H = 2.0 + (b - r) / delta; if (Math.Abs(b - max) < 0.001) hlsColor.H = 4.0 + (r - g) / delta; hlsColor.H *= 60; if (hlsColor.H < 0) hlsColor.H += 360; // Set A hlsColor.A = a; // Set return value return hlsColor; } /// /// Converts a WPF HSL color to an RGB color /// /// The HSL color to convert. /// An RGB color object equivalent to the HSL color object passed in. public static Color HlsToRgb(ColorHls hlsColor) { return HlsToRgb(hlsColor.H, hlsColor.L, hlsColor.S, hlsColor.A); } /// /// Converts a WPF HSL color to an RGB color /// /// The Hue to convert /// The Luminosity to convert /// The Saturation to convert /// The Alpha value to convert /// An RGB color object equivalent to the HSL color object passed in. public static Color HlsToRgb(double H, double L, double S, double A) { // Initialize result var rgbColor = new Color(); Debug.Assert(H >= 0); Debug.Assert(H <= 360); /* If S = 0, that means we are dealing with a shade * of gray. So, we set R, G, and B to L and exit. */ // Special case: Gray if (Math.Abs(S) < 0.001) { rgbColor.R = (byte)(L * 255); rgbColor.G = (byte)(L * 255); rgbColor.B = (byte)(L * 255); rgbColor.A = (byte)(A * 255); return rgbColor; } L = Math.Min(1d, L); S = Math.Min(1d, S); double t1; if (L < 0.5) { t1 = L * (1.0 + S); } else { t1 = L + S - L * S; } var t2 = 2.0 * L - t1; // Convert H from degrees to a percentage var h = H / 360; // Set colors as percentage values var tR = h + 1.0 / 3.0; var r = SetColor(t1, t2, tR); var tG = h; var g = SetColor(t1, t2, tG); var tB = h - 1.0 / 3.0; var b = SetColor(t1, t2, tB); // Assign colors to Color object rgbColor.R = (byte)(r * 255); rgbColor.G = (byte)(g * 255); rgbColor.B = (byte)(b * 255); rgbColor.A = (byte)(A * 255); // Set return value return rgbColor; } #region Utility Methods /// /// Used by the HSL-to-RGB converter. /// /// A temporary variable. /// A temporary variable. /// A temporary variable. /// An RGB color value, in decimal format. private static double SetColor(double t1, double t2, double t3) { if (t3 < 0) t3 += 1.0; if (t3 > 1) t3 -= 1.0; double color; if (6.0 * t3 < 1) { color = t2 + (t1 - t2) * 6.0 * t3; } else if (2.0 * t3 < 1) { color = t1; } else if (3.0 * t3 < 2) { color = t2 + (t1 - t2) * (2.0 / 3.0 - t3) * 6.0; } else { color = t2; } // Set return value return color; } #endregion } [StructLayout(LayoutKind.Sequential)] public struct PixelColor { public byte Blue; public byte Green; public byte Red; public byte Alpha; } public static class ImageUtilities { #if UNSAFE public unsafe static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int stride, int offset) { fixed(PixelColor* buffer = &pixels[0, 0]) source.CopyPixels( new Int32Rect(0, 0, source.PixelWidth, source.PixelHeight), (IntPtr)(buffer + offset), pixels.GetLength(0) * pixels.GetLength(1) * sizeof(PixelColor), stride); } #else public static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int stride, int offset) { var height = source.PixelHeight; var width = source.PixelWidth; var pixelBytes = new byte[height * width * 4]; source.CopyPixels(pixelBytes, stride, 0); var y0 = offset / width; var x0 = offset - width * y0; for (var y = 0; y < height; y++) for (var x = 0; x < width; x++) pixels[x + x0, y + y0] = new PixelColor { Blue = pixelBytes[(y * width + x) * 4 + 0], Green = pixelBytes[(y * width + x) * 4 + 1], Red = pixelBytes[(y * width + x) * 4 + 2], Alpha = pixelBytes[(y * width + x) * 4 + 3], }; } #endif public static PixelColor[,] GetPixels(BitmapSource source) { if (source.Format != PixelFormats.Bgra32) source = new FormatConvertedBitmap(source, PixelFormats.Bgra32, null, 0); var width = source.PixelWidth; var height = source.PixelHeight; var result = new PixelColor[width, height]; CopyPixels(source, result, width * 4, 0); return result; } } }