-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
000ffaf
commit c2eef18
Showing
16 changed files
with
573 additions
and
0 deletions.
There are no files selected for viewing
13 changes: 13 additions & 0 deletions
13
src/lib/Kaligraphy.Contract/DataClasses/Generation/FontImageData.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
using Kaligraphy.Contract.DataClasses.Generation.Packing; | ||
using SixLabors.ImageSharp; | ||
using SixLabors.ImageSharp.PixelFormats; | ||
|
||
namespace Kaligraphy.Contract.DataClasses.Generation | ||
{ | ||
public class FontImageData | ||
{ | ||
public required Image<Rgba32> Image { get; init; } | ||
|
||
public required IList<PackedGylphData> Glyphs { get; init; } | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
src/lib/Kaligraphy.Contract/DataClasses/Generation/Packing/PackedElement.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using SixLabors.ImageSharp; | ||
|
||
namespace Kaligraphy.Contract.DataClasses.Generation.Packing | ||
{ | ||
public class PackedElement<TElement> | ||
{ | ||
public required TElement Element { get; init; } | ||
public required Point Position { get; init; } | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
src/lib/Kaligraphy.Contract/DataClasses/Generation/Packing/PackedGylphData.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
namespace Kaligraphy.Contract.DataClasses.Generation.Packing | ||
{ | ||
public class PackedGylphData : PackedElement<GlyphData> | ||
{ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using SixLabors.ImageSharp.PixelFormats; | ||
using SixLabors.ImageSharp; | ||
|
||
namespace Kaligraphy.Contract.DataClasses | ||
{ | ||
public class GlyphData | ||
{ | ||
/// <summary> | ||
/// The glyph. | ||
/// </summary> | ||
public required Image<Rgba32> Glyph { get; init; } | ||
|
||
/// <summary> | ||
/// Gets a description of the glyph, including position and size of the glyph after additional adjustments. | ||
/// </summary> | ||
public required GlyphDescriptionData Description { get; init; } | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
src/lib/Kaligraphy.Contract/DataClasses/GlyphDescriptionData.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
using SixLabors.ImageSharp; | ||
|
||
namespace Kaligraphy.Contract.DataClasses | ||
{ | ||
public class GlyphDescriptionData | ||
{ | ||
/// <summary> | ||
/// The position into the glyph, where the non-white space starts. | ||
/// </summary> | ||
public required Point Position { get; init; } | ||
|
||
/// <summary> | ||
/// The size of the non-white space glyph. | ||
/// </summary> | ||
public required Size Size { get; init; } | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
src/lib/Kaligraphy.Contract/Generation/Packing/IBinPacker.T2.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using Kaligraphy.Contract.DataClasses.Generation.Packing; | ||
|
||
namespace Kaligraphy.Contract.Generation.Packing | ||
{ | ||
public interface IBinPacker<in TElement, out TPacked> | ||
where TPacked : PackedElement<TElement> | ||
{ | ||
IEnumerable<TPacked> Pack(IEnumerable<TElement> elements); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 17 | ||
VisualStudioVersion = 17.12.35514.174 d17.12 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kaligraphy", "Kaligraphy\Kaligraphy.csproj", "{74A8B153-8D2A-49C9-9EB6-48629B68B6A6}" | ||
EndProject | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kaligraphy.Contract", "Kaligraphy.Contract\Kaligraphy.Contract.csproj", "{B4099C9C-4132-41B3-A908-68395AD1807A}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{74A8B153-8D2A-49C9-9EB6-48629B68B6A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{74A8B153-8D2A-49C9-9EB6-48629B68B6A6}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{74A8B153-8D2A-49C9-9EB6-48629B68B6A6}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{74A8B153-8D2A-49C9-9EB6-48629B68B6A6}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{B4099C9C-4132-41B3-A908-68395AD1807A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{B4099C9C-4132-41B3-A908-68395AD1807A}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{B4099C9C-4132-41B3-A908-68395AD1807A}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{B4099C9C-4132-41B3-A908-68395AD1807A}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
EndGlobal |
35 changes: 35 additions & 0 deletions
35
src/lib/Kaligraphy/DataClasses/Generation/Packing/BinPackerNode.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
using SixLabors.ImageSharp; | ||
|
||
namespace Kaligraphy.DataClasses.Generation.Packing | ||
{ | ||
/// <summary> | ||
/// A node representing part of the canvas in packing. | ||
/// </summary> | ||
internal class BinPackerNode | ||
{ | ||
/// <summary> | ||
/// The position this node is set on the canvas. | ||
/// </summary> | ||
public required Point Position { get; init; } | ||
|
||
/// <summary> | ||
/// The size this node represents on the canvas. | ||
/// </summary> | ||
public required Size Size { get; init; } | ||
|
||
/// <summary> | ||
/// Is this node is already occupied by a box. | ||
/// </summary> | ||
public bool IsOccupied { get; set; } | ||
|
||
/// <summary> | ||
/// The right headed node. | ||
/// </summary> | ||
public BinPackerNode? RightNode { get; set; } | ||
|
||
/// <summary> | ||
/// The bottom headed node. | ||
/// </summary> | ||
public BinPackerNode? BottomNode { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
using Kaligraphy.Contract.DataClasses; | ||
using Kaligraphy.Contract.DataClasses.Generation; | ||
using Kaligraphy.Contract.DataClasses.Generation.Packing; | ||
using Kaligraphy.Generation.Packing; | ||
using SixLabors.ImageSharp; | ||
using SixLabors.ImageSharp.Drawing; | ||
using SixLabors.ImageSharp.Drawing.Processing; | ||
using SixLabors.ImageSharp.PixelFormats; | ||
using SixLabors.ImageSharp.Processing; | ||
|
||
namespace Kaligraphy.Generation | ||
{ | ||
/// <summary> | ||
/// Generates textures out of a given list of glyphs. | ||
/// </summary> | ||
public class FontTextureGenerator | ||
{ | ||
private readonly Size _canvasSize; | ||
private readonly FontBinPacker _fontPacker; | ||
|
||
/// <summary> | ||
/// Creates a new instance of <see cref="FontTextureGenerator"/>. | ||
/// </summary> | ||
/// <param name="canvasSize">The size of the canvas to draw on.</param> | ||
/// <param name="margin">The margin to the top and left side of each texture.</param> | ||
public FontTextureGenerator(Size canvasSize, int margin) | ||
{ | ||
_canvasSize = canvasSize; | ||
_fontPacker = new FontBinPacker(canvasSize, margin); | ||
} | ||
|
||
/// <summary> | ||
/// Generate font textures for the given glyphs. | ||
/// </summary> | ||
/// <param name="glyphs">The enumeration of adjusted glyphs.</param> | ||
/// <param name="textureCount">The maximum texture count.</param> | ||
/// <returns>The generated textures.</returns> | ||
public IList<FontImageData> Generate(IList<GlyphData> glyphs, int textureCount = -1) | ||
{ | ||
var fontTextures = new List<FontImageData>(textureCount >= 0 ? textureCount : 0); | ||
|
||
IList<GlyphData> remainingGlyphs = glyphs; | ||
while (remainingGlyphs.Count > 0) | ||
{ | ||
// Stop if the texture limit is reached | ||
if (textureCount > 0 && fontTextures.Count >= textureCount) | ||
break; | ||
|
||
// Create new font texture to draw on. | ||
var fontCanvas = new Image<Rgba32>(_canvasSize.Width, _canvasSize.Height); | ||
|
||
// Draw each positioned glyph on the font texture | ||
var packedGlyphs = new List<PackedGylphData>(remainingGlyphs.Count); | ||
foreach (PackedGylphData packedGlyph in _fontPacker.Pack(remainingGlyphs)) | ||
{ | ||
DrawGlyph(fontCanvas, packedGlyph); | ||
packedGlyphs.Add(packedGlyph); | ||
} | ||
|
||
var fontImage = new FontImageData | ||
{ | ||
Image = fontCanvas, | ||
Glyphs = packedGlyphs | ||
}; | ||
fontTextures.Add(fontImage); | ||
|
||
// Remove every handled glyph | ||
remainingGlyphs = remainingGlyphs.Except(packedGlyphs.Select(g => g.Element)).ToList(); | ||
} | ||
|
||
return fontTextures; | ||
} | ||
|
||
/// <summary> | ||
/// Draws a glyph onto the font texture. | ||
/// </summary> | ||
/// <param name="fontImage">The font texture to draw on.</param> | ||
/// <param name="packedGlyph">The adjusted glyph positioned in relation to the texture.</param> | ||
private void DrawGlyph(Image<Rgba32> fontImage, PackedGylphData packedGlyph) | ||
{ | ||
GlyphData glyph = packedGlyph.Element; | ||
|
||
var destRect = new Rectangle(packedGlyph.Position, glyph.Description.Size); | ||
var sourceRect = new Rectangle(glyph.Description.Position, glyph.Description.Size); | ||
|
||
fontImage.Mutate(i => i.Clip(new RectangularPolygon(destRect), context => context.DrawImage(glyph.Glyph, sourceRect, 1f))); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
using Kaligraphy.Contract.DataClasses.Generation.Packing; | ||
using Kaligraphy.Contract.Generation.Packing; | ||
using Kaligraphy.DataClasses.Generation.Packing; | ||
using SixLabors.ImageSharp; | ||
|
||
namespace Kaligraphy.Generation.Packing | ||
{ | ||
public abstract class BinPacker<TElement, TPacked> : IBinPacker<TElement, TPacked> | ||
where TPacked : PackedElement<TElement> | ||
{ | ||
/// <summary> | ||
/// Gets the total size of the canvas. | ||
/// </summary> | ||
protected Size CanvasSize { get; } | ||
|
||
/// <summary> | ||
/// The margin between all elements. | ||
/// </summary> | ||
protected Size Margin { get; } | ||
|
||
/// <summary> | ||
/// Creates a new instance of <see cref="BinPacker{TElement,TPacked}"/>"/>. | ||
/// </summary> | ||
/// <param name="canvasSize">The total size of the canvas.</param> | ||
/// <param name="margin">The margin between all elements.</param> | ||
protected BinPacker(Size canvasSize, Size margin) | ||
{ | ||
CanvasSize = canvasSize; | ||
Margin = new Size(margin); | ||
} | ||
|
||
/// <summary> | ||
/// Pack an enumeration of white space adjusted glyphs into the given canvas. | ||
/// </summary> | ||
/// <param name="elements">The enumeration of glyphs.</param> | ||
/// <returns>Position information to a glyph.</returns> | ||
public IEnumerable<TPacked> Pack(IEnumerable<TElement> elements) | ||
{ | ||
var rootNode = new BinPackerNode | ||
{ | ||
Position = Point.Empty, | ||
Size = CanvasSize - Margin | ||
}; | ||
|
||
foreach (TElement element in elements.OrderByDescending(CalculateVolume)) | ||
{ | ||
Size elementSize = CalculateSize(element); | ||
|
||
BinPackerNode? foundNode = FindNode(rootNode, elementSize); | ||
if (foundNode == null) | ||
continue; | ||
|
||
SplitNode(foundNode, elementSize); | ||
yield return CreatePackedElement(element, foundNode.Position); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Calculates the volume of an element. | ||
/// </summary> | ||
/// <param name="element">The element to calculate the volume from.</param> | ||
/// <returns>The calculated volume.</returns> | ||
protected abstract int CalculateVolume(TElement element); | ||
|
||
/// <summary> | ||
/// Calculates the size of the element. | ||
/// </summary> | ||
/// <param name="element">The element to calculate the size from.</param> | ||
/// <returns>The calculated size.</returns> | ||
protected abstract Size CalculateSize(TElement element); | ||
|
||
/// <summary> | ||
/// Creates the packed element. | ||
/// </summary> | ||
/// <param name="element">The element to pack.</param> | ||
/// <param name="position">The position of the element.</param> | ||
/// <returns>The packed element.</returns> | ||
protected abstract TPacked CreatePackedElement(TElement element, Point position); | ||
|
||
/// <summary> | ||
/// Find a node to fit the box in. | ||
/// </summary> | ||
/// <param name="node">The current node to search through.</param> | ||
/// <param name="boxSize">The size of the box.</param> | ||
/// <returns>The found node.</returns> | ||
private BinPackerNode? FindNode(BinPackerNode node, Size boxSize) | ||
{ | ||
if (node.IsOccupied) | ||
{ | ||
BinPackerNode? nextNode = null; | ||
if (node.BottomNode is not null) | ||
{ | ||
nextNode = FindNode(node.BottomNode, boxSize); | ||
if (nextNode is null && node.RightNode is not null) | ||
nextNode = FindNode(node.RightNode, boxSize); | ||
} | ||
else | ||
{ | ||
if (node.RightNode is not null) | ||
nextNode = FindNode(node.RightNode, boxSize); | ||
} | ||
|
||
return nextNode; | ||
} | ||
|
||
if (boxSize.Width <= node.Size.Width && boxSize.Height <= node.Size.Height) | ||
return node; | ||
|
||
return null; | ||
} | ||
|
||
/// <summary> | ||
/// Splits a node to fit the box. | ||
/// </summary> | ||
/// <param name="node">The node to split.</param> | ||
/// <param name="boxSize">The size of the box.</param> | ||
private void SplitNode(BinPackerNode node, Size boxSize) | ||
{ | ||
node.IsOccupied = true; | ||
|
||
node.BottomNode = new BinPackerNode | ||
{ | ||
Position = new Point(node.Position.X + boxSize.Width, node.Position.Y), | ||
Size = new Size(node.Size.Width - boxSize.Width, node.Size.Height), | ||
}; | ||
node.RightNode = new BinPackerNode | ||
{ | ||
Position = new Point(node.Position.X, node.Position.Y + boxSize.Height), | ||
Size = new Size(boxSize.Width, node.Size.Height - boxSize.Height) | ||
}; | ||
} | ||
} | ||
} |
Oops, something went wrong.