diff --git a/libnavui-androidauto/changelog/unreleased/bugfixes/6859.md b/libnavui-androidauto/changelog/unreleased/bugfixes/6859.md new file mode 100644 index 00000000000..31371f7a2d8 --- /dev/null +++ b/libnavui-androidauto/changelog/unreleased/bugfixes/6859.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..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,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.SpeedLimitBitmapRenderer 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 = SpeedLimitBitmapRenderer().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 = SpeedLimitBitmapRenderer().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 = SpeedLimitBitmapRenderer().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 = SpeedLimitBitmapRenderer().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 = SpeedLimitBitmapRenderer().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 = SpeedLimitBitmapRenderer().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 = SpeedLimitBitmapRenderer().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 = SpeedLimitBitmapRenderer().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 = SpeedLimitBitmapRenderer().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 = SpeedLimitBitmapRenderer().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 = SpeedLimitBitmapRenderer().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 = SpeedLimitBitmapRenderer().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/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/SpeedLimitBitmapRenderer.kt b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapRenderer.kt new file mode 100644 index 00000000000..85a97783675 --- /dev/null +++ b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapRenderer.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 SpeedLimitBitmapRenderer { + 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..b64670e78cc --- /dev/null +++ b/libnavui-androidauto/src/main/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitDrawable.kt @@ -0,0 +1,83 @@ +package com.mapbox.androidauto.navigation.speedlimit + +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, + ) + } + } +} 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..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 @@ -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 bitmapRenderer: SpeedLimitBitmapRenderer, + position: WidgetPosition, + marginX: Float, + marginY: Float +) : BitmapWidget( + bitmap = bitmapRenderer.getBitmap(initialSignFormat), + position, + marginX, + marginY ) { + constructor(initialSignFormat: SpeedLimitSign = SpeedLimitSign.MUTCD) : this( + initialSignFormat = initialSignFormat, + bitmapRenderer = SpeedLimitBitmapRenderer(), + 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(bitmapRenderer.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(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/SpeedLimitBitmapRendererTest.kt b/libnavui-androidauto/src/test/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapRendererTest.kt new file mode 100644 index 00000000000..6f95d903017 --- /dev/null +++ b/libnavui-androidauto/src/test/java/com/mapbox/androidauto/navigation/speedlimit/SpeedLimitBitmapRendererTest.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 SpeedLimitBitmapRendererTest { + + private val sut = SpeedLimitBitmapRenderer() + + @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) + } +}