mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
721 lines
16 KiB
Beef
721 lines
16 KiB
Beef
using System;
|
|
using FreeType;
|
|
using GlitchyEngine.Math;
|
|
using GlitchyEngine.Core;
|
|
using System.Collections;
|
|
using msdfgen;
|
|
using System.Diagnostics;
|
|
using static FreeType.HarfBuzz;
|
|
|
|
using internal GlitchyEngine.Renderer.Text;
|
|
|
|
namespace GlitchyEngine.Renderer.Text
|
|
{
|
|
public class Font : RefCounter
|
|
{
|
|
internal class GlyphDescriptor
|
|
{
|
|
public Font Font;
|
|
|
|
public FT_UInt GlyphIndex;
|
|
|
|
// TODO: Consider using floats
|
|
public int3 MapCoord;
|
|
public int32 Width, Height;
|
|
|
|
public double TranslationX, TranslationY;
|
|
//public double Scale;
|
|
|
|
/// How many pixels we have to move the pen after drawing this glyph.
|
|
public float Advance;
|
|
|
|
// Aligns the image of the glyph with the baseline
|
|
public float AdjustToBaseLine;
|
|
// Aligns the image of the glyph with the pen
|
|
public float AdjustToPen;
|
|
|
|
public bool IsBitmap;
|
|
|
|
public bool IsCalculated = false;
|
|
public bool IsRendered = false;
|
|
}
|
|
|
|
// TODO: I don't understand why freeing the face and hb_font sometimes results in an access violation...
|
|
internal FT_Face _face;//TODO!: ~ FreeType.Done_Face(_face);
|
|
internal hb_font_t* _harfBuzzFont;//TODO:!! ~ hb_font_destroy(_);
|
|
|
|
private Font _fallback ~ _?.ReleaseRef();
|
|
|
|
internal uint32 _fontSize;
|
|
private int32 _faceIndex;
|
|
private bool _hasColor;
|
|
|
|
private int3 _penPos;
|
|
private int32 _lastRowHeight;
|
|
internal Texture2D _atlas ~ _?.ReleaseRef();
|
|
private int3 _atlasSize;
|
|
|
|
private Dictionary<char32, GlyphDescriptor> _glyphs = new .() ~ delete _;//DeleteDictionaryAndValues!(_);
|
|
private Dictionary<uint32, GlyphDescriptor> _glyphsById = new .() ~ DeleteDictionaryAndValues!(_);
|
|
GlyphDescriptor _missingGlyph;
|
|
|
|
private SamplerState _sampler ~ _.ReleaseRef();
|
|
|
|
/// How much we have to scale the geometry to fit it into our desired pixels
|
|
private double _geometryScaler;
|
|
|
|
internal double _range = 4.0;
|
|
|
|
/// The space between two lines.
|
|
internal double _linespace;
|
|
|
|
/**
|
|
* Gets or sets the fallback Font for this Font.
|
|
* The Fallback font will be used to render Glyphs that are not defined in the current font.
|
|
* @remarks Example: "Arial" doesn't define emojis. Without a fallback font the FontRenderer would draw the null-Glyph (probably just a rectangle).
|
|
* By defining "Segoe UI Emoji" as the Fallback the FontRenderer will use "Arial" to render letters and "Segoe UI Emoji" to render emojis.
|
|
*/
|
|
public Font Fallback
|
|
{
|
|
get => _fallback;
|
|
set
|
|
{
|
|
if(_fallback == value)
|
|
return;
|
|
|
|
_fallback = value..AddRef();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets or sets the Sampler that will be used to sample the spritefont.
|
|
* Setting to null will result in the use of LinearClamp
|
|
*/
|
|
public SamplerState Sampler
|
|
{
|
|
get => _sampler;
|
|
set
|
|
{
|
|
// when value and _sampler are null we don't shortcut because we set _sampler to LinearClamp
|
|
if(_sampler == value && value != null)
|
|
return;
|
|
|
|
_sampler?.ReleaseRef();
|
|
|
|
_sampler = (value ?? SamplerStateManager.LinearClamp)..AddRef();
|
|
|
|
_atlas?.SamplerState = _sampler;
|
|
}
|
|
}
|
|
|
|
[Inline]
|
|
double F26Dot6ToDouble(int32 value)
|
|
{
|
|
return (double(value) / 64.0);
|
|
}
|
|
|
|
public this(String fontPath, uint32 fontSize, bool hasColor = true, char32 firstChar = '\0', uint32 charCount = 128, int32 faceIndex = 0)
|
|
{
|
|
Debug.Profiler.ProfileResourceFunction!();
|
|
|
|
// Set default sampler
|
|
Sampler = null;
|
|
|
|
_fontSize = fontSize;
|
|
_faceIndex = faceIndex;
|
|
_hasColor = hasColor;
|
|
|
|
{
|
|
Debug.Profiler.ProfileResourceScope!("Freetype.New_Face");
|
|
|
|
FT_Error res = FreeType.New_Face(FontRenderer.s_Library, fontPath, faceIndex, &_face);
|
|
Log.EngineLogger.Assert(res.Success, scope $"New_Face failed({(int)res}): {res}");
|
|
}
|
|
|
|
{
|
|
Debug.Profiler.ProfileResourceScope!("Freetype.Set_Pixel_Sizes");
|
|
|
|
FT_Error res = FreeType.Set_Pixel_Sizes(_face, 0, _fontSize);
|
|
Log.EngineLogger.Assert(res.Success, scope $"Set_Pixel_Sizes failed({(int)res}): {res}");
|
|
}
|
|
|
|
// HarfBuzz
|
|
{
|
|
Debug.Profiler.ProfileResourceScope!("hb_ft_font_create_referenced");
|
|
|
|
_harfBuzzFont = hb_ft_font_create_referenced(_face);
|
|
hb_font_set_scale(_harfBuzzFont, (.)fontSize * 64, (.)fontSize * 64);
|
|
}
|
|
|
|
double unitsPerEm = F26Dot6ToDouble(_face.units_per_EM);
|
|
_geometryScaler = _fontSize / unitsPerEm;
|
|
|
|
_range = 4.0 / _geometryScaler;
|
|
|
|
_linespace = ((_face.size.metrics.ascender - _face.size.metrics.descender) / 64);
|
|
|
|
GlyphDescriptor nullDesc = new GlyphDescriptor(){Font = this};
|
|
nullDesc.GlyphIndex = FreeType.Get_Char_Index(_face, '\0');
|
|
_glyphs.Add('\0', nullDesc);
|
|
_glyphsById.Add(nullDesc.GlyphIndex, nullDesc);
|
|
|
|
LoadGlyphs(firstChar, charCount);
|
|
|
|
//TestMSDF();
|
|
}
|
|
|
|
public void LoadGlyphs(char32 firstChar, uint32 charCount)
|
|
{
|
|
Debug.Profiler.ProfileResourceFunction!();
|
|
|
|
ExtendRange(firstChar, firstChar + charCount);
|
|
|
|
UpdateAtlas();
|
|
}
|
|
|
|
public void LoadGlyphs(uint32 firstGlyph, uint32 charCount)
|
|
{
|
|
Debug.Profiler.ProfileResourceFunction!();
|
|
|
|
ExtendRange(firstGlyph, firstGlyph + charCount);
|
|
|
|
UpdateAtlas();
|
|
}
|
|
|
|
/// Returns the font that can draw the char
|
|
internal Font GetDrawingFont(char32 char)
|
|
{
|
|
uint32 glyphId = FreeType.Get_Char_Index(_face, char);
|
|
|
|
if (glyphId != 0)
|
|
{
|
|
return this;
|
|
}
|
|
else if (Fallback != null)
|
|
{
|
|
return Fallback.GetDrawingFont(char);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
internal GlyphDescriptor GetGlyph(uint32 glyphId, bool allowDynamicLoading = true)
|
|
{
|
|
GlyphDescriptor desc;
|
|
|
|
if(_glyphsById.TryGetValue(glyphId, out desc))
|
|
{
|
|
// if the char is not defined in the current font try to load it from the fallback
|
|
if(desc == null && _fallback != null)
|
|
{
|
|
desc = _fallback.GetGlyph(glyphId, allowDynamicLoading);
|
|
}
|
|
}
|
|
else if(allowDynamicLoading)
|
|
{
|
|
// TODO: make reloading optional
|
|
uint32 start = (glyphId >> 4) << 4;
|
|
LoadGlyphs(start, 64);
|
|
|
|
desc = GetGlyph(glyphId, false);
|
|
}
|
|
|
|
return desc ?? _missingGlyph;
|
|
}
|
|
|
|
internal GlyphDescriptor GetGlyph(char32 char, bool allowDynamicLoading = true)
|
|
{
|
|
GlyphDescriptor desc;
|
|
|
|
if(_glyphs.TryGetValue(char, out desc))
|
|
{
|
|
// if the char is not defined in the current font try to load it from the fallback
|
|
if(desc == null && _fallback != null)
|
|
{
|
|
desc = _fallback.GetGlyph(char, allowDynamicLoading);
|
|
}
|
|
}
|
|
else if(allowDynamicLoading)
|
|
{
|
|
// TODO: make reloading optional
|
|
char32 start = (char >> 4) << 4;
|
|
LoadGlyphs(start, 64);
|
|
|
|
desc = GetGlyph(char, false);
|
|
}
|
|
|
|
return desc ?? _missingGlyph;
|
|
}
|
|
|
|
void ExtendRange(char32 firstChar, char32 rangeEnd)
|
|
{
|
|
Debug.Profiler.ProfileResourceFunction!();
|
|
|
|
// TODO: refactor
|
|
|
|
for(char32 char = firstChar; char < rangeEnd; char++)
|
|
{
|
|
GlyphDescriptor desc = new GlyphDescriptor();
|
|
if(_glyphs.TryAdd(char, desc))
|
|
{
|
|
//desc.Face = _face;
|
|
desc.Font = this;
|
|
desc.GlyphIndex = FreeType.Get_Char_Index(_face, char);
|
|
|
|
_glyphsById.TryAdd(desc.GlyphIndex, desc);
|
|
|
|
// if the char is not contained by the font we set the desc to null
|
|
if(desc.GlyphIndex == 0)
|
|
{
|
|
delete desc;
|
|
_glyphs[char] = null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
delete desc;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ExtendRange(uint32 firstGlyph, uint32 rangeEnd)
|
|
{
|
|
Debug.Profiler.ProfileResourceFunction!();
|
|
|
|
// TODO: refactor
|
|
|
|
for(uint32 glyphId = firstGlyph; glyphId < rangeEnd; glyphId++)
|
|
{
|
|
GlyphDescriptor desc = new GlyphDescriptor();
|
|
if(_glyphsById.TryAdd(glyphId, desc))
|
|
{
|
|
//desc.Face = _face;
|
|
desc.Font = this;
|
|
desc.GlyphIndex = glyphId;//FreeType.Get_Char_Index(_face, char);
|
|
|
|
// if the char is not contained by the font we set the desc to null
|
|
if(desc.GlyphIndex == 0)
|
|
{
|
|
delete desc;
|
|
_glyphsById[glyphId] = null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
delete desc;
|
|
}
|
|
}
|
|
}
|
|
|
|
void UpdateAtlas()
|
|
{
|
|
DrawAtlas();
|
|
}
|
|
|
|
/// Debug only
|
|
private void RedrawAtlas()
|
|
{
|
|
for(let (char, desc) in _glyphs)
|
|
{
|
|
desc?.IsCalculated = false;
|
|
desc?.IsRendered = false;
|
|
}
|
|
|
|
_penPos = .Zero;
|
|
_lastRowHeight = 0;
|
|
|
|
int8[] data = new int8[_atlas.Width * _atlas.Height * 4];
|
|
|
|
_atlas.SetData<Color>((Color*)data.Ptr);
|
|
|
|
UpdateAtlas();
|
|
}
|
|
|
|
int3 PrepareAtlas()
|
|
{
|
|
Debug.Profiler.ProfileResourceFunction!();
|
|
|
|
const uint32 maxRes = 16384; // D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION
|
|
const uint32 maxArray = 2048; // D3D11_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION
|
|
|
|
ref int3 pen = ref _penPos;
|
|
ref int32 rowHeight = ref _lastRowHeight;
|
|
|
|
int32 atlasWidth = _atlasSize.X;
|
|
int32 atlasHeight = _atlasSize.Y;
|
|
int32 atlasArray = _atlasSize.Z;
|
|
|
|
for(var (char, desc) in _glyphsById)
|
|
{
|
|
if(desc == null || desc.IsCalculated)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(!Calculate(ref desc))
|
|
continue;
|
|
|
|
const int32 border = 1;
|
|
|
|
if(desc.Width != 0 && desc.Height != 0)
|
|
{
|
|
int32 glyphRight = pen.X + desc.Width + border;
|
|
|
|
if(glyphRight > maxRes)
|
|
{
|
|
pen.X = 0;
|
|
pen.Y += rowHeight + border;
|
|
rowHeight = 0;
|
|
}
|
|
|
|
int32 glyphBottom = pen.Y + desc.Height + border;
|
|
|
|
if(glyphBottom > maxRes)
|
|
{
|
|
pen.X = 0;
|
|
pen.Y = 0;
|
|
rowHeight = 0;
|
|
|
|
pen.Z++;
|
|
|
|
if(pen.Z >= maxArray)
|
|
{
|
|
Log.EngineLogger.Error("Sprite font exceeded maximum texture size.");
|
|
}
|
|
}
|
|
}
|
|
|
|
desc.MapCoord = pen;
|
|
|
|
if(desc.GlyphIndex == 0 && _missingGlyph == null)
|
|
{
|
|
_missingGlyph = desc;
|
|
}
|
|
|
|
pen.X += desc.Width + border;
|
|
|
|
if (pen.X > atlasWidth)
|
|
atlasWidth = pen.X;
|
|
|
|
if (desc.Height > rowHeight)
|
|
rowHeight = desc.Height;
|
|
|
|
if (pen.Y + desc.Height > atlasHeight)
|
|
atlasHeight = pen.Y + desc.Height;
|
|
|
|
if (pen.Z == atlasArray)
|
|
atlasArray++;
|
|
|
|
desc.IsCalculated = true;
|
|
}
|
|
|
|
return .(atlasWidth, atlasHeight, atlasArray);
|
|
}
|
|
|
|
public bool secondTime = false;
|
|
|
|
void DrawAtlas()
|
|
{
|
|
Debug.Profiler.ProfileResourceFunction!();
|
|
|
|
int3 oldAtlasSize = _atlasSize;
|
|
_atlasSize = PrepareAtlas();
|
|
|
|
if(any(_atlasSize != oldAtlasSize))
|
|
{
|
|
Debug.Profiler.ProfileResourceScope!("Recreate Atlas");
|
|
|
|
var oldAtlas = _atlas;
|
|
|
|
Texture2DDesc desc;
|
|
desc.Format = .R8G8B8A8_SNorm;
|
|
desc.MipLevels = 1;
|
|
desc.Width = (.)_atlasSize.X;
|
|
desc.Height = (.)_atlasSize.Y;
|
|
desc.ArraySize = (.)_atlasSize.Z;
|
|
desc.CpuAccess = .None;
|
|
desc.Usage = .Default;
|
|
|
|
_atlas = new Texture2D(desc);
|
|
_atlas.SamplerState = _sampler;
|
|
|
|
if(oldAtlas != null)
|
|
{
|
|
oldAtlas.CopyTo(_atlas);
|
|
oldAtlas.ReleaseRef();
|
|
}
|
|
}
|
|
|
|
for(let (char, desc) in _glyphsById)
|
|
{
|
|
if(desc == null)
|
|
continue;
|
|
|
|
var glyphIndex = desc.GlyphIndex;
|
|
|
|
if(desc.IsRendered || glyphIndex == 0 || desc.Width == 0 || desc.Height == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
GenerateMSDF(desc);
|
|
|
|
desc.IsRendered = true;
|
|
}
|
|
}
|
|
|
|
bool Calculate(ref GlyphDescriptor desc)
|
|
{
|
|
Debug.Profiler.ProfileResourceFunction!();
|
|
|
|
// prepare shape
|
|
|
|
double advance = 0;
|
|
|
|
Shape shape;
|
|
|
|
{
|
|
Debug.Profiler.ProfileResourceScope!("msdfgen.LoadGlyph");
|
|
|
|
if(!msdfgen.LoadGlyph(out shape, ref _face, desc.GlyphIndex, out advance) || !shape.Validate())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
desc.Advance = (float)(advance * _geometryScaler);
|
|
|
|
//shape.OrientContours();
|
|
{
|
|
Debug.Profiler.ProfileResourceScope!("msdfgen.ResolveShapeGeometry");
|
|
|
|
msdfgen.ResolveShapeGeometry(shape);
|
|
}
|
|
|
|
shape.Normalize();
|
|
|
|
var bounds = shape.GetBounds();
|
|
|
|
// prepare projection
|
|
|
|
double width;
|
|
double height;
|
|
|
|
double translationX;
|
|
double translationY;
|
|
|
|
if(bounds.Left < bounds.Right && bounds.Bottom < bounds.Top)
|
|
{
|
|
double l = bounds.Left;
|
|
double r = bounds.Right;
|
|
double b = bounds.Bottom;
|
|
double t = bounds.Top;
|
|
|
|
l -= 0.5 * _range;
|
|
b -= 0.5 * _range;
|
|
r += 0.5 * _range;
|
|
t += 0.5 * _range;
|
|
|
|
// TODO: miter?
|
|
//if (miterLimit > 0)
|
|
// shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1);
|
|
|
|
double w = _geometryScaler * (r - l);
|
|
double h = _geometryScaler * (t - b);
|
|
|
|
width = Math.Ceiling(w) + 1;
|
|
height = Math.Ceiling(h) + 1;
|
|
|
|
translationX = -l + 0.5 * (width - w) / _geometryScaler;
|
|
translationY = -b + 0.5 * (height - h) / _geometryScaler;
|
|
}
|
|
else
|
|
{
|
|
width = 0;
|
|
height = 0;
|
|
translationX = 0;
|
|
translationY = 0;
|
|
}
|
|
|
|
desc.Width = (.)width;
|
|
desc.Height = (.)height;
|
|
|
|
desc.TranslationX = translationX;
|
|
desc.TranslationY = translationY;
|
|
|
|
//desc.Scale = scale;
|
|
|
|
desc.AdjustToBaseLine = (float)(-translationY * _geometryScaler);
|
|
|
|
desc.AdjustToPen = (float)(-translationX);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
void TestMSDF()
|
|
{
|
|
GlyphDescriptor desc = scope .();
|
|
desc.GlyphIndex = FreeType.Get_Char_Index(_face, 'A');
|
|
|
|
var v = _range;
|
|
|
|
_range = 4.0f;
|
|
_geometryScaler = 1.0f;
|
|
|
|
Calculate(ref desc);
|
|
|
|
// prepare shape
|
|
|
|
double advance = 0;
|
|
|
|
Shape shape;
|
|
msdfgen.LoadGlyph(out shape, ref _face, 36, out advance);
|
|
|
|
bool b = msdfgen.ResolveShapeGeometry(shape);
|
|
|
|
if(!b)
|
|
{
|
|
|
|
}
|
|
|
|
shape.Normalize();
|
|
|
|
var bounds = shape.GetBounds();
|
|
|
|
//shape.ReverseIfNeeded(bounds);
|
|
|
|
msdfgen.EdgeColoringSimple(shape, 3.0);
|
|
|
|
// prepare projection
|
|
msdfgen.Projection projection = .();
|
|
projection.ScaleX = _geometryScaler;
|
|
projection.ScaleY = _geometryScaler;
|
|
|
|
projection.TranslationX = desc.TranslationX;
|
|
projection.TranslationY = desc.TranslationY;
|
|
|
|
int bufferX = desc.Width;
|
|
int bufferY = desc.Height;
|
|
|
|
using(Bitmap<ColorRGB, const 1> bitmap = .((.)bufferX, (.)bufferY))
|
|
{
|
|
MSDFGeneratorConfig config = .();
|
|
|
|
msdfgen.GenerateMSDF(*(Bitmap<float, const 3>*)&bitmap, shape, projection, _range, config);
|
|
|
|
// Check if we have to invert
|
|
/*
|
|
Line 1039
|
|
// Get sign of signed distance outside bounds
|
|
Point2 p(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1);
|
|
double distance = SimpleTrueShapeDistanceFinder::oneShotDistance(shape, p);
|
|
orientation = distance <= 0 ? KEEP : REVERSE;
|
|
|
|
if (invert)
|
|
{
|
|
invertColor<3>(bitmap);
|
|
}
|
|
*/
|
|
|
|
uint8[] pixels = new:ScopedAlloc! uint8[desc.Width * desc.Height * 4];
|
|
|
|
//int8 ToInt8(float f) => (.)Math.Clamp(127f * f, int8.MinValue, int8.MaxValue);
|
|
uint8 ToInt8(float f) => (.)Math.Clamp(255 * f, uint8.MinValue, uint8.MaxValue);
|
|
|
|
for(int y = 0; y < desc.Height; y++)
|
|
for(int x = 0; x < desc.Width; x++)
|
|
{
|
|
ColorRGB pixel = bitmap.Pixels[(y) * bufferX + x];
|
|
|
|
int index = ((desc.Height - y - 1) * desc.Width + x) * 4;
|
|
|
|
pixels[index + 0] = ToInt8(pixel.Red);
|
|
pixels[index + 1] = ToInt8(pixel.Green);
|
|
pixels[index + 2] = ToInt8(pixel.Blue);
|
|
|
|
pixels[index + 3] = uint8.MaxValue;
|
|
}
|
|
|
|
LodePng.LodePng.Encode32File("testA.png", pixels.CArray(), (.)desc.Width, (.)desc.Height);
|
|
|
|
//_atlas.SetData<Color>((Color*)pixels.Ptr, (.)desc.MapCoord.X, (.)desc.MapCoord.Y,
|
|
// (.)desc.Width, (.)desc.Height, (.)desc.MapCoord.Z);
|
|
}
|
|
|
|
_range = v;
|
|
}
|
|
*/
|
|
|
|
void GenerateMSDF(GlyphDescriptor desc)
|
|
{
|
|
Debug.Profiler.ProfileResourceFunction!();
|
|
|
|
// prepare shape
|
|
|
|
double advance = 0;
|
|
|
|
Shape shape;
|
|
|
|
{
|
|
Debug.Profiler.ProfileResourceScope!("msdfgen.LoadGlyph");
|
|
|
|
msdfgen.LoadGlyph(out shape, ref _face, desc.GlyphIndex, out advance);
|
|
}
|
|
|
|
msdfgen.ResolveShapeGeometry(shape);
|
|
|
|
shape.Normalize();
|
|
|
|
//var bounds = shape.GetBounds();
|
|
|
|
//shape.ReverseIfNeeded(bounds);
|
|
|
|
msdfgen.EdgeColoringSimple(shape, 3.0);
|
|
|
|
// prepare projection
|
|
msdfgen.Projection projection = .();
|
|
projection.ScaleX = _geometryScaler;
|
|
projection.ScaleY = _geometryScaler;
|
|
|
|
projection.TranslationX = desc.TranslationX;
|
|
projection.TranslationY = desc.TranslationY;
|
|
|
|
int bufferX = desc.Width;
|
|
int bufferY = desc.Height;
|
|
|
|
using(Bitmap<ColorRGB, const 1> bitmap = .((.)bufferX, (.)bufferY))
|
|
{
|
|
Debug.Profiler.ProfileResourceScope!("GenerateMSDF");
|
|
|
|
MSDFGeneratorConfig config = .();
|
|
|
|
msdfgen.GenerateMSDF(*(Bitmap<float, const 3>*)&bitmap, shape, projection, _range, config);
|
|
|
|
int8[] pixels = new:ScopedAlloc! int8[desc.Width * desc.Height * 4];
|
|
|
|
int8 ToInt8(float f) => (.)Math.Clamp(127f * f, int8.MinValue, int8.MaxValue);
|
|
|
|
for(int y = 0; y < desc.Height; y++)
|
|
for(int x = 0; x < desc.Width; x++)
|
|
{
|
|
ColorRGB pixel = bitmap.Pixels[(y) * bufferX + x];
|
|
|
|
int index = ((desc.Height - y - 1) * desc.Width + x) * 4;
|
|
|
|
pixels[index + 0] = ToInt8(pixel.R);
|
|
pixels[index + 1] = ToInt8(pixel.G);
|
|
pixels[index + 2] = ToInt8(pixel.B);
|
|
|
|
pixels[index + 3] = Int8.MaxValue;
|
|
}
|
|
|
|
_atlas.SetData<Color>((Color*)pixels.Ptr, (.)desc.MapCoord.X, (.)desc.MapCoord.Y,
|
|
(.)desc.Width, (.)desc.Height, (.)desc.MapCoord.Z);
|
|
}
|
|
}
|
|
}
|
|
}
|