initial support for MSDF font
This commit is contained in:
@@ -21,6 +21,10 @@ namespace FlaxEditor.Windows.Assets
|
||||
/// </summary>
|
||||
private sealed class PropertiesProxy
|
||||
{
|
||||
[DefaultValue(FontRasterMode.Bitmap)]
|
||||
[EditorOrder(5), EditorDisplay("Properties"), Tooltip("The rasterization mode used when generating font atlases.")]
|
||||
public FontRasterMode RasterMode;
|
||||
|
||||
[DefaultValue(FontHinting.Default)]
|
||||
[EditorOrder(10), EditorDisplay("Properties"), Tooltip("The font hinting used when rendering characters.")]
|
||||
public FontHinting Hinting;
|
||||
@@ -41,7 +45,8 @@ namespace FlaxEditor.Windows.Assets
|
||||
{
|
||||
options = new FontOptions
|
||||
{
|
||||
Hinting = Hinting
|
||||
Hinting = Hinting,
|
||||
RasterMode = RasterMode
|
||||
};
|
||||
if (AntiAliasing)
|
||||
options.Flags |= FontFlags.AntiAliasing;
|
||||
@@ -57,6 +62,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
AntiAliasing = (options.Flags & FontFlags.AntiAliasing) == FontFlags.AntiAliasing;
|
||||
Bold = (options.Flags & FontFlags.Bold) == FontFlags.Bold;
|
||||
Italic = (options.Flags & FontFlags.Italic) == FontFlags.Italic;
|
||||
RasterMode = options.RasterMode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "BinaryAssetUpgrader.h"
|
||||
#include "Engine/Render2D/FontAsset.h"
|
||||
|
||||
/// <summary>
|
||||
/// Font Asset Upgrader
|
||||
@@ -17,10 +18,33 @@ public:
|
||||
{
|
||||
const Upgrader upgraders[] =
|
||||
{
|
||||
{},
|
||||
{ 3, 4, &Upgrade_3_To_4 },
|
||||
};
|
||||
setup(upgraders, ARRAY_COUNT(upgraders));
|
||||
}
|
||||
|
||||
private:
|
||||
struct FontOptionsOld
|
||||
{
|
||||
FontHinting Hinting;
|
||||
FontFlags Flags;
|
||||
};
|
||||
|
||||
static bool Upgrade_3_To_4(AssetMigrationContext& context)
|
||||
{
|
||||
ASSERT(context.Input.SerializedVersion == 3 && context.Output.SerializedVersion == 4);
|
||||
|
||||
FontOptionsOld optionsOld;
|
||||
Platform::MemoryCopy(&optionsOld, context.Input.CustomData.Get(), sizeof(FontOptionsOld));
|
||||
|
||||
FontOptions options;
|
||||
options.Hinting = optionsOld.Hinting;
|
||||
options.Flags = optionsOld.Flags;
|
||||
options.RasterMode = FontRasterMode::Bitmap;
|
||||
context.Output.CustomData.Copy(&options);
|
||||
|
||||
return CopyChunk(context, 0);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -12,12 +12,13 @@
|
||||
CreateAssetResult ImportFont::Import(CreateAssetContext& context)
|
||||
{
|
||||
// Base
|
||||
IMPORT_SETUP(FontAsset, 3);
|
||||
IMPORT_SETUP(FontAsset, 4);
|
||||
|
||||
// Setup header
|
||||
FontOptions options;
|
||||
options.Hinting = FontHinting::Default;
|
||||
options.Flags = FontFlags::AntiAliasing;
|
||||
options.RasterMode = FontRasterMode::Bitmap;
|
||||
context.Data.CustomData.Copy(&options);
|
||||
|
||||
// Open the file
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
partial struct FontOptions
|
||||
@@ -11,7 +13,7 @@ namespace FlaxEngine
|
||||
/// <returns><c>true</c> if this object has the same value as <paramref name="other" />; otherwise, <c>false</c> </returns>
|
||||
public bool Equals(FontOptions other)
|
||||
{
|
||||
return Hinting == other.Hinting && Flags == other.Flags;
|
||||
return Hinting == other.Hinting && Flags == other.Flags && RasterMode == other.RasterMode;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -23,10 +25,7 @@ namespace FlaxEngine
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return ((int)Hinting * 397) ^ (int)Flags;
|
||||
}
|
||||
return HashCode.Combine((int)Hinting, (int)Flags, (int)RasterMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -37,7 +36,7 @@ namespace FlaxEngine
|
||||
/// <returns><c>true</c> if <paramref name="left" /> has the same value as <paramref name="right" />; otherwise, <c>false</c>.</returns>
|
||||
public static bool operator ==(FontOptions left, FontOptions right)
|
||||
{
|
||||
return left.Hinting == right.Hinting && left.Flags == right.Flags;
|
||||
return left.Hinting == right.Hinting && left.Flags == right.Flags && left.RasterMode == right.RasterMode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -48,7 +47,7 @@ namespace FlaxEngine
|
||||
/// <returns><c>true</c> if <paramref name="left" /> has a different value than <paramref name="right" />; otherwise,<c>false</c>.</returns>
|
||||
public static bool operator !=(FontOptions left, FontOptions right)
|
||||
{
|
||||
return left.Hinting != right.Hinting || left.Flags != right.Flags;
|
||||
return left.Hinting != right.Hinting || left.Flags != right.Flags || left.RasterMode != right.RasterMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,22 @@ API_ENUM(Attributes="Flags") enum class FontFlags : byte
|
||||
Italic = 4,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The font rasterization mode.
|
||||
/// </summary>
|
||||
API_ENUM() enum class FontRasterMode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Use the default FreeType rasterizer to render font atlases.
|
||||
/// </summary>
|
||||
Bitmap,
|
||||
|
||||
/// <summary>
|
||||
/// Use the MSDF generator to render font atlases. Need to be rendered with a compatible material.
|
||||
/// </summary>
|
||||
MSDF,
|
||||
};
|
||||
|
||||
DECLARE_ENUM_OPERATORS(FontFlags);
|
||||
|
||||
/// <summary>
|
||||
@@ -84,6 +100,11 @@ API_STRUCT() struct FontOptions
|
||||
/// The flags.
|
||||
/// </summary>
|
||||
API_FIELD() FontFlags Flags;
|
||||
|
||||
/// <summary>
|
||||
/// The rasterization mode.
|
||||
/// </summary>
|
||||
API_FIELD() FontRasterMode RasterMode;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -91,7 +112,7 @@ API_STRUCT() struct FontOptions
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API FontAsset : public BinaryAsset
|
||||
{
|
||||
DECLARE_BINARY_ASSET_HEADER(FontAsset, 3);
|
||||
DECLARE_BINARY_ASSET_HEADER(FontAsset, 4);
|
||||
friend Font;
|
||||
|
||||
private:
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <ThirdParty/freetype/ftsynth.h>
|
||||
#include <ThirdParty/freetype/ftbitmap.h>
|
||||
#include <ThirdParty/freetype/internal/ftdrv.h>
|
||||
#include "Engine/Render2D/MSDFGenerator.h"
|
||||
|
||||
namespace FontManagerImpl
|
||||
{
|
||||
@@ -125,7 +126,11 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry)
|
||||
// Set load flags
|
||||
uint32 glyphFlags = FT_LOAD_NO_BITMAP;
|
||||
const bool useAA = EnumHasAnyFlags(options.Flags, FontFlags::AntiAliasing);
|
||||
if (useAA)
|
||||
if (options.RasterMode == FontRasterMode::MSDF)
|
||||
{
|
||||
glyphFlags |= FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING;
|
||||
}
|
||||
else if (useAA)
|
||||
{
|
||||
switch (options.Hinting)
|
||||
{
|
||||
@@ -185,75 +190,102 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry)
|
||||
FT_GlyphSlot_Oblique(face->glyph);
|
||||
}
|
||||
|
||||
// Render glyph to the bitmap
|
||||
FT_GlyphSlot glyph = face->glyph;
|
||||
FT_Render_Glyph(glyph, useAA ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO);
|
||||
|
||||
FT_Bitmap* bitmap = &glyph->bitmap;
|
||||
FT_Bitmap tmpBitmap;
|
||||
if (bitmap->pixel_mode != FT_PIXEL_MODE_GRAY)
|
||||
int32 glyphWidth = 0;
|
||||
int32 glyphHeight = 0;
|
||||
if (options.RasterMode == FontRasterMode::Bitmap)
|
||||
{
|
||||
// Convert the bitmap to 8bpp grayscale
|
||||
FT_Bitmap_New(&tmpBitmap);
|
||||
FT_Bitmap_Convert(Library, bitmap, &tmpBitmap, 4);
|
||||
bitmap = &tmpBitmap;
|
||||
}
|
||||
ASSERT(bitmap && bitmap->pixel_mode == FT_PIXEL_MODE_GRAY);
|
||||
// Render glyph to the bitmap
|
||||
FT_GlyphSlot glyph = face->glyph;
|
||||
FT_Render_Glyph(glyph, useAA ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO);
|
||||
|
||||
// Fill the character data
|
||||
entry.AdvanceX = Convert26Dot6ToRoundedPixel<int16>(glyph->advance.x);
|
||||
entry.OffsetY = glyph->bitmap_top;
|
||||
entry.OffsetX = glyph->bitmap_left;
|
||||
entry.IsValid = true;
|
||||
entry.BearingY = Convert26Dot6ToRoundedPixel<int16>(glyph->metrics.horiBearingY);
|
||||
entry.Height = Convert26Dot6ToRoundedPixel<int16>(glyph->metrics.height);
|
||||
FT_Bitmap* bitmap = &glyph->bitmap;
|
||||
FT_Bitmap tmpBitmap;
|
||||
if (bitmap->pixel_mode != FT_PIXEL_MODE_GRAY) {
|
||||
// Convert the bitmap to 8bpp grayscale
|
||||
FT_Bitmap_New(&tmpBitmap);
|
||||
FT_Bitmap_Convert(Library, bitmap, &tmpBitmap, 4);
|
||||
bitmap = &tmpBitmap;
|
||||
}
|
||||
ASSERT(bitmap && bitmap->pixel_mode == FT_PIXEL_MODE_GRAY);
|
||||
|
||||
// Allocate memory
|
||||
const int32 glyphWidth = bitmap->width;
|
||||
const int32 glyphHeight = bitmap->rows;
|
||||
GlyphImageData.Clear();
|
||||
GlyphImageData.Resize(glyphWidth * glyphHeight);
|
||||
// Fill the character data
|
||||
entry.AdvanceX = Convert26Dot6ToRoundedPixel<int16>(glyph->advance.x);
|
||||
entry.OffsetY = glyph->bitmap_top;
|
||||
entry.OffsetX = glyph->bitmap_left;
|
||||
entry.IsValid = true;
|
||||
entry.BearingY = Convert26Dot6ToRoundedPixel<int16>(glyph->metrics.horiBearingY);
|
||||
entry.Height = Convert26Dot6ToRoundedPixel<int16>(glyph->metrics.height);
|
||||
|
||||
// End for empty glyphs
|
||||
if (GlyphImageData.IsEmpty())
|
||||
{
|
||||
entry.TextureIndex = MAX_uint8;
|
||||
if (bitmap == &tmpBitmap)
|
||||
// Allocate memory
|
||||
glyphWidth = bitmap->width;
|
||||
glyphHeight = bitmap->rows;
|
||||
GlyphImageData.Clear();
|
||||
GlyphImageData.Resize(glyphWidth * glyphHeight);
|
||||
|
||||
// End for empty glyphs
|
||||
if (GlyphImageData.IsEmpty())
|
||||
{
|
||||
entry.TextureIndex = MAX_uint8;
|
||||
if (bitmap == &tmpBitmap)
|
||||
{
|
||||
FT_Bitmap_Done(Library, bitmap);
|
||||
bitmap = nullptr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy glyph data after rasterization (row by row)
|
||||
for (int32 row = 0; row < glyphHeight; row++)
|
||||
{
|
||||
Platform::MemoryCopy(&GlyphImageData[row * glyphWidth], &bitmap->buffer[row * bitmap->pitch], glyphWidth);
|
||||
}
|
||||
|
||||
// Normalize gray scale images not using 256 colors
|
||||
if (bitmap->num_grays != 256) {
|
||||
const int32 scale = 255 / (bitmap->num_grays - 1);
|
||||
for (byte& pixel : GlyphImageData) {
|
||||
pixel *= scale;
|
||||
}
|
||||
}
|
||||
|
||||
// Free temporary bitmap if used
|
||||
if (bitmap == &tmpBitmap) {
|
||||
FT_Bitmap_Done(Library, bitmap);
|
||||
bitmap = nullptr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy glyph data after rasterization (row by row)
|
||||
for (int32 row = 0; row < glyphHeight; row++)
|
||||
else
|
||||
{
|
||||
Platform::MemoryCopy(&GlyphImageData[row * glyphWidth], &bitmap->buffer[row * bitmap->pitch], glyphWidth);
|
||||
}
|
||||
// Generate bitmap for MSDF
|
||||
FT_GlyphSlot glyph = face->glyph;
|
||||
int16 msdf_top = 0;
|
||||
int16 msdf_left = 0;
|
||||
MSDFGenerator::GenerateMSDF(glyph, GlyphImageData, glyphWidth, glyphHeight, msdf_top, msdf_left);
|
||||
|
||||
// Normalize gray scale images not using 256 colors
|
||||
if (bitmap->num_grays != 256)
|
||||
{
|
||||
const int32 scale = 255 / (bitmap->num_grays - 1);
|
||||
for (byte& pixel : GlyphImageData)
|
||||
{
|
||||
pixel *= scale;
|
||||
// End for empty glyphs
|
||||
if (GlyphImageData.IsEmpty()) {
|
||||
entry.TextureIndex = MAX_uint8;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Free temporary bitmap if used
|
||||
if (bitmap == &tmpBitmap)
|
||||
{
|
||||
FT_Bitmap_Done(Library, bitmap);
|
||||
bitmap = nullptr;
|
||||
// Fill the character data
|
||||
entry.AdvanceX = Convert26Dot6ToRoundedPixel<int16>(glyph->advance.x);
|
||||
entry.OffsetY = msdf_top;
|
||||
entry.OffsetX = msdf_left;
|
||||
entry.IsValid = true;
|
||||
entry.BearingY = Convert26Dot6ToRoundedPixel<int16>(glyph->metrics.horiBearingY);
|
||||
entry.Height = Convert26Dot6ToRoundedPixel<int16>(glyph->metrics.height);
|
||||
}
|
||||
|
||||
// Find atlas for the character texture
|
||||
PixelFormat requiredFormat = options.RasterMode == FontRasterMode::MSDF ? PixelFormat::R8G8B8A8_UNorm : PixelFormat::R8_UNorm;
|
||||
int32 atlasIndex = 0;
|
||||
const FontTextureAtlasSlot* slot = nullptr;
|
||||
for (; atlasIndex < Atlases.Count() && slot == nullptr; atlasIndex++)
|
||||
for (; atlasIndex < Atlases.Count() && slot == nullptr; atlasIndex++) {
|
||||
if (Atlases[atlasIndex]->GetPixelFormat() != requiredFormat)
|
||||
continue;
|
||||
slot = Atlases[atlasIndex]->AddEntry(glyphWidth, glyphHeight, GlyphImageData);
|
||||
}
|
||||
atlasIndex--;
|
||||
|
||||
// Check if there is no atlas for this character
|
||||
@@ -261,7 +293,7 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry)
|
||||
{
|
||||
// Create new atlas
|
||||
auto atlas = Content::CreateVirtualAsset<FontTextureAtlas>();
|
||||
atlas->Setup(PixelFormat::R8_UNorm, FontTextureAtlas::PaddingStyle::PadWithZero);
|
||||
atlas->Setup(requiredFormat, FontTextureAtlas::PaddingStyle::PadWithZero);
|
||||
Atlases.Add(atlas);
|
||||
atlasIndex++;
|
||||
|
||||
|
||||
@@ -105,6 +105,14 @@ public:
|
||||
return _height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the atlas pixel format.
|
||||
/// </summary>
|
||||
FORCE_INLINE PixelFormat GetPixelFormat() const
|
||||
{
|
||||
return _format;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the atlas size.
|
||||
/// </summary>
|
||||
@@ -186,8 +194,8 @@ public:
|
||||
/// <summary>
|
||||
/// Returns glyph's bitmap data of the slot.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot in atlas.</param>
|
||||
/// <param name="width">The width of the slot.</param>
|
||||
/// <param name="slot">The slot in atlas.</param>
|
||||
/// <param name="width">The width of the slot.</param>
|
||||
/// <param name="height">The height of the slot.</param>
|
||||
/// <param name="stride">The stride of the slot.</param>
|
||||
/// <returns>The pointer to the bitmap data of the given slot.</returns>
|
||||
|
||||
129
Source/Engine/Render2D/MSDFGenerator.h
Normal file
129
Source/Engine/Render2D/MSDFGenerator.h
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#include <ThirdParty/freetype/ftoutln.h>
|
||||
#include <ThirdParty/msdfgen/msdfgen.h>
|
||||
|
||||
class MSDFGenerator
|
||||
{
|
||||
using Point2 = msdfgen::Point2;
|
||||
using Shape = msdfgen::Shape;
|
||||
using Contour = msdfgen::Contour;
|
||||
using EdgeHolder = msdfgen::EdgeHolder;
|
||||
|
||||
static Point2 ftPoint2(const FT_Vector& vector, double scale)
|
||||
{
|
||||
return Point2(scale * vector.x, scale * vector.y);
|
||||
}
|
||||
|
||||
struct FtContext
|
||||
{
|
||||
double scale;
|
||||
Point2 position;
|
||||
Shape* shape;
|
||||
Contour* contour;
|
||||
};
|
||||
|
||||
static int ftMoveTo(const FT_Vector* to, void* user)
|
||||
{
|
||||
FtContext* context = reinterpret_cast<FtContext*>(user);
|
||||
if (!(context->contour && context->contour->edges.empty()))
|
||||
context->contour = &context->shape->addContour();
|
||||
context->position = ftPoint2(*to, context->scale);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ftLineTo(const FT_Vector* to, void* user)
|
||||
{
|
||||
FtContext* context = reinterpret_cast<FtContext*>(user);
|
||||
Point2 endpoint = ftPoint2(*to, context->scale);
|
||||
if (endpoint != context->position)
|
||||
{
|
||||
context->contour->addEdge(EdgeHolder(context->position, endpoint));
|
||||
context->position = endpoint;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ftConicTo(const FT_Vector* control, const FT_Vector* to, void* user)
|
||||
{
|
||||
FtContext* context = reinterpret_cast<FtContext*>(user);
|
||||
Point2 endpoint = ftPoint2(*to, context->scale);
|
||||
if (endpoint != context->position)
|
||||
{
|
||||
context->contour->addEdge(EdgeHolder(context->position, ftPoint2(*control, context->scale), endpoint));
|
||||
context->position = endpoint;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ftCubicTo(const FT_Vector* control1, const FT_Vector* control2, const FT_Vector* to, void* user)
|
||||
{
|
||||
FtContext* context = reinterpret_cast<FtContext*>(user);
|
||||
Point2 endpoint = ftPoint2(*to, context->scale);
|
||||
if (endpoint != context->position || msdfgen::crossProduct(ftPoint2(*control1, context->scale) - endpoint, ftPoint2(*control2, context->scale) - endpoint))
|
||||
{
|
||||
context->contour->addEdge(EdgeHolder(context->position, ftPoint2(*control1, context->scale), ftPoint2(*control2, context->scale), endpoint));
|
||||
context->position = endpoint;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public:
|
||||
static void GenerateMSDF(FT_GlyphSlot glyph, Array<byte>& output, int32& outputWidth, int32& outputHeight, int16& top, int16& left)
|
||||
{
|
||||
Shape shape;
|
||||
shape.contours.clear();
|
||||
shape.inverseYAxis = true;
|
||||
|
||||
FtContext context = { };
|
||||
context.scale = 1.0 / 64.0;
|
||||
context.shape = &shape;
|
||||
FT_Outline_Funcs ftFunctions;
|
||||
ftFunctions.move_to = &ftMoveTo;
|
||||
ftFunctions.line_to = &ftLineTo;
|
||||
ftFunctions.conic_to = &ftConicTo;
|
||||
ftFunctions.cubic_to = &ftCubicTo;
|
||||
ftFunctions.shift = 0;
|
||||
ftFunctions.delta = 0;
|
||||
FT_Outline_Decompose(&glyph->outline, &ftFunctions, &context);
|
||||
|
||||
shape.normalize();
|
||||
edgeColoringSimple(shape, 3.0);
|
||||
|
||||
// Todo: make this configurable
|
||||
// Also hard-coded in material: MSDFFontMaterial
|
||||
const double pxRange = 4.0;
|
||||
|
||||
msdfgen::Shape::Bounds bounds = shape.getBounds();
|
||||
int32 width = static_cast<int32>(Math::CeilToInt(bounds.r - bounds.l + pxRange));
|
||||
int32 height = static_cast<int32>(Math::CeilToInt(bounds.t - bounds.b + pxRange));
|
||||
msdfgen::Bitmap<float, 4> msdf(width, height);
|
||||
|
||||
msdfgen::SDFTransformation t(
|
||||
msdfgen::Projection(1.0, msdfgen::Vector2(-bounds.l + pxRange / 2.0, -bounds.b + pxRange / 2.0)), msdfgen::Range(pxRange)
|
||||
);
|
||||
generateMTSDF(msdf, shape, t);
|
||||
|
||||
output.Resize(width * height * 4);
|
||||
|
||||
const msdfgen::BitmapConstRef<float, 4>& bitmap = msdf;
|
||||
for (int y = 0; y < height; ++y)
|
||||
{
|
||||
for (int x = 0; x < width; ++x)
|
||||
{
|
||||
output[(y * width + x) * 4] = msdfgen::pixelFloatToByte(bitmap(x, y)[0]);
|
||||
output[(y * width + x) * 4 + 1] = msdfgen::pixelFloatToByte(bitmap(x, y)[1]);
|
||||
output[(y * width + x) * 4 + 2] = msdfgen::pixelFloatToByte(bitmap(x, y)[2]);
|
||||
output[(y * width + x) * 4 + 3] = msdfgen::pixelFloatToByte(bitmap(x, y)[3]);
|
||||
}
|
||||
}
|
||||
outputWidth = width;
|
||||
outputHeight = height;
|
||||
top = static_cast<int16>(Math::CeilToInt(bounds.t + pxRange / 2.0));
|
||||
left = static_cast<int16>(Math::FloorToInt(bounds.l + pxRange / 2.0));
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user