public void setCreature(string species, int[] colorIDs) { this.colorIDs = colorIDs; int si = Values.V.speciesNames.IndexOf(species); if (si >= 0 && Values.V.species[si].colors != null) { colorRegions = Values.V.species[si].colors; } else { // species-info is not available, show all region-buttons colorRegions = new List <ColorRegion>(); for (int i = 0; i < 6; i++) { colorRegions.Add(new ColorRegion()); colorRegions[i].name = "n/a"; } } for (int r = 0; r < buttonColors.Length; r++) { ColorRegionsUseds[r] = colorRegions[r].name != null; buttonColors[r].Visible = ColorRegionsUseds[r]; if (buttonColors[r].Visible) { setColorButton(buttonColors[r], CreatureColors.creatureColor(colorIDs[r])); tt.SetToolTip(buttonColors[r], colorRegions[r].name); } } }
public void Clear() { for (int r = 0; r < buttonColors.Length; r++) { buttonColors[r].Hide(); setColorButton(buttonColors[r], CreatureColors.creatureColor(0)); } }
private static int parseColor(string text) { double.TryParse(text.Substring(3, 8), out double r); double.TryParse(text.Substring(14, 8), out double g); double.TryParse(text.Substring(25, 8), out double b); return(CreatureColors.closestColorIDFromRGB(LinearColorComponentToColorComponent(r), LinearColorComponentToColorComponent(g), LinearColorComponentToColorComponent(b))); }
private static int parseColor(string text) { double.TryParse(text.Substring(3, 8), System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowLeadingSign, System.Globalization.CultureInfo.GetCultureInfo("en-US"), out double r); double.TryParse(text.Substring(14, 8), System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowLeadingSign, System.Globalization.CultureInfo.GetCultureInfo("en-US"), out double g); double.TryParse(text.Substring(25, 8), System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowLeadingSign, System.Globalization.CultureInfo.GetCultureInfo("en-US"), out double b); return(CreatureColors.closestColorIDFromRGB(LinearColorComponentToColorComponent(r), LinearColorComponentToColorComponent(g), LinearColorComponentToColorComponent(b))); }
private void setColorButton(Button bt, int region) { int colorId = _colorIDs[region]; Color cl = CreatureColors.creatureColor(colorId); bt.BackColor = cl; bt.ForeColor = Utils.ForeColor(cl); // tooltip if (colorRegions != null) { tt.SetToolTip(bt, colorRegions[region].name + " (" + region.ToString() + "):\n" + CreatureColors.creatureColorName(colorId) + " (" + colorId.ToString() + ")"); } }
private void setColorButton(Button bt, int region) { int colorId = _colorIDs[region]; Color cl = CreatureColors.creatureColor(colorId); bt.BackColor = cl; bt.ForeColor = Utils.ForeColor(cl); // tooltip if (colorRegions != null) { tt.SetToolTip(bt, $"{colorRegions[region].name} ({region}):\n{CreatureColors.creatureColorName(colorId)} ({colorId})"); } }
private void chooseColor(int region, Button sender) { if (creatureList[0] != null && !cp.isShown) { cp.SetColors(colors[region], "Region " + region); if (cp.ShowDialog() == DialogResult.OK) { // color was chosen colors[region] = cp.SelectedColorId; sender.SetBackColorAndAccordingForeColor(CreatureColors.CreatureColor(colors[region])); pictureBox1.SetImageAndDisposeOld(CreatureColored.GetColoredCreature(colors, uniqueSpecies ? creatureList[0].Species : null, new[] { true, true, true, true, true, true })); } } }
private void SetColorButton(Button bt, int region) { int colorId = _selectedRegionColorIds[region]; bt.SetBackColorAndAccordingForeColor(CreatureColors.CreatureColor(colorId)); if (VerboseButtonTexts) { bt.Text = $"[{region}]: {colorId}"; } // tooltip if (_colorRegions?[region] != null) { _tt.SetToolTip(bt, $"{_colorRegions[region].name} ({region}):\n{CreatureColors.CreatureColorName(colorId)} ({colorId})"); } }
public static string RegionColorInfo(Species species, int[] colorIds) { string creatureRegionColors = ""; if (species != null) { var cs = species.colors; creatureRegionColors = "Colors:"; for (int r = 0; r < 6; r++) { if (!string.IsNullOrEmpty(cs[r]?.name)) { creatureRegionColors += $"\n{cs[r].name} ({r}): {CreatureColors.creatureColorName(colorIds[r])} ({colorIds[r]})"; } } } return(creatureRegionColors); }
public static string RegionColorInfo(string species, int[] colorIds) { string creatureRegionColors = ""; int si = Values.V.speciesIndex(species); if (si >= 0) { var cs = Values.V.species[si].colors; creatureRegionColors = "Colors:"; for (int r = 0; r < 6; r++) { if (cs[r].name != null) { creatureRegionColors += $"\n{cs[r].name} ({r}): {CreatureColors.creatureColorName(colorIds[r])} ({colorIds[r]})"; } } } return(creatureRegionColors); }
public static string RegionColorInfo(Species species, int[] colorIds) { if (species == null || colorIds == null) { return(null); } var creatureRegionColors = new StringBuilder("Colors:"); var cs = species.colors; for (int r = 0; r < Species.ColorRegionCount; r++) { if (!string.IsNullOrEmpty(cs[r]?.name)) { creatureRegionColors.Append($"\n{cs[r].name} ({r}): {CreatureColors.CreatureColorName(colorIds[r])} ({colorIds[r]})"); } } return(creatureRegionColors.ToString()); }
private static string FunctionColor(Match m, NamePatternParameters p) { // parameter 1: region id (0,...,5), 2: if not empty, the color name instead of the numerical id is returned, 3: if not empty, the function will return also a value even if the color region is not used on that species. if (!int.TryParse(m.Groups[2].Value, out int regionId) || regionId < 0 || regionId > 5) { return(ParametersInvalid("color region id has to be a number in the range 0 - 5", m.Groups[0].Value, p.DisplayError)); } if (!p.Creature.Species.EnabledColorRegions[regionId] && string.IsNullOrWhiteSpace(m.Groups[4].Value)) { return(string.Empty); // species does not use this region and user doesn't want it (param 3 is empty) } if (p.Creature.colors == null) { return(string.Empty); // no color info } if (string.IsNullOrWhiteSpace(m.Groups[3].Value)) { return(p.Creature.colors[regionId].ToString()); } return(CreatureColors.CreatureColorName(p.Creature.colors[regionId])); }
/// <summary> /// Creates an image with infos about the creature. /// </summary> /// <param name="creature"></param> /// <returns></returns> public static Bitmap InfoGraphic(this Creature creature) { if (creature == null) { return(null); } const int width = 300; const int height = 150; var bmp = new Bitmap(width, height); using (var g = Graphics.FromImage(bmp)) using (var font = new Font("Arial", 10)) using (var fontSmall = new Font("Arial", 8)) using (var fontHeader = new Font("Arial", 12, FontStyle.Bold)) using (var fontBrush = new SolidBrush(Color.Black)) using (var stringFormatRight = new StringFormat() { Alignment = StringAlignment.Far }) { using (var backgroundBrush = new SolidBrush(Color.AntiqueWhite)) g.FillRectangle(backgroundBrush, 0, 0, width, height); g.DrawString($"{creature.Species.DescriptiveNameAndMod} (Lvl {creature.LevelHatched})", fontHeader, fontBrush, 3, 3); const int yBelowHeader = 20; // levels const int xStatName = 8; int xLevelValue = xStatName + 30 + (creature.levelsWild[2].ToString().Length) * 7; g.DrawString("Levels", font, fontBrush, xStatName, yBelowHeader); int statDisplayIndex = 0; for (int si = 0; si < Values.STATS_COUNT; si++) { int statIndex = Values.statsDisplayOrder[si]; if (creature.Species.UsesStat(statIndex)) { int y = yBelowHeader + 20 + (statDisplayIndex++) * 13; g.DrawString($"{Utils.statName(statIndex, true, creature.Species.IsGlowSpecies)}", font, fontBrush, xStatName, y); g.DrawString($"{creature.levelsWild[statIndex]}", font, fontBrush, xLevelValue, y, stringFormatRight); } } // colors int xColor = xLevelValue + 20; g.DrawString("Colors", font, fontBrush, xColor, yBelowHeader); int colorIndex = 0; for (int ci = 0; ci < Species.COLOR_REGION_COUNT; ci++) { if (!string.IsNullOrEmpty(creature.Species.colors[ci]?.name)) { const int circleDiameter = 16; const int rowHeight = circleDiameter + 2; int y = yBelowHeader + 20 + (colorIndex++) * rowHeight; Color c = CreatureColors.creatureColor(creature.colors[ci]); Color fc = Utils.ForeColor(c); using (var b = new SolidBrush(c)) g.FillEllipse(b, xColor, y, circleDiameter, circleDiameter); using (var p = new Pen(Color.Black, 1)) g.DrawEllipse(p, xColor, y, circleDiameter, circleDiameter); g.DrawString($"[{ci}] {creature.Species.colors[ci].name}: [{creature.colors[ci]}] {CreatureColors.creatureColorName(creature.colors[ci])}", fontSmall, fontBrush, xColor + circleDiameter + 4, y); } } } return(bmp); }
private void DrawData(Creature creature, bool highlight, int highlightStatIndex, ToolTip tt) { if (creature?.Species == null) { return; } var usedStats = Enumerable.Range(0, Values.STATS_COUNT).Where(si => si != (int)StatNames.Torpidity && creature.Species.UsesStat(si)).ToArray(); var anglePerStat = 360f / usedStats.Length; const int borderWidth = 1; // used for the tooltip text var colors = new ArkColor[Species.ColorRegionCount]; (_statInheritances, _mutationInColor) = DetermineInheritanceAndMutations(creature, usedStats); var mutationOccurred = _mutationInColor != null; Bitmap bmp = new Bitmap(Width, Height); using (Graphics g = Graphics.FromImage(bmp)) using (var font = new Font("Microsoft Sans Serif", _fontSize)) using (var pen = new Pen(Color.Black)) using (var brush = new SolidBrush(Color.Black)) { g.SmoothingMode = SmoothingMode.AntiAlias; var borderColor = Color.FromArgb(219, 219, 219); float drawnBorderWidth = borderWidth; if (highlight) { borderColor = Color.DodgerBlue; drawnBorderWidth = 1.5f; } else { Cursor = Cursors.Hand; if (highlightStatIndex != -1) { borderColor = Color.Black; } } if (mutationOccurred) { borderColor = Utils.MutationMarkerColor; drawnBorderWidth = 1.5f; } pen.Color = borderColor; pen.Width = drawnBorderWidth; g.DrawRectangle(pen, drawnBorderWidth, drawnBorderWidth, Width - 2 * drawnBorderWidth, Height - 2 * drawnBorderWidth); // stats var chartMax = CreatureCollection.CurrentCreatureCollection?.maxChartLevel ?? 50; int radiusInnerCircle = (_statSize - 2 * borderWidth) / 7; int centerCoord = _statSize / 2 - 1; var i = 0; if (creature.levelsWild != null) { pen.Color = Color.Black; foreach (var si in usedStats) { var level = creature.levelsWild[si]; var statSize = Math.Min((double)level / chartMax, 1); var pieRadius = (int)(radiusInnerCircle + (centerCoord - radiusInnerCircle - borderWidth) * statSize); var leftTop = centerCoord - pieRadius; var angle = AngleOffset + anglePerStat * i++; brush.Color = Utils.GetColorFromPercent((int)(100 * statSize), creature.topBreedingStats[si] ? 0 : 0.7); g.FillPie(brush, leftTop, leftTop, 2 * pieRadius, 2 * pieRadius, angle, anglePerStat); pen.Width = highlightStatIndex == si ? 2 : 1; g.DrawPie(pen, leftTop, leftTop, 2 * pieRadius, 2 * pieRadius, angle, anglePerStat); var mutationStatus = _statInheritances[si]; const int anyMutationMask = 0b01110111; if ((mutationStatus & anyMutationMask) == 0) { continue; } const int mutationIsNotGuaranteedMask = 0b10001000; var guaranteedMutation = (mutationStatus & mutationIsNotGuaranteedMask) == 0; var anglePosition = Math.PI * 2 / 360 * (angle + anglePerStat / 2); var x = (int)Math.Round(pieRadius * Math.Cos(anglePosition) + centerCoord - _mutationMarkerRadius - 1); var y = (int)Math.Round(pieRadius * Math.Sin(anglePosition) + centerCoord - _mutationMarkerRadius - 1); DrawFilledCircle(g, brush, pen, guaranteedMutation ? Utils.MutationMarkerColor : Utils.MutationMarkerPossibleColor, x, y, 2 * _mutationMarkerRadius); } } // draw sex in the center g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; brush.Color = Utils.AdjustColorLight(Utils.SexColor(creature.sex), 0.2); g.FillEllipse(brush, centerCoord - radiusInnerCircle, centerCoord - radiusInnerCircle, 2 * radiusInnerCircle, 2 * radiusInnerCircle); pen.Width = 1; g.DrawEllipse(pen, centerCoord - radiusInnerCircle, centerCoord - radiusInnerCircle, 2 * radiusInnerCircle, 2 * radiusInnerCircle); brush.Color = Color.Black; using (var format = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }) { g.DrawString(Utils.SexSymbol(creature.sex), font, brush, new RectangleF(centerCoord - radiusInnerCircle + 1, centerCoord - radiusInnerCircle + 2, 2 * radiusInnerCircle, 2 * radiusInnerCircle), format); g.DrawString(creature.name, font, brush, new RectangleF(borderWidth, _statSize + borderWidth - 2, ControlWidth - borderWidth, _colorSize), format); } // colors if (creature.colors != null) { var displayedColorRegions = Enumerable.Range(0, Species.ColorRegionCount) .Where(ci => creature.Species.EnabledColorRegions[ci]).ToArray(); var usedColorRegionCount = displayedColorRegions.Length; if (usedColorRegionCount != 0) { const int margin = 1; var colorSize = new Size(_colorSize - 3 * margin - borderWidth, (_statSize - 2 * borderWidth) / usedColorRegionCount - 3 * margin); // only check for color mutations if the colors of both parents are available mutationOccurred = mutationOccurred && creature.Mother?.colors != null && creature.Father?.colors != null; i = 0; var left = _statSize + 2 * margin; foreach (var ci in displayedColorRegions) { var color = CreatureColors.CreatureArkColor(creature.colors[ci]); colors[ci] = color; brush.Color = color.Color; var y = borderWidth + margin + i++ *(colorSize.Height + 2 * margin); g.FillRectangle(brush, left, y, colorSize.Width, colorSize.Height); g.DrawRectangle(pen, left, y, colorSize.Width, colorSize.Height); var colorMutationOccurred = mutationOccurred && creature.colors[ci] != creature.Mother.colors[ci] && creature.colors[ci] != creature.Father.colors[ci]; if (colorMutationOccurred) { var x = left - _colorMutationMarkerRadius - 2; y = y + colorSize.Height / 2 - _colorMutationMarkerRadius; DrawFilledCircle(g, brush, pen, Color.Yellow, x, y, 2 * _colorMutationMarkerRadius); _mutationInColor[ci] = true; } } } } if (_mutationInColor != null && !_mutationInColor.Any(m => m)) { _mutationInColor = null; // not needed, no possible mutations } // mutation indicator if (!creature.flags.HasFlag(CreatureFlags.Placeholder)) { int yMarker = _statSize - _mutationIndicatorSize - 1 - borderWidth; Color mutationColor = creature.Mutations == 0 ? Color.GreenYellow : creature.Mutations < GameConstants.MutationPossibleWithLessThan ? Utils.MutationColor : Color.DarkRed; DrawFilledCircle(g, brush, pen, mutationColor, borderWidth + 1, yMarker, _mutationIndicatorSize); } } var oldImage = Image; Image = bmp; oldImage?.Dispose(); var statNames = creature.Species?.statNames; var toolTipText = $"{creature.name} ({Utils.SexSymbol(creature.sex)})"; if (creature.flags.HasFlag(CreatureFlags.Placeholder)) { toolTipText += "\nThis creature is not yet in this library. This entry is a placeholder and contains no more info"; } else { string InheritanceExplanation(int statIndex) { var mutationStatus = _statInheritances[statIndex]; if (mutationStatus == 0) { return(null); } var resultMother = Mutation(true); var resultFather = Mutation(false); if (resultMother == null && resultFather == null) { return(null); } return($" ({resultMother}{(resultMother != null && resultFather != null ? " or " : null)}{resultFather})"); string Mutation(bool mother) { var status = (mutationStatus >> (!mother ? 4 : 0)) & 0xf; if (status == 0) { return(null); } var sex = mother ? "♀" : "♂"; if (status == 8) { return(sex); } if (status > 8) { var mutationCount = status & 7; return($"{sex} with possible {mutationCount} mutation{(mutationCount > 1 ? "s" : null)}"); } return($"{sex} with {status} mutation{(status > 1 ? "s" : null)}"); } } if (creature.levelsWild != null) { toolTipText += $"\n{string.Join("\n", usedStats.Select(si => $"{Utils.StatName(si, true, statNames)}:\t{creature.levelsWild[si],3}{InheritanceExplanation(si)}"))}"; } toolTipText += $"\n{Loc.S("Mutations")}: {creature.Mutations} = {creature.mutationsMaternal} (♀) + {creature.mutationsPaternal} (♂)"; if (creature.colors != null) { toolTipText += $"\n{Loc.S("Colors")}\n{string.Join("\n", colors.Select((c, i) => c == null ? null : $"[{i}]:\t{c.Id} ({c.Name}){((_mutationInColor?[i] ?? false) ? " (mutated color)" : null)}").Where(s => s != null))}"; } }
/// <summary> /// Creates a colored species image and saves it as cache file. Returns true when created successful. /// </summary> /// <returns></returns> private static bool CreateAndSaveCacheSpeciesFile(int[] colorIds, bool[] enabledColorRegions, string speciesBackgroundFilePath, string speciesColorMaskFilePath, string cacheFilePath, int outputSize = 256) { if (string.IsNullOrEmpty(cacheFilePath) || !File.Exists(speciesBackgroundFilePath)) { return(false); } using (Bitmap bmpBackground = new Bitmap(speciesBackgroundFilePath)) using (Bitmap bmpColoredCreature = new Bitmap(bmpBackground.Width, bmpBackground.Height, PixelFormat.Format32bppArgb)) using (Graphics graph = Graphics.FromImage(bmpColoredCreature)) { bool imageFine = true; graph.SmoothingMode = SmoothingMode.AntiAlias; //// ellipse shadow const int scx = TemplateSize / 2; const int scy = (int)(scx * 1.6); const double perspectiveFactor = 0.3; const int yStart = scy - (int)(perspectiveFactor * .7 * scx); const int yEnd = (int)(2 * perspectiveFactor * scx); GraphicsPath pathShadow = new GraphicsPath(); pathShadow.AddEllipse(0, yStart, TemplateSize, yEnd); var colorBlend = new ColorBlend { Colors = new[] { Color.FromArgb(0), Color.FromArgb(40, 0, 0, 0), Color.FromArgb(80, 0, 0, 0) }, Positions = new[] { 0, 0.6f, 1 } }; PathGradientBrush pthGrBrush = new PathGradientBrush(pathShadow) { InterpolationColors = colorBlend }; graph.FillEllipse(pthGrBrush, 0, yStart, TemplateSize, yEnd); // shadow done // shaded base image graph.DrawImage(bmpBackground, 0, 0, TemplateSize, TemplateSize); // if species has color regions, apply colors if (File.Exists(speciesColorMaskFilePath)) { var rgb = new byte[Species.ColorRegionCount][]; var useColorRegions = new bool[Species.ColorRegionCount]; for (int c = 0; c < Species.ColorRegionCount; c++) { useColorRegions[c] = enabledColorRegions[c] && colorIds[c] != 0; if (useColorRegions[c]) { Color cl = CreatureColors.CreatureColor(colorIds[c]); rgb[c] = new[] { cl.R, cl.G, cl.B }; } } imageFine = ApplyColorsUnsafe(rgb, useColorRegions, speciesColorMaskFilePath, TemplateSize, bmpBackground, bmpColoredCreature); } if (imageFine) { string cacheFolder = Path.GetDirectoryName(cacheFilePath); if (string.IsNullOrEmpty(cacheFolder)) { return(false); } if (!Directory.Exists(cacheFolder)) { Directory.CreateDirectory(cacheFolder); } if (outputSize == TemplateSize) { return(SaveBitmapToFile(bmpColoredCreature, cacheFilePath)); } using (var resized = new Bitmap(outputSize, outputSize)) using (var g = Graphics.FromImage(resized)) { g.CompositingQuality = CompositingQuality.HighQuality; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.SmoothingMode = SmoothingMode.HighQuality; g.PixelOffsetMode = PixelOffsetMode.HighQuality; g.DrawImage(bmpColoredCreature, 0, 0, outputSize, outputSize); return(SaveBitmapToFile(resized, cacheFilePath)); } } } return(false); }
/// <summary> /// Creates an image with infos about the creature. /// </summary> /// <param name="creature"></param> /// <param name="cc">CreatureCollection for server settings.</param> /// <returns></returns> public static Bitmap InfoGraphic(this Creature creature, CreatureCollection cc) { if (creature == null) { return(null); } int maxGraphLevel = cc?.maxChartLevel ?? 0; if (maxGraphLevel < 1) { maxGraphLevel = 50; } const int width = 330; const int height = 180; var bmp = new Bitmap(width, height); using (var g = Graphics.FromImage(bmp)) using (var font = new Font("Arial", 10)) using (var fontSmall = new Font("Arial", 8)) using (var fontHeader = new Font("Arial", 12, FontStyle.Bold)) using (var fontBrush = new SolidBrush(Color.Black)) using (var penBlack = new Pen(Color.Black, 1)) using (var stringFormatRight = new StringFormat() { Alignment = StringAlignment.Far }) { g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; int currentYPosition = 3; using (var backgroundBrush = new SolidBrush(Color.AntiqueWhite)) g.FillRectangle(backgroundBrush, 0, 0, width, height); g.DrawString(creature.Species.DescriptiveNameAndMod, fontHeader, fontBrush, 3, currentYPosition); currentYPosition += 19; g.DrawString($"Level {creature.LevelHatched} | {Utils.SexSymbol(creature.sex) + (creature.flags.HasFlag(CreatureFlags.Neutered) ? $" ({Loc.S(creature.sex == Sex.Female ? "Spayed" : "Neutered")})" : string.Empty)} | {creature.Mutations} mutations | generation {creature.generation}", font, fontBrush, 8, currentYPosition); currentYPosition += 17; using (var p = new Pen(Color.LightGray, 1)) g.DrawLine(p, 0, currentYPosition, width, currentYPosition); currentYPosition += 2; // levels const int xStatName = 8; int xLevelValue = xStatName + 30 + (creature.levelsWild[2].ToString().Length) * 7; int xRightBrValue = xLevelValue + 14 + MaxBreedingValueLength(creature.valuesBreeding) * 7; int maxBoxLength = xRightBrValue - xStatName; const int statBoxHeight = 2; g.DrawString("Levels", font, fontBrush, xLevelValue, currentYPosition, stringFormatRight); g.DrawString("Values", font, fontBrush, xRightBrValue, currentYPosition, stringFormatRight); int statDisplayIndex = 0; for (int si = 0; si < Values.STATS_COUNT; si++) { int statIndex = Values.statsDisplayOrder[si]; if (statIndex == (int)StatNames.Torpidity || !creature.Species.UsesStat(statIndex)) { continue; } int y = currentYPosition + 20 + (statDisplayIndex++) * 15; // box // empty box to show the max possible length using (var b = new SolidBrush(Color.DarkGray)) g.FillRectangle(b, xStatName, y + 14, maxBoxLength, statBoxHeight); double levelFractionOfMax = Math.Min(1, (double)creature.levelsWild[statIndex] / maxGraphLevel); if (levelFractionOfMax < 0) { levelFractionOfMax = 0; } int levelPercentageOfMax = (int)(100 * levelFractionOfMax); int statBoxLength = Math.Max((int)(maxBoxLength * levelFractionOfMax), 1); var statColor = Utils.GetColorFromPercent(levelPercentageOfMax); using (var b = new SolidBrush(statColor)) g.FillRectangle(b, xStatName, y + 14, statBoxLength, statBoxHeight); using (var b = new SolidBrush(Color.FromArgb(10, statColor))) { for (int r = 4; r > 0; r--) { g.FillRectangle(b, xStatName - r, y + 13 - r, statBoxLength + 2 * r, statBoxHeight + 2 * r); } } using (var p = new Pen(Utils.GetColorFromPercent(levelPercentageOfMax, -0.5), 1)) g.DrawRectangle(p, xStatName, y + 14, statBoxLength, statBoxHeight); // stat name g.DrawString($"{Utils.StatName(statIndex, true, creature.Species.statNames)}", font, fontBrush, xStatName, y); // stat level number g.DrawString($"{creature.levelsWild[statIndex]}", font, fontBrush, xLevelValue, y, stringFormatRight); // stat breeding value string statValueRepresentation; if (Utils.Precision(statIndex) == 3) { statValueRepresentation = (100 * creature.valuesBreeding[statIndex]).ToString("0.0"); g.DrawString("%", font, fontBrush, xRightBrValue, y); } else { statValueRepresentation = creature.valuesBreeding[statIndex].ToString("0.0"); } g.DrawString(statValueRepresentation, font, fontBrush, xRightBrValue, y, stringFormatRight); } // colors var enabledColorRegions = creature.Species.EnabledColorRegions; bool creatureImageShown = false; const int imageSize = 125; using (var crBmp = CreatureColored.GetColoredCreature(creature.colors, creature.Species, enabledColorRegions, imageSize, onlyImage: true, creatureSex: creature.sex)) { if (crBmp != null) { g.DrawImage(crBmp, 200, 40, imageSize, imageSize); creatureImageShown = true; } } int xColor = xRightBrValue + 25; const int circleDiameter = 16; const int rowHeight = circleDiameter + 2; int maxColorNameLength = (width - xColor - circleDiameter) / 6; // max char length for the color region name if (maxColorNameLength < 0) { maxColorNameLength = 0; } g.DrawString("Colors", font, fontBrush, xColor, currentYPosition); int colorRow = 0; for (int ci = 0; ci < Species.ColorRegionCount; ci++) { if (!enabledColorRegions[ci]) { continue; } int y = currentYPosition + 20 + (colorRow++) * rowHeight; Color c = CreatureColors.CreatureColor(creature.colors[ci]); //Color fc = Utils.ForeColor(c); using (var b = new SolidBrush(c)) g.FillEllipse(b, xColor, y, circleDiameter, circleDiameter); g.DrawEllipse(penBlack, xColor, y, circleDiameter, circleDiameter); string colorRegionName = null; //string colorName = CreatureColors.CreatureColorName(creature.colors[ci]); if (!creatureImageShown) { colorRegionName = creature.Species.colors[ci].name; int totalColorLenght = colorRegionName.Length + 11; if (totalColorLenght > maxColorNameLength) { // shorten color region name int lengthForRegionName = colorRegionName.Length - (totalColorLenght - maxColorNameLength); colorRegionName = lengthForRegionName < 2 ? string.Empty : colorRegionName.Substring(0, lengthForRegionName - 1) + "…"; } if (!string.IsNullOrEmpty(colorRegionName)) { colorRegionName = " (" + colorRegionName + ")"; } } g.DrawString($"{creature.colors[ci]} - [{ci}]{colorRegionName}", fontSmall, fontBrush, xColor + circleDiameter + 4, y); } // max wild level on server if (cc != null) { g.DrawString($"max wild level: {cc.maxWildLevel}", fontSmall, fontBrush, width - 4, height - 14, stringFormatRight); } // frame using (var p = new Pen(Color.DarkRed, 1)) g.DrawRectangle(p, 0, 0, width - 1, height - 1); } return(bmp); }
public static Bitmap getColoredCreature(int[] colorIds, Species species, bool[] enabledColorRegions, int size = 128, int pieSize = 64, bool onlyColors = false, bool dontCache = false) { //float[][] hsl = new float[6][]; int[][] rgb = new int[6][]; for (int c = 0; c < 6; c++) { Color cl = CreatureColors.creatureColor(colorIds[c]); rgb[c] = new int[] { cl.R, cl.G, cl.B }; } Bitmap bm = new Bitmap(size, size); using (Graphics graph = Graphics.FromImage(bm)) { graph.SmoothingMode = SmoothingMode.AntiAlias; string imgFolder = Path.Combine(FileService.GetPath(), imageFolderName); string cacheFolder = Path.Combine(FileService.GetPath(), imageFolderName, cacheFolderName); string speciesName = species?.name ?? string.Empty; string cacheFileName = Path.Combine(cacheFolder, speciesName.Substring(0, Math.Min(speciesName.Length, 5)) + "_" + (speciesName + string.Join("", colorIds.Select(i => i.ToString()).ToArray())).GetHashCode().ToString("X8") + extension); if (!onlyColors && File.Exists(Path.Combine(imgFolder, speciesName + extension)) && File.Exists(Path.Combine(imgFolder, speciesName + "_m" + extension))) { if (!File.Exists(cacheFileName)) { const int defaultSizeOfTemplates = 256; Bitmap bmC = new Bitmap(Path.Combine(imgFolder, speciesName + extension)); graph.DrawImage(new Bitmap(Path.Combine(imgFolder, speciesName + extension)), 0, 0, defaultSizeOfTemplates, defaultSizeOfTemplates); Bitmap mask = new Bitmap(defaultSizeOfTemplates, defaultSizeOfTemplates); Graphics.FromImage(mask).DrawImage(new Bitmap(Path.Combine(imgFolder, speciesName + "_m" + extension)), 0, 0, defaultSizeOfTemplates, defaultSizeOfTemplates); float o = 0; bool imageFine = false; try { for (int i = 0; i < bmC.Width; i++) { for (int j = 0; j < bmC.Height; j++) { Color bc = bmC.GetPixel(i, j); if (bc.A > 0) { int r = mask.GetPixel(i, j).R; int g = mask.GetPixel(i, j).G; int b = mask.GetPixel(i, j).B; for (int m = 0; m < 6; m++) { if (!enabledColorRegions[m] || colorIds[m] == 0) { continue; } switch (m) { case 0: o = Math.Max(0, r - g - b) / 255f; break; case 1: o = Math.Max(0, g - r - b) / 255f; break; case 2: o = Math.Max(0, b - r - g) / 255f; break; case 3: o = Math.Min(g, b) / 255f; break; case 4: o = Math.Min(r, g) / 255f; break; case 5: o = Math.Min(r, b) / 255f; break; } if (o == 0) { continue; } // using "grain merge", e.g. see https://docs.gimp.org/en/gimp-concepts-layer-modes.html int rMix = bc.R + rgb[m][0] - 128; if (rMix < 0) { rMix = 0; } else if (rMix > 255) { rMix = 255; } int gMix = bc.G + rgb[m][1] - 128; if (gMix < 0) { gMix = 0; } else if (gMix > 255) { gMix = 255; } int bMix = bc.B + rgb[m][2] - 128; if (bMix < 0) { bMix = 0; } else if (bMix > 255) { bMix = 255; } Color c = Color.FromArgb(rMix, gMix, bMix); bc = Color.FromArgb(bc.A, (int)(o * c.R + (1 - o) * bc.R), (int)(o * c.G + (1 - o) * bc.G), (int)(o * c.B + (1 - o) * bc.B)); } bmC.SetPixel(i, j, bc); } } } imageFine = true; } catch { // error during drawing, maybe mask is smaller than image } if (imageFine) { if (!Directory.Exists(cacheFolder)) { Directory.CreateDirectory(cacheFolder); } bmC.Save(cacheFileName); // safe in cache} } } } if (File.Exists(cacheFileName)) { graph.CompositingMode = CompositingMode.SourceCopy; graph.CompositingQuality = CompositingQuality.HighQuality; graph.InterpolationMode = InterpolationMode.HighQualityBicubic; graph.SmoothingMode = SmoothingMode.HighQuality; graph.PixelOffsetMode = PixelOffsetMode.HighQuality; graph.DrawImage(new Bitmap(cacheFileName), 0, 0, size, size); } else { // draw piechart int pieAngle = enabledColorRegions.Count(c => c); pieAngle = 360 / (pieAngle > 0 ? pieAngle : 1); int pieNr = 0; for (int c = 0; c < 6; c++) { if (enabledColorRegions[c]) { if (colorIds[c] > 0) { using (var b = new SolidBrush(CreatureColors.creatureColor(colorIds[c]))) { graph.FillPie(b, (size - pieSize) / 2, (size - pieSize) / 2, pieSize, pieSize, pieNr * pieAngle + 270, pieAngle); } } pieNr++; } } graph.DrawEllipse(new Pen(Color.Gray), (size - pieSize) / 2, (size - pieSize) / 2, pieSize, pieSize); } } return(bm); }
/// <summary> /// Resolves the naming-pattern functions /// </summary> /// <param name="m"></param> /// <param name="creature"></param> /// <param name="customReplacings"></param> /// <param name="displayError"></param> /// <param name="processNumberField">The number field {n} will add the lowest possible positive integer for the name to be unique. It has to be processed after all other functions.</param> /// <returns></returns> private static string ResolveFunction(Match m, Creature creature, int[] speciesTopLevels, Dictionary <string, string> customReplacings, bool displayError, bool processNumberField) { // function parameters can be nonnumeric if numbers are parsed try { // first parameter value string p1 = m.Groups[2].Value; if (!processNumberField && p1.Contains("{n}")) { return(m.Groups[0].Value); } // switch function name switch (m.Groups[1].Value.ToLower()) { case "if": // Group3 contains the result if true // Group4 (optional) contains the result if false int p1Length = p1.Length; if (p1Length < 7) { return(ParametersInvalid($"The condition-parameter expects exactly 7 or 10 characters, e.g. \"isTopHP\" or \"isNewTopHP\", given is \"{p1}\"")); } string conditional = p1.ToLower(); if (p1Length == 7 && conditional.Substring(0, 5) == "istop") { int si = StatIndexFromAbbreviation(conditional.Substring(5, 2)); if (si == -1) { return(ParametersInvalid($"Invalid stat name \"{p1}\".")); } return(m.Groups[speciesTopLevels == null ? (creature.levelsWild[si] > 0 ? 3 : 4) : (creature.levelsWild[si] >= speciesTopLevels[si] ? 3 : 4)].Value); } else if (p1Length == 10 && conditional.Substring(0, 8) == "isnewtop") { int si = StatIndexFromAbbreviation(conditional.Substring(8, 2)); if (si == -1) { return(ParametersInvalid($"Invalid stat name \"{p1}\".")); } return(m.Groups[speciesTopLevels == null ? (creature.levelsWild[si] > 0 ? 3 : 4) : (creature.levelsWild[si] > speciesTopLevels[si] ? 3 : 4)].Value); } else { return(ParametersInvalid($"The condition-parameter \"{p1}\"is invalid. It has to start with \"isTop\" or \"isNewTop\" followed by a stat specifier, e.g. \"hp\"")); } case "ifexpr": // tries to evaluate the expression // possible operators are ==, !=, <, >, =<, => var match = Regex.Match(p1, @"\A\s*(\d+(?:\.\d*)?)\s*(==|!=|<|<=|>|>=)\s*(\d+(?:\.\d*)?)\s*\Z"); if (match.Success && double.TryParse(match.Groups[1].Value, out double d1) && double.TryParse(match.Groups[3].Value, out double d2) ) { switch (match.Groups[2].Value) { case "==": return(d1 == d2 ? m.Groups[3].Value : m.Groups[4].Value); case "!=": return(d1 != d2 ? m.Groups[3].Value : m.Groups[4].Value); case "<": return(d1 < d2 ? m.Groups[3].Value : m.Groups[4].Value); case "<=": return(d1 <= d2 ? m.Groups[3].Value : m.Groups[4].Value); case ">": return(d1 > d2 ? m.Groups[3].Value : m.Groups[4].Value); case ">=": return(d1 >= d2 ? m.Groups[3].Value : m.Groups[4].Value); } } else { // compare the values as strings match = Regex.Match(p1, @"\A\s*(.*?)\s*(==|!=|<=|<|>=|>)\s*(.*?)\s*\Z"); if (match.Success) { int stringComparingResult = match.Groups[1].Value.CompareTo(match.Groups[3].Value); switch (match.Groups[2].Value) { case "==": return(stringComparingResult == 0 ? m.Groups[3].Value : m.Groups[4].Value); case "!=": return(stringComparingResult != 0 ? m.Groups[3].Value : m.Groups[4].Value); case "<": return(stringComparingResult < 0 ? m.Groups[3].Value : m.Groups[4].Value); case "<=": return(stringComparingResult <= 0 ? m.Groups[3].Value : m.Groups[4].Value); case ">": return(stringComparingResult > 0 ? m.Groups[3].Value : m.Groups[4].Value); case ">=": return(stringComparingResult >= 0 ? m.Groups[3].Value : m.Groups[4].Value); } } } return(ParametersInvalid($"The expression for ifexpr invalid: \"{p1}\"")); case "substring": // check param number: 1: substring, 2: p1, 3: pos, 4: length int pos = Convert.ToInt32(m.Groups[3].Value); bool fromEnd = pos < 0; pos = Math.Min(Math.Abs(pos), p1.Length); if (string.IsNullOrEmpty(m.Groups[4].Value)) { if (fromEnd) { return(p1.Substring(p1.Length - pos)); } else { return(p1.Substring(pos)); } } else { int length = Math.Min(Convert.ToInt32(Convert.ToInt32(m.Groups[4].Value)), fromEnd ? pos : p1.Length - pos); if (fromEnd) { return(p1.Substring(p1.Length - pos, length)); } else { return(p1.Substring(pos, length)); } } case "format": // check param number: 1: format, 2: p1, 3: formatString // only use last param string fmt_str = m.Groups[3].Value; if (!string.IsNullOrEmpty(fmt_str)) { // convert to double double value = Convert.ToDouble(p1); // format it return(value.ToString(fmt_str)); } else { return(ParametersInvalid("No Format string given")); } case "padleft": // check param number: 1: padleft, 2: p1, 3: desired length, 4: padding char int pad_len = Convert.ToInt32(m.Groups[3].Value); string pad_char = m.Groups[4].Value; if (!string.IsNullOrEmpty(pad_char)) { return(p1.PadLeft(pad_len, pad_char[0])); } else { ParametersInvalid($"No padding char given."); return(p1); } case "padright": // check param number: 1: padright, 2: p1, 3: desired length, 4: padding char pad_len = Convert.ToInt32(m.Groups[3].Value); pad_char = m.Groups[4].Value; if (!string.IsNullOrEmpty(pad_char)) { return(p1.PadRight(pad_len, pad_char[0])); } else { return(ParametersInvalid($"No padding char given.")); } case "float_div": // returns an float after dividing the parsed number // parameter: 1: div, 2: number, 3: divided by 4: format string double f_number = double.Parse(p1); double f_div = double.Parse(m.Groups[3].Value); if (f_div > 0) { return(((f_number / f_div)).ToString(m.Groups[4].Value)); } else { return(ParametersInvalid("Division by 0")); } case "div": // returns an integer after dividing the parsed number // parameter: 1: div, 2: number, 3: divided by double number = double.Parse(p1); double div = double.Parse(m.Groups[3].Value); if (div > 0) { return(((int)(number / div)).ToString()); } else { return(ParametersInvalid("Division by 0")); } case "casing": // parameter: 1: casing, 2: text, 3: U for UPPER, L for lower, T for Title switch (m.Groups[3].Value.ToLower()) { case "u": return(p1.ToUpperInvariant()); case "l": return(p1.ToLowerInvariant()); case "t": return(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(p1)); } return(ParametersInvalid($"casing expects 'U', 'L' or 'T', given is '{m.Groups[3].Value}'")); case "replace": // parameter: 1: replace, 2: text, 3: find, 4: replace if (string.IsNullOrEmpty(p1) || string.IsNullOrEmpty(m.Groups[3].Value)) { return(p1); } return(p1.Replace(m.Groups[3].Value.Replace(" ", " "), m.Groups[4].Value.Replace(" ", " "))); case "customreplace": // parameter: 1: customreplace, 2: key, 3: return if key not available if (customReplacings == null || string.IsNullOrEmpty(p1) || !customReplacings.ContainsKey(p1)) { return(m.Groups[3].Value); } return(customReplacings[p1]); case "time": // parameter: 1: time, 2: format return(DateTime.Now.ToString(p1)); case "color": // parameter 1: region id (0,...,5), 2: if not empty, the color name instead of the numerical id is returned if (!int.TryParse(p1, out int regionId) || regionId < 0 || regionId > 5) { return(ParametersInvalid("color region id has to be a number in the range 0 - 5")); } if ((creature.colors?[regionId] ?? 0) == 0) { return(string.Empty); // no color info } if (string.IsNullOrWhiteSpace(m.Groups[3].Value)) { return(creature.colors[regionId].ToString()); } return(CreatureColors.creatureColorName(creature.colors[regionId])); } } catch (Exception ex) { MessageBox.Show($"The syntax of the following pattern function\n{m.Groups[0].Value}\ncannot be processed and will be ignored.\n\nSpecific error-message:\n{ex.Message}", "Naming pattern function error", MessageBoxButtons.OK, MessageBoxIcon.Error); } return(string.Empty); string ParametersInvalid(string specificError) { if (displayError) { MessageBox.Show($"The syntax of the following pattern function\n{m.Groups[0].Value}\ncannot be processed and will be ignored." + (string.IsNullOrEmpty(specificError) ? string.Empty : $"\n\nSpecific error:\n{specificError}"), "Naming pattern function error", MessageBoxButtons.OK, MessageBoxIcon.Error); } return(displayError ? m.Groups[2].Value : specificError); } }
public static string RegionColorInfo(string species, int[] colorIds) { string creatureRegionColors = ""; int si = Values.V.speciesIndex(species); if (si >= 0) { var cs = Values.V.species[si].colors; creatureRegionColors = "Colors:"; for (int r = 0; r < 6; r++) { if (cs[r].name != null) { creatureRegionColors += "\n" + cs[r].name + " (" + r.ToString() + "): " + CreatureColors.creatureColorName(colorIds[r]) + " (" + colorIds[r].ToString() + ")"; } } } return(creatureRegionColors); }
public static Bitmap getColoredCreature(int[] colorIds, string species, bool[] enabledColorRegions, int size = 128, int pieSize = 64, bool onlyColors = false, bool dontCache = false) { //float[][] hsl = new float[6][]; int[][] rgb = new int[6][]; for (int c = 0; c < 6; c++) { Color cl = CreatureColors.creatureColor(colorIds[c]); rgb[c] = new int[] { cl.R, cl.G, cl.B }; } Bitmap bm = new Bitmap(size, size); using (Graphics graph = Graphics.FromImage(bm)) { graph.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; string imgFolder = "img/"; // cachefilename string cf = imgFolder + "cache/" + species.Substring(0, Math.Min(species.Length, 5)) + "_" + (species + string.Join("", colorIds.Select(i => i.ToString()).ToArray())).GetHashCode().ToString("X8") + ".png"; if (!onlyColors && System.IO.File.Exists(imgFolder + species + ".png") && System.IO.File.Exists(imgFolder + species + "_m.png")) { if (!System.IO.File.Exists(cf)) { int defaultSizeOfTemplates = 256; Bitmap bmC = new Bitmap(imgFolder + species + ".png"); graph.DrawImage(new Bitmap(imgFolder + species + ".png"), 0, 0, defaultSizeOfTemplates, defaultSizeOfTemplates); Bitmap mask = new Bitmap(defaultSizeOfTemplates, defaultSizeOfTemplates); Graphics.FromImage(mask).DrawImage(new Bitmap(imgFolder + species + "_m.png"), 0, 0, defaultSizeOfTemplates, defaultSizeOfTemplates); float o = 0, l; Color c = Color.Black, bc = Color.Black; int r, g, b, rMix, gMix, bMix; bool imageFine = false; try { for (int i = 0; i < bmC.Width; i++) { for (int j = 0; j < bmC.Height; j++) { bc = bmC.GetPixel(i, j); if (bc.A > 0) { r = mask.GetPixel(i, j).R; g = mask.GetPixel(i, j).G; b = mask.GetPixel(i, j).B; l = (Math.Max(bc.R, (Math.Max(bc.G, bc.B))) + Math.Min(bc.R, Math.Min(bc.G, bc.B))) / 510f; for (int m = 0; m < 6; m++) { if (!enabledColorRegions[m] || colorIds[m] == 0) { continue; } switch (m) { case 0: o = Math.Max(0, r - g - b) / 255f; break; case 1: o = Math.Max(0, g - r - b) / 255f; break; case 2: o = Math.Max(0, b - r - g) / 255f; break; case 3: o = Math.Min(g, b) / 255f; break; case 4: o = Math.Min(r, g) / 255f; break; case 5: o = Math.Min(r, b) / 255f; break; } if (o == 0) { continue; } rMix = bc.R + rgb[m][0] - 128; if (rMix < 0) { rMix = 0; } else if (rMix > 255) { rMix = 255; } gMix = bc.G + rgb[m][1] - 128; if (gMix < 0) { gMix = 0; } else if (gMix > 255) { gMix = 255; } bMix = bc.B + rgb[m][2] - 128; if (bMix < 0) { bMix = 0; } else if (bMix > 255) { bMix = 255; } c = Color.FromArgb(rMix, gMix, bMix); bc = Color.FromArgb(bc.A, (int)(o * c.R + (1 - o) * bc.R), (int)(o * c.G + (1 - o) * bc.G), (int)(o * c.B + (1 - o) * bc.B)); } bmC.SetPixel(i, j, bc); } } } imageFine = true; } catch { // error during drawing, maybe mask is smaller than image } if (imageFine) { if (!System.IO.Directory.Exists("img/cache")) { System.IO.Directory.CreateDirectory("img/cache"); } bmC.Save(cf); // safe in cache} } } } if (System.IO.File.Exists(cf)) { graph.CompositingMode = CompositingMode.SourceCopy; graph.CompositingQuality = CompositingQuality.HighQuality; graph.InterpolationMode = InterpolationMode.HighQualityBicubic; graph.SmoothingMode = SmoothingMode.HighQuality; graph.PixelOffsetMode = PixelOffsetMode.HighQuality; graph.DrawImage(new Bitmap(cf), 0, 0, size, size); } else { // draw piechart int pieAngle = enabledColorRegions.Count(c => c); pieAngle = 360 / (pieAngle > 0 ? pieAngle : 1); int pieNr = 0; for (int c = 0; c < 6; c++) { if (enabledColorRegions[c]) { if (colorIds[c] > 0) { using (var b = new SolidBrush(CreatureColors.creatureColor(colorIds[c]))) { graph.FillPie(b, (size - pieSize) / 2, (size - pieSize) / 2, pieSize, pieSize, pieNr * pieAngle + 270, pieAngle); } } pieNr++; } } graph.DrawEllipse(new Pen(Color.Gray), (size - pieSize) / 2, (size - pieSize) / 2, pieSize, pieSize); } } return(bm); }
/// <summary> /// Creates a colored species image and saves it as cache file. Returns true when created successful. /// </summary> /// <returns></returns> private static bool CreateAndSaveCacheSpeciesFile(int[] colorIds, bool[] enabledColorRegions, string speciesBackgroundFilePath, string speciesColorMaskFilePath, string cacheFilePath, int outputSize = 256) { if (!File.Exists(speciesBackgroundFilePath)) { return(false); } using (Bitmap bmpBackground = new Bitmap(speciesBackgroundFilePath)) using (Bitmap bmpColoredCreature = new Bitmap(bmpBackground.Width, bmpBackground.Height, PixelFormat.Format32bppArgb)) using (Graphics graph = Graphics.FromImage(bmpColoredCreature)) { bool imageFine = true; graph.SmoothingMode = SmoothingMode.AntiAlias; // shadow using (var b = new SolidBrush(Color.FromArgb(12, 0, 0, 0))) { int scx = TemplateSize / 2; int scy = (int)(scx * 1.6); int factor = 25; int sr = scx - 2 * factor; double heightFactor = 0.3; for (int i = 2; i >= 0; i--) { int radius = sr + i * factor; graph.FillEllipse(b, scx - radius, scy - (int)(heightFactor * .7 * radius), 2 * radius, (int)(2 * heightFactor * radius)); } } // shaded base image graph.DrawImage(bmpBackground, 0, 0, TemplateSize, TemplateSize); // if species has color regions, apply colors if (File.Exists(speciesColorMaskFilePath)) { var rgb = new byte[Species.ColorRegionCount][]; var useColorRegions = new bool[Species.ColorRegionCount]; for (int c = 0; c < Species.ColorRegionCount; c++) { useColorRegions[c] = enabledColorRegions[c] && colorIds[c] != 0; if (useColorRegions[c]) { Color cl = CreatureColors.CreatureColor(colorIds[c]); rgb[c] = new byte[] { cl.R, cl.G, cl.B }; } } imageFine = ApplyColorsUnsafe(rgb, useColorRegions, speciesColorMaskFilePath, TemplateSize, bmpBackground, bmpColoredCreature); } if (imageFine) { string cacheFolder = Path.GetDirectoryName(cacheFilePath); if (!Directory.Exists(cacheFolder)) { Directory.CreateDirectory(cacheFolder); } if (outputSize == TemplateSize) { return(SaveBitmapToFile(bmpColoredCreature, cacheFilePath)); } using (var resized = new Bitmap(outputSize, outputSize)) using (var g = Graphics.FromImage(resized)) { g.CompositingQuality = CompositingQuality.HighQuality; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.SmoothingMode = SmoothingMode.HighQuality; g.PixelOffsetMode = PixelOffsetMode.HighQuality; g.DrawImage(bmpColoredCreature, 0, 0, outputSize, outputSize); return(SaveBitmapToFile(resized, cacheFilePath)); } } } return(false); }
/// <summary> /// Creates an image with infos about the creature. /// </summary> /// <param name="creature"></param> /// <param name="cc">CreatureCollection for server settings.</param> /// <returns></returns> public static Bitmap InfoGraphic(this Creature creature, CreatureCollection cc) { if (creature == null) { return(null); } int maxGraphLevel = cc?.maxChartLevel ?? 0; if (maxGraphLevel < 1) { maxGraphLevel = 50; } const int width = 300; const int height = 180; var bmp = new Bitmap(width, height); using (var g = Graphics.FromImage(bmp)) using (var font = new Font("Arial", 10)) using (var fontSmall = new Font("Arial", 8)) using (var fontHeader = new Font("Arial", 12, FontStyle.Bold)) using (var fontBrush = new SolidBrush(Color.Black)) using (var penBlack = new Pen(Color.Black, 1)) using (var stringFormatRight = new StringFormat() { Alignment = StringAlignment.Far }) { g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; int currentYPosition = 3; using (var backgroundBrush = new SolidBrush(Color.AntiqueWhite)) g.FillRectangle(backgroundBrush, 0, 0, width, height); g.DrawString(creature.Species.DescriptiveNameAndMod, fontHeader, fontBrush, 3, currentYPosition); currentYPosition += 19; g.DrawString($"Lvl {creature.LevelHatched} | {Utils.sexSymbol(creature.sex) + (creature.flags.HasFlag(CreatureFlags.Neutered) ? $" ({Loc.s(creature.sex == Sex.Female ? "Spayed" : "Neutered")})" : string.Empty)} | {creature.Mutations} mutations", font, fontBrush, 8, currentYPosition); currentYPosition += 17; using (var p = new Pen(Color.LightGray, 1)) g.DrawLine(p, 0, currentYPosition, width, currentYPosition); currentYPosition += 2; // levels const int xStatName = 8; int xLevelValue = xStatName + 30 + (creature.levelsWild[2].ToString().Length) * 7; int maxBoxLenght = xLevelValue - xStatName; g.DrawString("Levels", font, fontBrush, xStatName, currentYPosition); int statDisplayIndex = 0; for (int si = 0; si < Values.STATS_COUNT; si++) { int statIndex = Values.statsDisplayOrder[si]; if (statIndex == (int)StatNames.Torpidity || !creature.Species.UsesStat(statIndex)) { continue; } int y = currentYPosition + 20 + (statDisplayIndex++) * 15; // box double levelFractionOfMax = Math.Min(1, (double)creature.levelsWild[statIndex] / maxGraphLevel); if (levelFractionOfMax < 0) { levelFractionOfMax = 0; } int levelPercentageOfMax = (int)(100 * levelFractionOfMax); int statBoxLength = Math.Max((int)(maxBoxLenght * levelFractionOfMax), 1); const int statBoxHeight = 2; var statColor = Utils.getColorFromPercent(levelPercentageOfMax); using (var b = new SolidBrush(statColor)) g.FillRectangle(b, xStatName, y + 14, statBoxLength, statBoxHeight); using (var b = new SolidBrush(Color.FromArgb(10, statColor))) { for (int r = 4; r > 0; r--) { g.FillRectangle(b, xStatName - r, y + 13 - r, statBoxLength + 2 * r, statBoxHeight + 2 * r); } } using (var p = new Pen(Utils.getColorFromPercent(levelPercentageOfMax, -0.5), 1)) g.DrawRectangle(p, xStatName, y + 14, statBoxLength, statBoxHeight); // stat name g.DrawString($"{Utils.statName(statIndex, true, creature.Species.IsGlowSpecies)}", font, fontBrush, xStatName, y); // stat level number g.DrawString($"{creature.levelsWild[statIndex]}", font, fontBrush, xLevelValue, y, stringFormatRight); } // colors const int maxColorNameLength = 38; int xColor = xLevelValue + 20; g.DrawString("Colors", font, fontBrush, xColor, currentYPosition); int colorIndex = 0; for (int ci = 0; ci < Species.COLOR_REGION_COUNT; ci++) { if (!string.IsNullOrEmpty(creature.Species.colors[ci]?.name)) { const int circleDiameter = 16; const int rowHeight = circleDiameter + 2; int y = currentYPosition + 20 + (colorIndex++) * rowHeight; Color c = CreatureColors.creatureColor(creature.colors[ci]); Color fc = Utils.ForeColor(c); using (var b = new SolidBrush(c)) g.FillEllipse(b, xColor, y, circleDiameter, circleDiameter); g.DrawEllipse(penBlack, xColor, y, circleDiameter, circleDiameter); string colorRegionName = creature.Species.colors[ci].name; string colorName = CreatureColors.creatureColorName(creature.colors[ci]); int totalColorLenght = colorRegionName.Length + colorName.Length + 9; if (totalColorLenght > maxColorNameLength) { // shorten color region name int lengthForRegionName = colorRegionName.Length - (totalColorLenght - maxColorNameLength); colorRegionName = lengthForRegionName <= 0 ? string.Empty : lengthForRegionName < colorRegionName.Length ? colorRegionName.Substring(0, lengthForRegionName) : colorRegionName; } g.DrawString($"[{ci}] {colorRegionName}: [{creature.colors[ci]}] {colorName}", fontSmall, fontBrush, xColor + circleDiameter + 4, y); } } // max wild level on server if (cc != null) { g.DrawString($"max wild level: {cc.maxWildLevel}", fontSmall, fontBrush, width - 4, height - 14, stringFormatRight); } // frame using (var p = new Pen(Color.DarkRed, 1)) g.DrawRectangle(p, 0, 0, width - 1, height - 1); } return(bmp); }
/// <summary> /// Returns a bitmap image that represents the given colors. If a species color file is available, that is used, else a pic-chart like representation. /// </summary> /// <param name="colorIds"></param> /// <param name="species"></param> /// <param name="enabledColorRegions"></param> /// <param name="size"></param> /// <param name="pieSize"></param> /// <param name="onlyColors">Only return a pie-chart like color representation.</param> /// <param name="onlyImage">Only return an image of the colored creature. If that's not possible, return null.</param> /// <param name="creatureSex">If given, it's tried for find a sex-specific image.</param> /// <returns></returns> public static Bitmap GetColoredCreature(int[] colorIds, Species species, bool[] enabledColorRegions, int size = 128, int pieSize = 64, bool onlyColors = false, bool onlyImage = false, Sex creatureSex = Sex.Unknown) { if (colorIds == null) { return(null); } string speciesName = SpeciesImageName(species?.name); // check if there are sex specific images if (creatureSex != Sex.Unknown) { string speciesNameWithSex; switch (creatureSex) { case Sex.Female: speciesNameWithSex = speciesName + "F"; if (File.Exists(Path.Combine(ImgFolder, speciesNameWithSex + Extension))) { speciesName = speciesNameWithSex; } break; case Sex.Male: speciesNameWithSex = speciesName + "M"; if (File.Exists(Path.Combine(ImgFolder, speciesNameWithSex + Extension))) { speciesName = speciesNameWithSex; } break; } } string speciesBackgroundFilePath = Path.Combine(ImgFolder, speciesName + Extension); string speciesColorMaskFilePath = Path.Combine(ImgFolder, speciesName + "_m" + Extension); string cacheFilePath = ColoredCreatureCacheFilePath(speciesName, colorIds); bool cacheFileExists = File.Exists(cacheFilePath); if (!onlyColors && !cacheFileExists) { cacheFileExists = CreateAndSaveCacheSpeciesFile(colorIds, enabledColorRegions, speciesBackgroundFilePath, speciesColorMaskFilePath, cacheFilePath); } if (onlyImage && !cacheFileExists) { return(null); } if (cacheFileExists && size == TemplateSize) { return(new Bitmap(cacheFilePath)); } Bitmap bm = new Bitmap(size, size); using (Graphics graph = Graphics.FromImage(bm)) { graph.SmoothingMode = SmoothingMode.AntiAlias; if (cacheFileExists) { graph.CompositingMode = CompositingMode.SourceCopy; graph.CompositingQuality = CompositingQuality.HighQuality; graph.InterpolationMode = InterpolationMode.HighQualityBicubic; graph.SmoothingMode = SmoothingMode.HighQuality; graph.PixelOffsetMode = PixelOffsetMode.HighQuality; graph.DrawImage(new Bitmap(cacheFilePath), 0, 0, size, size); } else { // draw piechart int pieAngle = enabledColorRegions?.Count(c => c) ?? Species.ColorRegionCount; pieAngle = 360 / (pieAngle > 0 ? pieAngle : 1); int pieNr = 0; for (int c = 0; c < Species.ColorRegionCount; c++) { if (enabledColorRegions?[c] ?? true) { if (colorIds[c] > 0) { using (var b = new SolidBrush(CreatureColors.CreatureColor(colorIds[c]))) { graph.FillPie(b, (size - pieSize) / 2, (size - pieSize) / 2, pieSize, pieSize, pieNr * pieAngle + 270, pieAngle); } } pieNr++; } } using (var pen = new Pen(Color.Gray)) graph.DrawEllipse(pen, (size - pieSize) / 2, (size - pieSize) / 2, pieSize, pieSize); } } return(bm); }
/// <summary> /// Resolves the naming-pattern functions /// </summary> /// <param name="m"></param> /// <param name="creature"></param> /// <param name="customReplacings"></param> /// <param name="displayError"></param> /// <param name="processNumberField">The number field {n} will add the lowest possible positive integer for the name to be unique. It has to be processed after all other functions.</param> /// <returns></returns> private static string ResolveFunction(Match m, Creature creature, Dictionary <string, string> customReplacings, bool displayError, bool processNumberField) { // function parameters can be non numeric if numbers are parsed try { // first parameter value string p1 = m.Groups[2].Value; if (!processNumberField && p1.Contains("{n}")) { return(m.Groups[0].Value); } // switch function name switch (m.Groups[1].Value.ToLower()) { case "if": // check if Group2 !isNullOrWhiteSpace // Group3 contains the result if true // Group4 (optional) contains the result if false return(string.IsNullOrWhiteSpace(p1) ? m.Groups[4].Value : m.Groups[3].Value); case "ifexpr": // tries to evaluate the expression // possible operators are ==, !=, <, >, =<, => var match = Regex.Match(p1, @"\A\s*(\d+(?:\.\d*)?)\s*(==|!=|<|<=|>|>=)\s*(\d+(?:\.\d*)?)\s*\Z"); if (match.Success && double.TryParse(match.Groups[1].Value, out double d1) && double.TryParse(match.Groups[3].Value, out double d2) ) { switch (match.Groups[2].Value) { case "==": return(d1 == d2 ? m.Groups[3].Value : m.Groups[4].Value); case "!=": return(d1 != d2 ? m.Groups[3].Value : m.Groups[4].Value); case "<": return(d1 < d2 ? m.Groups[3].Value : m.Groups[4].Value); case "<=": return(d1 <= d2 ? m.Groups[3].Value : m.Groups[4].Value); case ">": return(d1 > d2 ? m.Groups[3].Value : m.Groups[4].Value); case ">=": return(d1 >= d2 ? m.Groups[3].Value : m.Groups[4].Value); } } else { // compare the values as strings match = Regex.Match(p1, @"\A\s*(.*?)\s*(==|!=|<=|<|>=|>)\s*(.*?)\s*\Z"); if (match.Success) { int stringComparingResult = match.Groups[1].Value.CompareTo(match.Groups[3].Value); switch (match.Groups[2].Value) { case "==": return(stringComparingResult == 0 ? m.Groups[3].Value : m.Groups[4].Value); case "!=": return(stringComparingResult != 0 ? m.Groups[3].Value : m.Groups[4].Value); case "<": return(stringComparingResult < 0 ? m.Groups[3].Value : m.Groups[4].Value); case "<=": return(stringComparingResult <= 0 ? m.Groups[3].Value : m.Groups[4].Value); case ">": return(stringComparingResult > 0 ? m.Groups[3].Value : m.Groups[4].Value); case ">=": return(stringComparingResult >= 0 ? m.Groups[3].Value : m.Groups[4].Value); } } } return(ParametersInvalid($"The expression for ifexpr invalid: \"{p1}\"")); case "expr": // tries to calculate the result of the expression // possible operators are +, -, *, / match = Regex.Match(p1, @"\A\s*(\d+(?:\.\d*)?)\s*(\+|\-|\*|\/)\s*(\d+(?:\.\d*)?)\s*\Z"); if (match.Success && double.TryParse(match.Groups[1].Value, out d1) && double.TryParse(match.Groups[3].Value, out d2) ) { switch (match.Groups[2].Value) { case "+": return((d1 + d2).ToString()); case "-": return((d1 - d2).ToString()); case "*": return((d1 * d2).ToString()); case "/": return(d2 == 0 ? "divByZero" : (d1 / d2).ToString()); } } return(ParametersInvalid($"The expression for expr is invalid: \"{p1}\"")); case "len": // returns the length of the parameter return(p1.Length.ToString()); case "substring": // check param number: 1: substring, 2: p1, 3: pos, 4: length if (!int.TryParse(m.Groups[3].Value, out var pos)) { return(p1); } bool fromEnd = pos < 0; pos = Math.Min(Math.Abs(pos), p1.Length); if (string.IsNullOrEmpty(m.Groups[4].Value)) { if (fromEnd) { return(p1.Substring(p1.Length - pos)); } else { return(p1.Substring(pos)); } } else { int length = Math.Min(Convert.ToInt32(Convert.ToInt32(m.Groups[4].Value)), fromEnd ? pos : p1.Length - pos); if (fromEnd) { return(p1.Substring(p1.Length - pos, length)); } else { return(p1.Substring(pos, length)); } } case "format": // check param number: 1: format, 2: p1, 3: formatString // only use last param string formatString = m.Groups[3].Value; if (!string.IsNullOrEmpty(formatString)) { // convert to double double value = Convert.ToDouble(p1); // format it return(value.ToString(formatString)); } else { return(ParametersInvalid("No Format string given")); } case "padleft": // check param number: 1: padleft, 2: p1, 3: desired length, 4: padding char int padLen = Convert.ToInt32(m.Groups[3].Value); string padChar = m.Groups[4].Value; if (!string.IsNullOrEmpty(padChar)) { return(p1.PadLeft(padLen, padChar[0])); } else { ParametersInvalid($"No padding char given."); return(p1); } case "padright": // check param number: 1: padright, 2: p1, 3: desired length, 4: padding char padLen = Convert.ToInt32(m.Groups[3].Value); padChar = m.Groups[4].Value; if (!string.IsNullOrEmpty(padChar)) { return(p1.PadRight(padLen, padChar[0])); } else { return(ParametersInvalid($"No padding char given.")); } case "float_div": // returns an float after dividing the parsed number // parameter: 1: div, 2: number, 3: divided by 4: format string double dividend = double.Parse(p1); double divisor = double.Parse(m.Groups[3].Value); if (divisor > 0) { return(((dividend / divisor)).ToString(m.Groups[4].Value)); } else { return(ParametersInvalid("Division by 0")); } case "div": // returns an integer after dividing the parsed number // parameter: 1: div, 2: number, 3: divided by double number = double.Parse(p1); double div = double.Parse(m.Groups[3].Value); if (div > 0) { return(((int)(number / div)).ToString()); } else { return(ParametersInvalid("Division by 0")); } case "casing": // parameter: 1: casing, 2: text, 3: U for UPPER, L for lower, T for Title switch (m.Groups[3].Value.ToLower()) { case "u": return(p1.ToUpperInvariant()); case "l": return(p1.ToLowerInvariant()); case "t": return(System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(p1)); } return(ParametersInvalid($"casing expects 'U', 'L' or 'T', given is '{m.Groups[3].Value}'")); case "replace": // parameter: 1: replace, 2: text, 3: find, 4: replace if (string.IsNullOrEmpty(p1) || string.IsNullOrEmpty(m.Groups[3].Value)) { return(p1); } return(p1.Replace(m.Groups[3].Value.Replace(" ", " "), m.Groups[4].Value.Replace(" ", " "))); case "customreplace": // parameter: 1: customreplace, 2: key, 3: return if key not available if (customReplacings == null || string.IsNullOrEmpty(p1) || !customReplacings.ContainsKey(p1)) { return(m.Groups[3].Value); } return(customReplacings[p1]); case "time": // parameter: 1: time, 2: format return(DateTime.Now.ToString(p1)); case "color": // parameter 1: region id (0,...,5), 2: if not empty, the color name instead of the numerical id is returned, 3: if not empty, the function will return also a value even if the color region is not used on that species. if (!int.TryParse(p1, out int regionId) || regionId < 0 || regionId > 5) { return(ParametersInvalid("color region id has to be a number in the range 0 - 5")); } if (!creature.Species.EnabledColorRegions[regionId] && string.IsNullOrWhiteSpace(m.Groups[4].Value)) { return(string.Empty); // species does not use this region and user doesn't want it (param 3 is empty) } if (creature.colors == null) { return(string.Empty); // no color info } if (string.IsNullOrWhiteSpace(m.Groups[3].Value)) { return(creature.colors[regionId].ToString()); } return(CreatureColors.CreatureColorName(creature.colors[regionId])); case "indexof": // parameter: 1: source string, 2: string to find if (string.IsNullOrEmpty(p1) || string.IsNullOrEmpty(m.Groups[3].Value)) { return(string.Empty); } int index = p1.IndexOf(m.Groups[3].Value); return(index >= 0 ? index.ToString() : string.Empty); } } catch (Exception ex) { MessageBoxes.ExceptionMessageBox(ex, $"The syntax of the following pattern function\n{m.Groups[0].Value}\ncannot be processed and will be ignored.", "Naming pattern function error"); } return(string.Empty); string ParametersInvalid(string specificError) { if (displayError) { MessageBoxes.ShowMessageBox($"The syntax of the following pattern function\n{m.Groups[0].Value}\ncannot be processed and will be ignored." + (string.IsNullOrEmpty(specificError) ? string.Empty : $"\n\nSpecific error:\n{specificError}"), $"Naming pattern function error"); } return(displayError ? m.Groups[2].Value : specificError); } }
/// <summary> /// Export info for a spreadsheet. /// </summary> /// <param name="creatures"></param> public static void ExportTable(IEnumerable <Creature> creatures) { var fields = Properties.Settings.Default.CreatureTableExportFields; if (fields == null) { fields = new[] { 0, 2, 3, 4, 5, 6, 16 }; } if (!fields.Any()) { return; } var output = new StringBuilder(); // header foreach (TableExportFields f in fields) { switch (f) { case TableExportFields.WildLevels: foreach (var si in Values.statsDisplayOrder) { output.Append(Utils.StatName(si, true) + "_w\t"); } break; case TableExportFields.DomLevels: foreach (var si in Values.statsDisplayOrder) { output.Append(Utils.StatName(si, true) + "_d\t"); } break; case TableExportFields.BreedingValues: foreach (var si in Values.statsDisplayOrder) { output.Append(Utils.StatName(si, true) + "_b\t"); } break; case TableExportFields.CurrentValues: foreach (var si in Values.statsDisplayOrder) { output.Append(Utils.StatName(si, true) + "_v\t"); } break; case TableExportFields.ParentIds: output.Append("MotherId\tFatherId\t"); break; case TableExportFields.ParentNames: output.Append("MotherName\tFatherName\t"); break; case TableExportFields.ColorIds: output.Append(string.Join("\t", Enumerable.Range(0, Species.ColorRegionCount).Select(i => $"c{i}")) + "\t"); break; case TableExportFields.ColorNames: output.Append(string.Join("\t", Enumerable.Range(0, Species.ColorRegionCount).Select(i => $"c{i}_Name")) + "\t"); break; default: output.Append($"{f}\t"); break; } } output.Length--; // remove last tab output.AppendLine(); // creature rows foreach (var c in creatures) { foreach (TableExportFields f in fields) { switch (f) { case TableExportFields.Species: output.Append(c.Species.name + "\t"); break; case TableExportFields.SpeciesLongName: output.Append(c.Species.DescriptiveNameAndMod + "\t"); break; case TableExportFields.Name: output.Append(c.name + "\t"); break; case TableExportFields.Sex: output.Append(c.sex + "\t"); break; case TableExportFields.Owner: output.Append(c.owner + "\t"); break; case TableExportFields.Tribe: output.Append(c.tribe + "\t"); break; case TableExportFields.WildLevels: foreach (var si in Values.statsDisplayOrder) { output.Append($"{c.levelsWild[si]}\t"); } break; case TableExportFields.DomLevels: foreach (var si in Values.statsDisplayOrder) { output.Append($"{c.levelsDom[si]}\t"); } break; case TableExportFields.BreedingValues: foreach (var si in Values.statsDisplayOrder) { output.Append($"{c.valuesBreeding[si]}\t"); } break; case TableExportFields.CurrentValues: foreach (var si in Values.statsDisplayOrder) { output.Append($"{c.valuesDom[si]}\t"); } break; case TableExportFields.IdInGame: output.Append(c.ArkIdInGame + "\t"); break; case TableExportFields.ParentIds: output.Append((c.Mother?.ArkIdInGame ?? string.Empty) + "\t" + (c.Father?.ArkIdInGame ?? string.Empty) + "\t"); break; case TableExportFields.ParentNames: output.Append((c.Mother?.name ?? string.Empty) + "\t" + (c.Father?.name ?? string.Empty) + "\t"); break; case TableExportFields.MutationCount: output.Append(c.Mutations + "\t"); break; case TableExportFields.Fertility: output.Append((c.flags.HasFlag(CreatureFlags.Neutered) ? "neutered" : string.Empty) + "\t"); break; case TableExportFields.Notes: output.Append(c.note + "\t"); break; case TableExportFields.ColorIds: for (int ci = 0; ci < Species.ColorRegionCount; ci++) { output.Append($"{c.colors[ci]}\t"); } break; case TableExportFields.ColorNames: for (int ci = 0; ci < Species.ColorRegionCount; ci++) { output.Append($"{CreatureColors.CreatureColorName(c.colors[ci])}\t"); } break; case TableExportFields.ServerName: output.Append(c.server + "\t"); break; case TableExportFields.AddedToLibrary: output.Append((c.addedToLibrary?.ToString() ?? string.Empty) + "\t"); break; } } output.Length--; // remove last tab output.AppendLine(); } try { Clipboard.SetText(output.ToString()); } catch (Exception ex) { MessageBoxes.ExceptionMessageBox(ex); } }
/// <summary> /// Creates an image with infos about the creature. /// </summary> /// <param name="creature"></param> /// <param name="cc">CreatureCollection for server settings.</param> /// <returns></returns> public static Bitmap InfoGraphic(this Creature creature, CreatureCollection cc) { if (creature == null) { return(null); } int maxGraphLevel = cc?.maxChartLevel ?? 0; if (maxGraphLevel < 1) { maxGraphLevel = 50; } int width = Properties.Settings.Default.InfoGraphicWidth; // 330 int height = width * 6 / 11; //180 int fontSize = Math.Max(1, height / 18); // 10 int fontSizeSmall = Math.Max(1, height * 2 / 45); // 8 int fontSizeHeader = Math.Max(1, height / 15); // 12 int frameThickness = Math.Max(1, height / 180); int statLineHeight = height * 5 / 59; // 15 var fontName = Properties.Settings.Default.InfoGraphicFontName; if (string.IsNullOrWhiteSpace(fontName)) { fontName = "Arial"; Properties.Settings.Default.InfoGraphicFontName = fontName; } var bmp = new Bitmap(width, height); using (var g = Graphics.FromImage(bmp)) using (var font = new Font(fontName, fontSize)) using (var fontSmall = new Font(fontName, fontSizeSmall)) using (var fontHeader = new Font(fontName, fontSizeHeader, FontStyle.Bold)) using (var fontBrush = new SolidBrush(Properties.Settings.Default.InfoGraphicForeColor)) using (var borderAroundColors = new Pen(Utils.ForeColor(Properties.Settings.Default.InfoGraphicBackColor), 1)) using (var stringFormatRight = new StringFormat { Alignment = StringAlignment.Far }) { g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; int currentYPosition = frameThickness * 3; using (var backgroundBrush = new SolidBrush(Properties.Settings.Default.InfoGraphicBackColor)) g.FillRectangle(backgroundBrush, 0, 0, width, height); g.DrawString(creature.Species.DescriptiveNameAndMod, fontHeader, fontBrush, 3, currentYPosition); currentYPosition += height * 19 / 180; //19 string creatureInfos = $"{Loc.S("Level")} {creature.LevelHatched} | {Utils.SexSymbol(creature.sex) + (creature.flags.HasFlag(CreatureFlags.Neutered) ? $" ({Loc.S(creature.sex == Sex.Female ? "Spayed" : "Neutered")})" : string.Empty)}"; if (Properties.Settings.Default.InfoGraphicDisplayMutations) { creatureInfos += $" | {creature.Mutations} {Loc.S("Mutations")}"; } if (Properties.Settings.Default.InfoGraphicDisplayGeneration) { creatureInfos += $" | {Loc.S("generation")} {creature.generation}"; } g.DrawString(creatureInfos, font, fontBrush, width * 4 / 165, currentYPosition); currentYPosition += height * 17 / 180; //17 using (var p = new Pen(Color.FromArgb(50, Properties.Settings.Default.InfoGraphicForeColor), 1)) g.DrawLine(p, 0, currentYPosition, width, currentYPosition); currentYPosition += 2; // levels double meanLetterWidth = fontSize * 7d / 10; int xStatName = (int)meanLetterWidth; // x position of level number. torpor is the largest level number. bool showDomLevel = Properties.Settings.Default.InfoGraphicWithDomLevels; int xRightLevelValue = (int)(xStatName + ((showDomLevel ? 6 : 5) + creature.levelsWild[2].ToString().Length) * meanLetterWidth); int xRightLevelDomValue = xRightLevelValue; if (showDomLevel) { xRightLevelDomValue += (int)((creature.levelsDom.Max().ToString().Length) * meanLetterWidth); } int xRightBrValue = (int)(xRightLevelDomValue + (2 + MaxCharLength(creature.valuesBreeding)) * meanLetterWidth); int maxBoxLength = xRightBrValue - xStatName; int statBoxHeight = Math.Max(2, height / 90); g.DrawString(Loc.S("Levels"), font, fontBrush, xRightLevelDomValue, currentYPosition, stringFormatRight); g.DrawString(Loc.S("Values"), font, fontBrush, xRightBrValue, currentYPosition, stringFormatRight); int statDisplayIndex = 0; for (int si = 0; si < Values.STATS_COUNT; si++) { int statIndex = Values.statsDisplayOrder[si]; if (statIndex == (int)StatNames.Torpidity || !creature.Species.UsesStat(statIndex)) { continue; } int y = currentYPosition + (height / 9) + (statDisplayIndex++) * statLineHeight; // box // empty box to show the max possible length using (var b = new SolidBrush(Color.DarkGray)) g.FillRectangle(b, xStatName, y + statLineHeight - 1, maxBoxLength, statBoxHeight); double levelFractionOfMax = Math.Min(1, (double)creature.levelsWild[statIndex] / maxGraphLevel); if (levelFractionOfMax < 0) { levelFractionOfMax = 0; } int levelPercentageOfMax = (int)(100 * levelFractionOfMax); int statBoxLength = Math.Max((int)(maxBoxLength * levelFractionOfMax), 1); var statColor = Utils.GetColorFromPercent(levelPercentageOfMax); using (var b = new SolidBrush(statColor)) g.FillRectangle(b, xStatName, y + statLineHeight - 1, statBoxLength, statBoxHeight); using (var b = new SolidBrush(Color.FromArgb(10, statColor))) { for (int r = 4; r > 0; r--) { g.FillRectangle(b, xStatName - r, y + statLineHeight - 2 - r, statBoxLength + 2 * r, statBoxHeight + 2 * r); } } using (var p = new Pen(Utils.GetColorFromPercent(levelPercentageOfMax, -0.5), 1)) g.DrawRectangle(p, xStatName, y + statLineHeight - 1, statBoxLength, statBoxHeight); // stat name g.DrawString($"{Utils.StatName(statIndex, true, creature.Species.statNames)}", font, fontBrush, xStatName, y); // stat level number g.DrawString($"{creature.levelsWild[statIndex]}{(showDomLevel ? " +" : string.Empty)}", font, fontBrush, xRightLevelValue, y, stringFormatRight); // dom level number if (showDomLevel) { g.DrawString($"{creature.levelsDom[statIndex]}", font, fontBrush, xRightLevelDomValue, y, stringFormatRight); } // stat breeding value double displayedValue = showDomLevel ? creature.valuesDom[statIndex] : creature.valuesBreeding[statIndex]; string statValueRepresentation; if (Utils.Precision(statIndex) == 3) { statValueRepresentation = (100 * displayedValue).ToString("0.0"); g.DrawString("%", font, fontBrush, xRightBrValue, y); } else { statValueRepresentation = displayedValue.ToString("0.0"); } g.DrawString(statValueRepresentation, font, fontBrush, xRightBrValue, y, stringFormatRight); } // colors var enabledColorRegions = creature.Species.EnabledColorRegions; int xColor = (int)(xRightBrValue + meanLetterWidth * 3.5); int circleDiameter = height * 4 / 45; int colorRowHeight = circleDiameter + 2; int maxColorNameLength = (int)((width - xColor - circleDiameter) / meanLetterWidth); // max char length for the color region name if (maxColorNameLength < 0) { maxColorNameLength = 0; } bool creatureImageShown = false; bool displayMaxWild = Properties.Settings.Default.InfoGraphicShowMaxWildLevel; int extraMarginBottom = displayMaxWild ? fontSizeSmall : 0; int imageSize = (int)Math.Min(width - xColor - circleDiameter - 8 * meanLetterWidth - frameThickness * 4, height - currentYPosition - frameThickness * 4 - extraMarginBottom); if (imageSize > 5) { using (var crBmp = CreatureColored.GetColoredCreature(creature.colors, creature.Species, enabledColorRegions, imageSize, onlyImage: true, creatureSex: creature.sex)) { if (crBmp != null) { g.DrawImage(crBmp, width - imageSize - frameThickness * 4, height - imageSize - frameThickness * 4 - extraMarginBottom, imageSize, imageSize); creatureImageShown = true; } } } if (creature.colors != null) { g.DrawString(Loc.S("Colors"), font, fontBrush, xColor, currentYPosition); int colorRow = 0; for (int ci = 0; ci < Species.ColorRegionCount; ci++) { if (!enabledColorRegions[ci]) { continue; } int y = currentYPosition + (height / 9) + (colorRow++) * colorRowHeight; Color c = CreatureColors.CreatureColor(creature.colors[ci]); //Color fc = Utils.ForeColor(c); using (var b = new SolidBrush(c)) g.FillEllipse(b, xColor, y, circleDiameter, circleDiameter); g.DrawEllipse(borderAroundColors, xColor, y, circleDiameter, circleDiameter); string colorRegionName = null; //string colorName = CreatureColors.CreatureColorName(creature.colors[ci]); if (!creatureImageShown) { colorRegionName = creature.Species.colors[ci].name; int totalColorLength = colorRegionName.Length + 11; if (totalColorLength > maxColorNameLength) { // shorten color region name int lengthForRegionName = colorRegionName.Length - (totalColorLength - maxColorNameLength); colorRegionName = lengthForRegionName < 2 ? string.Empty : colorRegionName.Substring(0, lengthForRegionName - 1) + "…"; } if (!string.IsNullOrEmpty(colorRegionName)) { colorRegionName = " (" + colorRegionName + ")"; } } g.DrawString($"{creature.colors[ci]} - [{ci}]{colorRegionName}", fontSmall, fontBrush, xColor + circleDiameter + 4, y); } } // imprinting if (showDomLevel) { g.DrawString($"Imp: {creature.imprintingBonus * 100:0.0} %", font, fontBrush, xColor + (int)((Loc.S("Colors").Length + 3) * meanLetterWidth), currentYPosition); } // max wild level on server if (cc != null && displayMaxWild) { g.DrawString($"{Loc.S("max wild level")}: {cc.maxWildLevel}", fontSmall, fontBrush, width - 2 * frameThickness, height - fontSizeSmall - 4 * frameThickness, stringFormatRight); } // frame using (var p = new Pen(Properties.Settings.Default.InfoGraphicBorderColor, frameThickness)) g.DrawRectangle(p, 0, 0, width - frameThickness, height - frameThickness); } return(bmp); }
/// <summary> /// Returns a bitmap image that represents the given colors. If a species color file is available, that is used, else a pic-chart like representation. /// </summary> /// <param name="colorIds"></param> /// <param name="species"></param> /// <param name="enabledColorRegions"></param> /// <param name="size"></param> /// <param name="pieSize"></param> /// <param name="onlyColors">Only return a pie-chart like color representation.</param> /// <param name="onlyImage">Only return an image of the colored creature. If that's not possible, return null.</param> /// <returns></returns> public static Bitmap GetColoredCreature(int[] colorIds, Species species, bool[] enabledColorRegions, int size = 128, int pieSize = 64, bool onlyColors = false, bool onlyImage = false, Library.Sex creatureSex = Sex.Unknown) { if (colorIds == null) { return(null); } //float[][] hsl = new float[Species.ColorRegionCount][]; int[][] rgb = new int[Species.ColorRegionCount][]; for (int c = 0; c < Species.ColorRegionCount; c++) { Color cl = CreatureColors.CreatureColor(colorIds[c]); rgb[c] = new int[] { cl.R, cl.G, cl.B }; } string imgFolder = Path.Combine(FileService.GetPath(), imageFolderName); string cacheFolder = Path.Combine(FileService.GetPath(), imageFolderName, cacheFolderName); string speciesName = species?.name ?? string.Empty; // check if there are sex specific images if (creatureSex != Sex.Unknown) { string speciesNameWithSex = null; switch (creatureSex) { case Sex.Female: speciesNameWithSex = speciesName + "F"; if (File.Exists(Path.Combine(imgFolder, speciesNameWithSex + extension))) { speciesName = speciesNameWithSex; } break; case Sex.Male: speciesNameWithSex = speciesName + "M"; if (File.Exists(Path.Combine(imgFolder, speciesNameWithSex + extension))) { speciesName = speciesNameWithSex; } break; } } string speciesBackgroundFilePath = Path.Combine(imgFolder, speciesName + extension); string cacheFileName = Path.Combine(cacheFolder, speciesName.Substring(0, Math.Min(speciesName.Length, 5)) + "_" + (speciesName + string.Join(".", colorIds.Select(i => i.ToString()))).GetHashCode().ToString("X8") + extension); string speciesColorMaskFilePath = Path.Combine(imgFolder, speciesName + "_m" + extension); if (!onlyColors && File.Exists(speciesBackgroundFilePath) && File.Exists(speciesColorMaskFilePath) && !File.Exists(cacheFileName)) { using (Bitmap bmpBackground = new Bitmap(speciesBackgroundFilePath)) using (Bitmap bmpCreature = new Bitmap(bmpBackground.Width, bmpBackground.Height, PixelFormat.Format32bppArgb)) using (Graphics graph = Graphics.FromImage(bmpCreature)) { bool imageFine = false; graph.SmoothingMode = SmoothingMode.AntiAlias; const int defaultSizeOfTemplates = 256; using (Bitmap bmpMask = new Bitmap(defaultSizeOfTemplates, defaultSizeOfTemplates)) { using (var g = Graphics.FromImage(bmpMask)) using (var bmpMaskOriginal = new Bitmap(speciesColorMaskFilePath)) g.DrawImage(bmpMaskOriginal, 0, 0, defaultSizeOfTemplates, defaultSizeOfTemplates); float o = 0; try { // shadow using (var b = new SolidBrush(Color.FromArgb(12, 0, 0, 0))) { int scx = defaultSizeOfTemplates / 2; int scy = (int)(scx * 1.6); int factor = 25; int sr = scx - 2 * factor; double heightFactor = 0.3; for (int i = 2; i >= 0; i--) { int radius = sr + i * factor; graph.FillEllipse(b, scx - radius, scy - (int)(heightFactor * .7 * radius), 2 * radius, (int)(2 * heightFactor * radius)); } } graph.DrawImage(bmpBackground, 0, 0, defaultSizeOfTemplates, defaultSizeOfTemplates); for (int i = 0; i < bmpBackground.Width; i++) { for (int j = 0; j < bmpBackground.Height; j++) { Color bc = bmpBackground.GetPixel(i, j); if (bc.A > 0) { var cl = bmpMask.GetPixel(i, j); int r = cl.R; int g = cl.G; int b = cl.B; for (int m = 0; m < Species.ColorRegionCount; m++) { if (!enabledColorRegions[m] || colorIds[m] == 0) { continue; } switch (m) { case 0: o = Math.Max(0, r - g - b) / 255f; break; case 1: o = Math.Max(0, g - r - b) / 255f; break; case 2: o = Math.Max(0, b - r - g) / 255f; break; case 3: o = Math.Min(g, b) / 255f; break; case 4: o = Math.Min(r, g) / 255f; break; case 5: o = Math.Min(r, b) / 255f; break; } if (o == 0) { continue; } // using "grain merge", e.g. see https://docs.gimp.org/en/gimp-concepts-layer-modes.html int rMix = bc.R + rgb[m][0] - 128; if (rMix < 0) { rMix = 0; } else if (rMix > 255) { rMix = 255; } int gMix = bc.G + rgb[m][1] - 128; if (gMix < 0) { gMix = 0; } else if (gMix > 255) { gMix = 255; } int bMix = bc.B + rgb[m][2] - 128; if (bMix < 0) { bMix = 0; } else if (bMix > 255) { bMix = 255; } Color c = Color.FromArgb(rMix, gMix, bMix); bc = Color.FromArgb(bc.A, (int)(o * c.R + (1 - o) * bc.R), (int)(o * c.G + (1 - o) * bc.G), (int)(o * c.B + (1 - o) * bc.B)); } bmpCreature.SetPixel(i, j, bc); } } } imageFine = true; } catch { // error during drawing, maybe mask is smaller than image } } if (imageFine) { if (!Directory.Exists(cacheFolder)) { Directory.CreateDirectory(cacheFolder); } bmpCreature.Save(cacheFileName); // safe in cache} } } } bool cacheFileExists = File.Exists(cacheFileName); if (onlyImage && !cacheFileExists) { return(null); } Bitmap bm = new Bitmap(size, size); using (Graphics graph = Graphics.FromImage(bm)) { graph.SmoothingMode = SmoothingMode.AntiAlias; if (cacheFileExists) { graph.CompositingMode = CompositingMode.SourceCopy; graph.CompositingQuality = CompositingQuality.HighQuality; graph.InterpolationMode = InterpolationMode.HighQualityBicubic; graph.SmoothingMode = SmoothingMode.HighQuality; graph.PixelOffsetMode = PixelOffsetMode.HighQuality; graph.DrawImage(new Bitmap(cacheFileName), 0, 0, size, size); } else { // draw piechart int pieAngle = enabledColorRegions.Count(c => c); pieAngle = 360 / (pieAngle > 0 ? pieAngle : 1); int pieNr = 0; for (int c = 0; c < Species.ColorRegionCount; c++) { if (enabledColorRegions[c]) { if (colorIds[c] > 0) { using (var b = new SolidBrush(CreatureColors.CreatureColor(colorIds[c]))) { graph.FillPie(b, (size - pieSize) / 2, (size - pieSize) / 2, pieSize, pieSize, pieNr * pieAngle + 270, pieAngle); } } pieNr++; } } using (var pen = new Pen(Color.Gray)) graph.DrawEllipse(pen, (size - pieSize) / 2, (size - pieSize) / 2, pieSize, pieSize); } } return(bm); }