Loading packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt +13 −29 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import android.graphics.fonts.FontVariationAxis import android.util.Log import android.util.LruCache import android.util.MathUtils import android.util.MathUtils.abs import androidx.annotation.VisibleForTesting import java.lang.Float.max import java.lang.Float.min Loading @@ -30,8 +29,6 @@ private const val TAG_WGHT = "wght" private const val TAG_ITAL = "ital" private const val FONT_WEIGHT_DEFAULT_VALUE = 400f private const val FONT_WEIGHT_ANIMATION_FRAME_COUNT = 100 private const val FONT_ITALIC_MAX = 1f private const val FONT_ITALIC_MIN = 0f private const val FONT_ITALIC_ANIMATION_STEP = 0.1f Loading @@ -39,11 +36,12 @@ private const val FONT_ITALIC_DEFAULT_VALUE = 0f // Benchmarked via Perfetto, difference between 10 and 50 entries is about 0.3ms in // frame draw time on a Pixel 6. @VisibleForTesting const val FONT_CACHE_MAX_ENTRIES = 10 @VisibleForTesting const val DEFAULT_FONT_CACHE_MAX_ENTRIES = 10 /** Provide interpolation of two fonts by adjusting font variation settings. */ class FontInterpolator { class FontInterpolator( numberOfAnimationSteps: Int? = null, ) { /** * Cache key for the interpolated font. * Loading Loading @@ -88,8 +86,9 @@ class FontInterpolator { // Font interpolator has two level caches: one for input and one for font with different // variation settings. No synchronization is needed since FontInterpolator is not designed to be // thread-safe and can be used only on UI thread. private val interpCache = LruCache<InterpKey, Font>(FONT_CACHE_MAX_ENTRIES) private val verFontCache = LruCache<VarFontKey, Font>(FONT_CACHE_MAX_ENTRIES) val cacheMaxEntries = numberOfAnimationSteps?.let { it * 2 } ?: DEFAULT_FONT_CACHE_MAX_ENTRIES private val interpCache = LruCache<InterpKey, Font>(cacheMaxEntries) private val verFontCache = LruCache<VarFontKey, Font>(cacheMaxEntries) // Mutable keys for recycling. private val tmpInterpKey = InterpKey(null, null, 0f) Loading Loading @@ -128,18 +127,12 @@ class FontInterpolator { val newAxes = lerp(startAxes, endAxes) { tag, startValue, endValue -> when (tag) { // TODO: Good to parse 'fvar' table for retrieving default value. TAG_WGHT -> { adaptiveAdjustWeight( TAG_WGHT -> MathUtils.lerp( startValue ?: FONT_WEIGHT_DEFAULT_VALUE, endValue ?: FONT_WEIGHT_DEFAULT_VALUE, progress ), startValue ?: FONT_WEIGHT_DEFAULT_VALUE, endValue ?: FONT_WEIGHT_DEFAULT_VALUE, ) } TAG_ITAL -> adjustItalic( MathUtils.lerp( Loading Loading @@ -175,9 +168,9 @@ class FontInterpolator { val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build() interpCache.put(InterpKey(start, end, progress), newFont) verFontCache.put(VarFontKey(start, newAxes), newFont) if (DEBUG) { Log.d(LOG_TAG, "[$progress] Cache MISS for $tmpInterpKey / $tmpVarFontKey") } // Cache misses are likely to create memory leaks, so this is logged at error level. Log.e(LOG_TAG, "[$progress] Cache MISS for $tmpInterpKey / $tmpVarFontKey") return newFont } Loading Loading @@ -225,15 +218,6 @@ class FontInterpolator { return result } // For the performance reasons, we animate weight with adaptive step. This helps // Cache hit ratio in the Skia glyph cache. // The reason we don't use fix step is because the range of weight axis is not normalized, // some are from 50 to 100, others are from 0 to 1000, so we cannot give a constant proper step private fun adaptiveAdjustWeight(value: Float, start: Float, end: Float): Float { val step = max(abs(end - start) / FONT_WEIGHT_ANIMATION_FRAME_COUNT, 1F) return coerceInWithStep(value, min(start, end), max(start, end), step) } // For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps // Cache hit ratio in the Skia glyph cache. private fun adjustItalic(value: Float) = Loading packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +2 −1 Original line number Diff line number Diff line Loading @@ -108,7 +108,8 @@ class TextAnimator( } // Following two members are for mutable for testing purposes. public var textInterpolator: TextInterpolator = TextInterpolator(layout, typefaceCache) public var textInterpolator: TextInterpolator = TextInterpolator(layout, typefaceCache, numberOfAnimationSteps) public var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply { duration = DEFAULT_ANIMATION_DURATION Loading packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt +2 −1 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import java.lang.Math.max class TextInterpolator( layout: Layout, var typefaceCache: TypefaceVariantCache, numberOfAnimationSteps: Int? = null, ) { /** * Returns base paint used for interpolation. Loading Loading @@ -85,7 +86,7 @@ class TextInterpolator( private class Line(val runs: List<Run>) private var lines = listOf<Line>() private val fontInterpolator = FontInterpolator() private val fontInterpolator = FontInterpolator(numberOfAnimationSteps) // Recycling object for glyph drawing and tweaking. private val tmpPaint = TextPaint() Loading packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt +5 −5 Original line number Diff line number Diff line Loading @@ -37,7 +37,7 @@ class FontInterpolatorTest : SysuiTestCase() { private fun assertSameAxes(expect: Font, actual: Font) { val expectAxes = expect.axes?.also { it.sortBy { axis -> axis.tag } } val actualAxes = actual.axes?.also { it.sortBy { axis -> axis.tag } } assertThat(expectAxes).isEqualTo(actualAxes) assertThat(actualAxes).isEqualTo(expectAxes) } private fun assertSameAxes(expectVarSettings: String, actual: Font) { Loading @@ -46,7 +46,7 @@ class FontInterpolatorTest : SysuiTestCase() { it.sortBy { axis -> axis.tag } } val actualAxes = actual.axes?.also { it.sortBy { axis -> axis.tag } } assertThat(expectAxes).isEqualTo(actualAxes) assertThat(actualAxes).isEqualTo(expectAxes) } @Test Loading @@ -61,7 +61,7 @@ class FontInterpolatorTest : SysuiTestCase() { val interp = FontInterpolator() assertSameAxes(startFont, interp.lerp(startFont, endFont, 0f)) assertSameAxes(endFont, interp.lerp(startFont, endFont, 1f)) assertSameAxes("'wght' 496, 'ital' 0.5, 'GRAD' 450", interp.lerp(startFont, endFont, 0.5f)) assertSameAxes("'wght' 500, 'ital' 0.5, 'GRAD' 450", interp.lerp(startFont, endFont, 0.5f)) } @Test Loading @@ -74,7 +74,7 @@ class FontInterpolatorTest : SysuiTestCase() { .build() val interp = FontInterpolator() assertSameAxes("'wght' 249, 'ital' 0.5", interp.lerp(startFont, endFont, 0.5f)) assertSameAxes("'wght' 250, 'ital' 0.5", interp.lerp(startFont, endFont, 0.5f)) } @Test Loading Loading @@ -118,7 +118,7 @@ class FontInterpolatorTest : SysuiTestCase() { .setFontVariationSettings("'wght' 1") .build() val resultFont = interp.lerp(startFont, endFont, 0.5f) for (i in 0..FONT_CACHE_MAX_ENTRIES + 1) { for (i in 0..interp.cacheMaxEntries + 1) { val f1 = Font.Builder(sFont) .setFontVariationSettings("'wght' ${i * 100}") .build() Loading Loading
packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt +13 −29 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import android.graphics.fonts.FontVariationAxis import android.util.Log import android.util.LruCache import android.util.MathUtils import android.util.MathUtils.abs import androidx.annotation.VisibleForTesting import java.lang.Float.max import java.lang.Float.min Loading @@ -30,8 +29,6 @@ private const val TAG_WGHT = "wght" private const val TAG_ITAL = "ital" private const val FONT_WEIGHT_DEFAULT_VALUE = 400f private const val FONT_WEIGHT_ANIMATION_FRAME_COUNT = 100 private const val FONT_ITALIC_MAX = 1f private const val FONT_ITALIC_MIN = 0f private const val FONT_ITALIC_ANIMATION_STEP = 0.1f Loading @@ -39,11 +36,12 @@ private const val FONT_ITALIC_DEFAULT_VALUE = 0f // Benchmarked via Perfetto, difference between 10 and 50 entries is about 0.3ms in // frame draw time on a Pixel 6. @VisibleForTesting const val FONT_CACHE_MAX_ENTRIES = 10 @VisibleForTesting const val DEFAULT_FONT_CACHE_MAX_ENTRIES = 10 /** Provide interpolation of two fonts by adjusting font variation settings. */ class FontInterpolator { class FontInterpolator( numberOfAnimationSteps: Int? = null, ) { /** * Cache key for the interpolated font. * Loading Loading @@ -88,8 +86,9 @@ class FontInterpolator { // Font interpolator has two level caches: one for input and one for font with different // variation settings. No synchronization is needed since FontInterpolator is not designed to be // thread-safe and can be used only on UI thread. private val interpCache = LruCache<InterpKey, Font>(FONT_CACHE_MAX_ENTRIES) private val verFontCache = LruCache<VarFontKey, Font>(FONT_CACHE_MAX_ENTRIES) val cacheMaxEntries = numberOfAnimationSteps?.let { it * 2 } ?: DEFAULT_FONT_CACHE_MAX_ENTRIES private val interpCache = LruCache<InterpKey, Font>(cacheMaxEntries) private val verFontCache = LruCache<VarFontKey, Font>(cacheMaxEntries) // Mutable keys for recycling. private val tmpInterpKey = InterpKey(null, null, 0f) Loading Loading @@ -128,18 +127,12 @@ class FontInterpolator { val newAxes = lerp(startAxes, endAxes) { tag, startValue, endValue -> when (tag) { // TODO: Good to parse 'fvar' table for retrieving default value. TAG_WGHT -> { adaptiveAdjustWeight( TAG_WGHT -> MathUtils.lerp( startValue ?: FONT_WEIGHT_DEFAULT_VALUE, endValue ?: FONT_WEIGHT_DEFAULT_VALUE, progress ), startValue ?: FONT_WEIGHT_DEFAULT_VALUE, endValue ?: FONT_WEIGHT_DEFAULT_VALUE, ) } TAG_ITAL -> adjustItalic( MathUtils.lerp( Loading Loading @@ -175,9 +168,9 @@ class FontInterpolator { val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build() interpCache.put(InterpKey(start, end, progress), newFont) verFontCache.put(VarFontKey(start, newAxes), newFont) if (DEBUG) { Log.d(LOG_TAG, "[$progress] Cache MISS for $tmpInterpKey / $tmpVarFontKey") } // Cache misses are likely to create memory leaks, so this is logged at error level. Log.e(LOG_TAG, "[$progress] Cache MISS for $tmpInterpKey / $tmpVarFontKey") return newFont } Loading Loading @@ -225,15 +218,6 @@ class FontInterpolator { return result } // For the performance reasons, we animate weight with adaptive step. This helps // Cache hit ratio in the Skia glyph cache. // The reason we don't use fix step is because the range of weight axis is not normalized, // some are from 50 to 100, others are from 0 to 1000, so we cannot give a constant proper step private fun adaptiveAdjustWeight(value: Float, start: Float, end: Float): Float { val step = max(abs(end - start) / FONT_WEIGHT_ANIMATION_FRAME_COUNT, 1F) return coerceInWithStep(value, min(start, end), max(start, end), step) } // For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps // Cache hit ratio in the Skia glyph cache. private fun adjustItalic(value: Float) = Loading
packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +2 −1 Original line number Diff line number Diff line Loading @@ -108,7 +108,8 @@ class TextAnimator( } // Following two members are for mutable for testing purposes. public var textInterpolator: TextInterpolator = TextInterpolator(layout, typefaceCache) public var textInterpolator: TextInterpolator = TextInterpolator(layout, typefaceCache, numberOfAnimationSteps) public var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply { duration = DEFAULT_ANIMATION_DURATION Loading
packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt +2 −1 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import java.lang.Math.max class TextInterpolator( layout: Layout, var typefaceCache: TypefaceVariantCache, numberOfAnimationSteps: Int? = null, ) { /** * Returns base paint used for interpolation. Loading Loading @@ -85,7 +86,7 @@ class TextInterpolator( private class Line(val runs: List<Run>) private var lines = listOf<Line>() private val fontInterpolator = FontInterpolator() private val fontInterpolator = FontInterpolator(numberOfAnimationSteps) // Recycling object for glyph drawing and tweaking. private val tmpPaint = TextPaint() Loading
packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt +5 −5 Original line number Diff line number Diff line Loading @@ -37,7 +37,7 @@ class FontInterpolatorTest : SysuiTestCase() { private fun assertSameAxes(expect: Font, actual: Font) { val expectAxes = expect.axes?.also { it.sortBy { axis -> axis.tag } } val actualAxes = actual.axes?.also { it.sortBy { axis -> axis.tag } } assertThat(expectAxes).isEqualTo(actualAxes) assertThat(actualAxes).isEqualTo(expectAxes) } private fun assertSameAxes(expectVarSettings: String, actual: Font) { Loading @@ -46,7 +46,7 @@ class FontInterpolatorTest : SysuiTestCase() { it.sortBy { axis -> axis.tag } } val actualAxes = actual.axes?.also { it.sortBy { axis -> axis.tag } } assertThat(expectAxes).isEqualTo(actualAxes) assertThat(actualAxes).isEqualTo(expectAxes) } @Test Loading @@ -61,7 +61,7 @@ class FontInterpolatorTest : SysuiTestCase() { val interp = FontInterpolator() assertSameAxes(startFont, interp.lerp(startFont, endFont, 0f)) assertSameAxes(endFont, interp.lerp(startFont, endFont, 1f)) assertSameAxes("'wght' 496, 'ital' 0.5, 'GRAD' 450", interp.lerp(startFont, endFont, 0.5f)) assertSameAxes("'wght' 500, 'ital' 0.5, 'GRAD' 450", interp.lerp(startFont, endFont, 0.5f)) } @Test Loading @@ -74,7 +74,7 @@ class FontInterpolatorTest : SysuiTestCase() { .build() val interp = FontInterpolator() assertSameAxes("'wght' 249, 'ital' 0.5", interp.lerp(startFont, endFont, 0.5f)) assertSameAxes("'wght' 250, 'ital' 0.5", interp.lerp(startFont, endFont, 0.5f)) } @Test Loading Loading @@ -118,7 +118,7 @@ class FontInterpolatorTest : SysuiTestCase() { .setFontVariationSettings("'wght' 1") .build() val resultFont = interp.lerp(startFont, endFont, 0.5f) for (i in 0..FONT_CACHE_MAX_ENTRIES + 1) { for (i in 0..interp.cacheMaxEntries + 1) { val f1 = Font.Builder(sFont) .setFontVariationSettings("'wght' ${i * 100}") .build() Loading