(ded4a3e0a) v0.9.0.7

This commit is contained in:
Joonas Rikkonen
2019-06-25 16:00:44 +03:00
parent e5ae622c77
commit 4a51db77b5
1777 changed files with 421528 additions and 917 deletions
@@ -0,0 +1,49 @@
// MonoGame - Copyright (C) The MonoGame Team
// This file is subject to the terms and conditions defined in
// file 'LICENSE.txt', which is part of this source code package.
using System;
using Microsoft.Xna.Framework;
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
{
// Assorted helpers for doing useful things with bitmaps.
internal static class BitmapUtils
{
// Checks whether an area of a bitmap contains entirely the specified alpha value.
public static bool IsAlphaEntirely(byte expectedAlpha, BitmapContent bitmap, Rectangle? region = null)
{
var bitmapRegion = region.HasValue ? region.Value : new Rectangle(0, 0, bitmap.Width, bitmap.Height);
// Works with PixelBitmapContent<byte> at this stage
if (bitmap is PixelBitmapContent<byte>)
{
var bmp = bitmap as PixelBitmapContent<byte>;
for (int y = 0; y < bitmapRegion.Height; y++)
{
for (int x = 0; x < bitmapRegion.Width; x++)
{
var alpha = bmp.GetPixel(bitmapRegion.X + x, bitmapRegion.Y + y);
if (alpha != expectedAlpha)
return false;
}
}
return true;
}
else if (bitmap is PixelBitmapContent<Color>)
{
var bmp = bitmap as PixelBitmapContent<Color>;
for (int y = 0; y < bitmapRegion.Height; y++)
{
for (int x = 0; x < bitmapRegion.Width; x++)
{
var alpha = bmp.GetPixel(bitmapRegion.X + x, bitmapRegion.Y + y).A;
if (alpha != expectedAlpha)
return false;
}
}
return true;
}
throw new ArgumentException("Expected PixelBitmapContent<byte> or PixelBitmapContent<Color>, got " + bitmap.GetType().Name, "bitmap");
}
}
}
@@ -0,0 +1,90 @@
using System;
using System.ComponentModel;
using System.Collections.Generic;
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
{
// Describes a range of consecutive characters that should be included in the font.
[TypeConverter(typeof(CharacterRegionTypeConverter))]
public struct CharacterRegion
{
public char Start;
public char End;
// Enumerates all characters within the region.
public IEnumerable<Char> Characters()
{
for (var c = Start; c <= End; c++)
{
yield return c;
}
}
// Constructor.
public CharacterRegion(char start, char end)
{
if (start > end)
throw new ArgumentException();
Start = start;
End = end;
}
// Default to just the base ASCII character set.
public static CharacterRegion Default = new CharacterRegion(' ', '~');
/// <summary>
/// Test if there is an element in this enumeration.
/// </summary>
/// <typeparam name="T">Type of the element</typeparam>
/// <param name="source">The enumerable source.</param>
/// <returns><c>true</c> if there is an element in this enumeration, <c>false</c> otherwise</returns>
public static bool Any<T>(IEnumerable<T> source)
{
return source.GetEnumerator().MoveNext();
}
/// <summary>
/// Select elements from an enumeration.
/// </summary>
/// <typeparam name="TSource">The type of the T source.</typeparam>
/// <typeparam name="TResult">The type of the T result.</typeparam>
/// <param name="source">The source.</param>
/// <param name="selector">The selector.</param>
/// <returns>A enumeration of selected values</returns>
public static IEnumerable<TResult> SelectMany<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
{
foreach (TSource sourceItem in source)
{
foreach (TResult result in selector(sourceItem))
yield return result;
}
}
/// <summary>
/// Selects distinct elements from an enumeration.
/// </summary>
/// <typeparam name="TSource">The type of the T source.</typeparam>
/// <param name="source">The source.</param>
/// <param name="comparer">The comparer.</param>
/// <returns>A enumeration of selected values</returns>
public static IEnumerable<TSource> Distinct<TSource>(IEnumerable<TSource> source, IEqualityComparer<TSource> comparer = null)
{
if (comparer == null)
comparer = EqualityComparer<TSource>.Default;
// using Dictionary is not really efficient but easy to implement
var values = new Dictionary<TSource, object>(comparer);
foreach (TSource sourceItem in source)
{
if (!values.ContainsKey(sourceItem))
{
values.Add(sourceItem, null);
yield return sourceItem;
}
}
}
}
}
@@ -0,0 +1,71 @@
using System;
using System.ComponentModel;
using System.Globalization;
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
{
public class CharacterRegionTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
// Input must be a string.
string source = value as string;
if (string.IsNullOrEmpty(source))
{
throw new ArgumentException();
}
// Supported input formats:
// A
// A-Z
// 32-127
// 0x20-0x7F
var splitStr = source.Split('-');
var split = new char[splitStr.Length];
for (int i = 0; i < splitStr.Length; i++)
{
split[i] = ConvertCharacter(splitStr[i]);
}
switch (split.Length)
{
case 1:
// Only a single character (eg. "a").
return new CharacterRegion(split[0], split[0]);
case 2:
// Range of characters (eg. "a-z").
return new CharacterRegion(split[0], split[1]);
default:
throw new ArgumentException();
}
}
static char ConvertCharacter(string value)
{
if (value.Length == 1)
{
// Single character directly specifies a codepoint.
return value[0];
}
else
{
// Otherwise it must be an integer (eg. "32" or "0x20").
return (char)(int)intConverter.ConvertFromInvariantString(value);
}
}
static TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int));
}
}
@@ -0,0 +1,49 @@
// MonoGame - Copyright (C) The MonoGame Team
// This file is subject to the terms and conditions defined in
// file 'LICENSE.txt', which is part of this source code package.
using System;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework;
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
{
[StructLayout(LayoutKind.Sequential)]
public struct ABCFloat
{
public float A;
public float B;
public float C;
}
// Represents a single character within a font.
internal class Glyph
{
// Constructor.
public Glyph(char character, BitmapContent bitmap, Rectangle? subrect = null)
{
this.Character = character;
this.Bitmap = bitmap;
this.Subrect = subrect.GetValueOrDefault(new Rectangle(0, 0, bitmap.Width, bitmap.Height));
this.Width = bitmap.Width;
this.Height = bitmap.Height;
}
// Unicode codepoint.
public char Character;
// Glyph image data (may only use a portion of a larger bitmap).
public BitmapContent Bitmap;
public Rectangle Subrect;
// Layout information.
public float XOffset;
public float YOffset;
public int Width;
public int Height;
public float XAdvance;
public ABCFloat CharacterWidths;
}
}
@@ -0,0 +1,49 @@
// MonoGame - Copyright (C) The MonoGame Team
// This file is subject to the terms and conditions defined in
// file 'LICENSE.txt', which is part of this source code package.
using System;
using Microsoft.Xna.Framework;
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
{
// Crops unused space from around the edge of a glyph bitmap.
internal static class GlyphCropper
{
public static void Crop(Glyph glyph)
{
// Crop the top.
while ((glyph.Subrect.Height > 1) && BitmapUtils.IsAlphaEntirely(0, glyph.Bitmap, new Rectangle(glyph.Subrect.X, glyph.Subrect.Y, glyph.Subrect.Width, 1)))
{
glyph.Subrect.Y++;
glyph.Subrect.Height--;
glyph.YOffset++;
}
// Crop the bottom.
while ((glyph.Subrect.Height > 1) && BitmapUtils.IsAlphaEntirely(0, glyph.Bitmap, new Rectangle(glyph.Subrect.X, glyph.Subrect.Bottom - 1, glyph.Subrect.Width, 1)))
{
glyph.Subrect.Height--;
}
// Crop the left.
while ((glyph.Subrect.Width > 1) && BitmapUtils.IsAlphaEntirely(0, glyph.Bitmap, new Rectangle(glyph.Subrect.X, glyph.Subrect.Y, 1, glyph.Subrect.Height)))
{
glyph.Subrect.X++;
glyph.Subrect.Width--;
glyph.XOffset++;
}
// Crop the right.
while ((glyph.Subrect.Width > 1) && BitmapUtils.IsAlphaEntirely(0, glyph.Bitmap, new Rectangle(glyph.Subrect.Right - 1, glyph.Subrect.Y, 1, glyph.Subrect.Height)))
{
glyph.Subrect.Width--;
glyph.XAdvance++;
}
}
}
}
@@ -0,0 +1,209 @@
// MonoGame - Copyright (C) The MonoGame Team
// This file is subject to the terms and conditions defined in
// file 'LICENSE.txt', which is part of this source code package.
using System;
using System.Collections.Generic;
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
{
// Helper for arranging many small bitmaps onto a single larger surface.
internal static class GlyphPacker
{
public static BitmapContent ArrangeGlyphs(Glyph[] sourceGlyphs, bool requirePOT, bool requireSquare)
{
// Build up a list of all the glyphs needing to be arranged.
var glyphs = new List<ArrangedGlyph>();
for (int i = 0; i < sourceGlyphs.Length; i++)
{
var glyph = new ArrangedGlyph();
glyph.Source = sourceGlyphs[i];
// Leave a one pixel border around every glyph in the output bitmap.
glyph.Width = sourceGlyphs[i].Subrect.Width + 2;
glyph.Height = sourceGlyphs[i].Subrect.Height + 2;
glyphs.Add(glyph);
}
// Sort so the largest glyphs get arranged first.
glyphs.Sort(CompareGlyphSizes);
// Work out how big the output bitmap should be.
int outputWidth = GuessOutputWidth(sourceGlyphs);
int outputHeight = 0;
// Choose positions for each glyph, one at a time.
for (int i = 0; i < glyphs.Count; i++)
{
PositionGlyph(glyphs, i, outputWidth);
outputHeight = Math.Max(outputHeight, glyphs[i].Y + glyphs[i].Height);
}
// Create the merged output bitmap.
outputHeight = MakeValidTextureSize(outputHeight, requirePOT);
if (requireSquare)
{
outputHeight = Math.Max (outputWidth, outputHeight);
outputWidth = outputHeight;
}
return CopyGlyphsToOutput(glyphs, outputWidth, outputHeight);
}
// Once arranging is complete, copies each glyph to its chosen position in the single larger output bitmap.
static BitmapContent CopyGlyphsToOutput(List<ArrangedGlyph> glyphs, int width, int height)
{
var output = new PixelBitmapContent<Color>(width, height);
foreach (var glyph in glyphs)
{
var sourceGlyph = glyph.Source;
var sourceRegion = sourceGlyph.Subrect;
var destinationRegion = new Rectangle(glyph.X + 1, glyph.Y + 1, sourceRegion.Width, sourceRegion.Height);
BitmapContent.Copy(sourceGlyph.Bitmap, sourceRegion, output, destinationRegion);
sourceGlyph.Bitmap = output;
sourceGlyph.Subrect = destinationRegion;
}
return output;
}
// Internal helper class keeps track of a glyph while it is being arranged.
class ArrangedGlyph
{
public Glyph Source;
public int X;
public int Y;
public int Width;
public int Height;
}
// Works out where to position a single glyph.
static void PositionGlyph(List<ArrangedGlyph> glyphs, int index, int outputWidth)
{
int x = 0;
int y = 0;
while (true)
{
// Is this position free for us to use?
int intersects = FindIntersectingGlyph(glyphs, index, x, y);
if (intersects < 0)
{
glyphs[index].X = x;
glyphs[index].Y = y;
return;
}
// Skip past the existing glyph that we collided with.
x = glyphs[intersects].X + glyphs[intersects].Width;
// If we ran out of room to move to the right, try the next line down instead.
if (x + glyphs[index].Width > outputWidth)
{
x = 0;
y++;
}
}
}
// Checks if a proposed glyph position collides with anything that we already arranged.
static int FindIntersectingGlyph(List<ArrangedGlyph> glyphs, int index, int x, int y)
{
int w = glyphs[index].Width;
int h = glyphs[index].Height;
for (int i = 0; i < index; i++)
{
if (glyphs[i].X >= x + w)
continue;
if (glyphs[i].X + glyphs[i].Width <= x)
continue;
if (glyphs[i].Y >= y + h)
continue;
if (glyphs[i].Y + glyphs[i].Height <= y)
continue;
return i;
}
return -1;
}
// Comparison function for sorting glyphs by size.
static int CompareGlyphSizes(ArrangedGlyph a, ArrangedGlyph b)
{
const int heightWeight = 1024;
int aSize = a.Height * heightWeight + a.Width;
int bSize = b.Height * heightWeight + b.Width;
if (aSize != bSize)
return bSize.CompareTo(aSize);
else
return a.Source.Character.CompareTo(b.Source.Character);
}
// Heuristic guesses what might be a good output width for a list of glyphs.
static int GuessOutputWidth(Glyph[] sourceGlyphs)
{
int maxWidth = 0;
int totalSize = 0;
foreach (var glyph in sourceGlyphs)
{
maxWidth = Math.Max(maxWidth, glyph.Bitmap.Width);
totalSize += glyph.Bitmap.Width * glyph.Bitmap.Height;
}
int width = Math.Max((int)Math.Sqrt(totalSize), maxWidth);
return MakeValidTextureSize(width, true);
}
// Rounds a value up to the next larger valid texture size.
static int MakeValidTextureSize(int value, bool requirePowerOfTwo)
{
// In case we want to compress the texture, make sure the size is a multiple of 4.
const int blockSize = 4;
if (requirePowerOfTwo)
{
// Round up to a power of two.
int powerOfTwo = blockSize;
while (powerOfTwo < value)
powerOfTwo <<= 1;
return powerOfTwo;
}
else
{
// Round up to the specified block size.
return (value + blockSize - 1) & ~(blockSize - 1);
}
}
}
}
@@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
{
// Importer interface allows the conversion tool to support multiple source font formats.
internal interface IFontImporter
{
void Import(FontDescription options, string fontName);
IEnumerable<Glyph> Glyphs { get; }
float LineSpacing { get; }
int YOffsetMin { get; }
}
}
@@ -0,0 +1,184 @@
// MonoGame - Copyright (C) The MonoGame Team
// This file is subject to the terms and conditions defined in
// file 'LICENSE.txt', which is part of this source code package.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using SharpFont;
namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics
{
// Uses FreeType to rasterize TrueType fonts into a series of glyph bitmaps.
internal class SharpFontImporter : IFontImporter
{
// Properties hold the imported font data.
public IEnumerable<Glyph> Glyphs { get; private set; }
public float LineSpacing { get; private set; }
public int YOffsetMin { get; private set; }
// Size of the temp surface used for GDI+ rasterization.
const int MaxGlyphSize = 1024;
Library lib = null;
public void Import(FontDescription options, string fontName)
{
lib = new Library();
// Create a bunch of GDI+ objects.
var face = CreateFontFace(options, fontName);
try
{
// Which characters do we want to include?
var characters = options.Characters;
var glyphList = new List<Glyph>();
// Rasterize each character in turn.
foreach (char character in characters)
{
var glyph = ImportGlyph(character, face);
glyphList.Add(glyph);
}
Glyphs = glyphList;
// Store the font height.
LineSpacing = face.Size.Metrics.Height >> 6;
// The height used to calculate the Y offset for each character.
YOffsetMin = -face.Size.Metrics.Ascender >> 6;
}
finally
{
if (face != null)
face.Dispose();
if (lib != null)
{
lib.Dispose();
lib = null;
}
}
}
// Attempts to instantiate the requested GDI+ font object.
private Face CreateFontFace(FontDescription options, string fontName)
{
try
{
const uint dpi = 96;
var face = lib.NewFace(fontName, 0);
var fixedSize = ((int)options.Size) << 6;
face.SetCharSize(0, fixedSize, dpi, dpi);
if (face.FamilyName == "Microsoft Sans Serif" && options.FontName != "Microsoft Sans Serif")
throw new PipelineException(string.Format("Font {0} is not installed on this computer.", options.FontName));
return face;
// A font substitution must have occurred.
//throw new Exception(string.Format("Can't find font '{0}'.", options.FontName));
}
catch
{
throw;
}
}
// Rasterizes a single character glyph.
private Glyph ImportGlyph(char character, Face face)
{
uint glyphIndex = face.GetCharIndex(character);
face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
face.Glyph.RenderGlyph(RenderMode.Normal);
// Render the character.
BitmapContent glyphBitmap = null;
if (face.Glyph.Bitmap.Width > 0 && face.Glyph.Bitmap.Rows > 0)
{
glyphBitmap = new PixelBitmapContent<byte>(face.Glyph.Bitmap.Width, face.Glyph.Bitmap.Rows);
byte[] gpixelAlphas = new byte[face.Glyph.Bitmap.Width * face.Glyph.Bitmap.Rows];
//if the character bitmap has 1bpp we have to expand the buffer data to get the 8bpp pixel data
//each byte in bitmap.bufferdata contains the value of to 8 pixels in the row
//if bitmap is of width 10, each row has 2 bytes with 10 valid bits, and the last 6 bits of 2nd byte must be discarded
if(face.Glyph.Bitmap.PixelMode == PixelMode.Mono)
{
//variables needed for the expansion, amount of written data, length of the data to write
int written = 0, length = face.Glyph.Bitmap.Width * face.Glyph.Bitmap.Rows;
for(int i = 0; written < length; i++)
{
//width in pixels of each row
int width = face.Glyph.Bitmap.Width;
while(width > 0)
{
//valid data in the current byte
int stride = MathHelper.Min(8, width);
//copy the valid bytes to pixeldata
//System.Array.Copy(ExpandByte(face.Glyph.Bitmap.BufferData[i]), 0, gpixelAlphas, written, stride);
ExpandByteAndCopy(face.Glyph.Bitmap.BufferData[i], stride, gpixelAlphas, written);
written += stride;
width -= stride;
if(width > 0)
i++;
}
}
}
else
Marshal.Copy(face.Glyph.Bitmap.Buffer, gpixelAlphas, 0, gpixelAlphas.Length);
glyphBitmap.SetPixelData(gpixelAlphas);
}
if (glyphBitmap == null)
{
var gHA = face.Glyph.Metrics.HorizontalAdvance >> 6;
var gVA = face.Size.Metrics.Height >> 6;
gHA = gHA > 0 ? gHA : gVA;
gVA = gVA > 0 ? gVA : gHA;
glyphBitmap = new PixelBitmapContent<byte>(gHA, gVA);
}
// not sure about this at all
var abc = new ABCFloat ();
abc.A = face.Glyph.Metrics.HorizontalBearingX >> 6;
abc.B = face.Glyph.Metrics.Width >> 6;
abc.C = (face.Glyph.Metrics.HorizontalAdvance >> 6) - (abc.A + abc.B);
// Construct the output Glyph object.
return new Glyph(character, glyphBitmap)
{
XOffset = -(face.Glyph.Advance.X >> 6),
XAdvance = face.Glyph.Metrics.HorizontalAdvance >> 6,
YOffset = -(face.Glyph.Metrics.HorizontalBearingY >> 6),
CharacterWidths = abc
};
}
/// <summary>
/// Reads each individual bit of a byte from left to right and expands it to a full byte,
/// ones get byte.maxvalue, and zeros get byte.minvalue.
/// </summary>
/// <param name="origin">Byte to expand and copy</param>
/// <param name="length">Number of Bits of the Byte to copy, from 1 to 8</param>
/// <param name="destination">Byte array where to copy the results</param>
/// <param name="startIndex">Position where to begin copying the results in destination</param>
private static void ExpandByteAndCopy(byte origin, int length, byte[] destination, int startIndex)
{
byte tmp;
for(int i = 7; i > 7 - length; i--)
{
tmp = (byte) (1 << i);
if(origin / tmp == 1)
{
destination[startIndex + 7 - i] = byte.MaxValue;
origin -= tmp;
}
else
destination[startIndex + 7 - i] = byte.MinValue;
}
}
}
}