diff --git a/osu.Framework.Font.Tests/Shaders/OutlineShaderTest.cs b/osu.Framework.Font.Tests/Shaders/OutlineShaderTest.cs new file mode 100644 index 0000000..b0dcd5c --- /dev/null +++ b/osu.Framework.Font.Tests/Shaders/OutlineShaderTest.cs @@ -0,0 +1,47 @@ +// Copyright (c) karaoke.dev . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shaders; + +namespace osu.Framework.Font.Tests.Shaders +{ + public class OutlineShaderTest + { + [TestCase(10, -5, -5, 30, 30)] + [TestCase(0, 5, 5, 10, 10)] + [TestCase(-10, 5, 5, 10, 10)] + public void TestComputeCharacterDrawRectangle(int radius, float expectedX, float expectedY, float expectedWidth, float expectedHeight) + { + var shader = new OutlineShader + { + Radius = radius + }; + + var rectangle = shader.ComputeCharacterDrawRectangle(new RectangleF(5, 5, 10, 10)); + Assert.AreEqual(expectedX, rectangle.X); + Assert.AreEqual(expectedY, rectangle.Y); + Assert.AreEqual(expectedWidth, rectangle.Width); + Assert.AreEqual(expectedHeight, rectangle.Height); + } + + [TestCase(10, -5, -5, 30, 30)] + [TestCase(0, 5, 5, 10, 10)] + [TestCase(-10, 5, 5, 10, 10)] + public void TestComputeScreenSpaceDrawQuad(int radius, float expectedX, float expectedY, float expectedWidth, float expectedHeight) + { + var shader = new OutlineShader + { + Radius = radius + }; + + var quad = shader.ComputeScreenSpaceDrawQuad(new Quad(5, 5, 10, 10)); + var rectangle = quad.AABBFloat; + Assert.AreEqual(expectedX, rectangle.X); + Assert.AreEqual(expectedY, rectangle.Y); + Assert.AreEqual(expectedWidth, rectangle.Width); + Assert.AreEqual(expectedHeight, rectangle.Height); + } + } +} diff --git a/osu.Framework.Font.Tests/Shaders/ShadowShaderTest.cs b/osu.Framework.Font.Tests/Shaders/ShadowShaderTest.cs new file mode 100644 index 0000000..5a2e081 --- /dev/null +++ b/osu.Framework.Font.Tests/Shaders/ShadowShaderTest.cs @@ -0,0 +1,35 @@ +// Copyright (c) karaoke.dev . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shaders; +using osuTK; + +namespace osu.Framework.Font.Tests.Shaders +{ + public class ShadowShaderTest + { + private const float offset_x = 11; + private const float offset_y = 12; + + [TestCase(offset_x, 0, 5, 5, 10 + offset_x, 10)] // Offset right + [TestCase(-offset_x, 0, 5 - offset_x, 5, 10 + offset_x, 10)] // Offset left + [TestCase(0, offset_y, 5, 5, 10, 10 + offset_y)] // Offset down + [TestCase(0, -offset_y, 5, 5 - offset_y, 10, 10 + offset_y)] // Offset up + public void TestComputeScreenSpaceDrawQuad(float offsetX, float offsetY, float expectedX, float expectedY, float expectedWidth, float expectedHeight) + { + var shader = new ShadowShader + { + ShadowOffset = new Vector2(offsetX, offsetY) + }; + + var quad = shader.ComputeScreenSpaceDrawQuad(new Quad(5, 5, 10, 10)); + var rectangle = quad.AABBFloat; + Assert.AreEqual(expectedX, rectangle.X); + Assert.AreEqual(expectedY, rectangle.Y); + Assert.AreEqual(expectedWidth, rectangle.Width); + Assert.AreEqual(expectedHeight, rectangle.Height); + } + } +} diff --git a/osu.Framework.Font.Tests/Shaders/StepShaderTest.cs b/osu.Framework.Font.Tests/Shaders/StepShaderTest.cs new file mode 100644 index 0000000..884ed5c --- /dev/null +++ b/osu.Framework.Font.Tests/Shaders/StepShaderTest.cs @@ -0,0 +1,136 @@ +// Copyright (c) karaoke.dev . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shaders; +using osuTK; + +namespace osu.Framework.Font.Tests.Shaders +{ + public class StepShaderTest + { + [Test] + public void TestComputeCharacterDrawRectangle() + { + const int outline_radius = 10; + const int shadow_offset_x = 11; + const int shadow_offset_y = 12; + + var shader = new StepShader + { + StepShaders = new ICustomizedShader[] + { + new OutlineShader + { + Radius = outline_radius + }, + new ShadowShader + { + ShadowOffset = new Vector2(shadow_offset_x, shadow_offset_y) + } + } + }; + + var rectangle = shader.ComputeCharacterDrawRectangle(new RectangleF(5, 5, 10, 10)); + Assert.AreEqual(5 - outline_radius, rectangle.X); + Assert.AreEqual(5 - outline_radius, rectangle.Y); + Assert.AreEqual(10 + outline_radius * 2, rectangle.Width); + Assert.AreEqual(10 + outline_radius * 2, rectangle.Height); + } + + [Test] + public void TestComputeScreenSpaceDrawQuad() + { + const int outline_radius = 10; + const int shadow_offset_x = 11; + const int shadow_offset_y = 12; + + var shader = new StepShader + { + StepShaders = new ICustomizedShader[] + { + new OutlineShader + { + Radius = outline_radius + }, + new ShadowShader + { + ShadowOffset = new Vector2(shadow_offset_x, shadow_offset_y) + } + } + }; + + var quad = shader.ComputeScreenSpaceDrawQuad(new Quad(5, 5, 10, 10)); + var rectangle = quad.AABBFloat; + Assert.AreEqual(5 - outline_radius, rectangle.X); + Assert.AreEqual(5 - outline_radius, rectangle.Y); + Assert.AreEqual(10 + outline_radius * 2 + shadow_offset_x, rectangle.Width); + Assert.AreEqual(10 + outline_radius * 2 + shadow_offset_y, rectangle.Height); + } + + [Test] + public void TestComputeCharacterDrawRectangleWithEmptyShader() + { + var shader = new StepShader(); + + var rectangle = shader.ComputeCharacterDrawRectangle(new RectangleF(5, 5, 10, 10)); + Assert.AreEqual(5, rectangle.X); + Assert.AreEqual(5, rectangle.Y); + Assert.AreEqual(10, rectangle.Width); + Assert.AreEqual(10, rectangle.Height); + } + + [Test] + public void TestEmptyComputeScreenSpaceDrawQuadWithEmptyShader() + { + var shader = new StepShader(); + + var quad = shader.ComputeScreenSpaceDrawQuad(new Quad(5, 5, 10, 10)); + var rectangle = quad.AABBFloat; + Assert.AreEqual(5, rectangle.X); + Assert.AreEqual(5, rectangle.Y); + Assert.AreEqual(10, rectangle.Width); + Assert.AreEqual(10, rectangle.Height); + } + + [Test] + public void TestCharacterDrawRectangleWithNoneMatchedShader() + { + var shader = new StepShader + { + StepShaders = new[] + { + new PixelShader() + } + }; + + var rectangle = shader.ComputeCharacterDrawRectangle(new RectangleF(5, 5, 10, 10)); + Assert.AreEqual(5, rectangle.X); + Assert.AreEqual(5, rectangle.Y); + Assert.AreEqual(10, rectangle.Width); + Assert.AreEqual(10, rectangle.Height); + } + + [Test] + public void TestEmptyComputeScreenSpaceDrawQuadNoneMatchedShader() + { + var shader = new StepShader + { + StepShaders = new[] + { + new PixelShader() + } + }; + + var quad = shader.ComputeScreenSpaceDrawQuad(new Quad(5, 5, 10, 10)); + var rectangle = quad.AABBFloat; + Assert.AreEqual(5, rectangle.X); + Assert.AreEqual(5, rectangle.Y); + Assert.AreEqual(10, rectangle.Width); + Assert.AreEqual(10, rectangle.Height); + } + } +} + + diff --git a/osu.Framework.Font.Tests/Visual/Sprites/TestSceneKaraokeSpriteTextTransforms.cs b/osu.Framework.Font.Tests/Visual/Sprites/TestSceneKaraokeSpriteTextTransforms.cs index c97ef3b..c08c024 100644 --- a/osu.Framework.Font.Tests/Visual/Sprites/TestSceneKaraokeSpriteTextTransforms.cs +++ b/osu.Framework.Font.Tests/Visual/Sprites/TestSceneKaraokeSpriteTextTransforms.cs @@ -64,7 +64,7 @@ public TestSceneKaraokeSpriteTextTransforms() AddLabel("Timing"); - AddSliderStep("Adjust clock time", 0, end_time + exrea_time, start_time - exrea_time, time => + AddSliderStep("Adjust clock time", start_time - exrea_time, end_time + exrea_time, start_time, time => { manualClock.CurrentTime = time; }); diff --git a/osu.Framework.Font/Graphics/Shaders/IApplicableToCharacterSize.cs b/osu.Framework.Font/Graphics/Shaders/IApplicableToCharacterSize.cs new file mode 100644 index 0000000..3713b76 --- /dev/null +++ b/osu.Framework.Font/Graphics/Shaders/IApplicableToCharacterSize.cs @@ -0,0 +1,12 @@ +// Copyright (c) karaoke.dev . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Primitives; + +namespace osu.Framework.Graphics.Shaders +{ + public interface IApplicableToCharacterSize + { + RectangleF ComputeCharacterDrawRectangle(RectangleF originalCharacterDrawRectangle); + } +} diff --git a/osu.Framework.Font/Graphics/Shaders/IApplicableToDrawQuad.cs b/osu.Framework.Font/Graphics/Shaders/IApplicableToDrawQuad.cs new file mode 100644 index 0000000..bdcde53 --- /dev/null +++ b/osu.Framework.Font/Graphics/Shaders/IApplicableToDrawQuad.cs @@ -0,0 +1,12 @@ +// Copyright (c) karaoke.dev . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Primitives; + +namespace osu.Framework.Graphics.Shaders +{ + public interface IApplicableToDrawQuad + { + Quad ComputeScreenSpaceDrawQuad(Quad originDrawQuad); + } +} diff --git a/osu.Framework.Font/Graphics/Shaders/OutlineShader.cs b/osu.Framework.Font/Graphics/Shaders/OutlineShader.cs index 0d332dd..b3c46e0 100644 --- a/osu.Framework.Font/Graphics/Shaders/OutlineShader.cs +++ b/osu.Framework.Font/Graphics/Shaders/OutlineShader.cs @@ -1,13 +1,15 @@ // Copyright (c) karaoke.dev . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Graphics.OpenGL.Buffers; +using osu.Framework.Graphics.Primitives; using osuTK; using osuTK.Graphics; namespace osu.Framework.Graphics.Shaders { - public class OutlineShader : InternalShader + public class OutlineShader : InternalShader, IApplicableToCharacterSize, IApplicableToDrawQuad { public override string ShaderName => "Outline"; @@ -31,5 +33,14 @@ public override void ApplyValue(FrameBuffer current) var size = current.Size; GetUniform(@"g_TexSize").UpdateValue(ref size); } + + public RectangleF ComputeCharacterDrawRectangle(RectangleF originalCharacterDrawRectangle) + => originalCharacterDrawRectangle.Inflate(Math.Max(Radius, 0)); + + public Quad ComputeScreenSpaceDrawQuad(Quad originDrawQuad) + { + var rectangle = ComputeCharacterDrawRectangle(originDrawQuad.AABBFloat); + return Quad.FromRectangle(rectangle); + } } } diff --git a/osu.Framework.Font/Graphics/Shaders/ShadowShader.cs b/osu.Framework.Font/Graphics/Shaders/ShadowShader.cs index ccc5186..b0e3d8d 100644 --- a/osu.Framework.Font/Graphics/Shaders/ShadowShader.cs +++ b/osu.Framework.Font/Graphics/Shaders/ShadowShader.cs @@ -1,13 +1,15 @@ // Copyright (c) karaoke.dev . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Graphics.OpenGL.Buffers; +using osu.Framework.Graphics.Primitives; using osuTK; using osuTK.Graphics; namespace osu.Framework.Graphics.Shaders { - public class ShadowShader : InternalShader + public class ShadowShader : InternalShader, IApplicableToDrawQuad { public override string ShaderName => "Shadow"; @@ -26,5 +28,18 @@ public override void ApplyValue(FrameBuffer current) var size = current.Size; GetUniform(@"g_TexSize").UpdateValue(ref size); } + + public Quad ComputeScreenSpaceDrawQuad(Quad originDrawQuad) + { + var rectangle = originDrawQuad.AABBFloat.Inflate(new MarginPadding + { + Left = Math.Max(-ShadowOffset.X, 0), + Right = Math.Max(ShadowOffset.X, 0), + Top = Math.Max(-ShadowOffset.Y, 0), + Bottom = Math.Max(ShadowOffset.Y, 0), + }); + + return Quad.FromRectangle(rectangle); + } } } diff --git a/osu.Framework.Font/Graphics/Shaders/StepShader.cs b/osu.Framework.Font/Graphics/Shaders/StepShader.cs index 344afe5..ebbb0c0 100644 --- a/osu.Framework.Font/Graphics/Shaders/StepShader.cs +++ b/osu.Framework.Font/Graphics/Shaders/StepShader.cs @@ -5,10 +5,11 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics.OpenGL.Buffers; +using osu.Framework.Graphics.Primitives; namespace osu.Framework.Graphics.Shaders { - public class StepShader : IStepShader + public class StepShader : IStepShader, IApplicableToCharacterSize, IApplicableToDrawQuad { public string Name { get; set; } @@ -45,5 +46,13 @@ public bool IsLoaded public void ApplyValue(FrameBuffer current) => throw new NotSupportedException(); + + public RectangleF ComputeCharacterDrawRectangle(RectangleF originalCharacterDrawRectangle) => + StepShaders.OfType() + .Aggregate(originalCharacterDrawRectangle, (rectangle, shader) => shader.ComputeCharacterDrawRectangle(rectangle)); + + public Quad ComputeScreenSpaceDrawQuad(Quad originDrawQuad) => + StepShaders.OfType() + .Aggregate(originDrawQuad, (quad, shader) => shader.ComputeScreenSpaceDrawQuad(quad)); } } diff --git a/osu.Framework.Font/Graphics/Sprites/KaraokeSpriteText.cs b/osu.Framework.Font/Graphics/Sprites/KaraokeSpriteText.cs index f396107..5008b1b 100644 --- a/osu.Framework.Font/Graphics/Sprites/KaraokeSpriteText.cs +++ b/osu.Framework.Font/Graphics/Sprites/KaraokeSpriteText.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shaders; using osu.Framework.Layout; using osuTK; @@ -428,6 +429,8 @@ protected override bool OnInvalidate(Invalidation invalidation, InvalidationSour public virtual void RefreshStateTransforms() { + // todo: IApplicableToCharacterSize should affect padding in the masking container also. + // set initial width. // we should get width from child object because draw width haven't updated. var width = frontLyricText.Width; @@ -439,7 +442,6 @@ public virtual void RefreshStateTransforms() backLyricTextContainer.ClearTransforms(); // filter valid time-tag with order. - var characters = frontLyricText.Characters; var validTimeTag = TimeTags .Where(x => x.Key.Index >= 0 && x.Key.Index < Text.Length) .OrderBy(x => x.Value).ToArray(); @@ -456,8 +458,7 @@ public virtual void RefreshStateTransforms() foreach ((var textIndex, double time) in validTimeTag) { // calculate position and duration relative to precious time-tag time. - var characterRectangle = characters[textIndex.Index].DrawRectangle; - var position = textIndex.State == TextIndex.IndexState.Start ? characterRectangle.Left : characterRectangle.Right; + var position = getCharacterPosition(textIndex); var duration = Math.Max(time - relativeTime, 0); // apply the position with delay time. @@ -468,5 +469,27 @@ public virtual void RefreshStateTransforms() relativeTime = time; } } + + private float getCharacterPosition(TextIndex index) + { + var characterRectangle = getCharacterRectangle(index); + var computedRectangle = getComputeCharacterDrawRectangle(index.State, characterRectangle); + return index.State == TextIndex.IndexState.Start ? computedRectangle.Left : computedRectangle.Right; + + RectangleF getCharacterRectangle(TextIndex textIndex) + { + var characters = textIndex.State == TextIndex.IndexState.Start ? frontLyricText.Characters : backLyricText.Characters; + return characters[textIndex.Index].DrawRectangle; + } + + RectangleF getComputeCharacterDrawRectangle(TextIndex.IndexState state, RectangleF originalCharacterDrawRectangle) + { + // combine the rectangle to get the max value. + var lyricTextShaders = state == TextIndex.IndexState.Start ? LeftLyricTextShaders : RightLyricTextShaders; + return lyricTextShaders.OfType() + .Select(x => x.ComputeCharacterDrawRectangle(originalCharacterDrawRectangle)) + .Aggregate(originalCharacterDrawRectangle, RectangleF.Union); + } + } } } diff --git a/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs b/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs index 9026712..a0b5ac0 100644 --- a/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs +++ b/osu.Framework.Font/Graphics/Sprites/LyricSpriteText.cs @@ -113,7 +113,7 @@ public IReadOnlyList Shaders if (value != null) shaders.AddRange(value); - Invalidate(Invalidation.DrawNode); + Invalidate(); } } diff --git a/osu.Framework.Font/Graphics/Sprites/LyricSpriteText_DrawNode.cs b/osu.Framework.Font/Graphics/Sprites/LyricSpriteText_DrawNode.cs index 1d69013..d57a8d9 100644 --- a/osu.Framework.Font/Graphics/Sprites/LyricSpriteText_DrawNode.cs +++ b/osu.Framework.Font/Graphics/Sprites/LyricSpriteText_DrawNode.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using osu.Framework.Graphics.Extensions; +using System.Linq; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shaders; @@ -17,8 +17,12 @@ public partial class LyricSpriteText protected override Quad ComputeScreenSpaceDrawQuad() { // make draw size become bigger (for not masking the shader). - var newRectangle = DrawRectangle.Scale(2); - return ToScreenSpace(newRectangle); + var quad = ToScreenSpace(DrawRectangle); + var drawRectangele = Shaders.OfType() + .Select(x => x.ComputeScreenSpaceDrawQuad(quad).AABBFloat) + .Aggregate(quad.AABBFloat, RectangleF.Union); + + return Quad.FromRectangle(drawRectangele); } protected override DrawNode CreateDrawNode()