230 lines
7.2 KiB
C#
230 lines
7.2 KiB
C#
|
|
using UnityEngine;
|
||
|
|
|
||
|
|
using PlasticGui;
|
||
|
|
|
||
|
|
namespace Unity.PlasticSCM.Editor.UI.Avatar
|
||
|
|
{
|
||
|
|
internal static class AvatarGenerator
|
||
|
|
{
|
||
|
|
internal static Texture2D GenerateFromUserName(string userName)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrEmpty(userName))
|
||
|
|
return null;
|
||
|
|
|
||
|
|
Font font = GetDefaultFont();
|
||
|
|
|
||
|
|
if (font == null)
|
||
|
|
return null;
|
||
|
|
|
||
|
|
Color bgColor = AvatarColor.FromUserName(userName);
|
||
|
|
string initials = GetAvatarInitial.ForUserName(userName);
|
||
|
|
|
||
|
|
return Generate(bgColor, initials, font);
|
||
|
|
}
|
||
|
|
|
||
|
|
static Texture2D Generate(Color bgColor, string initials, Font font)
|
||
|
|
{
|
||
|
|
int fontSize = Mathf.RoundToInt(RENDER_SIZE * 0.48f);
|
||
|
|
font.RequestCharactersInTexture(initials, fontSize, FontStyle.Normal);
|
||
|
|
|
||
|
|
Texture2D textTexture = RenderTextToTexture(font, initials, fontSize);
|
||
|
|
Texture2D highRes = CompositeCircleAndText(bgColor, textTexture);
|
||
|
|
|
||
|
|
UnityEngine.Object.DestroyImmediate(textTexture, true);
|
||
|
|
|
||
|
|
Texture2D result = Downsample(highRes);
|
||
|
|
|
||
|
|
UnityEngine.Object.DestroyImmediate(highRes, true);
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
static Texture2D RenderTextToTexture(
|
||
|
|
Font font, string initials, int fontSize)
|
||
|
|
{
|
||
|
|
RenderTexture rt = RenderTexture.GetTemporary(
|
||
|
|
RENDER_SIZE, RENDER_SIZE, 0, RenderTextureFormat.ARGB32);
|
||
|
|
RenderTexture prev = RenderTexture.active;
|
||
|
|
RenderTexture.active = rt;
|
||
|
|
|
||
|
|
GL.Clear(true, true, Color.clear);
|
||
|
|
GL.PushMatrix();
|
||
|
|
GL.LoadPixelMatrix(0, RENDER_SIZE, RENDER_SIZE, 0);
|
||
|
|
|
||
|
|
DrawInitials(font, initials, fontSize);
|
||
|
|
|
||
|
|
GL.PopMatrix();
|
||
|
|
|
||
|
|
Texture2D result = new Texture2D(
|
||
|
|
RENDER_SIZE, RENDER_SIZE, TextureFormat.RGBA32, false);
|
||
|
|
result.ReadPixels(new Rect(0, 0, RENDER_SIZE, RENDER_SIZE), 0, 0);
|
||
|
|
result.Apply();
|
||
|
|
|
||
|
|
RenderTexture.active = prev;
|
||
|
|
RenderTexture.ReleaseTemporary(rt);
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
static Texture2D CompositeCircleAndText(
|
||
|
|
Color bgColor, Texture2D textTexture)
|
||
|
|
{
|
||
|
|
Color[] textPixels = textTexture.GetPixels();
|
||
|
|
Color[] pixels = new Color[RENDER_SIZE * RENDER_SIZE];
|
||
|
|
|
||
|
|
float center = RENDER_SIZE / 2f;
|
||
|
|
float radius = RENDER_SIZE / 2f;
|
||
|
|
|
||
|
|
for (int y = 0; y < RENDER_SIZE; y++)
|
||
|
|
{
|
||
|
|
for (int x = 0; x < RENDER_SIZE; x++)
|
||
|
|
{
|
||
|
|
int idx = y * RENDER_SIZE + x;
|
||
|
|
|
||
|
|
float dx = x + 0.5f - center;
|
||
|
|
float dy = y + 0.5f - center;
|
||
|
|
float dist = Mathf.Sqrt(dx * dx + dy * dy);
|
||
|
|
|
||
|
|
float circleAlpha = Mathf.Clamp01(radius - dist + 0.5f);
|
||
|
|
|
||
|
|
float textCoverage = textPixels[idx].r;
|
||
|
|
|
||
|
|
pixels[idx] = new Color(
|
||
|
|
Mathf.Lerp(bgColor.r, 1f, textCoverage),
|
||
|
|
Mathf.Lerp(bgColor.g, 1f, textCoverage),
|
||
|
|
Mathf.Lerp(bgColor.b, 1f, textCoverage),
|
||
|
|
circleAlpha);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Texture2D result = new Texture2D(
|
||
|
|
RENDER_SIZE, RENDER_SIZE, TextureFormat.RGBA32, false);
|
||
|
|
result.SetPixels(pixels);
|
||
|
|
result.Apply();
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
static Texture2D Downsample(Texture2D source)
|
||
|
|
{
|
||
|
|
int blockSize = RENDER_SIZE / OUTPUT_SIZE;
|
||
|
|
float invBlockArea = 1f / (blockSize * blockSize);
|
||
|
|
|
||
|
|
Color[] sourcePixels = source.GetPixels();
|
||
|
|
Color[] targetPixels = new Color[OUTPUT_SIZE * OUTPUT_SIZE];
|
||
|
|
|
||
|
|
for (int ty = 0; ty < OUTPUT_SIZE; ty++)
|
||
|
|
{
|
||
|
|
for (int tx = 0; tx < OUTPUT_SIZE; tx++)
|
||
|
|
{
|
||
|
|
float r = 0, g = 0, b = 0, a = 0;
|
||
|
|
|
||
|
|
for (int by = 0; by < blockSize; by++)
|
||
|
|
{
|
||
|
|
int rowOffset = (ty * blockSize + by) * RENDER_SIZE;
|
||
|
|
|
||
|
|
for (int bx = 0; bx < blockSize; bx++)
|
||
|
|
{
|
||
|
|
Color c = sourcePixels[rowOffset + tx * blockSize + bx];
|
||
|
|
r += c.r;
|
||
|
|
g += c.g;
|
||
|
|
b += c.b;
|
||
|
|
a += c.a;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
targetPixels[ty * OUTPUT_SIZE + tx] = new Color(
|
||
|
|
r * invBlockArea,
|
||
|
|
g * invBlockArea,
|
||
|
|
b * invBlockArea,
|
||
|
|
a * invBlockArea);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Texture2D result = new Texture2D(
|
||
|
|
OUTPUT_SIZE, OUTPUT_SIZE, TextureFormat.RGBA32, false);
|
||
|
|
result.filterMode = FilterMode.Bilinear;
|
||
|
|
result.SetPixels(targetPixels);
|
||
|
|
result.Apply();
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void DrawInitials(Font font, string initials, int fontSize)
|
||
|
|
{
|
||
|
|
if (font.material == null)
|
||
|
|
return;
|
||
|
|
|
||
|
|
font.material.SetPass(0);
|
||
|
|
|
||
|
|
CharacterInfo[] charInfos = new CharacterInfo[initials.Length];
|
||
|
|
float totalAdvance = 0;
|
||
|
|
float maxAscent = 0;
|
||
|
|
float maxDescent = 0;
|
||
|
|
|
||
|
|
for (int i = 0; i < initials.Length; i++)
|
||
|
|
{
|
||
|
|
if (!font.GetCharacterInfo(
|
||
|
|
initials[i], out charInfos[i], fontSize, FontStyle.Normal))
|
||
|
|
return;
|
||
|
|
|
||
|
|
totalAdvance += charInfos[i].advance;
|
||
|
|
maxAscent = Mathf.Max(maxAscent, charInfos[i].maxY);
|
||
|
|
maxDescent = Mathf.Min(maxDescent, charInfos[i].minY);
|
||
|
|
}
|
||
|
|
|
||
|
|
float baselineY = (RENDER_SIZE + maxAscent + maxDescent) / 2f;
|
||
|
|
float startX = (RENDER_SIZE - totalAdvance) / 2f;
|
||
|
|
|
||
|
|
GL.Begin(GL.QUADS);
|
||
|
|
GL.Color(Color.white);
|
||
|
|
|
||
|
|
float curX = startX;
|
||
|
|
for (int i = 0; i < initials.Length; i++)
|
||
|
|
{
|
||
|
|
CharacterInfo ci = charInfos[i];
|
||
|
|
|
||
|
|
float left = curX + ci.minX;
|
||
|
|
float right = curX + ci.maxX;
|
||
|
|
float top = baselineY - ci.maxY;
|
||
|
|
float bottom = baselineY - ci.minY;
|
||
|
|
|
||
|
|
GL.TexCoord2(ci.uvBottomLeft.x, ci.uvBottomLeft.y);
|
||
|
|
GL.Vertex3(left, bottom, 0);
|
||
|
|
|
||
|
|
GL.TexCoord2(ci.uvBottomRight.x, ci.uvBottomRight.y);
|
||
|
|
GL.Vertex3(right, bottom, 0);
|
||
|
|
|
||
|
|
GL.TexCoord2(ci.uvTopRight.x, ci.uvTopRight.y);
|
||
|
|
GL.Vertex3(right, top, 0);
|
||
|
|
|
||
|
|
GL.TexCoord2(ci.uvTopLeft.x, ci.uvTopLeft.y);
|
||
|
|
GL.Vertex3(left, top, 0);
|
||
|
|
|
||
|
|
curX += ci.advance;
|
||
|
|
}
|
||
|
|
|
||
|
|
GL.End();
|
||
|
|
}
|
||
|
|
|
||
|
|
static Font GetDefaultFont()
|
||
|
|
{
|
||
|
|
if (mFont != null)
|
||
|
|
return mFont;
|
||
|
|
|
||
|
|
#if UNITY_2022_1_OR_NEWER
|
||
|
|
mFont = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
|
||
|
|
#else
|
||
|
|
mFont = Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||
|
|
#endif
|
||
|
|
|
||
|
|
return mFont;
|
||
|
|
}
|
||
|
|
|
||
|
|
static Font mFont;
|
||
|
|
|
||
|
|
const int RENDER_SIZE = 256;
|
||
|
|
const int OUTPUT_SIZE = 32;
|
||
|
|
}
|
||
|
|
}
|