From d572aa1f47e3ac636ade00ee76ac31df7a48097a Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 11 Jul 2020 14:46:47 +0900 Subject: [PATCH 1/6] Add styled sprite text and update base class in LyricText --- .../Graphics/Sprites/LyricSpriteText.cs | 4 +- .../Graphics/Sprites/StyledSpriteText.cs | 613 ++++++++++++++++++ .../Sprites/StyledSpriteText_DrawNode.cs | 104 +++ 3 files changed, 719 insertions(+), 2 deletions(-) create mode 100644 osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs create mode 100644 osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs diff --git a/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs b/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs index 6ce5acc..8a80abd 100644 --- a/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs +++ b/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs @@ -10,7 +10,7 @@ namespace osu.Framework.Graphics.Sprites { - public class LyricSpriteText : SpriteText + public class LyricSpriteText : StyledSpriteText { public LyricSpriteText() { @@ -256,7 +256,7 @@ public bool Border protected TextBuilderGlyph[] Characters; /// - /// Creates a to generate the character layout for this . + /// Creates a to generate the character layout for this . /// /// The where characters should be retrieved from. /// The . diff --git a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs new file mode 100644 index 0000000..8578ba2 --- /dev/null +++ b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs @@ -0,0 +1,613 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Development; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.IO.Stores; +using osu.Framework.Layout; +using osu.Framework.Localisation; +using osu.Framework.Utils; +using osu.Framework.Text; +using osuTK; +using osuTK.Graphics; + +namespace osu.Framework.Graphics.Sprites +{ + /// + /// A container for simple text rendering purposes. If more complex text rendering is required, use instead. + /// + public partial class StyledSpriteText : Drawable, IHasLineBaseHeight, ITexturedShaderDrawable, IHasText, IHasFilterTerms, IFillFlowContainer, IHasCurrentValue + { + private const float default_text_size = 20; + private static readonly char[] default_never_fixed_width_characters = { '.', ',', ':', ' ' }; + + [Resolved] + private FontStore store { get; set; } + + [Resolved] + private LocalisationManager localisation { get; set; } + + private ILocalisedBindableString localisedText; + + public IShader TextureShader { get; private set; } + public IShader RoundedTextureShader { get; private set; } + + public StyledSpriteText() + { + current.BindValueChanged(text => Text = text.NewValue); + + AddLayout(charactersCache); + AddLayout(parentScreenSpaceCache); + AddLayout(localScreenSpaceCache); + AddLayout(shadowOffsetCache); + } + + [BackgroundDependencyLoader] + private void load(ShaderManager shaders) + { + localisedText = localisation.GetLocalisedString(text); + localisedText.BindValueChanged(str => + { + if (string.IsNullOrEmpty(str.NewValue)) + { + // We'll become not present and won't update the characters to set the size to 0, so do it manually + if (requiresAutoSizedWidth) + base.Width = Padding.TotalHorizontal; + if (requiresAutoSizedHeight) + base.Height = Padding.TotalVertical; + } + + invalidate(true); + }, true); + + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); + RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); + + // Pre-cache the characters in the texture store + foreach (var character in displayedText) + { + var unused = store.Get(font.FontName, character) ?? store.Get(null, character); + } + } + + private LocalisedString text = string.Empty; + + /// + /// Gets or sets the text to be displayed. + /// + public LocalisedString Text + { + get => text; + set + { + if (text == value) + return; + + text = value; + + current.Value = text; + + if (localisedText != null) + localisedText.Text = value; + } + } + + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private string displayedText => localisedText?.Value ?? text.Text.Original; + + string IHasText.Text + { + get => Text; + set => Text = value; + } + + private FontUsage font = FontUsage.Default; + + /// + /// Contains information on the font used to display the text. + /// + public FontUsage Font + { + get => font; + set + { + font = value; + + invalidate(true); + shadowOffsetCache.Invalidate(); + } + } + + private bool allowMultiline = true; + + /// + /// True if the text should be wrapped if it gets too wide. Note that \n does NOT cause a line break. If you need explicit line breaks, use instead. + /// + /// + /// If enabled, will be disabled. + /// + public bool AllowMultiline + { + get => allowMultiline; + set + { + if (allowMultiline == value) + return; + + if (value) + Truncate = false; + + allowMultiline = value; + + invalidate(true); + } + } + + private bool shadow; + + /// + /// True if a shadow should be displayed around the text. + /// + public bool Shadow + { + get => shadow; + set + { + if (shadow == value) + return; + + shadow = value; + + Invalidate(Invalidation.DrawNode); + } + } + + private Color4 shadowColour = new Color4(0, 0, 0, 0.2f); + + /// + /// The colour of the shadow displayed around the text. A shadow will only be displayed if the property is set to true. + /// + public Color4 ShadowColour + { + get => shadowColour; + set + { + if (shadowColour == value) + return; + + shadowColour = value; + + Invalidate(Invalidation.DrawNode); + } + } + + private Vector2 shadowOffset = new Vector2(0, 0.06f); + + /// + /// The offset of the shadow displayed around the text. A shadow will only be displayed if the property is set to true. + /// + public Vector2 ShadowOffset + { + get => shadowOffset; + set + { + if (shadowOffset == value) + return; + + shadowOffset = value; + + invalidate(true); + shadowOffsetCache.Invalidate(); + } + } + + private bool useFullGlyphHeight = true; + + /// + /// True if the 's vertical size should be equal to (the full height) or precisely the size of used characters. + /// Set to false to allow better centering of individual characters/numerals/etc. + /// + public bool UseFullGlyphHeight + { + get => useFullGlyphHeight; + set + { + if (useFullGlyphHeight == value) + return; + + useFullGlyphHeight = value; + + invalidate(true); + } + } + + private bool truncate; + + /// + /// If true, text should be truncated when it exceeds the of this . + /// + /// + /// Has no effect if no or custom sizing is set. + /// If enabled, will be disabled. + /// + public bool Truncate + { + get => truncate; + set + { + if (truncate == value) return; + + if (value) + AllowMultiline = false; + + truncate = value; + invalidate(true); + } + } + + private string ellipsisString = "…"; + + /// + /// When is enabled, this decides what string is used to signify that truncation has occured. + /// Defaults to "…". + /// + public string EllipsisString + { + get => ellipsisString; + set + { + if (ellipsisString == value) return; + + ellipsisString = value; + invalidate(true); + } + } + + private bool requiresAutoSizedWidth => explicitWidth == null && (RelativeSizeAxes & Axes.X) == 0; + + private bool requiresAutoSizedHeight => explicitHeight == null && (RelativeSizeAxes & Axes.Y) == 0; + + private float? explicitWidth; + + /// + /// Gets or sets the width of this . The will maintain this width when set. + /// + public override float Width + { + get + { + if (requiresAutoSizedWidth) + computeCharacters(); + return base.Width; + } + set + { + if (explicitWidth == value) + return; + + base.Width = value; + explicitWidth = value; + + invalidate(true); + } + } + + private float maxWidth = float.PositiveInfinity; + + /// + /// The maximum width of this . Affects both auto and fixed sizing modes. + /// + /// + /// This becomes a relative value if this is relatively-sized on the X-axis. + /// + public float MaxWidth + { + get => maxWidth; + set + { + if (maxWidth == value) + return; + + maxWidth = value; + invalidate(true); + } + } + + private float? explicitHeight; + + /// + /// Gets or sets the height of this . The will maintain this height when set. + /// + public override float Height + { + get + { + if (requiresAutoSizedHeight) + computeCharacters(); + return base.Height; + } + set + { + if (explicitHeight == value) + return; + + base.Height = value; + explicitHeight = value; + + invalidate(true); + } + } + + /// + /// Gets or sets the size of this . The will maintain this size when set. + /// + public override Vector2 Size + { + get + { + if (requiresAutoSizedWidth || requiresAutoSizedHeight) + computeCharacters(); + return base.Size; + } + set + { + Width = value.X; + Height = value.Y; + } + } + + private Vector2 spacing; + + /// + /// Gets or sets the spacing between characters of this . + /// + public Vector2 Spacing + { + get => spacing; + set + { + if (spacing == value) + return; + + spacing = value; + + invalidate(true); + } + } + + private MarginPadding padding; + + /// + /// Shrinks the space which may be occupied by characters of this by the specified amount on each side. + /// + public MarginPadding Padding + { + get => padding; + set + { + if (padding.Equals(value)) + return; + + if (!Validation.IsFinite(value)) throw new ArgumentException($@"{nameof(Padding)} must be finite, but is {value}."); + + padding = value; + + invalidate(true); + } + } + + public override bool IsPresent => base.IsPresent && (AlwaysPresent || !string.IsNullOrEmpty(displayedText)); + + #region Characters + + private readonly LayoutValue charactersCache = new LayoutValue(Invalidation.DrawSize | Invalidation.Presence, InvalidationSource.Parent); + private readonly List charactersBacking = new List(); + + /// + /// The characters in local space. + /// + private List characters + { + get + { + computeCharacters(); + return charactersBacking; + } + } + + private bool isComputingCharacters; + + /// + /// Compute character textures and positions. + /// + private void computeCharacters() + { + /* + if (LoadState >= LoadState.Loaded) + ThreadSafety.EnsureUpdateThread(); + */ + + if (store == null) + return; + + if (charactersCache.IsValid) + return; + + charactersBacking.Clear(); + + Debug.Assert(!isComputingCharacters, "Cyclic invocation of computeCharacters()!"); + isComputingCharacters = true; + + TextBuilder textBuilder = null; + + try + { + if (string.IsNullOrEmpty(displayedText)) + return; + + textBuilder = CreateTextBuilder(store); + textBuilder.AddText(displayedText); + } + finally + { + if (requiresAutoSizedWidth) + base.Width = (textBuilder?.Bounds.X ?? 0) + Padding.Right; + if (requiresAutoSizedHeight) + base.Height = (textBuilder?.Bounds.Y ?? 0) + Padding.Bottom; + + base.Width = Math.Min(base.Width, MaxWidth); + + isComputingCharacters = false; + charactersCache.Validate(); + } + } + + private readonly LayoutValue parentScreenSpaceCache = new LayoutValue(Invalidation.DrawSize | Invalidation.Presence | Invalidation.DrawInfo, InvalidationSource.Parent); + private readonly LayoutValue localScreenSpaceCache = new LayoutValue(Invalidation.MiscGeometry, InvalidationSource.Self); + + private readonly List screenSpaceCharactersBacking = new List(); + + /// + /// The characters in screen space. These are ready to be drawn. + /// + private List screenSpaceCharacters + { + get + { + computeScreenSpaceCharacters(); + return screenSpaceCharactersBacking; + } + } + + private void computeScreenSpaceCharacters() + { + if (!parentScreenSpaceCache.IsValid) + { + localScreenSpaceCache.Invalidate(); + parentScreenSpaceCache.Validate(); + } + + if (localScreenSpaceCache.IsValid) + return; + + screenSpaceCharactersBacking.Clear(); + + Vector2 inflationAmount = DrawInfo.MatrixInverse.ExtractScale().Xy; + + foreach (var character in characters) + { + screenSpaceCharactersBacking.Add(new ScreenSpaceCharacterPart + { + DrawQuad = ToScreenSpace(character.DrawRectangle.Inflate(inflationAmount)), + InflationPercentage = Vector2.Divide(inflationAmount, character.DrawRectangle.Size), + Texture = character.Texture + }); + } + + localScreenSpaceCache.Validate(); + } + + private readonly LayoutValue shadowOffsetCache = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); + + private Vector2 premultipliedShadowOffset => + shadowOffsetCache.IsValid ? shadowOffsetCache.Value : shadowOffsetCache.Value = ToScreenSpace(shadowOffset * Font.Size) - ToScreenSpace(Vector2.Zero); + + #endregion + + #region Invalidation + + private void invalidate(bool layout = false) + { + if (layout) + charactersCache.Invalidate(); + parentScreenSpaceCache.Invalidate(); + localScreenSpaceCache.Invalidate(); + + Invalidate(Invalidation.DrawNode); + } + + #endregion + + #region DrawNode + + protected override DrawNode CreateDrawNode() => new SpriteTextDrawNode(this); + + #endregion + + /// + /// The characters that should be excluded from fixed-width application. Defaults to (".", ",", ":", " ") if null. + /// + protected virtual char[] FixedWidthExcludeCharacters { get; } = null; + + /// + /// The character to fallback to use if a character glyph lookup failed. + /// + protected virtual char FallbackCharacter => '?'; + + /// + /// Creates a to generate the character layout for this . + /// + /// The where characters should be retrieved from. + /// The . + protected virtual TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) + { + var excludeCharacters = FixedWidthExcludeCharacters ?? default_never_fixed_width_characters; + + float builderMaxWidth = requiresAutoSizedWidth + ? MaxWidth + : ApplyRelativeAxes(RelativeSizeAxes, new Vector2(Math.Min(MaxWidth, base.Width), base.Height), FillMode).X - Padding.Right; + + if (AllowMultiline) + { + return new MultilineTextBuilder(store, Font, builderMaxWidth, UseFullGlyphHeight, new Vector2(Padding.Left, Padding.Top), Spacing, charactersBacking, + excludeCharacters, FallbackCharacter); + } + + if (Truncate) + { + return new TruncatingTextBuilder(store, Font, builderMaxWidth, ellipsisString, UseFullGlyphHeight, new Vector2(Padding.Left, Padding.Top), Spacing, charactersBacking, + excludeCharacters, FallbackCharacter); + } + + return new TextBuilder(store, Font, builderMaxWidth, UseFullGlyphHeight, new Vector2(Padding.Left, Padding.Top), Spacing, charactersBacking, + excludeCharacters, FallbackCharacter); + } + + public override string ToString() => $@"""{displayedText}"" " + base.ToString(); + + /// + /// Gets the base height of the font used by this text. If the font of this text is invalid, 0 is returned. + /// + public float LineBaseHeight + { + get + { + var baseHeight = store.GetBaseHeight(Font.FontName); + if (baseHeight.HasValue) + return baseHeight.Value * Font.Size; + + if (string.IsNullOrEmpty(displayedText)) + return 0; + + return store.GetBaseHeight(displayedText[0]).GetValueOrDefault() * Font.Size; + } + } + + public IEnumerable FilterTerms => displayedText.Yield(); + } +} diff --git a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs new file mode 100644 index 0000000..475c59c --- /dev/null +++ b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs @@ -0,0 +1,104 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.OpenGL.Vertices; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Textures; +using osuTK; +using osuTK.Graphics; + +namespace osu.Framework.Graphics.Sprites +{ + public partial class StyledSpriteText + { + internal class SpriteTextDrawNode : TexturedShaderDrawNode + { + protected new StyledSpriteText Source => (StyledSpriteText)base.Source; + + private bool shadow; + private ColourInfo shadowColour; + private Vector2 shadowOffset; + + private readonly List parts = new List(); + + public SpriteTextDrawNode(StyledSpriteText source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + parts.Clear(); + parts.AddRange(Source.screenSpaceCharacters); + shadow = Source.Shadow; + + if (shadow) + { + shadowColour = Source.ShadowColour; + shadowOffset = Source.premultipliedShadowOffset; + } + } + + public override void Draw(Action vertexAction) + { + base.Draw(vertexAction); + + Shader.Bind(); + + var avgColour = (Color4)DrawColourInfo.Colour.AverageColour; + float shadowAlpha = MathF.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2); + + //adjust shadow alpha based on highest component intensity to avoid muddy display of darker text. + //squared result for quadratic fall-off seems to give the best result. + var finalShadowColour = DrawColourInfo.Colour; + finalShadowColour.ApplyChild(shadowColour.MultiplyAlpha(shadowAlpha)); + + for (int i = 0; i < parts.Count; i++) + { + if (shadow) + { + var shadowQuad = parts[i].DrawQuad; + + DrawQuad(parts[i].Texture, + new Quad( + shadowQuad.TopLeft + shadowOffset, + shadowQuad.TopRight + shadowOffset, + shadowQuad.BottomLeft + shadowOffset, + shadowQuad.BottomRight + shadowOffset), + finalShadowColour, vertexAction: vertexAction, inflationPercentage: parts[i].InflationPercentage); + } + + DrawQuad(parts[i].Texture, parts[i].DrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction, inflationPercentage: parts[i].InflationPercentage); + } + + Shader.Unbind(); + } + } + + /// + /// A character of a provided with screen space draw coordinates. + /// + internal struct ScreenSpaceCharacterPart + { + /// + /// The screen-space quad for the character to be drawn in. + /// + public Quad DrawQuad; + + /// + /// Extra padding for the character's texture. + /// + public Vector2 InflationPercentage; + + /// + /// The texture to draw the character with. + /// + public Texture Texture; + } + } +} From fa1e12dfa975c8dc34c14c8ec898a9955f6c6de8 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 11 Jul 2020 14:59:15 +0900 Subject: [PATCH 2/6] Add outline property in StyledSpriteText --- .../Sprites/TestSceneKarakeSpriteText.cs | 2 + .../Sprites/TestSceneLyricSpriteText.cs | 2 + .../Graphics/Sprites/KarokeSpriteText.cs | 16 +++---- .../Graphics/Sprites/LyricSpriteText.cs | 30 ------------ .../Graphics/Sprites/StyledSpriteText.cs | 46 +++++++++++++++++++ .../Sprites/StyledSpriteText_DrawNode.cs | 9 ++++ 6 files changed, 67 insertions(+), 38 deletions(-) diff --git a/osu.Framework.Font.Tests/Visual/Sprites/TestSceneKarakeSpriteText.cs b/osu.Framework.Font.Tests/Visual/Sprites/TestSceneKarakeSpriteText.cs index 2335e5e..5bd1945 100644 --- a/osu.Framework.Font.Tests/Visual/Sprites/TestSceneKarakeSpriteText.cs +++ b/osu.Framework.Font.Tests/Visual/Sprites/TestSceneKarakeSpriteText.cs @@ -116,6 +116,8 @@ private Drawable createCustomizeTimeTagKaroakeText(Dictionary ru Margin = new MarginPadding(30), Shadow = true, ShadowOffset = new Vector2(3), + Outline = true, + OutlineRadius = 3f, ShadowTexture = new SolidTexture { SolidColor = Color4.Red }, TextTexture = new SolidTexture { SolidColor = Color4.White } }; diff --git a/osu.Framework.Font/Graphics/Sprites/KarokeSpriteText.cs b/osu.Framework.Font/Graphics/Sprites/KarokeSpriteText.cs index d94a32f..7a6de78 100644 --- a/osu.Framework.Font/Graphics/Sprites/KarokeSpriteText.cs +++ b/osu.Framework.Font/Graphics/Sprites/KarokeSpriteText.cs @@ -262,23 +262,23 @@ public LyricTextAlignment RomajiAlignment } } - public float BorderRadius + public bool Outline { - get => frontLyricText.BorderRadius; + get => frontLyricText.Outline; set { - frontLyricText.BorderRadius = value; - backLyricText.BorderRadius = value; + frontLyricText.Outline = value; + backLyricText.Outline = value; } } - public bool Border + public float OutlineRadius { - get => frontLyricText.Border; + get => frontLyricText.OutlineRadius; set { - frontLyricText.Border = value; - backLyricText.Border = value; + frontLyricText.OutlineRadius = value; + backLyricText.OutlineRadius = value; } } diff --git a/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs b/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs index 8a80abd..256a585 100644 --- a/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs +++ b/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs @@ -216,36 +216,6 @@ public LyricTextAlignment RomajiAlignment } } - private float borderRadius; - - public float BorderRadius - { - get => borderRadius; - set - { - if (borderRadius == value) - return; - - borderRadius = value; - Invalidate(Invalidation.All); - } - } - - private bool border; - - public bool Border - { - get => border; - set - { - if (border == value) - return; - - border = value; - Invalidate(Invalidation.All); - } - } - public new Vector2 ShadowOffset { get => base.ShadowOffset * Font.Size; diff --git a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs index 8578ba2..37e0dc9 100644 --- a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs +++ b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs @@ -48,6 +48,7 @@ public StyledSpriteText() AddLayout(parentScreenSpaceCache); AddLayout(localScreenSpaceCache); AddLayout(shadowOffsetCache); + AddLayout(outlineOffsetCache); } [BackgroundDependencyLoader] @@ -130,6 +131,7 @@ public FontUsage Font invalidate(true); shadowOffsetCache.Invalidate(); + outlineOffsetCache.Invalidate(); } } @@ -216,6 +218,45 @@ public Vector2 ShadowOffset } } + private bool outline; + + /// + /// True if a outline should be displayed around the text. + /// + public bool Outline + { + get => outline; + set + { + if (outline == value) + return; + + outline = value; + + Invalidate(Invalidation.DrawNode); + } + } + + private float outlineRadius; + + /// + /// The offset of the outline displayed around the text. A outline will only be displayed if the property is set to true. + /// + public float OutlineRadius + { + get => outlineRadius; + set + { + if (outlineRadius == value) + return; + + outlineRadius = value; + + invalidate(true); + outlineOffsetCache.Invalidate(); + } + } + private bool useFullGlyphHeight = true; /// @@ -527,6 +568,11 @@ private void computeScreenSpaceCharacters() private Vector2 premultipliedShadowOffset => shadowOffsetCache.IsValid ? shadowOffsetCache.Value : shadowOffsetCache.Value = ToScreenSpace(shadowOffset * Font.Size) - ToScreenSpace(Vector2.Zero); + private readonly LayoutValue outlineOffsetCache = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); + + private Vector2 premultipliedOutlineOffset => + outlineOffsetCache.IsValid ? outlineOffsetCache.Value : outlineOffsetCache.Value = ToScreenSpace(shadowOffset * Font.Size) - ToScreenSpace(Vector2.Zero); + #endregion #region Invalidation diff --git a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs index 475c59c..c418eca 100644 --- a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs +++ b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs @@ -22,6 +22,9 @@ internal class SpriteTextDrawNode : TexturedShaderDrawNode private ColourInfo shadowColour; private Vector2 shadowOffset; + private bool outline; + private float outlineRadius; + private readonly List parts = new List(); public SpriteTextDrawNode(StyledSpriteText source) @@ -36,12 +39,18 @@ public override void ApplyState() parts.Clear(); parts.AddRange(Source.screenSpaceCharacters); shadow = Source.Shadow; + outline = Source.Outline; if (shadow) { shadowColour = Source.ShadowColour; shadowOffset = Source.premultipliedShadowOffset; } + + if (outline) + { + outlineRadius = Source.outlineRadius; + } } public override void Draw(Action vertexAction) From d962b7caa19439b53059b0bcdcc4324abde1d32d Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 11 Jul 2020 16:07:32 +0900 Subject: [PATCH 3/6] Trying to create shader. Writing shader in testcase because caanot read resource file in `osu.Framework.Font` --- .../Resources/Shaders/sh_Outline.fs | 38 +++++++++++++++++++ .../osu.Framework.Font.Tests.csproj | 3 ++ .../Graphics/Sprites/StyledSpriteText.cs | 2 +- .../Sprites/StyledSpriteText_DrawNode.cs | 5 +++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 osu.Framework.Font.Tests/Resources/Shaders/sh_Outline.fs diff --git a/osu.Framework.Font.Tests/Resources/Shaders/sh_Outline.fs b/osu.Framework.Font.Tests/Resources/Shaders/sh_Outline.fs new file mode 100644 index 0000000..502f969 --- /dev/null +++ b/osu.Framework.Font.Tests/Resources/Shaders/sh_Outline.fs @@ -0,0 +1,38 @@ +precision highp float; +uniform sampler2D texture; +uniform vec2 pixelSize; +uniform vec4 color; +varying vec2 texCoord; +void main() +{ + const int WIDTH = 5; + bool isInside = false; + int count = 0; + float coverage = 0.0; + float dist = 1e6; + for (int y = -WIDTH; y <= WIDTH; ++y) { + for (int x = -WIDTH; x <= WIDTH; ++x) { + vec2 dUV = vec2(float(x) * pixelSize.x, float(y) * pixelSize.y); + float mask = texture2D(texture, texCoord + dUV).r; + coverage += mask; + if (mask >= 0.5) { + dist = min(dist, sqrt(float(x * x + y * y))); + } + if (x == 0 && y == 0) { + isInside = (mask > 0.5); + } + count += 1; + } + } + coverage /= float(count); + float a; + if (isInside) { + a = min(1.0, (1.0 - coverage) / 0.75); + } else { + const float solid = 0.3 * float(WIDTH); + const float fuzzy = float(WIDTH) - solid; + a = 1.0 - min(1.0, max(0.0, dist - solid) / fuzzy); + } + gl_FragColor = color; + gl_FragColor.a = a; +} \ No newline at end of file diff --git a/osu.Framework.Font.Tests/osu.Framework.Font.Tests.csproj b/osu.Framework.Font.Tests/osu.Framework.Font.Tests.csproj index 69977a4..7c7f66d 100644 --- a/osu.Framework.Font.Tests/osu.Framework.Font.Tests.csproj +++ b/osu.Framework.Font.Tests/osu.Framework.Font.Tests.csproj @@ -18,4 +18,7 @@ + + + \ No newline at end of file diff --git a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs index 37e0dc9..b86176f 100644 --- a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs +++ b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs @@ -70,7 +70,7 @@ private void load(ShaderManager shaders) }, true); TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); - RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); + RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "Outline"); // Pre-cache the characters in the texture store foreach (var character in displayedText) diff --git a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs index c418eca..93c384d 100644 --- a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs +++ b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs @@ -69,6 +69,11 @@ public override void Draw(Action vertexAction) for (int i = 0; i < parts.Count; i++) { + if (outline) + { + + } + if (shadow) { var shadowQuad = parts[i].DrawQuad; From 9aea95887bb36cf02a4a775bb4ab98ecd454016b Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 12 Jul 2020 13:05:25 +0900 Subject: [PATCH 4/6] This is the very basic shader i could make Orz --- .../Resources/Shaders/sh_Outline.fs | 57 +++++++------------ .../Graphics/Sprites/StyledSpriteText.cs | 5 +- .../Sprites/StyledSpriteText_DrawNode.cs | 56 +++++++++++++----- 3 files changed, 66 insertions(+), 52 deletions(-) diff --git a/osu.Framework.Font.Tests/Resources/Shaders/sh_Outline.fs b/osu.Framework.Font.Tests/Resources/Shaders/sh_Outline.fs index 502f969..8083232 100644 --- a/osu.Framework.Font.Tests/Resources/Shaders/sh_Outline.fs +++ b/osu.Framework.Font.Tests/Resources/Shaders/sh_Outline.fs @@ -1,38 +1,21 @@ -precision highp float; -uniform sampler2D texture; -uniform vec2 pixelSize; -uniform vec4 color; -varying vec2 texCoord; -void main() +varying mediump vec2 v_TexCoord; +uniform lowp sampler2D m_Sampler; + +const float offset = 1.0 / 256.0; + +void main(void) { - const int WIDTH = 5; - bool isInside = false; - int count = 0; - float coverage = 0.0; - float dist = 1e6; - for (int y = -WIDTH; y <= WIDTH; ++y) { - for (int x = -WIDTH; x <= WIDTH; ++x) { - vec2 dUV = vec2(float(x) * pixelSize.x, float(y) * pixelSize.y); - float mask = texture2D(texture, texCoord + dUV).r; - coverage += mask; - if (mask >= 0.5) { - dist = min(dist, sqrt(float(x * x + y * y))); - } - if (x == 0 && y == 0) { - isInside = (mask > 0.5); - } - count += 1; - } - } - coverage /= float(count); - float a; - if (isInside) { - a = min(1.0, (1.0 - coverage) / 0.75); - } else { - const float solid = 0.3 * float(WIDTH); - const float fuzzy = float(WIDTH) - solid; - a = 1.0 - min(1.0, max(0.0, dist - solid) / fuzzy); - } - gl_FragColor = color; - gl_FragColor.a = a; -} \ No newline at end of file + vec4 col = texture2D(m_Sampler, v_TexCoord); + if (col.a > 0.5) + gl_FragColor = col; + else { + float a = texture2D(m_Sampler, vec2(v_TexCoord.x + offset, v_TexCoord.y)).a + + texture2D(m_Sampler, vec2(v_TexCoord.x, v_TexCoord.y - offset)).a + + texture2D(m_Sampler, vec2(v_TexCoord.x - offset, v_TexCoord.y)).a + + texture2D(m_Sampler, vec2(v_TexCoord.x, v_TexCoord.y + offset)).a; + if (col.a < 1.0 && a > 0.0) + gl_FragColor = vec4(0.0, 0.0, 1.0, 0.8); + else + gl_FragColor = col; + } +} diff --git a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs index b86176f..15169fc 100644 --- a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs +++ b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs @@ -40,6 +40,8 @@ public partial class StyledSpriteText : Drawable, IHasLineBaseHeight, ITexturedS public IShader TextureShader { get; private set; } public IShader RoundedTextureShader { get; private set; } + private IShader outlineShader; + public StyledSpriteText() { current.BindValueChanged(text => Text = text.NewValue); @@ -70,7 +72,8 @@ private void load(ShaderManager shaders) }, true); TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); - RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "Outline"); + RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); + outlineShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "Outline"); // Pre-cache the characters in the texture store foreach (var character in displayedText) diff --git a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs index 93c384d..ea6cf07 100644 --- a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs +++ b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs @@ -6,7 +6,9 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; +using osu.Framework.Utils; using osuTK; using osuTK.Graphics; @@ -23,7 +25,10 @@ internal class SpriteTextDrawNode : TexturedShaderDrawNode private Vector2 shadowOffset; private bool outline; - private float outlineRadius; + private int outlineRadius; + private float outlineSigma = 10; + + private IShader outlineShader; private readonly List parts = new List(); @@ -49,7 +54,8 @@ public override void ApplyState() if (outline) { - outlineRadius = Source.outlineRadius; + outlineRadius = (int)Source.outlineRadius; + outlineShader = Source.outlineShader; } } @@ -57,7 +63,8 @@ public override void Draw(Action vertexAction) { base.Draw(vertexAction); - Shader.Bind(); + outlineSigma = 100; + var avgColour = (Color4)DrawColourInfo.Colour.AverageColour; float shadowAlpha = MathF.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2); @@ -67,30 +74,51 @@ public override void Draw(Action vertexAction) var finalShadowColour = DrawColourInfo.Colour; finalShadowColour.ApplyChild(shadowColour.MultiplyAlpha(shadowAlpha)); - for (int i = 0; i < parts.Count; i++) - { - if (outline) - { - - } + Shader.Bind(); + foreach (var current in parts) + { if (shadow) { - var shadowQuad = parts[i].DrawQuad; + var shadowQuad = current.DrawQuad; - DrawQuad(parts[i].Texture, + DrawQuad(current.Texture, new Quad( shadowQuad.TopLeft + shadowOffset, shadowQuad.TopRight + shadowOffset, shadowQuad.BottomLeft + shadowOffset, shadowQuad.BottomRight + shadowOffset), - finalShadowColour, vertexAction: vertexAction, inflationPercentage: parts[i].InflationPercentage); + finalShadowColour, vertexAction: vertexAction, inflationPercentage: current.InflationPercentage); } - DrawQuad(parts[i].Texture, parts[i].DrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction, inflationPercentage: parts[i].InflationPercentage); + DrawQuad(current.Texture, current.DrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction, inflationPercentage: current.InflationPercentage); } - + Shader.Unbind(); + + if (outline) + { + outlineShader.Bind(); + + foreach (var current in parts) + { + /* + outlineShader.GetUniform(@"g_Radius").UpdateValue(ref outlineRadius); + outlineShader.GetUniform(@"g_Sigma").UpdateValue(ref outlineSigma); + + Vector2 size = current.DrawQuad.Size; + outlineShader.GetUniform(@"g_TexSize").UpdateValue(ref size); + + float radians = -MathUtils.DegreesToRadians(0); + Vector2 blur = new Vector2(-0.1f, -0.1f); + outlineShader.GetUniform(@"g_BlurDirection").UpdateValue(ref blur); + */ + + DrawQuad(current.Texture, current.DrawQuad, Color4.Blue, vertexAction: vertexAction, inflationPercentage: current.InflationPercentage); + } + + outlineShader.Unbind(); + } } } From 5b31dab766e8963731a3adf9122a7ffb52fd0e14 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 12 Jul 2020 15:17:33 +0900 Subject: [PATCH 5/6] Add outline colour property. --- .../Sprites/TestSceneKarakeSpriteText.cs | 3 ++- .../Sprites/TestSceneLyricSpriteText.cs | 5 ++-- .../Graphics/Sprites/LyricSpriteText.cs | 12 ++++++--- .../Graphics/Sprites/StyledSpriteText.cs | 27 ++++++++++++++++--- .../Sprites/StyledSpriteText_DrawNode.cs | 9 +++---- 5 files changed, 40 insertions(+), 16 deletions(-) diff --git a/osu.Framework.Font.Tests/Visual/Sprites/TestSceneKarakeSpriteText.cs b/osu.Framework.Font.Tests/Visual/Sprites/TestSceneKarakeSpriteText.cs index 5bd1945..1fa2d45 100644 --- a/osu.Framework.Font.Tests/Visual/Sprites/TestSceneKarakeSpriteText.cs +++ b/osu.Framework.Font.Tests/Visual/Sprites/TestSceneKarakeSpriteText.cs @@ -118,8 +118,9 @@ private Drawable createCustomizeTimeTagKaroakeText(Dictionary ru Rubies = rubies?.ToArray(), Romajies = romajies?.ToArray(), Margin = new MarginPadding(30), - Shadow = true, + Shadow = false, ShadowOffset = new Vector2(3), Outline = true, OutlineRadius = 3f, + TextTexture = new SolidTexture { SolidColor = Color4.Blue }, ShadowTexture = new SolidTexture { SolidColor = Color4.Red }, - TextTexture = new SolidTexture { SolidColor = Color4.White } + BorderTexture = new SolidTexture { SolidColor = Color4.White } }; } } diff --git a/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs b/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs index 256a585..3087dec 100644 --- a/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs +++ b/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs @@ -124,6 +124,8 @@ public Vector2 RubySpacing } } + protected Vector2 CalculatedRubySpacing => Outline ? RubySpacing + new Vector2(OutlineRadius) : RubySpacing; + private Vector2 romajiSpacing; public Vector2 RomajiSpacing @@ -139,6 +141,8 @@ public Vector2 RomajiSpacing } } + protected Vector2 CalculatedRomajiSpacing => Outline ? RomajiSpacing + new Vector2(OutlineRadius) : RomajiSpacing; + private ILyricTexture textTexture; public ILyricTexture TextTexture @@ -241,20 +245,20 @@ protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store var contentPosition = rubyYPosition + RubyFont.Size / 2 + RubyMargin; // Print and save main texts - var charactersBacking = createMainTexts(Text,Font, contentPosition, Spacing); + var charactersBacking = createMainTexts(Text,Font, contentPosition, CalculatedSpacing); Characters = charactersBacking.ToArray(); // Print ruby texts - createTexts(Rubies, RubyFont, rubyYPosition, RubySpacing); + createTexts(Rubies, RubyFont, rubyYPosition, CalculatedRubySpacing); // Calculate position and print romaji texts var romajiYPosition = contentPosition + Characters.FirstOrDefault().Height + Characters.FirstOrDefault().YOffset + RomajiMargin; - createTexts(Romajies, RomajiFont, romajiYPosition, RomajiSpacing); + createTexts(Romajies, RomajiFont, romajiYPosition, CalculatedRomajiSpacing); // Calculate position and return TextBuilder that do not renderer text anymore var romajiTextSize = RomajiMargin + ((Romajies?.Any() ?? false) ? (charactersBacking.LastOrDefault().Height + charactersBacking.LastOrDefault().YOffset) : 0); return new TextBuilder(store, Font, builder_max_width, UseFullGlyphHeight, - new Vector2(Padding.Left, contentPosition + romajiTextSize), Spacing, null, + new Vector2(Padding.Left, contentPosition + romajiTextSize), CalculatedSpacing, null, excludeCharacters, FallbackCharacter); // Create main text diff --git a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs index 15169fc..e42e27e 100644 --- a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs +++ b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs @@ -240,6 +240,25 @@ public bool Outline } } + private Color4 outlineColour = new Color4(0, 0, 0, 0.2f); + + /// + /// The colour of the outline displayed around the text. A outline will only be displayed if the property is set to true. + /// + public Color4 OutlineColour + { + get => outlineColour; + set + { + if (outlineColour == value) + return; + + outlineColour = value; + + Invalidate(Invalidation.DrawNode); + } + } + private float outlineRadius; /// @@ -434,6 +453,8 @@ public Vector2 Spacing } } + protected Vector2 CalculatedSpacing => Outline ? Spacing + new Vector2(OutlineRadius) : Spacing; + private MarginPadding padding; /// @@ -623,17 +644,17 @@ protected virtual TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) if (AllowMultiline) { - return new MultilineTextBuilder(store, Font, builderMaxWidth, UseFullGlyphHeight, new Vector2(Padding.Left, Padding.Top), Spacing, charactersBacking, + return new MultilineTextBuilder(store, Font, builderMaxWidth, UseFullGlyphHeight, new Vector2(Padding.Left, Padding.Top), CalculatedSpacing, charactersBacking, excludeCharacters, FallbackCharacter); } if (Truncate) { - return new TruncatingTextBuilder(store, Font, builderMaxWidth, ellipsisString, UseFullGlyphHeight, new Vector2(Padding.Left, Padding.Top), Spacing, charactersBacking, + return new TruncatingTextBuilder(store, Font, builderMaxWidth, ellipsisString, UseFullGlyphHeight, new Vector2(Padding.Left, Padding.Top), CalculatedSpacing, charactersBacking, excludeCharacters, FallbackCharacter); } - return new TextBuilder(store, Font, builderMaxWidth, UseFullGlyphHeight, new Vector2(Padding.Left, Padding.Top), Spacing, charactersBacking, + return new TextBuilder(store, Font, builderMaxWidth, UseFullGlyphHeight, new Vector2(Padding.Left, Padding.Top), CalculatedSpacing, charactersBacking, excludeCharacters, FallbackCharacter); } diff --git a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs index ea6cf07..4be515f 100644 --- a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs +++ b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs @@ -25,9 +25,8 @@ internal class SpriteTextDrawNode : TexturedShaderDrawNode private Vector2 shadowOffset; private bool outline; + private ColourInfo outlineColour; private int outlineRadius; - private float outlineSigma = 10; - private IShader outlineShader; private readonly List parts = new List(); @@ -54,6 +53,7 @@ public override void ApplyState() if (outline) { + outlineColour = Source.OutlineColour; outlineRadius = (int)Source.outlineRadius; outlineShader = Source.outlineShader; } @@ -63,9 +63,6 @@ public override void Draw(Action vertexAction) { base.Draw(vertexAction); - outlineSigma = 100; - - var avgColour = (Color4)DrawColourInfo.Colour.AverageColour; float shadowAlpha = MathF.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2); @@ -114,7 +111,7 @@ public override void Draw(Action vertexAction) outlineShader.GetUniform(@"g_BlurDirection").UpdateValue(ref blur); */ - DrawQuad(current.Texture, current.DrawQuad, Color4.Blue, vertexAction: vertexAction, inflationPercentage: current.InflationPercentage); + DrawQuad(current.Texture, current.DrawQuad, outlineColour, vertexAction: vertexAction, inflationPercentage: current.InflationPercentage); } outlineShader.Unbind(); From fa42f83f66f62499111b0f08fff3fbeaf278f8f7 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 12 Jul 2020 16:37:32 +0900 Subject: [PATCH 6/6] Works better. But seems will use last assigned value. --- .../Resources/Shaders/sh_Outline.fs | 14 ++--- .../Sprites/TestSceneLyricSpriteText.cs | 2 +- .../Graphics/Sprites/LyricSpriteText.cs | 1 + .../Graphics/Sprites/StyledSpriteText.cs | 7 +-- .../Sprites/StyledSpriteText_DrawNode.cs | 56 +++++++------------ 5 files changed, 30 insertions(+), 50 deletions(-) diff --git a/osu.Framework.Font.Tests/Resources/Shaders/sh_Outline.fs b/osu.Framework.Font.Tests/Resources/Shaders/sh_Outline.fs index 8083232..0ad39fa 100644 --- a/osu.Framework.Font.Tests/Resources/Shaders/sh_Outline.fs +++ b/osu.Framework.Font.Tests/Resources/Shaders/sh_Outline.fs @@ -1,7 +1,7 @@ varying mediump vec2 v_TexCoord; uniform lowp sampler2D m_Sampler; - -const float offset = 1.0 / 256.0; +uniform float g_outlineRadius; +uniform vec4 g_outlineColour; void main(void) { @@ -9,12 +9,12 @@ void main(void) if (col.a > 0.5) gl_FragColor = col; else { - float a = texture2D(m_Sampler, vec2(v_TexCoord.x + offset, v_TexCoord.y)).a + - texture2D(m_Sampler, vec2(v_TexCoord.x, v_TexCoord.y - offset)).a + - texture2D(m_Sampler, vec2(v_TexCoord.x - offset, v_TexCoord.y)).a + - texture2D(m_Sampler, vec2(v_TexCoord.x, v_TexCoord.y + offset)).a; + float a = texture2D(m_Sampler, vec2(v_TexCoord.x + g_outlineRadius, v_TexCoord.y)).a + + texture2D(m_Sampler, vec2(v_TexCoord.x, v_TexCoord.y - g_outlineRadius)).a + + texture2D(m_Sampler, vec2(v_TexCoord.x - g_outlineRadius, v_TexCoord.y)).a + + texture2D(m_Sampler, vec2(v_TexCoord.x, v_TexCoord.y + g_outlineRadius)).a; if (col.a < 1.0 && a > 0.0) - gl_FragColor = vec4(0.0, 0.0, 1.0, 0.8); + gl_FragColor = g_outlineColour; else gl_FragColor = col; } diff --git a/osu.Framework.Font.Tests/Visual/Sprites/TestSceneLyricSpriteText.cs b/osu.Framework.Font.Tests/Visual/Sprites/TestSceneLyricSpriteText.cs index 3827237..4f4c121 100644 --- a/osu.Framework.Font.Tests/Visual/Sprites/TestSceneLyricSpriteText.cs +++ b/osu.Framework.Font.Tests/Visual/Sprites/TestSceneLyricSpriteText.cs @@ -129,7 +129,7 @@ private LyricSpriteText createLyricSpriteText(string text, List ru Rubies = rubies?.ToArray(), Romajies = romajies?.ToArray(), Margin = new MarginPadding(30), - Shadow = false, + Shadow = true, ShadowOffset = new Vector2(3), Outline = true, OutlineRadius = 3f, diff --git a/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs b/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs index 3087dec..0865381 100644 --- a/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs +++ b/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs @@ -186,6 +186,7 @@ public ILyricTexture BorderTexture return; borderTexture = value; + OutlineColour = (borderTexture as SolidTexture)?.SolidColor ?? Color4.White; Invalidate(Invalidation.All); } } diff --git a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs index e42e27e..04a2932 100644 --- a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs +++ b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText.cs @@ -40,8 +40,6 @@ public partial class StyledSpriteText : Drawable, IHasLineBaseHeight, ITexturedS public IShader TextureShader { get; private set; } public IShader RoundedTextureShader { get; private set; } - private IShader outlineShader; - public StyledSpriteText() { current.BindValueChanged(text => Text = text.NewValue); @@ -71,9 +69,8 @@ private void load(ShaderManager shaders) invalidate(true); }, true); - TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); - RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); - outlineShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "Outline"); + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "Outline"); + RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "Outline"); // Pre-cache the characters in the texture store foreach (var character in displayedText) diff --git a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs index 4be515f..80ef5cd 100644 --- a/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs +++ b/osu.Framework.Font/Graphics/Sprites/StyledSpriteText_DrawNode.cs @@ -3,12 +3,9 @@ using System; using System.Collections.Generic; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; -using osu.Framework.Utils; using osuTK; using osuTK.Graphics; @@ -21,13 +18,15 @@ internal class SpriteTextDrawNode : TexturedShaderDrawNode protected new StyledSpriteText Source => (StyledSpriteText)base.Source; private bool shadow; - private ColourInfo shadowColour; + private Vector4 shadowOutlineColour; + private Colour4 shadowColour; private Vector2 shadowOffset; private bool outline; - private ColourInfo outlineColour; - private int outlineRadius; - private IShader outlineShader; + private Colour4 outlineColour; + private float outlineRadius; + + private Vector4 textColour; private readonly List parts = new List(); @@ -45,17 +44,20 @@ public override void ApplyState() shadow = Source.Shadow; outline = Source.Outline; + var color4 = DrawColourInfo.Colour.AverageColour.Linear; + textColour = new Vector4(color4.R, color4.G, color4.B, color4.A); + if (shadow) { shadowColour = Source.ShadowColour; + shadowOutlineColour = new Vector4(Source.ShadowColour.R, Source.ShadowColour.G, Source.ShadowColour.B, Source.ShadowColour.A); shadowOffset = Source.premultipliedShadowOffset; } if (outline) { outlineColour = Source.OutlineColour; - outlineRadius = (int)Source.outlineRadius; - outlineShader = Source.outlineShader; + outlineRadius = Source.outlineRadius / 512; } } @@ -68,15 +70,17 @@ public override void Draw(Action vertexAction) //adjust shadow alpha based on highest component intensity to avoid muddy display of darker text. //squared result for quadratic fall-off seems to give the best result. - var finalShadowColour = DrawColourInfo.Colour; - finalShadowColour.ApplyChild(shadowColour.MultiplyAlpha(shadowAlpha)); + //var finalShadowColour = DrawColourInfo.Colour; + //finalShadowColour.ApplyChild(shadowColour.MultiplyAlpha(shadowAlpha)); Shader.Bind(); + Shader.GetUniform(@"g_outlineRadius").UpdateValue(ref outlineRadius); foreach (var current in parts) { if (shadow) { + Shader.GetUniform(@"g_outlineColour").UpdateValue(ref shadowOutlineColour); var shadowQuad = current.DrawQuad; DrawQuad(current.Texture, @@ -85,37 +89,15 @@ public override void Draw(Action vertexAction) shadowQuad.TopRight + shadowOffset, shadowQuad.BottomLeft + shadowOffset, shadowQuad.BottomRight + shadowOffset), - finalShadowColour, vertexAction: vertexAction, inflationPercentage: current.InflationPercentage); + shadowColour, vertexAction: vertexAction, inflationPercentage: current.InflationPercentage); } - DrawQuad(current.Texture, current.DrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction, inflationPercentage: current.InflationPercentage); + Shader.GetUniform(@"g_outlineColour").UpdateValue(ref textColour); + + DrawQuad(current.Texture, current.DrawQuad, outlineColour, vertexAction: vertexAction, inflationPercentage: current.InflationPercentage); } Shader.Unbind(); - - if (outline) - { - outlineShader.Bind(); - - foreach (var current in parts) - { - /* - outlineShader.GetUniform(@"g_Radius").UpdateValue(ref outlineRadius); - outlineShader.GetUniform(@"g_Sigma").UpdateValue(ref outlineSigma); - - Vector2 size = current.DrawQuad.Size; - outlineShader.GetUniform(@"g_TexSize").UpdateValue(ref size); - - float radians = -MathUtils.DegreesToRadians(0); - Vector2 blur = new Vector2(-0.1f, -0.1f); - outlineShader.GetUniform(@"g_BlurDirection").UpdateValue(ref blur); - */ - - DrawQuad(current.Texture, current.DrawQuad, outlineColour, vertexAction: vertexAction, inflationPercentage: current.InflationPercentage); - } - - outlineShader.Unbind(); - } } }