Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 05aa7826 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Limit interpolated axis values to set of valid values" into main

parents ca8ecbeb a1e84d08
Loading
Loading
Loading
Loading
+28 −65
Original line number Diff line number Diff line
@@ -22,19 +22,8 @@ import android.util.Log
import android.util.LruCache
import android.util.MathUtils
import androidx.annotation.VisibleForTesting
import java.lang.Float.max
import java.lang.Float.min
import kotlin.math.roundToInt

private const val TAG_WGHT = "wght"
private const val TAG_ITAL = "ital"

private const val FONT_WEIGHT_DEFAULT_VALUE = 400f
private const val FONT_ITALIC_MAX = 1f
private const val FONT_ITALIC_MIN = 0f
private const val FONT_ITALIC_ANIMATION_STEP = 0.1f
private const val FONT_ITALIC_DEFAULT_VALUE = 0f

/** Caches for font interpolation */
interface FontCache {
    val animationFrameCount: Int
@@ -91,11 +80,8 @@ class FontCacheImpl(override val animationFrameCount: Int = DEFAULT_FONT_CACHE_M
class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) {
    /** Linear interpolate the font variation settings. */
    fun lerp(start: Font, end: Font, progress: Float, linearProgress: Float): Font {
        if (progress == 0f) {
            return start
        } else if (progress == 1f) {
            return end
        }
        if (progress <= 0f) return start
        if (progress >= 1f) return end

        val startAxes = start.axes ?: EMPTY_AXES
        val endAxes = end.axes ?: EMPTY_AXES
@@ -110,7 +96,7 @@ class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) {
            InterpKey(start, end, (linearProgress * fontCache.animationFrameCount).roundToInt())
        fontCache.get(iKey)?.let {
            if (DEBUG) {
                Log.d(LOG_TAG, "[$progress] Interp. cache hit for $iKey")
                Log.d(LOG_TAG, "[$progress, $linearProgress] Interp. cache hit for $iKey")
            }
            return it
        }
@@ -121,37 +107,16 @@ class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) {
        // and also pre-fill the missing axes value with default value from 'fvar' table.
        val newAxes =
            lerp(startAxes, endAxes) { tag, startValue, endValue ->
                when (tag) {
                    TAG_WGHT ->
                        MathUtils.lerp(
                            startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
                            endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
                            progress,
                        )
                    TAG_ITAL ->
                        adjustItalic(
                            MathUtils.lerp(
                                startValue ?: FONT_ITALIC_DEFAULT_VALUE,
                                endValue ?: FONT_ITALIC_DEFAULT_VALUE,
                                progress,
                            )
                        )
                    else -> {
                        require(startValue != null && endValue != null) {
                            "Unable to interpolate due to unknown default axes value : $tag"
                        }
                MathUtils.lerp(startValue, endValue, progress)
            }
                }
            }

        // Check if we already make font for this axes. This is typically happens if the animation
        // happens backward.
        // happens backward and is being linearly interpolated.
        val vKey = VarFontKey(start, newAxes)
        fontCache.get(vKey)?.let {
            fontCache.put(iKey, it)
            if (DEBUG) {
                Log.d(LOG_TAG, "[$progress] Axis cache hit for $vKey")
                Log.d(LOG_TAG, "[$progress, $linearProgress] Axis cache hit for $vKey")
            }
            return it
        }
@@ -164,14 +129,14 @@ class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) {
        fontCache.put(vKey, newFont)

        // Cache misses are likely to create memory leaks, so this is logged at error level.
        Log.e(LOG_TAG, "[$progress] Cache MISS for $iKey / $vKey")
        Log.e(LOG_TAG, "[$progress, $linearProgress] Cache MISS for $iKey / $vKey")
        return newFont
    }

    private fun lerp(
        start: Array<FontVariationAxis>,
        end: Array<FontVariationAxis>,
        filter: (tag: String, left: Float?, right: Float?) -> Float,
        filter: (tag: String, left: Float, right: Float) -> Float,
    ): List<FontVariationAxis> {
        // Safe to modify result of Font#getAxes since it returns cloned object.
        start.sortBy { axis -> axis.tag }
@@ -191,39 +156,37 @@ class FontInterpolator(val fontCache: FontCache = FontCacheImpl()) {
                    else -> tagA.compareTo(tagB)
                }

            val axis =
            val tag =
                when {
                    comp == 0 -> {
                        val v = filter(tagA!!, start[i++].styleValue, end[j++].styleValue)
                        FontVariationAxis(tagA, v)
                    comp == 0 -> tagA!!
                    comp < 0 -> tagA!!
                    else -> tagB!!
                }
                    comp < 0 -> {
                        val v = filter(tagA!!, start[i++].styleValue, null)
                        FontVariationAxis(tagA, v)
                    }
                    else -> { // comp > 0
                        val v = filter(tagB!!, null, end[j++].styleValue)
                        FontVariationAxis(tagB, v)

            val axisDefinition = GSFAxes.getAxis(tag)
            require(comp == 0 || axisDefinition != null) {
                "Unable to interpolate due to unknown default axes value: $tag"
            }

            val axisValue =
                when {
                    comp == 0 -> filter(tag, start[i++].styleValue, end[j++].styleValue)
                    comp < 0 -> filter(tag, start[i++].styleValue, axisDefinition!!.defaultValue)
                    else -> filter(tag, axisDefinition!!.defaultValue, end[j++].styleValue)
                }

            result.add(axis)
            // Round axis value to valid intermediate steps. This improves the cache hit rate.
            val step = axisDefinition?.animationStep ?: DEFAULT_ANIMATION_STEP
            result.add(FontVariationAxis(tag, (axisValue / step).roundToInt() * step))
        }
        return result
    }

    // 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) =
        coerceInWithStep(value, FONT_ITALIC_MIN, FONT_ITALIC_MAX, FONT_ITALIC_ANIMATION_STEP)

    private fun coerceInWithStep(v: Float, min: Float, max: Float, step: Float) =
        (v.coerceIn(min, max) / step).toInt() * step

    companion object {
        private const val LOG_TAG = "FontInterpolator"
        private val DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG)
        private val EMPTY_AXES = arrayOf<FontVariationAxis>()
        private const val DEFAULT_ANIMATION_STEP = 1f

        // Returns true if given two font instance can be interpolated.
        fun canInterpolate(start: Font, end: Font) =
+20 −12
Original line number Diff line number Diff line
package com.android.systemui.animation
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

object GSFAxes {
    const val WEIGHT = "wght"
    const val WIDTH = "wdth"
    const val SLANT = "slnt"
    const val ROUND = "ROND"
    const val OPTICAL_SIZE = "opsz"
}
package com.android.systemui.animation

class FontVariationUtils {
    private var mWeight = -1
@@ -46,20 +54,20 @@ class FontVariationUtils {
        }
        var resultString = ""
        if (mWeight >= 0) {
            resultString += "'${GSFAxes.WEIGHT}' $mWeight"
            resultString += "'${GSFAxes.WEIGHT.tag}' $mWeight"
        }
        if (mWidth >= 0) {
            resultString +=
                (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.WIDTH}' $mWidth"
                (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.WIDTH.tag}' $mWidth"
        }
        if (mOpticalSize >= 0) {
            resultString +=
                (if (resultString.isBlank()) "" else ", ") +
                    "'${GSFAxes.OPTICAL_SIZE}' $mOpticalSize"
                    "'${GSFAxes.OPTICAL_SIZE.tag}' $mOpticalSize"
        }
        if (mRoundness >= 0) {
            resultString +=
                (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.ROUND}' $mRoundness"
                (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.ROUND.tag}' $mRoundness"
        }
        return if (isUpdated) resultString else ""
    }
+98 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.animation

data class AxisDefinition(
    val tag: String,
    val minValue: Float,
    val defaultValue: Float,
    val maxValue: Float,
    val animationStep: Float,
)

object GSFAxes {
    val WEIGHT =
        AxisDefinition(
            tag = "wght",
            minValue = 1f,
            defaultValue = 400f,
            maxValue = 1000f,
            animationStep = 10f,
        )

    val WIDTH =
        AxisDefinition(
            tag = "wdth",
            minValue = 25f,
            defaultValue = 100f,
            maxValue = 151f,
            animationStep = 1f,
        )

    val SLANT =
        AxisDefinition(
            tag = "slnt",
            minValue = 0f,
            defaultValue = 0f,
            maxValue = -10f,
            animationStep = 0.1f,
        )

    val ROUND =
        AxisDefinition(
            tag = "ROND",
            minValue = 0f,
            defaultValue = 0f,
            maxValue = 100f,
            animationStep = 1f,
        )

    val GRADE =
        AxisDefinition(
            tag = "GRAD",
            minValue = 0f,
            defaultValue = 0f,
            maxValue = 100f,
            animationStep = 1f,
        )

    val OPTICAL_SIZE =
        AxisDefinition(
            tag = "opsz",
            minValue = 6f,
            defaultValue = 18f,
            maxValue = 144f,
            animationStep = 1f,
        )

    // Not a GSF Axis, but present for FontInterpolator compatibility
    val ITALIC =
        AxisDefinition(
            tag = "ITAL",
            minValue = 0f,
            defaultValue = 0f,
            maxValue = 1f,
            animationStep = 0.1f,
        )

    private val AXIS_MAP =
        listOf(WEIGHT, WIDTH, SLANT, ROUND, GRADE, OPTICAL_SIZE, ITALIC)
            .map { def -> def.tag.toLowerCase() to def }
            .toMap()

    fun getAxis(axis: String): AxisDefinition? = AXIS_MAP[axis.toLowerCase()]
}
+8 −22
Original line number Diff line number Diff line
@@ -130,39 +130,25 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController

        private val FONT_AXES =
            listOf(
                ClockFontAxis(
                    key = GSFAxes.WEIGHT,
                GSFAxes.WEIGHT.toClockAxis(
                    type = AxisType.Float,
                    minValue = 25f,
                    currentValue = 400f,
                    maxValue = 1000f,
                    name = "Weight",
                    description = "Glyph Weight",
                ),
                ClockFontAxis(
                    key = GSFAxes.WIDTH,
                GSFAxes.WIDTH.toClockAxis(
                    type = AxisType.Float,
                    minValue = 25f,
                    currentValue = 85f,
                    maxValue = 151f,
                    name = "Width",
                    description = "Glyph Width",
                ),
                ClockFontAxis(
                    key = GSFAxes.ROUND,
                GSFAxes.ROUND.toClockAxis(
                    type = AxisType.Boolean,
                    minValue = 0f,
                    currentValue = 0f,
                    maxValue = 100f,
                    name = "Round",
                    description = "Glyph Roundness",
                ),
                ClockFontAxis(
                    key = GSFAxes.SLANT,
                GSFAxes.SLANT.toClockAxis(
                    type = AxisType.Boolean,
                    minValue = 0f,
                    currentValue = 0f,
                    maxValue = -10f,
                    name = "Slant",
                    description = "Glyph Slant",
                ),
@@ -170,10 +156,10 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController

        private val LEGACY_FLEX_SETTINGS =
            listOf(
                ClockFontAxisSetting(GSFAxes.WEIGHT, 600f),
                ClockFontAxisSetting(GSFAxes.WIDTH, 100f),
                ClockFontAxisSetting(GSFAxes.ROUND, 100f),
                ClockFontAxisSetting(GSFAxes.SLANT, 0f),
                GSFAxes.WEIGHT.toClockAxisSetting(600f),
                GSFAxes.WIDTH.toClockAxisSetting(100f),
                GSFAxes.ROUND.toClockAxisSetting(100f),
                GSFAxes.SLANT.toClockAxisSetting(0f),
            )
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -132,7 +132,7 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock:
            if (!isLargeClock) {
                axes =
                    axes.map { axis ->
                        if (axis.key == GSFAxes.WIDTH && axis.value > SMALL_CLOCK_MAX_WDTH) {
                        if (axis.key == GSFAxes.WIDTH.tag && axis.value > SMALL_CLOCK_MAX_WDTH) {
                            axis.copy(value = SMALL_CLOCK_MAX_WDTH)
                        } else {
                            axis
Loading