Loading packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt +43 −32 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import android.renderscript.Allocation import android.renderscript.Element import android.renderscript.RenderScript import android.renderscript.ScriptIntrinsicBlur import android.util.Log import android.util.MathUtils import com.android.internal.graphics.ColorUtils import com.android.systemui.statusbar.notification.MediaNotificationProcessor Loading @@ -32,6 +33,7 @@ import com.android.systemui.statusbar.notification.MediaNotificationProcessor import javax.inject.Inject import javax.inject.Singleton private const val TAG = "MediaArtworkProcessor" private const val COLOR_ALPHA = (255 * 0.7f).toInt() private const val BLUR_RADIUS = 25f private const val DOWNSAMPLE = 6 Loading @@ -42,16 +44,20 @@ class MediaArtworkProcessor @Inject constructor() { private val mTmpSize = Point() private var mArtworkCache: Bitmap? = null fun processArtwork(context: Context, artwork: Bitmap): Bitmap { fun processArtwork(context: Context, artwork: Bitmap): Bitmap? { if (mArtworkCache != null) { return mArtworkCache!! return mArtworkCache } context.display.getSize(mTmpSize) val renderScript = RenderScript.create(context) val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)) var input: Allocation? = null var output: Allocation? = null var inBitmap: Bitmap? = null try { context.display.getSize(mTmpSize) val rect = Rect(0, 0, artwork.width, artwork.height) MathUtils.fitRect(rect, Math.max(mTmpSize.x / DOWNSAMPLE, mTmpSize.y / DOWNSAMPLE)) var inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(), inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(), true /* filter */) // Render script blurs only support ARGB_8888, we need a conversion if we got a // different bitmap config. Loading @@ -60,12 +66,13 @@ class MediaArtworkProcessor @Inject constructor() { inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */) oldIn.recycle() } val input = Allocation.createFromBitmap(renderScript, inBitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE) val outBitmap = Bitmap.createBitmap(inBitmap.width, inBitmap.height, Bitmap.Config.ARGB_8888) val output = Allocation.createFromBitmap(renderScript, outBitmap) val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)) input = Allocation.createFromBitmap(renderScript, inBitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE) output = Allocation.createFromBitmap(renderScript, outBitmap) blur.setRadius(BLUR_RADIUS) blur.setInput(input) blur.forEach(output) Loading @@ -73,14 +80,18 @@ class MediaArtworkProcessor @Inject constructor() { val swatch = MediaNotificationProcessor.findBackgroundSwatch(artwork) input.destroy() output.destroy() inBitmap.recycle() blur.destroy() val canvas = Canvas(outBitmap) canvas.drawColor(ColorUtils.setAlphaComponent(swatch.rgb, COLOR_ALPHA)) return outBitmap } catch (ex: IllegalArgumentException) { Log.e(TAG, "error while processing artwork", ex) return null } finally { input?.destroy() output?.destroy() blur.destroy() inBitmap?.recycle() } } fun clearCache() { Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt 0 → 100644 +109 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.statusbar import com.google.common.truth.Truth.assertThat import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color import android.graphics.Point import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith private const val WIDTH = 200 private const val HEIGHT = 200 @RunWith(AndroidTestingRunner::class) @SmallTest class MediaArtworkProcessorTest : SysuiTestCase() { private var screenWidth = 0 private var screenHeight = 0 private lateinit var processor: MediaArtworkProcessor @Before fun setUp() { processor = MediaArtworkProcessor() val point = Point() context.display.getSize(point) screenWidth = point.x screenHeight = point.y } @After fun tearDown() { processor.clearCache() } @Test fun testProcessArtwork() { // GIVEN some "artwork", which is just a solid blue image val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) Canvas(artwork).drawColor(Color.BLUE) // WHEN the background is created from the artwork val background = processor.processArtwork(context, artwork)!! // THEN the background has the size of the screen that has been downsamples assertThat(background.height).isLessThan(screenHeight) assertThat(background.width).isLessThan(screenWidth) assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888) } @Test fun testCache() { // GIVEN a solid blue image val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) Canvas(artwork).drawColor(Color.BLUE) // WHEN the background is processed twice val background1 = processor.processArtwork(context, artwork)!! val background2 = processor.processArtwork(context, artwork)!! // THEN the two bitmaps are the same // Note: This is currently broken and trying to use caching causes issues assertThat(background1).isNotSameAs(background2) } @Test fun testConfig() { // GIVEN some which is not ARGB_8888 val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ALPHA_8) Canvas(artwork).drawColor(Color.BLUE) // WHEN the background is created from the artwork val background = processor.processArtwork(context, artwork)!! // THEN the background has Config ARGB_8888 assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888) } @Test fun testRecycledArtwork() { // GIVEN some "artwork", which is just a solid blue image val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) Canvas(artwork).drawColor(Color.BLUE) // AND the artwork is recycled artwork.recycle() // WHEN the background is created from the artwork val background = processor.processArtwork(context, artwork) // THEN the processed bitmap is null assertThat(background).isNull() } } No newline at end of file Loading
packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt +43 −32 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import android.renderscript.Allocation import android.renderscript.Element import android.renderscript.RenderScript import android.renderscript.ScriptIntrinsicBlur import android.util.Log import android.util.MathUtils import com.android.internal.graphics.ColorUtils import com.android.systemui.statusbar.notification.MediaNotificationProcessor Loading @@ -32,6 +33,7 @@ import com.android.systemui.statusbar.notification.MediaNotificationProcessor import javax.inject.Inject import javax.inject.Singleton private const val TAG = "MediaArtworkProcessor" private const val COLOR_ALPHA = (255 * 0.7f).toInt() private const val BLUR_RADIUS = 25f private const val DOWNSAMPLE = 6 Loading @@ -42,16 +44,20 @@ class MediaArtworkProcessor @Inject constructor() { private val mTmpSize = Point() private var mArtworkCache: Bitmap? = null fun processArtwork(context: Context, artwork: Bitmap): Bitmap { fun processArtwork(context: Context, artwork: Bitmap): Bitmap? { if (mArtworkCache != null) { return mArtworkCache!! return mArtworkCache } context.display.getSize(mTmpSize) val renderScript = RenderScript.create(context) val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)) var input: Allocation? = null var output: Allocation? = null var inBitmap: Bitmap? = null try { context.display.getSize(mTmpSize) val rect = Rect(0, 0, artwork.width, artwork.height) MathUtils.fitRect(rect, Math.max(mTmpSize.x / DOWNSAMPLE, mTmpSize.y / DOWNSAMPLE)) var inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(), inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(), true /* filter */) // Render script blurs only support ARGB_8888, we need a conversion if we got a // different bitmap config. Loading @@ -60,12 +66,13 @@ class MediaArtworkProcessor @Inject constructor() { inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */) oldIn.recycle() } val input = Allocation.createFromBitmap(renderScript, inBitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE) val outBitmap = Bitmap.createBitmap(inBitmap.width, inBitmap.height, Bitmap.Config.ARGB_8888) val output = Allocation.createFromBitmap(renderScript, outBitmap) val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)) input = Allocation.createFromBitmap(renderScript, inBitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE) output = Allocation.createFromBitmap(renderScript, outBitmap) blur.setRadius(BLUR_RADIUS) blur.setInput(input) blur.forEach(output) Loading @@ -73,14 +80,18 @@ class MediaArtworkProcessor @Inject constructor() { val swatch = MediaNotificationProcessor.findBackgroundSwatch(artwork) input.destroy() output.destroy() inBitmap.recycle() blur.destroy() val canvas = Canvas(outBitmap) canvas.drawColor(ColorUtils.setAlphaComponent(swatch.rgb, COLOR_ALPHA)) return outBitmap } catch (ex: IllegalArgumentException) { Log.e(TAG, "error while processing artwork", ex) return null } finally { input?.destroy() output?.destroy() blur.destroy() inBitmap?.recycle() } } fun clearCache() { Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt 0 → 100644 +109 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.statusbar import com.google.common.truth.Truth.assertThat import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color import android.graphics.Point import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith private const val WIDTH = 200 private const val HEIGHT = 200 @RunWith(AndroidTestingRunner::class) @SmallTest class MediaArtworkProcessorTest : SysuiTestCase() { private var screenWidth = 0 private var screenHeight = 0 private lateinit var processor: MediaArtworkProcessor @Before fun setUp() { processor = MediaArtworkProcessor() val point = Point() context.display.getSize(point) screenWidth = point.x screenHeight = point.y } @After fun tearDown() { processor.clearCache() } @Test fun testProcessArtwork() { // GIVEN some "artwork", which is just a solid blue image val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) Canvas(artwork).drawColor(Color.BLUE) // WHEN the background is created from the artwork val background = processor.processArtwork(context, artwork)!! // THEN the background has the size of the screen that has been downsamples assertThat(background.height).isLessThan(screenHeight) assertThat(background.width).isLessThan(screenWidth) assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888) } @Test fun testCache() { // GIVEN a solid blue image val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) Canvas(artwork).drawColor(Color.BLUE) // WHEN the background is processed twice val background1 = processor.processArtwork(context, artwork)!! val background2 = processor.processArtwork(context, artwork)!! // THEN the two bitmaps are the same // Note: This is currently broken and trying to use caching causes issues assertThat(background1).isNotSameAs(background2) } @Test fun testConfig() { // GIVEN some which is not ARGB_8888 val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ALPHA_8) Canvas(artwork).drawColor(Color.BLUE) // WHEN the background is created from the artwork val background = processor.processArtwork(context, artwork)!! // THEN the background has Config ARGB_8888 assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888) } @Test fun testRecycledArtwork() { // GIVEN some "artwork", which is just a solid blue image val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) Canvas(artwork).drawColor(Color.BLUE) // AND the artwork is recycled artwork.recycle() // WHEN the background is created from the artwork val background = processor.processArtwork(context, artwork) // THEN the processed bitmap is null assertThat(background).isNull() } } No newline at end of file