diff --git a/Output/Cover/Blue.xml b/Output/Cover/Blue.xml
index 9befe7d3..069bb79c 100644
--- a/Output/Cover/Blue.xml
+++ b/Output/Cover/Blue.xml
@@ -5,4 +5,42 @@
Blue
flokuep
+
+
+
+
+ %TEXT%
+
+ Outline
+
+
+ 500
+
+
+ 0110.5
+ 24
+ 100
+
+ EmptyCover.png
+ true
+ 0.8
+
+
+
+
+ %TEXT%
+
+ Outline
+
+
+ 500
+
+
+ 0100.5
+ 24
+ 100
+
+ EmptyCover.png
+
+
diff --git a/Vocaluxe/Base/CCover.cs b/Vocaluxe/Base/CCover.cs
index 61692ce7..177444d8 100644
--- a/Vocaluxe/Base/CCover.cs
+++ b/Vocaluxe/Base/CCover.cs
@@ -20,6 +20,7 @@ using System.Collections.Generic;
using System.IO;
using VocaluxeLib;
using VocaluxeLib.Draw;
+using VocaluxeLib.Songs;
namespace Vocaluxe.Base
{
@@ -32,7 +33,11 @@ namespace Vocaluxe.Base
static class CCover
{
+ private const string _NoCoverName = "No Cover";
+ private const string _NoCoverNameAlt = "NoCover";
+ private const string _DefaultTypeName = "Default";
private static readonly Dictionary _Covers = new Dictionary();
+ private static readonly Dictionary _NoCoverThemes = new Dictionary();
private static readonly List _CoverThemes = new List();
public static CTexture NoCover { get; private set; }
@@ -92,6 +97,24 @@ namespace Vocaluxe.Base
}
}
+ public static CTexture GenerateCover(string text, ESongSorting sorting, CCategory category)
+ {
+ CSong firstSong = category.GetSong(0);
+ CTexture cover = _NoCoverThemes[_SongSortingToType(sorting)].GetCover(text, firstSong != null ? Path.Combine(firstSong.Folder, firstSong.CoverFileName) : null);
+ if (cover == null)
+ cover = _NoCoverThemes[_DefaultTypeName].GetCover(text, firstSong != null ? Path.Combine(firstSong.Folder, firstSong.CoverFileName) : null);
+ if (cover == null)
+ cover = NoCover;
+ else
+ {
+ lock (_Covers)
+ {
+ _Covers.Add(text, cover);
+ }
+ }
+ return cover;
+ }
+
///
/// Returns true if a cover with the given name exists.
/// MUST HOLD _Covers lock at this point
@@ -121,6 +144,10 @@ namespace Vocaluxe.Base
}
_Covers.Clear();
}
+ lock (_NoCoverThemes)
+ {
+ _NoCoverThemes.Clear();
+ }
}
///
@@ -168,7 +195,7 @@ namespace Vocaluxe.Base
}
///
- /// Loads all cover which are defined in the cover config file.
+ /// Loads all covers from the theme
///
private static void _LoadCovers(string coverThemeName)
{
@@ -184,8 +211,62 @@ namespace Vocaluxe.Base
{
foreach (string file in files)
_AddCover(Path.GetFileNameWithoutExtension(file), file);
- if (_CoverExists("No Cover"))
- NoCover = _Covers["No Cover"];
+ if (_CoverExists(_NoCoverName))
+ NoCover = _Covers[_NoCoverName];
+ else if (_CoverExists(_NoCoverNameAlt))
+ NoCover = _Covers[_NoCoverNameAlt];
+ else
+ CBase.Log.LogError("Covertheme \"" + coverThemeName + "\" does not include a cover file named \"" + _NoCoverName + "\" and cannot be used!", true, true);
+ }
+ _LoadNoCoverThemes(coverTheme);
+ }
+
+ private static string _SongSortingToType(ESongSorting sorting)
+ {
+ switch (sorting)
+ {
+ case ESongSorting.TR_CONFIG_NONE:
+ return _DefaultTypeName;
+ case ESongSorting.TR_CONFIG_FOLDER:
+ return "Folder";
+ case ESongSorting.TR_CONFIG_ARTIST:
+ return "Artist";
+ case ESongSorting.TR_CONFIG_ARTIST_LETTER:
+ case ESongSorting.TR_CONFIG_TITLE_LETTER:
+ return "Letter";
+ case ESongSorting.TR_CONFIG_EDITION:
+ return "Edition";
+ case ESongSorting.TR_CONFIG_GENRE:
+ return "Genre";
+ case ESongSorting.TR_CONFIG_LANGUAGE:
+ return "Language";
+ case ESongSorting.TR_CONFIG_YEAR:
+ return "Year";
+ case ESongSorting.TR_CONFIG_DECADE:
+ return "Decade";
+ case ESongSorting.TR_CONFIG_DATEADDED:
+ return "Date";
+ default:
+ throw new ArgumentOutOfRangeException("sorting");
+ }
+ }
+
+ private static void _LoadNoCoverThemes(SCoverTheme coverTheme)
+ {
+ CXMLReader xmlReader = CXMLReader.OpenFile(Path.Combine(CSettings.ProgramFolder, CSettings.FolderNameCover, coverTheme.File));
+ List types = new List();
+ foreach (ESongSorting sorting in Enum.GetValues(typeof(ESongSorting)))
+ types.Add(_SongSortingToType(sorting));
+ string coverPath = Path.Combine(CSettings.ProgramFolder, CSettings.FolderNameCover, coverTheme.Folder);
+ lock (_NoCoverThemes)
+ {
+ foreach (string type in types)
+ {
+ if (_NoCoverThemes.ContainsKey(type))
+ continue;
+ CNoCoverGenerator el = new CNoCoverGenerator(xmlReader, "//root/MissingCover/" + type, coverPath);
+ _NoCoverThemes.Add(type, el);
+ }
}
}
diff --git a/Vocaluxe/Base/CNoCoverGenerator.cs b/Vocaluxe/Base/CNoCoverGenerator.cs
new file mode 100644
index 00000000..9f5d8a97
--- /dev/null
+++ b/Vocaluxe/Base/CNoCoverGenerator.cs
@@ -0,0 +1,278 @@
+#region license
+// This file is part of Vocaluxe.
+//
+// Vocaluxe is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Vocaluxe is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Vocaluxe. If not, see .
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.Drawing.Text;
+using System.IO;
+using Vocaluxe.Base.Fonts;
+using VocaluxeLib;
+using VocaluxeLib.Draw;
+using System.Linq;
+
+namespace Vocaluxe.Base
+{
+ ///
+ /// Class that generates Covers if the right Cover is missing
+ /// Themeable via the xml of the Cover theme
+ ///
+ class CNoCoverGenerator
+ {
+ private readonly bool _Valid;
+ private readonly string _Text;
+ private readonly SColorF _TextColor;
+ private readonly string _TextColorName;
+ private readonly float _TextMargin;
+ private readonly float _TextIndent;
+ private readonly int _TextSize;
+ private readonly string _Font;
+ private readonly EStyle _Style;
+ private readonly string _Image;
+ private readonly bool _ShowFirstCover;
+ private readonly float _ImageAlpha;
+ private const float _LineSpace = 5;
+
+ public CNoCoverGenerator(CXMLReader xmlReader, string xPath, string basePath)
+ {
+ _Valid = xmlReader.ItemExists(xPath);
+ if (!_Valid)
+ return;
+ _Valid &= xmlReader.GetValue(xPath + "/Text/Text", out _Text);
+ _Valid &= xmlReader.GetValue(xPath + "/Text/Font", out _Font);
+ _Valid &= xmlReader.TryGetEnumValue(xPath + "/Text/Style", ref _Style);
+ _Valid &= xmlReader.TryGetIntValue(xPath + "/Text/Size", ref _TextSize);
+ if (xmlReader.GetValue(xPath + "/Text/Color", out _TextColorName))
+ _Valid &= CBase.Theme.GetColor(_TextColorName, CBase.Theme.GetSkinIndex(-1), out _TextColor);
+ else
+ _Valid &= xmlReader.TryGetColorFromRGBA(xPath + "/Text", ref _TextColor);
+ _Valid &= xmlReader.TryGetFloatValue(xPath + "/Text/Margin", ref _TextMargin);
+ if (!xmlReader.TryGetFloatValue(xPath + "/Text/Indent", ref _TextIndent))
+ _TextIndent = 6 * _TextMargin;
+ _Valid &= xmlReader.GetValue(xPath + "/Image", out _Image);
+ string tmp;
+ if (xmlReader.GetValue(xPath + "/ShowFirstCover", out tmp))
+ Boolean.TryParse(tmp, out _ShowFirstCover);
+ else
+ _ShowFirstCover = false;
+ if (!xmlReader.TryGetFloatValue(xPath + "/ImageAlpha", ref _ImageAlpha))
+ _ImageAlpha = 0.5f;
+ else
+ _ImageAlpha = _ImageAlpha.Clamp(0f, 1f);
+ if (_Valid)
+ {
+ // ReSharper disable AssignNullToNotNullAttribute
+ _Image = Path.Combine(basePath, _Image);
+ // ReSharper restore AssignNullToNotNullAttribute
+ _Valid = File.Exists(_Image);
+ }
+ }
+
+ public CTexture GetCover(string text, string firstCoverPath)
+ {
+ if (!_Valid)
+ return null;
+ text = CLanguage.Translate(_Text.Replace("%TEXT%", text));
+ using (Bitmap bmp = new Bitmap(_Image))
+ using (Graphics g = Graphics.FromImage(bmp))
+ {
+ g.SmoothingMode = SmoothingMode.AntiAlias;
+ g.InterpolationMode = InterpolationMode.HighQualityBicubic;
+ g.TextRenderingHint = TextRenderingHint.AntiAlias;
+
+ if (_ShowFirstCover && !String.IsNullOrEmpty(firstCoverPath) && File.Exists(firstCoverPath))
+ {
+ ColorMatrix cm = new ColorMatrix {Matrix33 = 1f - _ImageAlpha};
+ ImageAttributes ia = new ImageAttributes();
+ ia.SetColorMatrix(cm);
+ using (Bitmap bmp2 = new Bitmap(firstCoverPath))
+ g.DrawImage(bmp2, new Rectangle(0, 0, bmp.Width, bmp.Height), 0, 0, bmp2.Width, bmp2.Height, GraphicsUnit.Pixel, ia);
+ }
+ if (text != "")
+ {
+ List lines = _SplitText(text);
+ CFonts.SetFont(_Font);
+ CFonts.Style = _Style;
+ float allowedWidth = bmp.Width - 2 * _TextMargin;
+ if (lines.Count > 1)
+ allowedWidth -= _TextIndent;
+ float allowedHeight = bmp.Height - 2 * _TextMargin - _LineSpace * (lines.Count - 1);
+ CFonts.Height = allowedHeight / lines.Count;
+ Font fo = CFonts.GetFont();
+ List sizes = lines.Select(line => g.MeasureString(line, fo, -1, StringFormat.GenericTypographic)).ToList();
+ float maxWidth = sizes.Select(s => s.Width).Max();
+ float maxHeight = sizes.Select(s => s.Height).Max();
+ maxHeight = (maxHeight + CFonts.Height) / 2;
+ if (maxWidth > allowedWidth)
+ {
+ CFonts.Height *= allowedWidth / maxWidth;
+ maxHeight *= allowedWidth / maxWidth;
+ }
+ if (maxHeight > _TextSize)
+ {
+ CFonts.Height *= _TextSize / maxHeight;
+ maxHeight = _TextSize;
+ }
+ fo = CFonts.GetFont();
+
+ //Have to use size in em not pixels!
+ float emSize = fo.Size * fo.FontFamily.GetCellAscent(fo.Style) / fo.FontFamily.GetEmHeight(fo.Style);
+ float outlineSize = CFonts.Outline * CFonts.Height;
+ SColorF outlineColorF = CFonts.OutlineColor;
+ outlineColorF.A = outlineColorF.A * _TextColor.A;
+
+ using (var path = new GraphicsPath())
+ using (var pen = new Pen(outlineColorF.AsColor(), outlineSize / 2))
+ {
+ pen.LineJoin = LineJoin.Round;
+ pen.Alignment = PenAlignment.Outset;
+ float top = (allowedHeight - maxHeight * lines.Count) / 2;
+ for (int i = 0; i < lines.Count; i++)
+ {
+ string line = lines[i];
+ float left;
+ if (lines.Count == 1 || (i == 1 && lines.Count == 3))
+ {
+ //Center Text if this is the only line or the middle line
+ float width = g.MeasureString(line, fo, -1, StringFormat.GenericTypographic).Width;
+ left = (bmp.Width - width) / 2;
+ }
+ else if (i == lines.Count - 1)
+ {
+ //Place last line at right
+ float width = g.MeasureString(line, fo, -1, StringFormat.GenericTypographic).Width;
+ left = bmp.Width - width - _TextMargin;
+ }
+ else
+ left = _TextMargin;
+ //g.DrawString(line, fo, new SolidBrush(_TextColor.AsColor()), left, top, StringFormat.GenericTypographic);
+ path.AddString(line, fo.FontFamily, (int)fo.Style, emSize, new PointF(left, top), StringFormat.GenericTypographic);
+ top += maxHeight + _LineSpace;
+ }
+ g.DrawPath(pen, path);
+ // ReSharper disable ImpureMethodCallOnReadonlyValueField
+ g.FillPath(new SolidBrush(_TextColor.AsColor()), path);
+ // ReSharper restore ImpureMethodCallOnReadonlyValueField
+ }
+ }
+ return CDraw.AddTexture(bmp);
+ }
+ }
+
+ private static List _SplitText(string text)
+ {
+ text = text.Replace(".", ". ").Replace("-", "- ").Replace(" ", " ");
+ string[] parts = text.Split(' ');
+ List lines = new List();
+ string line = "";
+ bool appendingName = false;
+ foreach (string part in parts)
+ {
+ if (part.Length == 1 && !Char.IsLetter(part, 0))
+ {
+ //Add non-letter chars to the end of the line (e.g. in "19 - 20")
+ line = (line != "" ? " " : "") + part;
+ appendingName = false;
+ }
+ else if (part.Length == 1 || (part.Length == 2 && part[1] == '.'))
+ {
+ //We have something like the initials (e.g. "J. R. R. Tolkien") - Keep them together
+ if (!appendingName && line != "")
+ {
+ //When we are not already appending those, add the last line and start a new one
+ lines.Add(line);
+ line = part;
+ }
+ line = (line != "" ? " " : "") + part;
+ appendingName = true;
+ }
+ else
+ {
+ if (line != "")
+ lines.Add(line);
+ line = part;
+ appendingName = false;
+ }
+ }
+ if (line != "")
+ lines.Add(line);
+
+ int i;
+ //If we have more than 3 lines try to distribute them evenly
+ if (lines.Count > 3)
+ {
+ int len = lines.Sum(el => el.Length) + lines.Count - 1;
+ int avgLen = (int)Math.Ceiling(len / 3d);
+ i = 1;
+ while (i < lines.Count)
+ {
+ string prev = lines[i - 1];
+ string cur = lines[i];
+ if (prev.Length + cur.Length <= avgLen)
+ {
+ lines[i - 1] += " " + cur;
+ lines.RemoveAt(i);
+ }
+ else
+ i++;
+ }
+ }
+
+ //Further reduce the lines by taking the longest line as the max length
+ i = 1;
+ while (i < lines.Count)
+ {
+ string prev = lines[i - 1];
+ string cur = lines[i];
+ if (prev.Length + cur.Length <= lines.Max(el => el.Length))
+ {
+ lines[i - 1] += " " + cur;
+ lines.RemoveAt(i);
+ }
+ else
+ i++;
+ }
+
+ //If we still have more than 3 lines be more agressive
+ while (lines.Count > 3)
+ {
+ //Join the shortest line with shortest neighbour
+ int minLen = lines.Min(el => el.Length);
+ for (i = 0; i < lines.Count; i++)
+ {
+ if (lines[i].Length != minLen)
+ continue;
+ int other;
+ if (i == 0)
+ other = 1;
+ else if (i == lines.Count - 1)
+ other = i - 1;
+ else if (lines[i - 1].Length < lines[i + 1].Length)
+ other = i - 1;
+ else
+ other = i + 1;
+ lines[i] += " " + lines[other];
+ lines.RemoveAt(other);
+ break;
+ }
+ }
+ return lines;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Vocaluxe/Base/CSongCategorizer.cs b/Vocaluxe/Base/CSongCategorizer.cs
index 4c16bf3a..7104c795 100644
--- a/Vocaluxe/Base/CSongCategorizer.cs
+++ b/Vocaluxe/Base/CSongCategorizer.cs
@@ -178,7 +178,11 @@ namespace Vocaluxe.Base
}
foreach (CCategory cat in _Categories)
+ {
cat.CoverTextureSmall = CCover.Cover(cat.Name);
+ if (cat.CoverTextureSmall == CCover.NoCover)
+ cat.CoverTextureSmall = CCover.GenerateCover(cat.Name, CSongs.Sorter.SongSorting, cat);
+ }
_Changed = false;
}
}
diff --git a/Vocaluxe/Program.cs b/Vocaluxe/Program.cs
index b4720e87..a886ae3d 100644
--- a/Vocaluxe/Program.cs
+++ b/Vocaluxe/Program.cs
@@ -180,13 +180,6 @@ namespace Vocaluxe
Application.DoEvents();
- // Load Cover
- CLog.StartBenchmark("Init Cover");
- CCover.Init();
- CLog.StopBenchmark("Init Cover");
-
- Application.DoEvents();
-
// Theme System
CLog.StartBenchmark("Init Theme");
if (!CTheme.Init())
@@ -195,6 +188,13 @@ namespace Vocaluxe
Application.DoEvents();
+ // Load Cover
+ CLog.StartBenchmark("Init Cover");
+ CCover.Init();
+ CLog.StopBenchmark("Init Cover");
+
+ Application.DoEvents();
+
// Init Screens
CLog.StartBenchmark("Init Screens");
CGraphics.Init();
diff --git a/Vocaluxe/Vocaluxe.csproj b/Vocaluxe/Vocaluxe.csproj
index b8276467..0c355598 100644
--- a/Vocaluxe/Vocaluxe.csproj
+++ b/Vocaluxe/Vocaluxe.csproj
@@ -216,6 +216,7 @@
+