From 123c0d2e5a676928bdf48cdad1a5b482118ae46e Mon Sep 17 00:00:00 2001 From: Tomasz Rybakiewicz Date: Thu, 19 Jan 2023 16:34:16 -0500 Subject: [PATCH 1/3] [Android Auto] Optimized SpeedLimitWidget Bitmap usage by utilizing LruBitmapPool - Moved SpeedLimitWidget draw logic to separate Drawable classes. --- .../bugfixes/SpeedLimitWidgetOptimization.md | 1 + .../speedlimit/SpeedLimitRendererTest.kt | 99 +++++-- .../speedlimit/SpeedLimitBitmapProvider.kt | 49 +++ .../speedlimit/SpeedLimitDrawable.kt | 279 ++++++++++++++++++ .../navigation/speedlimit/SpeedLimitWidget.kt | 273 ++--------------- .../SpeedLimitBitmapProviderTest.kt | 53 ++++ 6 files changed, 476 insertions(+), 278 deletions(-) create mode 100644 libnavui-androidauto/changelog/unreleased/bugfixes/SpeedLimitWidgetOptimization.md create mode 100644 libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapProvider.kt create mode 100644 libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitDrawable.kt create mode 100644 libnavui-androidauto/src/test/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapProviderTest.kt diff --git a/libnavui-androidauto/changelog/unreleased/bugfixes/SpeedLimitWidgetOptimization.md b/libnavui-androidauto/changelog/unreleased/bugfixes/SpeedLimitWidgetOptimization.md new file mode 100644 index 00000000000..31371f7a2d8 --- /dev/null +++ b/libnavui-androidauto/changelog/unreleased/bugfixes/SpeedLimitWidgetOptimization.md @@ -0,0 +1 @@ +- Optimized `SpeedLimitWidget` memory usage. diff --git a/libnavui-androidauto/src/androidTest/java/com/mapbox/androidauto/car/navigation/speedlimit/SpeedLimitRendererTest.kt b/libnavui-androidauto/src/androidTest/java/com/mapbox/androidauto/car/navigation/speedlimit/SpeedLimitRendererTest.kt index 7fe36ea5ba6..e967b629fff 100644 --- a/libnavui-androidauto/src/androidTest/java/com/mapbox/androidauto/car/navigation/speedlimit/SpeedLimitRendererTest.kt +++ b/libnavui-androidauto/src/androidTest/java/com/mapbox/androidauto/car/navigation/speedlimit/SpeedLimitRendererTest.kt @@ -4,8 +4,9 @@ import android.Manifest import androidx.test.filters.SmallTest import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner import androidx.test.rule.GrantPermissionRule -import com.mapbox.androidauto.navigation.speedlimit.SpeedLimitWidget +import com.mapbox.androidauto.navigation.speedlimit.SpeedLimitBitmapProvider import com.mapbox.androidauto.testing.BitmapTestUtil +import com.mapbox.navigation.base.speed.model.SpeedLimitSign import org.junit.Rule import org.junit.Test import org.junit.rules.TestName @@ -32,85 +33,133 @@ class SpeedLimitRendererTest { @Test fun speed_limit_120_speed_150_mutcd() { - val bitmap = - SpeedLimitWidget.drawMutcdSpeedLimitSign(speedLimit = 120, speed = 150, warn = true) + val bitmap = SpeedLimitBitmapProvider().getBitmap( + SpeedLimitSign.MUTCD, + speedLimit = 120, + speed = 150, + warn = true + ) bitmapUtils.assertBitmapsSimilar(testName, bitmap) } @Test fun speed_limit_120_speed_90_mutcd() { - val bitmap = - SpeedLimitWidget.drawMutcdSpeedLimitSign(speedLimit = 120, speed = 90, warn = false) + val bitmap = SpeedLimitBitmapProvider().getBitmap( + SpeedLimitSign.MUTCD, + speedLimit = 120, + speed = 90, + warn = false + ) bitmapUtils.assertBitmapsSimilar(testName, bitmap) } @Test fun speed_limit_65_speed_30_mutcd() { - val bitmap = - SpeedLimitWidget.drawMutcdSpeedLimitSign(speedLimit = 65, speed = 30, warn = false) + val bitmap = SpeedLimitBitmapProvider().getBitmap( + SpeedLimitSign.MUTCD, + speedLimit = 65, + speed = 30, + warn = false + ) bitmapUtils.assertBitmapsSimilar(testName, bitmap) } @Test fun speed_limit_5_speed_30_mutcd() { - val bitmap = - SpeedLimitWidget.drawMutcdSpeedLimitSign(speedLimit = 5, speed = 30, warn = true) + val bitmap = SpeedLimitBitmapProvider().getBitmap( + SpeedLimitSign.MUTCD, + speedLimit = 5, + speed = 30, + warn = true + ) bitmapUtils.assertBitmapsSimilar(testName, bitmap) } @Test fun speed_limit_5_speed_0_mutcd() { - val bitmap = - SpeedLimitWidget.drawMutcdSpeedLimitSign(speedLimit = 5, speed = 0, warn = false) + val bitmap = SpeedLimitBitmapProvider().getBitmap( + SpeedLimitSign.MUTCD, + speedLimit = 5, + speed = 0, + warn = false + ) bitmapUtils.assertBitmapsSimilar(testName, bitmap) } @Test fun speed_limit_unknown_speed_5_mutcd() { - val bitmap = - SpeedLimitWidget.drawMutcdSpeedLimitSign(speedLimit = null, speed = 5, warn = false) + val bitmap = SpeedLimitBitmapProvider().getBitmap( + SpeedLimitSign.MUTCD, + speedLimit = null, + speed = 5, + warn = false + ) bitmapUtils.assertBitmapsSimilar(testName, bitmap) } @Test fun speed_limit_120_speed_150_vienna() { - val bitmap = - SpeedLimitWidget.drawViennaSpeedLimitSign(speedLimit = 120, speed = 150, warn = true) + val bitmap = SpeedLimitBitmapProvider().getBitmap( + SpeedLimitSign.VIENNA, + speedLimit = 120, + speed = 150, + warn = true + ) bitmapUtils.assertBitmapsSimilar(testName, bitmap) } @Test fun speed_limit_120_speed_90_vienna() { - val bitmap = - SpeedLimitWidget.drawViennaSpeedLimitSign(speedLimit = 120, speed = 90, warn = false) + val bitmap = SpeedLimitBitmapProvider().getBitmap( + SpeedLimitSign.VIENNA, + speedLimit = 120, + speed = 90, + warn = false + ) bitmapUtils.assertBitmapsSimilar(testName, bitmap) } @Test fun speed_limit_65_speed_30_vienna() { - val bitmap = - SpeedLimitWidget.drawViennaSpeedLimitSign(speedLimit = 65, speed = 30, warn = false) + val bitmap = SpeedLimitBitmapProvider().getBitmap( + SpeedLimitSign.VIENNA, + speedLimit = 65, + speed = 30, + warn = false + ) bitmapUtils.assertBitmapsSimilar(testName, bitmap) } @Test fun speed_limit_5_speed_30_vienna() { - val bitmap = - SpeedLimitWidget.drawViennaSpeedLimitSign(speedLimit = 5, speed = 30, warn = true) + val bitmap = SpeedLimitBitmapProvider().getBitmap( + SpeedLimitSign.VIENNA, + speedLimit = 5, + speed = 30, + warn = true + ) bitmapUtils.assertBitmapsSimilar(testName, bitmap) } @Test fun speed_limit_5_speed_0_vienna() { - val bitmap = - SpeedLimitWidget.drawViennaSpeedLimitSign(speedLimit = 5, speed = 0, warn = false) + val bitmap = SpeedLimitBitmapProvider().getBitmap( + SpeedLimitSign.VIENNA, + speedLimit = 5, + speed = 0, + warn = false + ) bitmapUtils.assertBitmapsSimilar(testName, bitmap) } @Test fun speed_limit_unknown_speed_5_vienna() { - val bitmap = - SpeedLimitWidget.drawViennaSpeedLimitSign(speedLimit = null, speed = 5, warn = false) + val bitmap = SpeedLimitBitmapProvider().getBitmap( + SpeedLimitSign.VIENNA, + speedLimit = null, + speed = 5, + warn = false + ) bitmapUtils.assertBitmapsSimilar(testName, bitmap) } } diff --git a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapProvider.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapProvider.kt new file mode 100644 index 00000000000..8af149072ee --- /dev/null +++ b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapProvider.kt @@ -0,0 +1,49 @@ +package com.mapbox.androidauto.navigation.speedlimit + +import android.graphics.Bitmap +import android.graphics.Canvas +import com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool +import com.mapbox.navigation.base.speed.model.SpeedLimitSign + +internal class SpeedLimitBitmapProvider { + private val mutcdDrawable: SpeedLimitDrawable = MutcdSpeedLimitDrawable() + private val viennaDrawable: SpeedLimitDrawable = ViennaSpeedLimitDrawable() + private val bitmapPool: LruBitmapPool = LruBitmapPool( + MutcdSpeedLimitDrawable.BITMAP_BYTE_SIZE + ViennaSpeedLimitDrawable.BITMAP_BYTE_SIZE + ) + + fun getBitmap( + signFormat: SpeedLimitSign, + speedLimit: Int? = null, + speed: Int = 0, + warn: Boolean = false, + ): Bitmap { + val drawable = when (signFormat) { + SpeedLimitSign.MUTCD -> mutcdDrawable + SpeedLimitSign.VIENNA -> viennaDrawable + } + drawable.speedLimit = speedLimit + drawable.speed = speed + drawable.warn = warn + + val bitmap = bitmapPool.get(signFormat) + drawable.draw(Canvas(bitmap)) + bitmapPool.put(bitmap) + return bitmap + } + + private fun LruBitmapPool.get(sign: SpeedLimitSign): Bitmap { + return when (sign) { + SpeedLimitSign.MUTCD -> get( + MutcdSpeedLimitDrawable.WIDTH, + MutcdSpeedLimitDrawable.HEIGHT, + Bitmap.Config.ARGB_8888 + ) + SpeedLimitSign.VIENNA -> get( + ViennaSpeedLimitDrawable.WIDTH, + ViennaSpeedLimitDrawable.HEIGHT, + Bitmap.Config.ARGB_8888 + ) + } + } +} diff --git a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitDrawable.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitDrawable.kt new file mode 100644 index 00000000000..d8658f26564 --- /dev/null +++ b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitDrawable.kt @@ -0,0 +1,279 @@ +package com.mapbox.androidauto.navigation.speedlimit + +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.ColorFilter +import android.graphics.Paint +import android.graphics.PixelFormat +import android.graphics.Rect +import android.graphics.RectF +import android.graphics.drawable.Drawable +import androidx.annotation.ColorInt + +internal abstract class SpeedLimitDrawable : Drawable() { + var speedLimit: Int? = null + var speed: Int = 0 + var warn: Boolean = false + + protected val titlePaint = createTextPaint(Color.BLACK, 12f) + protected val speedLimitPaintVienna = createTextPaint(Color.BLACK, textSize = 22.5f) + protected val speedPaintNormal = createTextPaint(COLOR_RED, textSize = 22.5f) + protected val speedPaintWarning = createTextPaint(Color.WHITE, textSize = 22.5f) + protected val borderPaint = createBackgroundPaint(COLOR_BORDER) + protected val backgroundPaintNormal = createBackgroundPaint(Color.WHITE) + protected val backgroundPaintWarning = createBackgroundPaint(COLOR_RED) + protected val speedLimitRect = Rect() + protected val speedRect = Rect() + + override fun setAlpha(alpha: Int) = Unit + + override fun setColorFilter(colorFilter: ColorFilter?) = Unit + + @Suppress("OVERRIDE_DEPRECATION") + override fun getOpacity(): Int = PixelFormat.OPAQUE + + companion object { + const val STROKE = 1f + const val RADIUS_SHADOW = 12f + const val OFFSET_SHADOW = 8f + const val RADIUS_SHADOW_SMALL = 4f + const val OFFSET_SHADOW_SMALL = 2f + const val BYTES_PER_ARGB_8888_PIXEL = 4 + const val SPEED_LIMIT_NO_DATA = "--" + val COLOR_BORDER = Color.parseColor("#CDD0D0") + val COLOR_RED = Color.parseColor("#BE3C30") + val COLOR_SHADOW = Color.parseColor("#1A000000") + + fun createBackgroundPaint(@ColorInt color: Int): Paint { + return createPaint(color).apply { + style = Paint.Style.FILL + } + } + + fun createTextPaint(@ColorInt color: Int, textSize: Float): Paint { + return createPaint(color).apply { + this.textSize = textSize + textAlign = Paint.Align.CENTER + } + } + + private fun createPaint(@ColorInt color: Int): Paint { + return Paint().apply { + this.color = color + isAntiAlias = true + } + } + + fun createFullRect(width: Int, height: Int, inset: Float): RectF { + return createRect(width, height - OFFSET_SHADOW - RADIUS_SHADOW, inset) + } + + fun createSquare(fullWidth: Int, inset: Float): RectF { + return createRect(fullWidth, fullWidth - 2 * RADIUS_SHADOW, inset) + } + + fun createRect(fullWidth: Int, height: Float, inset: Float): RectF { + return RectF( + RADIUS_SHADOW + inset, + inset, + fullWidth - RADIUS_SHADOW - inset, + height - inset, + ) + } + } +} + +internal class ViennaSpeedLimitDrawable : SpeedLimitDrawable() { + companion object { + const val WIDTH = 74 + const val HEIGHT = 108 + const val BITMAP_BYTE_SIZE: Long = + (WIDTH * HEIGHT * BYTES_PER_ARGB_8888_PIXEL).toLong() + + const val RADIUS = 25f + const val STROKE_SIGN = 4f + } + + private val signBorderViennaPaint = createBackgroundPaint(COLOR_RED) + private val borderRectVienna = createFullRect(WIDTH, HEIGHT, inset = 0f) + private val backgroundRectVienna = createFullRect(WIDTH, HEIGHT, STROKE) + private val signBorderRectVienna = createSquare(WIDTH, STROKE) + private val signBackgroundRectVienna = + createSquare(WIDTH, inset = STROKE + STROKE_SIGN) + + override fun draw(canvas: Canvas) { + drawShadows(canvas) + drawBackground(canvas) + drawSignBackground(canvas) + drawSignSpeedLimitText(canvas) + drawCurrentSpeedText(canvas) + } + + private fun drawShadows(canvas: Canvas) { + borderPaint.setShadowLayer(RADIUS_SHADOW_SMALL, 0f, OFFSET_SHADOW_SMALL, COLOR_SHADOW) + canvas.drawRoundRect(borderRectVienna, RADIUS, RADIUS, borderPaint) + borderPaint.setShadowLayer(RADIUS_SHADOW, 0f, OFFSET_SHADOW, COLOR_SHADOW) + } + + private fun drawBackground(canvas: Canvas) { + canvas.drawRoundRect(borderRectVienna, RADIUS, RADIUS, borderPaint) + canvas.drawRoundRect( + backgroundRectVienna, + RADIUS - STROKE, + RADIUS - STROKE, + if (warn) backgroundPaintWarning else backgroundPaintNormal, + ) + } + + private fun drawSignBackground(canvas: Canvas) { + val radiusSignBorder = RADIUS - STROKE + if (warn) { + signBorderViennaPaint.setShadowLayer( + RADIUS_SHADOW_SMALL, + 0f, + OFFSET_SHADOW_SMALL, + COLOR_SHADOW, + ) + canvas.drawRoundRect( + signBorderRectVienna, + radiusSignBorder, + radiusSignBorder, + signBorderViennaPaint, + ) + signBorderViennaPaint.setShadowLayer(RADIUS_SHADOW, 0f, OFFSET_SHADOW, COLOR_SHADOW) + } else { + signBorderViennaPaint.clearShadowLayer() + } + canvas.drawRoundRect( + signBorderRectVienna, + radiusSignBorder, + radiusSignBorder, + signBorderViennaPaint, + ) + canvas.drawRoundRect( + signBackgroundRectVienna, + radiusSignBorder - STROKE_SIGN, + radiusSignBorder - STROKE_SIGN, + backgroundPaintNormal, + ) + } + + private fun drawSignSpeedLimitText(canvas: Canvas) { + val speedLimitText = speedLimit?.toString() ?: SPEED_LIMIT_NO_DATA + speedLimitPaintVienna.getTextBounds( + speedLimitText, + 0, + speedLimitText.length, + speedLimitRect, + ) + val speedLimitY = WIDTH / 2 - RADIUS_SHADOW - speedLimitRect.exactCenterY() + canvas.drawText(speedLimitText, WIDTH / 2f, speedLimitY, speedLimitPaintVienna) + } + + private fun drawCurrentSpeedText(canvas: Canvas) { + val speedText = speed.toString() + val speedPaint = if (warn) speedPaintWarning else speedPaintNormal + speedPaint.getTextBounds(speedText, 0, speedText.length, speedRect) + val speedY = signBorderRectVienna.bottom + 16 - speedRect.exactCenterY() + canvas.drawText(speedText, WIDTH / 2f, speedY, speedPaint) + } +} + +internal class MutcdSpeedLimitDrawable : SpeedLimitDrawable() { + companion object { + const val WIDTH = 77 + const val HEIGHT = 115 + const val BITMAP_BYTE_SIZE: Long = + (WIDTH * HEIGHT * BYTES_PER_ARGB_8888_PIXEL).toLong() + const val HEIGHT_SIGN = 67f + const val RADIUS = 9f + const val STROKE_SIGN = 2f + const val TITLE_1 = "SPEED" + const val TITLE_2 = "LIMIT" + const val STROKE_PADDING = 2f + } + + private val speedLimitTextPaint = createTextPaint(Color.BLACK, textSize = 27f) + private val signBorderPaint = createBackgroundPaint(COLOR_BORDER) + + private val titleRect1 = Rect().apply { + titlePaint.getTextBounds(TITLE_1, 0, TITLE_1.length, this) + } + private val titleRect2 = Rect().apply { + titlePaint.getTextBounds(TITLE_2, 0, TITLE_2.length, this) + } + private val borderRect = createFullRect(WIDTH, HEIGHT, inset = 0f) + private val backgroundRect = createFullRect(WIDTH, HEIGHT, STROKE) + private val signBorderRect = + createRect(WIDTH, HEIGHT_SIGN, inset = STROKE + STROKE_PADDING) + private val signBackgroundRect = createRect( + WIDTH, + HEIGHT_SIGN, + inset = STROKE + STROKE_PADDING + STROKE_SIGN, + ) + + override fun draw(canvas: Canvas) { + drawShadows(canvas) + drawBackground(canvas) + drawSignBackground(canvas) + drawSignSpeedLimitText(canvas) + drawCurrentSpeedText(canvas) + } + + private fun drawShadows(canvas: Canvas) { + borderPaint.setShadowLayer(RADIUS_SHADOW_SMALL, 0f, OFFSET_SHADOW_SMALL, COLOR_SHADOW) + canvas.drawRoundRect(borderRect, RADIUS, RADIUS, borderPaint) + borderPaint.setShadowLayer(RADIUS_SHADOW, 0f, OFFSET_SHADOW, COLOR_SHADOW) + canvas.drawRoundRect(borderRect, RADIUS, RADIUS, borderPaint) + } + + private fun drawBackground(canvas: Canvas) { + canvas.drawRoundRect( + backgroundRect, + RADIUS - STROKE, + RADIUS - STROKE, + if (warn) backgroundPaintWarning else backgroundPaintNormal, + ) + } + + private fun drawSignBackground(canvas: Canvas) { + val radiusSignBorder = RADIUS - STROKE - STROKE_PADDING + canvas.drawRoundRect( + signBorderRect, + radiusSignBorder, + radiusSignBorder, + signBorderPaint, + ) + canvas.drawRoundRect( + signBackgroundRect, + radiusSignBorder - STROKE_SIGN, + radiusSignBorder - STROKE_SIGN, + backgroundPaintNormal, + ) + + val titleY1 = signBackgroundRect.top + 7.5f - titleRect1.exactCenterY() + canvas.drawText(TITLE_1, WIDTH / 2f, titleY1, titlePaint) + val titleY2 = signBackgroundRect.top + 19.5f - titleRect2.exactCenterY() + canvas.drawText(TITLE_2, WIDTH / 2f, titleY2, titlePaint) + } + + private fun drawSignSpeedLimitText(canvas: Canvas) { + val speedLimitText = speedLimit?.toString() ?: SPEED_LIMIT_NO_DATA + speedLimitTextPaint.getTextBounds( + speedLimitText, + 0, + speedLimitText.length, + speedLimitRect, + ) + val speedLimitY = signBackgroundRect.top + 41.5f - speedLimitRect.exactCenterY() + canvas.drawText(speedLimitText, WIDTH / 2f, speedLimitY, speedLimitPaintVienna) + } + + private fun drawCurrentSpeedText(canvas: Canvas) { + val speedText = speed.toString() + val speedPaint = if (warn) speedPaintWarning else speedPaintNormal + speedPaint.getTextBounds(speedText, 0, speedText.length, speedRect) + val speedY = signBorderRect.bottom + 14 - speedRect.exactCenterY() + canvas.drawText(speedText, WIDTH / 2f, speedY, speedPaint) + } +} diff --git a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitWidget.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitWidget.kt index 8c33fca5d1c..8962bdfdc8f 100644 --- a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitWidget.kt +++ b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitWidget.kt @@ -1,13 +1,5 @@ package com.mapbox.androidauto.navigation.speedlimit -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.graphics.Rect -import android.graphics.RectF -import androidx.annotation.ColorInt -import com.mapbox.androidauto.internal.logAndroidAuto import com.mapbox.maps.MapboxExperimental import com.mapbox.maps.renderer.widget.BitmapWidget import com.mapbox.maps.renderer.widget.WidgetPosition @@ -17,12 +9,25 @@ import com.mapbox.navigation.base.speed.model.SpeedLimitSign * Widget to display a speed limit sign on the map. */ @MapboxExperimental -class SpeedLimitWidget(initialSignFormat: SpeedLimitSign = SpeedLimitSign.MUTCD) : BitmapWidget( - drawSpeedLimitSign(speedLimit = null, speed = 0, initialSignFormat, warn = false), - WidgetPosition(WidgetPosition.Horizontal.RIGHT, WidgetPosition.Vertical.BOTTOM), - marginX = 14f, - marginY = 30f, +class SpeedLimitWidget private constructor( + initialSignFormat: SpeedLimitSign, + private val bitmapProvider: SpeedLimitBitmapProvider, + position: WidgetPosition, + marginX: Float, + marginY: Float +) : BitmapWidget( + bitmap = bitmapProvider.getBitmap(initialSignFormat), + position, + marginX, + marginY ) { + constructor(initialSignFormat: SpeedLimitSign = SpeedLimitSign.MUTCD) : this( + initialSignFormat = initialSignFormat, + bitmapProvider = SpeedLimitBitmapProvider(), + position = WidgetPosition(WidgetPosition.Horizontal.RIGHT, WidgetPosition.Vertical.BOTTOM), + marginX = 14f, + marginY = 30f, + ) private var lastSpeedLimit: Int? = null private var lastSpeed = 0 @@ -44,7 +49,7 @@ class SpeedLimitWidget(initialSignFormat: SpeedLimitSign = SpeedLimitSign.MUTCD) lastSignFormat = newSignFormat lastWarn = warn - updateBitmap(drawSpeedLimitSign(speedLimit, speed, newSignFormat, warn)) + updateBitmap(bitmapProvider.getBitmap(newSignFormat, speedLimit, speed, warn)) } fun update(signFormat: SpeedLimitSign?, threshold: Int) { @@ -56,244 +61,6 @@ class SpeedLimitWidget(initialSignFormat: SpeedLimitSign = SpeedLimitSign.MUTCD) lastSignFormat = newSignFormat lastWarn = warn - updateBitmap(drawSpeedLimitSign(speedLimit, speed, newSignFormat, warn)) - } - - internal companion object { - private const val TAG = "SpeedLimitWidget" - private const val WIDTH_VIENNA = 74 - private const val HEIGHT_VIENNA = 108 - private const val HEIGHT_SIGN = 67f - private const val WIDTH_MUTCD = 77 - private const val HEIGHT_MUTCD = 115 - private const val STROKE = 1f - private const val STROKE_SIGN_VIENNA = 4f - private const val STROKE_SIGN_MUTCD = 2f - private const val STROKE_PADDING = 2f - private const val RADIUS_VIENNA = 25f - private const val RADIUS_MUTCD = 9f - private const val RADIUS_SHADOW = 12f - private const val OFFSET_SHADOW = 8f - private const val RADIUS_SHADOW_SMALL = 4f - private const val OFFSET_SHADOW_SMALL = 2f - private const val TITLE_1 = "SPEED" - private const val TITLE_2 = "LIMIT" - private const val SPEED_LIMIT_NO_DATA = "--" - private val COLOR_BORDER = Color.parseColor("#CDD0D0") - private val COLOR_RED = Color.parseColor("#BE3C30") - private val COLOR_SHADOW = Color.parseColor("#1A000000") - - private val titlePaint = createTextPaint(Color.BLACK, 12f) - private val speedLimitPaintVienna = createTextPaint(Color.BLACK, textSize = 22.5f) - private val speedLimitPaintMutcd = createTextPaint(Color.BLACK, textSize = 27f) - private val speedPaintNormal = createTextPaint(COLOR_RED, textSize = 22.5f) - private val speedPaintWarning = createTextPaint(Color.WHITE, textSize = 22.5f) - private val borderPaint = createBackgroundPaint(COLOR_BORDER) - private val backgroundPaintNormal = createBackgroundPaint(Color.WHITE) - private val backgroundPaintWarning = createBackgroundPaint(COLOR_RED) - private val signBorderViennaPaint = createBackgroundPaint(COLOR_RED) - private val signBorderMutcdPaint = createBackgroundPaint(COLOR_BORDER) - private val titleRect1 = Rect().apply { - titlePaint.getTextBounds(TITLE_1, 0, TITLE_1.length, this) - } - private val titleRect2 = Rect().apply { - titlePaint.getTextBounds(TITLE_2, 0, TITLE_2.length, this) - } - private val speedLimitRect = Rect() - private val speedRect = Rect() - - private val borderRectVienna = createFullRect(WIDTH_VIENNA, HEIGHT_VIENNA, inset = 0f) - private val backgroundRectVienna = createFullRect(WIDTH_VIENNA, HEIGHT_VIENNA, STROKE) - private val signBorderRectVienna = createSquare(WIDTH_VIENNA, STROKE) - private val signBackgroundRectVienna = - createSquare(WIDTH_VIENNA, inset = STROKE + STROKE_SIGN_VIENNA) - private val borderRectMutcd = createFullRect(WIDTH_MUTCD, HEIGHT_MUTCD, inset = 0f) - private val backgroundRectMutcd = createFullRect(WIDTH_MUTCD, HEIGHT_MUTCD, STROKE) - private val signBorderRectMutcd = - createRect(WIDTH_MUTCD, HEIGHT_SIGN, inset = STROKE + STROKE_PADDING) - private val signBackgroundRectMutcd = createRect( - WIDTH_MUTCD, - HEIGHT_SIGN, - inset = STROKE + STROKE_PADDING + STROKE_SIGN_MUTCD, - ) - - private fun drawSpeedLimitSign( - speedLimit: Int?, - speed: Int, - signFormat: SpeedLimitSign, - warn: Boolean, - ): Bitmap { - return when (signFormat) { - SpeedLimitSign.MUTCD -> drawMutcdSpeedLimitSign(speedLimit, speed, warn) - SpeedLimitSign.VIENNA -> drawViennaSpeedLimitSign(speedLimit, speed, warn) - } - } - - internal fun drawViennaSpeedLimitSign(speedLimit: Int?, speed: Int, warn: Boolean): Bitmap { - logAndroidAuto( - "$TAG drawViennaSpeedLimitSign: speedLimit = " + - "$speedLimit, speed = $speed, warn = $warn", - ) - - val canvasBitmap = - Bitmap.createBitmap(WIDTH_VIENNA, HEIGHT_VIENNA, Bitmap.Config.ARGB_8888) - val canvas = Canvas(canvasBitmap) - - borderPaint.setShadowLayer(RADIUS_SHADOW_SMALL, 0f, OFFSET_SHADOW_SMALL, COLOR_SHADOW) - canvas.drawRoundRect(borderRectVienna, RADIUS_VIENNA, RADIUS_VIENNA, borderPaint) - borderPaint.setShadowLayer(RADIUS_SHADOW, 0f, OFFSET_SHADOW, COLOR_SHADOW) - canvas.drawRoundRect(borderRectVienna, RADIUS_VIENNA, RADIUS_VIENNA, borderPaint) - canvas.drawRoundRect( - backgroundRectVienna, - RADIUS_VIENNA - STROKE, - RADIUS_VIENNA - STROKE, - if (warn) backgroundPaintWarning else backgroundPaintNormal, - ) - - val radiusSignBorder = RADIUS_VIENNA - STROKE - if (warn) { - signBorderViennaPaint.setShadowLayer( - RADIUS_SHADOW_SMALL, - 0f, - OFFSET_SHADOW_SMALL, - COLOR_SHADOW, - ) - canvas.drawRoundRect( - signBorderRectVienna, - radiusSignBorder, - radiusSignBorder, - signBorderViennaPaint, - ) - signBorderViennaPaint.setShadowLayer(RADIUS_SHADOW, 0f, OFFSET_SHADOW, COLOR_SHADOW) - } else { - signBorderViennaPaint.clearShadowLayer() - } - canvas.drawRoundRect( - signBorderRectVienna, - radiusSignBorder, - radiusSignBorder, - signBorderViennaPaint, - ) - canvas.drawRoundRect( - signBackgroundRectVienna, - radiusSignBorder - STROKE_SIGN_VIENNA, - radiusSignBorder - STROKE_SIGN_VIENNA, - backgroundPaintNormal, - ) - - val speedLimitText = speedLimit?.toString() ?: SPEED_LIMIT_NO_DATA - speedLimitPaintVienna.getTextBounds( - speedLimitText, - 0, - speedLimitText.length, - speedLimitRect, - ) - val speedLimitY = WIDTH_VIENNA / 2 - RADIUS_SHADOW - speedLimitRect.exactCenterY() - canvas.drawText(speedLimitText, WIDTH_VIENNA / 2f, speedLimitY, speedLimitPaintVienna) - - val speedText = speed.toString() - val speedPaint = if (warn) speedPaintWarning else speedPaintNormal - speedPaint.getTextBounds(speedText, 0, speedText.length, speedRect) - val speedY = signBorderRectVienna.bottom + 16 - speedRect.exactCenterY() - canvas.drawText(speedText, WIDTH_VIENNA / 2f, speedY, speedPaint) - - return canvasBitmap - } - - internal fun drawMutcdSpeedLimitSign(speedLimit: Int?, speed: Int, warn: Boolean): Bitmap { - logAndroidAuto( - "$TAG drawMutcdSpeedLimitSign: speedLimit = " + - "$speedLimit, speed = $speed, warn = $warn", - ) - - val canvasBitmap = - Bitmap.createBitmap(WIDTH_MUTCD, HEIGHT_MUTCD, Bitmap.Config.ARGB_8888) - val canvas = Canvas(canvasBitmap) - - borderPaint.setShadowLayer(RADIUS_SHADOW_SMALL, 0f, OFFSET_SHADOW_SMALL, COLOR_SHADOW) - canvas.drawRoundRect(borderRectMutcd, RADIUS_MUTCD, RADIUS_MUTCD, borderPaint) - borderPaint.setShadowLayer(RADIUS_SHADOW, 0f, OFFSET_SHADOW, COLOR_SHADOW) - canvas.drawRoundRect(borderRectMutcd, RADIUS_MUTCD, RADIUS_MUTCD, borderPaint) - canvas.drawRoundRect( - backgroundRectMutcd, - RADIUS_MUTCD - STROKE, - RADIUS_MUTCD - STROKE, - if (warn) backgroundPaintWarning else backgroundPaintNormal, - ) - - val radiusSignBorder = RADIUS_MUTCD - STROKE - STROKE_PADDING - canvas.drawRoundRect( - signBorderRectMutcd, - radiusSignBorder, - radiusSignBorder, - signBorderMutcdPaint, - ) - canvas.drawRoundRect( - signBackgroundRectMutcd, - radiusSignBorder - STROKE_SIGN_MUTCD, - radiusSignBorder - STROKE_SIGN_MUTCD, - backgroundPaintNormal, - ) - - val titleY1 = signBackgroundRectMutcd.top + 7.5f - titleRect1.exactCenterY() - canvas.drawText(TITLE_1, WIDTH_MUTCD / 2f, titleY1, titlePaint) - val titleY2 = signBackgroundRectMutcd.top + 19.5f - titleRect2.exactCenterY() - canvas.drawText(TITLE_2, WIDTH_MUTCD / 2f, titleY2, titlePaint) - - val speedLimitText = speedLimit?.toString() ?: SPEED_LIMIT_NO_DATA - speedLimitPaintMutcd.getTextBounds( - speedLimitText, - 0, - speedLimitText.length, - speedLimitRect, - ) - val speedLimitY = signBackgroundRectMutcd.top + 41.5f - speedLimitRect.exactCenterY() - canvas.drawText(speedLimitText, WIDTH_MUTCD / 2f, speedLimitY, speedLimitPaintVienna) - - val speedText = speed.toString() - val speedPaint = if (warn) speedPaintWarning else speedPaintNormal - speedPaint.getTextBounds(speedText, 0, speedText.length, speedRect) - val speedY = signBorderRectMutcd.bottom + 14 - speedRect.exactCenterY() - canvas.drawText(speedText, WIDTH_MUTCD / 2f, speedY, speedPaint) - - return canvasBitmap - } - - private fun createBackgroundPaint(@ColorInt color: Int): Paint { - return createPaint(color).apply { - style = Paint.Style.FILL - } - } - - private fun createTextPaint(@ColorInt color: Int, textSize: Float): Paint { - return createPaint(color).apply { - this.textSize = textSize - textAlign = Paint.Align.CENTER - } - } - - private fun createPaint(@ColorInt color: Int): Paint { - return Paint().apply { - this.color = color - isAntiAlias = true - } - } - - private fun createFullRect(width: Int, height: Int, inset: Float): RectF { - return createRect(width, height - OFFSET_SHADOW - RADIUS_SHADOW, inset) - } - - private fun createSquare(fullWidth: Int, inset: Float): RectF { - return createRect(fullWidth, fullWidth - 2 * RADIUS_SHADOW, inset) - } - - private fun createRect(fullWidth: Int, height: Float, inset: Float): RectF { - return RectF( - RADIUS_SHADOW + inset, - inset, - fullWidth - RADIUS_SHADOW - inset, - height - inset, - ) - } + updateBitmap(bitmapProvider.getBitmap(newSignFormat, speedLimit, speed, warn)) } } diff --git a/libnavui-androidauto/src/test/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapProviderTest.kt b/libnavui-androidauto/src/test/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapProviderTest.kt new file mode 100644 index 00000000000..c5417746acd --- /dev/null +++ b/libnavui-androidauto/src/test/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapProviderTest.kt @@ -0,0 +1,53 @@ +package com.mapbox.androidauto.navigation.speedlimit + +import android.graphics.Bitmap +import android.os.Build +import com.mapbox.navigation.base.speed.model.SpeedLimitSign +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotSame +import org.junit.Assert.assertSame +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [Build.VERSION_CODES.O]) +class SpeedLimitBitmapProviderTest { + + private val sut = SpeedLimitBitmapProvider() + + @Test + fun `getBitmap - should return correctly sized bitmap for SpeedLimitSign VIENNA`() { + val bmp = sut.getBitmap(SpeedLimitSign.VIENNA) + + assertEquals(ViennaSpeedLimitDrawable.WIDTH, bmp.width) + assertEquals(ViennaSpeedLimitDrawable.HEIGHT, bmp.height) + assertEquals(Bitmap.Config.ARGB_8888, bmp.config) + } + + @Test + fun `getBitmap - should return correctly sized bitmap for SpeedLimitSign MUTCD`() { + val bmp = sut.getBitmap(SpeedLimitSign.MUTCD) + + assertEquals(MutcdSpeedLimitDrawable.WIDTH, bmp.width) + assertEquals(MutcdSpeedLimitDrawable.HEIGHT, bmp.height) + assertEquals(Bitmap.Config.ARGB_8888, bmp.config) + } + + @Test + fun `getBitmap - should return same bitmap for the same SpeedLimitSign`() { + val bmp1 = sut.getBitmap(SpeedLimitSign.VIENNA) + val bmp2 = sut.getBitmap(SpeedLimitSign.VIENNA) + + assertSame(bmp1, bmp2) + } + + @Test + fun `getBitmap - should return separate bitmaps for different SpeedLimitSign`() { + val bmp1 = sut.getBitmap(SpeedLimitSign.VIENNA) + val bmp2 = sut.getBitmap(SpeedLimitSign.MUTCD) + + assertNotSame(bmp1, bmp2) + } +} From 13b5512c53be29de65ada0acd7e4db0032d1797a Mon Sep 17 00:00:00 2001 From: runner Date: Fri, 20 Jan 2023 16:27:26 +0000 Subject: [PATCH 2/3] Rename changelog files --- .../bugfixes/{SpeedLimitWidgetOptimization.md => 6859.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename libnavui-androidauto/changelog/unreleased/bugfixes/{SpeedLimitWidgetOptimization.md => 6859.md} (100%) diff --git a/libnavui-androidauto/changelog/unreleased/bugfixes/SpeedLimitWidgetOptimization.md b/libnavui-androidauto/changelog/unreleased/bugfixes/6859.md similarity index 100% rename from libnavui-androidauto/changelog/unreleased/bugfixes/SpeedLimitWidgetOptimization.md rename to libnavui-androidauto/changelog/unreleased/bugfixes/6859.md From 321e5be57418fe24c2f12b99b88730c85ec7fcb1 Mon Sep 17 00:00:00 2001 From: Tomasz Rybakiewicz Date: Fri, 20 Jan 2023 15:30:26 -0500 Subject: [PATCH 3/3] [Android Auto] Addressing PR feedback. Renamed SpeedLimitBitmapProvider -> SpeedLimitBitmapRenderer. Moved MutcdSpeedLimitDrawable and ViennaSpeedLimitDrawable to separate files --- .../speedlimit/SpeedLimitRendererTest.kt | 26 +-- .../speedlimit/MutcdSpeedLimitDrawable.kt | 104 ++++++++++ ...rovider.kt => SpeedLimitBitmapRenderer.kt} | 2 +- .../speedlimit/SpeedLimitDrawable.kt | 196 ------------------ .../navigation/speedlimit/SpeedLimitWidget.kt | 10 +- .../speedlimit/ViennaSpeedLimitDrawable.kt | 99 +++++++++ ...est.kt => SpeedLimitBitmapRendererTest.kt} | 4 +- 7 files changed, 224 insertions(+), 217 deletions(-) create mode 100644 libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/MutcdSpeedLimitDrawable.kt rename libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/{SpeedLimitBitmapProvider.kt => SpeedLimitBitmapRenderer.kt} (97%) create mode 100644 libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/ViennaSpeedLimitDrawable.kt rename libnavui-androidauto/src/test/java/com/mapbox/androidauto/navigation/speedlimit/{SpeedLimitBitmapProviderTest.kt => SpeedLimitBitmapRendererTest.kt} (95%) diff --git a/libnavui-androidauto/src/androidTest/java/com/mapbox/androidauto/car/navigation/speedlimit/SpeedLimitRendererTest.kt b/libnavui-androidauto/src/androidTest/java/com/mapbox/androidauto/car/navigation/speedlimit/SpeedLimitRendererTest.kt index e967b629fff..23a037c7196 100644 --- a/libnavui-androidauto/src/androidTest/java/com/mapbox/androidauto/car/navigation/speedlimit/SpeedLimitRendererTest.kt +++ b/libnavui-androidauto/src/androidTest/java/com/mapbox/androidauto/car/navigation/speedlimit/SpeedLimitRendererTest.kt @@ -4,7 +4,7 @@ import android.Manifest import androidx.test.filters.SmallTest import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner import androidx.test.rule.GrantPermissionRule -import com.mapbox.androidauto.navigation.speedlimit.SpeedLimitBitmapProvider +import com.mapbox.androidauto.navigation.speedlimit.SpeedLimitBitmapRenderer import com.mapbox.androidauto.testing.BitmapTestUtil import com.mapbox.navigation.base.speed.model.SpeedLimitSign import org.junit.Rule @@ -33,7 +33,7 @@ class SpeedLimitRendererTest { @Test fun speed_limit_120_speed_150_mutcd() { - val bitmap = SpeedLimitBitmapProvider().getBitmap( + val bitmap = SpeedLimitBitmapRenderer().getBitmap( SpeedLimitSign.MUTCD, speedLimit = 120, speed = 150, @@ -44,7 +44,7 @@ class SpeedLimitRendererTest { @Test fun speed_limit_120_speed_90_mutcd() { - val bitmap = SpeedLimitBitmapProvider().getBitmap( + val bitmap = SpeedLimitBitmapRenderer().getBitmap( SpeedLimitSign.MUTCD, speedLimit = 120, speed = 90, @@ -55,7 +55,7 @@ class SpeedLimitRendererTest { @Test fun speed_limit_65_speed_30_mutcd() { - val bitmap = SpeedLimitBitmapProvider().getBitmap( + val bitmap = SpeedLimitBitmapRenderer().getBitmap( SpeedLimitSign.MUTCD, speedLimit = 65, speed = 30, @@ -66,7 +66,7 @@ class SpeedLimitRendererTest { @Test fun speed_limit_5_speed_30_mutcd() { - val bitmap = SpeedLimitBitmapProvider().getBitmap( + val bitmap = SpeedLimitBitmapRenderer().getBitmap( SpeedLimitSign.MUTCD, speedLimit = 5, speed = 30, @@ -77,7 +77,7 @@ class SpeedLimitRendererTest { @Test fun speed_limit_5_speed_0_mutcd() { - val bitmap = SpeedLimitBitmapProvider().getBitmap( + val bitmap = SpeedLimitBitmapRenderer().getBitmap( SpeedLimitSign.MUTCD, speedLimit = 5, speed = 0, @@ -88,7 +88,7 @@ class SpeedLimitRendererTest { @Test fun speed_limit_unknown_speed_5_mutcd() { - val bitmap = SpeedLimitBitmapProvider().getBitmap( + val bitmap = SpeedLimitBitmapRenderer().getBitmap( SpeedLimitSign.MUTCD, speedLimit = null, speed = 5, @@ -99,7 +99,7 @@ class SpeedLimitRendererTest { @Test fun speed_limit_120_speed_150_vienna() { - val bitmap = SpeedLimitBitmapProvider().getBitmap( + val bitmap = SpeedLimitBitmapRenderer().getBitmap( SpeedLimitSign.VIENNA, speedLimit = 120, speed = 150, @@ -110,7 +110,7 @@ class SpeedLimitRendererTest { @Test fun speed_limit_120_speed_90_vienna() { - val bitmap = SpeedLimitBitmapProvider().getBitmap( + val bitmap = SpeedLimitBitmapRenderer().getBitmap( SpeedLimitSign.VIENNA, speedLimit = 120, speed = 90, @@ -121,7 +121,7 @@ class SpeedLimitRendererTest { @Test fun speed_limit_65_speed_30_vienna() { - val bitmap = SpeedLimitBitmapProvider().getBitmap( + val bitmap = SpeedLimitBitmapRenderer().getBitmap( SpeedLimitSign.VIENNA, speedLimit = 65, speed = 30, @@ -132,7 +132,7 @@ class SpeedLimitRendererTest { @Test fun speed_limit_5_speed_30_vienna() { - val bitmap = SpeedLimitBitmapProvider().getBitmap( + val bitmap = SpeedLimitBitmapRenderer().getBitmap( SpeedLimitSign.VIENNA, speedLimit = 5, speed = 30, @@ -143,7 +143,7 @@ class SpeedLimitRendererTest { @Test fun speed_limit_5_speed_0_vienna() { - val bitmap = SpeedLimitBitmapProvider().getBitmap( + val bitmap = SpeedLimitBitmapRenderer().getBitmap( SpeedLimitSign.VIENNA, speedLimit = 5, speed = 0, @@ -154,7 +154,7 @@ class SpeedLimitRendererTest { @Test fun speed_limit_unknown_speed_5_vienna() { - val bitmap = SpeedLimitBitmapProvider().getBitmap( + val bitmap = SpeedLimitBitmapRenderer().getBitmap( SpeedLimitSign.VIENNA, speedLimit = null, speed = 5, diff --git a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/MutcdSpeedLimitDrawable.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/MutcdSpeedLimitDrawable.kt new file mode 100644 index 00000000000..2ce969d8b8c --- /dev/null +++ b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/MutcdSpeedLimitDrawable.kt @@ -0,0 +1,104 @@ +package com.mapbox.androidauto.navigation.speedlimit + +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Rect + +internal class MutcdSpeedLimitDrawable : SpeedLimitDrawable() { + companion object { + const val WIDTH = 77 + const val HEIGHT = 115 + const val BITMAP_BYTE_SIZE: Long = + (WIDTH * HEIGHT * BYTES_PER_ARGB_8888_PIXEL).toLong() + const val HEIGHT_SIGN = 67f + const val RADIUS = 9f + const val STROKE_SIGN = 2f + const val TITLE_1 = "SPEED" + const val TITLE_2 = "LIMIT" + const val STROKE_PADDING = 2f + } + + private val speedLimitTextPaint = createTextPaint(Color.BLACK, textSize = 27f) + private val signBorderPaint = createBackgroundPaint(COLOR_BORDER) + + private val titleRect1 = Rect().apply { + titlePaint.getTextBounds(TITLE_1, 0, TITLE_1.length, this) + } + private val titleRect2 = Rect().apply { + titlePaint.getTextBounds(TITLE_2, 0, TITLE_2.length, this) + } + private val borderRect = createFullRect(WIDTH, HEIGHT, inset = 0f) + private val backgroundRect = createFullRect(WIDTH, HEIGHT, STROKE) + private val signBorderRect = + createRect(WIDTH, HEIGHT_SIGN, inset = STROKE + STROKE_PADDING) + private val signBackgroundRect = createRect( + WIDTH, + HEIGHT_SIGN, + inset = STROKE + STROKE_PADDING + STROKE_SIGN, + ) + + override fun draw(canvas: Canvas) { + drawShadows(canvas) + drawBackground(canvas) + drawSignBackground(canvas) + drawSignSpeedLimitText(canvas) + drawCurrentSpeedText(canvas) + } + + private fun drawShadows(canvas: Canvas) { + borderPaint.setShadowLayer(RADIUS_SHADOW_SMALL, 0f, OFFSET_SHADOW_SMALL, COLOR_SHADOW) + canvas.drawRoundRect(borderRect, RADIUS, RADIUS, borderPaint) + borderPaint.setShadowLayer(RADIUS_SHADOW, 0f, OFFSET_SHADOW, COLOR_SHADOW) + canvas.drawRoundRect(borderRect, RADIUS, RADIUS, borderPaint) + } + + private fun drawBackground(canvas: Canvas) { + canvas.drawRoundRect( + backgroundRect, + RADIUS - STROKE, + RADIUS - STROKE, + if (warn) backgroundPaintWarning else backgroundPaintNormal, + ) + } + + private fun drawSignBackground(canvas: Canvas) { + val radiusSignBorder = RADIUS - STROKE - STROKE_PADDING + canvas.drawRoundRect( + signBorderRect, + radiusSignBorder, + radiusSignBorder, + signBorderPaint, + ) + canvas.drawRoundRect( + signBackgroundRect, + radiusSignBorder - STROKE_SIGN, + radiusSignBorder - STROKE_SIGN, + backgroundPaintNormal, + ) + + val titleY1 = signBackgroundRect.top + 7.5f - titleRect1.exactCenterY() + canvas.drawText(TITLE_1, WIDTH / 2f, titleY1, titlePaint) + val titleY2 = signBackgroundRect.top + 19.5f - titleRect2.exactCenterY() + canvas.drawText(TITLE_2, WIDTH / 2f, titleY2, titlePaint) + } + + private fun drawSignSpeedLimitText(canvas: Canvas) { + val speedLimitText = speedLimit?.toString() ?: SPEED_LIMIT_NO_DATA + speedLimitTextPaint.getTextBounds( + speedLimitText, + 0, + speedLimitText.length, + speedLimitRect, + ) + val speedLimitY = signBackgroundRect.top + 41.5f - speedLimitRect.exactCenterY() + canvas.drawText(speedLimitText, WIDTH / 2f, speedLimitY, speedLimitPaintVienna) + } + + private fun drawCurrentSpeedText(canvas: Canvas) { + val speedText = speed.toString() + val speedPaint = if (warn) speedPaintWarning else speedPaintNormal + speedPaint.getTextBounds(speedText, 0, speedText.length, speedRect) + val speedY = signBorderRect.bottom + 14 - speedRect.exactCenterY() + canvas.drawText(speedText, WIDTH / 2f, speedY, speedPaint) + } +} diff --git a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapProvider.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapRenderer.kt similarity index 97% rename from libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapProvider.kt rename to libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapRenderer.kt index 8af149072ee..85a97783675 100644 --- a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapProvider.kt +++ b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapRenderer.kt @@ -5,7 +5,7 @@ import android.graphics.Canvas import com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool import com.mapbox.navigation.base.speed.model.SpeedLimitSign -internal class SpeedLimitBitmapProvider { +internal class SpeedLimitBitmapRenderer { private val mutcdDrawable: SpeedLimitDrawable = MutcdSpeedLimitDrawable() private val viennaDrawable: SpeedLimitDrawable = ViennaSpeedLimitDrawable() private val bitmapPool: LruBitmapPool = LruBitmapPool( diff --git a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitDrawable.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitDrawable.kt index d8658f26564..b64670e78cc 100644 --- a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitDrawable.kt +++ b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitDrawable.kt @@ -1,6 +1,5 @@ package com.mapbox.androidauto.navigation.speedlimit -import android.graphics.Canvas import android.graphics.Color import android.graphics.ColorFilter import android.graphics.Paint @@ -82,198 +81,3 @@ internal abstract class SpeedLimitDrawable : Drawable() { } } } - -internal class ViennaSpeedLimitDrawable : SpeedLimitDrawable() { - companion object { - const val WIDTH = 74 - const val HEIGHT = 108 - const val BITMAP_BYTE_SIZE: Long = - (WIDTH * HEIGHT * BYTES_PER_ARGB_8888_PIXEL).toLong() - - const val RADIUS = 25f - const val STROKE_SIGN = 4f - } - - private val signBorderViennaPaint = createBackgroundPaint(COLOR_RED) - private val borderRectVienna = createFullRect(WIDTH, HEIGHT, inset = 0f) - private val backgroundRectVienna = createFullRect(WIDTH, HEIGHT, STROKE) - private val signBorderRectVienna = createSquare(WIDTH, STROKE) - private val signBackgroundRectVienna = - createSquare(WIDTH, inset = STROKE + STROKE_SIGN) - - override fun draw(canvas: Canvas) { - drawShadows(canvas) - drawBackground(canvas) - drawSignBackground(canvas) - drawSignSpeedLimitText(canvas) - drawCurrentSpeedText(canvas) - } - - private fun drawShadows(canvas: Canvas) { - borderPaint.setShadowLayer(RADIUS_SHADOW_SMALL, 0f, OFFSET_SHADOW_SMALL, COLOR_SHADOW) - canvas.drawRoundRect(borderRectVienna, RADIUS, RADIUS, borderPaint) - borderPaint.setShadowLayer(RADIUS_SHADOW, 0f, OFFSET_SHADOW, COLOR_SHADOW) - } - - private fun drawBackground(canvas: Canvas) { - canvas.drawRoundRect(borderRectVienna, RADIUS, RADIUS, borderPaint) - canvas.drawRoundRect( - backgroundRectVienna, - RADIUS - STROKE, - RADIUS - STROKE, - if (warn) backgroundPaintWarning else backgroundPaintNormal, - ) - } - - private fun drawSignBackground(canvas: Canvas) { - val radiusSignBorder = RADIUS - STROKE - if (warn) { - signBorderViennaPaint.setShadowLayer( - RADIUS_SHADOW_SMALL, - 0f, - OFFSET_SHADOW_SMALL, - COLOR_SHADOW, - ) - canvas.drawRoundRect( - signBorderRectVienna, - radiusSignBorder, - radiusSignBorder, - signBorderViennaPaint, - ) - signBorderViennaPaint.setShadowLayer(RADIUS_SHADOW, 0f, OFFSET_SHADOW, COLOR_SHADOW) - } else { - signBorderViennaPaint.clearShadowLayer() - } - canvas.drawRoundRect( - signBorderRectVienna, - radiusSignBorder, - radiusSignBorder, - signBorderViennaPaint, - ) - canvas.drawRoundRect( - signBackgroundRectVienna, - radiusSignBorder - STROKE_SIGN, - radiusSignBorder - STROKE_SIGN, - backgroundPaintNormal, - ) - } - - private fun drawSignSpeedLimitText(canvas: Canvas) { - val speedLimitText = speedLimit?.toString() ?: SPEED_LIMIT_NO_DATA - speedLimitPaintVienna.getTextBounds( - speedLimitText, - 0, - speedLimitText.length, - speedLimitRect, - ) - val speedLimitY = WIDTH / 2 - RADIUS_SHADOW - speedLimitRect.exactCenterY() - canvas.drawText(speedLimitText, WIDTH / 2f, speedLimitY, speedLimitPaintVienna) - } - - private fun drawCurrentSpeedText(canvas: Canvas) { - val speedText = speed.toString() - val speedPaint = if (warn) speedPaintWarning else speedPaintNormal - speedPaint.getTextBounds(speedText, 0, speedText.length, speedRect) - val speedY = signBorderRectVienna.bottom + 16 - speedRect.exactCenterY() - canvas.drawText(speedText, WIDTH / 2f, speedY, speedPaint) - } -} - -internal class MutcdSpeedLimitDrawable : SpeedLimitDrawable() { - companion object { - const val WIDTH = 77 - const val HEIGHT = 115 - const val BITMAP_BYTE_SIZE: Long = - (WIDTH * HEIGHT * BYTES_PER_ARGB_8888_PIXEL).toLong() - const val HEIGHT_SIGN = 67f - const val RADIUS = 9f - const val STROKE_SIGN = 2f - const val TITLE_1 = "SPEED" - const val TITLE_2 = "LIMIT" - const val STROKE_PADDING = 2f - } - - private val speedLimitTextPaint = createTextPaint(Color.BLACK, textSize = 27f) - private val signBorderPaint = createBackgroundPaint(COLOR_BORDER) - - private val titleRect1 = Rect().apply { - titlePaint.getTextBounds(TITLE_1, 0, TITLE_1.length, this) - } - private val titleRect2 = Rect().apply { - titlePaint.getTextBounds(TITLE_2, 0, TITLE_2.length, this) - } - private val borderRect = createFullRect(WIDTH, HEIGHT, inset = 0f) - private val backgroundRect = createFullRect(WIDTH, HEIGHT, STROKE) - private val signBorderRect = - createRect(WIDTH, HEIGHT_SIGN, inset = STROKE + STROKE_PADDING) - private val signBackgroundRect = createRect( - WIDTH, - HEIGHT_SIGN, - inset = STROKE + STROKE_PADDING + STROKE_SIGN, - ) - - override fun draw(canvas: Canvas) { - drawShadows(canvas) - drawBackground(canvas) - drawSignBackground(canvas) - drawSignSpeedLimitText(canvas) - drawCurrentSpeedText(canvas) - } - - private fun drawShadows(canvas: Canvas) { - borderPaint.setShadowLayer(RADIUS_SHADOW_SMALL, 0f, OFFSET_SHADOW_SMALL, COLOR_SHADOW) - canvas.drawRoundRect(borderRect, RADIUS, RADIUS, borderPaint) - borderPaint.setShadowLayer(RADIUS_SHADOW, 0f, OFFSET_SHADOW, COLOR_SHADOW) - canvas.drawRoundRect(borderRect, RADIUS, RADIUS, borderPaint) - } - - private fun drawBackground(canvas: Canvas) { - canvas.drawRoundRect( - backgroundRect, - RADIUS - STROKE, - RADIUS - STROKE, - if (warn) backgroundPaintWarning else backgroundPaintNormal, - ) - } - - private fun drawSignBackground(canvas: Canvas) { - val radiusSignBorder = RADIUS - STROKE - STROKE_PADDING - canvas.drawRoundRect( - signBorderRect, - radiusSignBorder, - radiusSignBorder, - signBorderPaint, - ) - canvas.drawRoundRect( - signBackgroundRect, - radiusSignBorder - STROKE_SIGN, - radiusSignBorder - STROKE_SIGN, - backgroundPaintNormal, - ) - - val titleY1 = signBackgroundRect.top + 7.5f - titleRect1.exactCenterY() - canvas.drawText(TITLE_1, WIDTH / 2f, titleY1, titlePaint) - val titleY2 = signBackgroundRect.top + 19.5f - titleRect2.exactCenterY() - canvas.drawText(TITLE_2, WIDTH / 2f, titleY2, titlePaint) - } - - private fun drawSignSpeedLimitText(canvas: Canvas) { - val speedLimitText = speedLimit?.toString() ?: SPEED_LIMIT_NO_DATA - speedLimitTextPaint.getTextBounds( - speedLimitText, - 0, - speedLimitText.length, - speedLimitRect, - ) - val speedLimitY = signBackgroundRect.top + 41.5f - speedLimitRect.exactCenterY() - canvas.drawText(speedLimitText, WIDTH / 2f, speedLimitY, speedLimitPaintVienna) - } - - private fun drawCurrentSpeedText(canvas: Canvas) { - val speedText = speed.toString() - val speedPaint = if (warn) speedPaintWarning else speedPaintNormal - speedPaint.getTextBounds(speedText, 0, speedText.length, speedRect) - val speedY = signBorderRect.bottom + 14 - speedRect.exactCenterY() - canvas.drawText(speedText, WIDTH / 2f, speedY, speedPaint) - } -} diff --git a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitWidget.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitWidget.kt index 8962bdfdc8f..2c6362f5e1b 100644 --- a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitWidget.kt +++ b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitWidget.kt @@ -11,19 +11,19 @@ import com.mapbox.navigation.base.speed.model.SpeedLimitSign @MapboxExperimental class SpeedLimitWidget private constructor( initialSignFormat: SpeedLimitSign, - private val bitmapProvider: SpeedLimitBitmapProvider, + private val bitmapRenderer: SpeedLimitBitmapRenderer, position: WidgetPosition, marginX: Float, marginY: Float ) : BitmapWidget( - bitmap = bitmapProvider.getBitmap(initialSignFormat), + bitmap = bitmapRenderer.getBitmap(initialSignFormat), position, marginX, marginY ) { constructor(initialSignFormat: SpeedLimitSign = SpeedLimitSign.MUTCD) : this( initialSignFormat = initialSignFormat, - bitmapProvider = SpeedLimitBitmapProvider(), + bitmapRenderer = SpeedLimitBitmapRenderer(), position = WidgetPosition(WidgetPosition.Horizontal.RIGHT, WidgetPosition.Vertical.BOTTOM), marginX = 14f, marginY = 30f, @@ -49,7 +49,7 @@ class SpeedLimitWidget private constructor( lastSignFormat = newSignFormat lastWarn = warn - updateBitmap(bitmapProvider.getBitmap(newSignFormat, speedLimit, speed, warn)) + updateBitmap(bitmapRenderer.getBitmap(newSignFormat, speedLimit, speed, warn)) } fun update(signFormat: SpeedLimitSign?, threshold: Int) { @@ -61,6 +61,6 @@ class SpeedLimitWidget private constructor( lastSignFormat = newSignFormat lastWarn = warn - updateBitmap(bitmapProvider.getBitmap(newSignFormat, speedLimit, speed, warn)) + updateBitmap(bitmapRenderer.getBitmap(newSignFormat, speedLimit, speed, warn)) } } diff --git a/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/ViennaSpeedLimitDrawable.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/ViennaSpeedLimitDrawable.kt new file mode 100644 index 00000000000..fedf3957e3e --- /dev/null +++ b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/ViennaSpeedLimitDrawable.kt @@ -0,0 +1,99 @@ +package com.mapbox.androidauto.navigation.speedlimit + +import android.graphics.Canvas + +internal class ViennaSpeedLimitDrawable : SpeedLimitDrawable() { + companion object { + const val WIDTH = 74 + const val HEIGHT = 108 + const val BITMAP_BYTE_SIZE: Long = + (WIDTH * HEIGHT * BYTES_PER_ARGB_8888_PIXEL).toLong() + + const val RADIUS = 25f + const val STROKE_SIGN = 4f + } + + private val signBorderViennaPaint = createBackgroundPaint(COLOR_RED) + private val borderRectVienna = createFullRect(WIDTH, HEIGHT, inset = 0f) + private val backgroundRectVienna = createFullRect(WIDTH, HEIGHT, STROKE) + private val signBorderRectVienna = createSquare(WIDTH, STROKE) + private val signBackgroundRectVienna = + createSquare(WIDTH, inset = STROKE + STROKE_SIGN) + + override fun draw(canvas: Canvas) { + drawShadows(canvas) + drawBackground(canvas) + drawSignBackground(canvas) + drawSignSpeedLimitText(canvas) + drawCurrentSpeedText(canvas) + } + + private fun drawShadows(canvas: Canvas) { + borderPaint.setShadowLayer(RADIUS_SHADOW_SMALL, 0f, OFFSET_SHADOW_SMALL, COLOR_SHADOW) + canvas.drawRoundRect(borderRectVienna, RADIUS, RADIUS, borderPaint) + borderPaint.setShadowLayer(RADIUS_SHADOW, 0f, OFFSET_SHADOW, COLOR_SHADOW) + } + + private fun drawBackground(canvas: Canvas) { + canvas.drawRoundRect(borderRectVienna, RADIUS, RADIUS, borderPaint) + canvas.drawRoundRect( + backgroundRectVienna, + RADIUS - STROKE, + RADIUS - STROKE, + if (warn) backgroundPaintWarning else backgroundPaintNormal, + ) + } + + private fun drawSignBackground(canvas: Canvas) { + val radiusSignBorder = RADIUS - STROKE + if (warn) { + signBorderViennaPaint.setShadowLayer( + RADIUS_SHADOW_SMALL, + 0f, + OFFSET_SHADOW_SMALL, + COLOR_SHADOW, + ) + canvas.drawRoundRect( + signBorderRectVienna, + radiusSignBorder, + radiusSignBorder, + signBorderViennaPaint, + ) + signBorderViennaPaint.setShadowLayer(RADIUS_SHADOW, 0f, OFFSET_SHADOW, COLOR_SHADOW) + } else { + signBorderViennaPaint.clearShadowLayer() + } + canvas.drawRoundRect( + signBorderRectVienna, + radiusSignBorder, + radiusSignBorder, + signBorderViennaPaint, + ) + canvas.drawRoundRect( + signBackgroundRectVienna, + radiusSignBorder - STROKE_SIGN, + radiusSignBorder - STROKE_SIGN, + backgroundPaintNormal, + ) + } + + private fun drawSignSpeedLimitText(canvas: Canvas) { + val speedLimitText = speedLimit?.toString() ?: SPEED_LIMIT_NO_DATA + speedLimitPaintVienna.getTextBounds( + speedLimitText, + 0, + speedLimitText.length, + speedLimitRect, + ) + val speedLimitY = WIDTH / 2 - RADIUS_SHADOW - speedLimitRect.exactCenterY() + canvas.drawText(speedLimitText, WIDTH / 2f, speedLimitY, speedLimitPaintVienna) + } + + private fun drawCurrentSpeedText(canvas: Canvas) { + val speedText = speed.toString() + val speedPaint = if (warn) speedPaintWarning else speedPaintNormal + speedPaint.getTextBounds(speedText, 0, speedText.length, speedRect) + val speedY = signBorderRectVienna.bottom + 16 - speedRect.exactCenterY() + canvas.drawText(speedText, WIDTH / 2f, speedY, speedPaint) + } +} diff --git a/libnavui-androidauto/src/test/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapProviderTest.kt b/libnavui-androidauto/src/test/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapRendererTest.kt similarity index 95% rename from libnavui-androidauto/src/test/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapProviderTest.kt rename to libnavui-androidauto/src/test/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapRendererTest.kt index c5417746acd..6f95d903017 100644 --- a/libnavui-androidauto/src/test/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapProviderTest.kt +++ b/libnavui-androidauto/src/test/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapRendererTest.kt @@ -13,9 +13,9 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.O]) -class SpeedLimitBitmapProviderTest { +class SpeedLimitBitmapRendererTest { - private val sut = SpeedLimitBitmapProvider() + private val sut = SpeedLimitBitmapRenderer() @Test fun `getBitmap - should return correctly sized bitmap for SpeedLimitSign VIENNA`() {