Skip to content

Commit 37b1f9d

Browse files
committed
Closes mozilla-mobile#2043 - Add ColorProcessor from Fennec
1 parent 3a8db85 commit 37b1f9d

File tree

6 files changed

+94
-21
lines changed

6 files changed

+94
-21
lines changed

buildSrc/src/main/java/Dependencies.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ object Versions {
4444
const val espresso = "3.1.1"
4545
const val room = "2.0.0"
4646
const val paging = "2.0.0"
47+
const val palette = "1.0.0"
4748
const val lifecycle = "2.0.0"
4849
const val work = "2.0.1"
4950
const val arch_core_testing = "2.0.0"
@@ -82,6 +83,7 @@ object Dependencies {
8283
const val androidx_lifecycle_extensions = "androidx.lifecycle:lifecycle-extensions:${Versions.AndroidX.lifecycle}"
8384
const val androidx_lifecycle_compiler = "androidx.lifecycle:lifecycle-compiler:${Versions.AndroidX.lifecycle}"
8485
const val androidx_paging = "androidx.paging:paging-runtime:${Versions.AndroidX.paging}"
86+
const val androidx_palette = "androidx.palette:palette-ktx:${Versions.AndroidX.palette}"
8587
const val androidx_recyclerview = "androidx.recyclerview:recyclerview:${Versions.AndroidX.recyclerview}"
8688
const val androidx_room_runtime = "androidx.room:room-runtime:${Versions.AndroidX.room}"
8789
const val androidx_room_compiler = "androidx.room:room-compiler:${Versions.AndroidX.room}"

components/browser/icons/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ dependencies {
4242
implementation Dependencies.kotlin_stdlib
4343
implementation Dependencies.kotlin_coroutines
4444
implementation Dependencies.androidx_annotation
45+
implementation Dependencies.androidx_palette
4546
implementation Dependencies.thirdparty_disklrucache
4647

4748
testImplementation project(':support-test')

components/browser/icons/src/main/java/mozilla/components/browser/icons/BrowserIcons.kt

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ class BrowserIcons(
112112
}
113113

114114
// (3) Finally process the icon.
115-
return process(context, processors, request, resource, icon)
115+
return process(context, processors, request, resource, icon).run {
116+
if (bitmap != null) this else generator.generate(context, request)
117+
}
116118
}
117119

118120
/**
@@ -135,16 +137,11 @@ class BrowserIcons(
135137
}
136138
}
137139

138-
private fun prepare(context: Context, preparers: List<IconPreprarer>, request: IconRequest): IconRequest {
139-
var preparedRequest: IconRequest = request
140-
141-
preparers.forEach { preparer ->
142-
preparedRequest = preparer.prepare(context, preparedRequest)
140+
private fun prepare(context: Context, preparers: List<IconPreprarer>, request: IconRequest): IconRequest =
141+
preparers.fold(request) { preparedRequest, preparer ->
142+
preparer.prepare(context, preparedRequest)
143143
}
144144

145-
return preparedRequest
146-
}
147-
148145
private fun load(
149146
context: Context,
150147
request: IconRequest,
@@ -192,12 +189,7 @@ private fun process(
192189
request: IconRequest,
193190
resource: IconRequest.Resource?,
194191
icon: Icon
195-
): Icon {
196-
var processedIcon = icon
197-
198-
processors.forEach { processor ->
199-
processedIcon = processor.process(context, request, resource, processedIcon)
192+
): Icon =
193+
processors.fold(icon) { processedIcon, processor ->
194+
processor.process(context, request, resource, processedIcon)
200195
}
201-
202-
return processedIcon
203-
}

components/browser/icons/src/main/java/mozilla/components/browser/icons/decoder/AndroidIconDecoder.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ import mozilla.components.support.base.log.logger.Logger
1616
class AndroidIconDecoder : IconDecoder {
1717
private val logger = Logger("AndroidIconDecoder")
1818

19-
override fun decode(data: ByteArray, targetSize: Int, maxSize: Int, maxScaleFactor: Float): Bitmap? {
19+
override fun decode(data: ByteArray, targetSize: Int, maxSize: Int, maxScaleFactor: Float): Bitmap? =
2020
try {
2121
val bitmap = decodeBitmap(data)
2222

23-
return when {
23+
when {
2424
bitmap == null -> null
2525

2626
bitmap.width <= 0 || bitmap.height <= 0 -> {
@@ -42,9 +42,8 @@ class AndroidIconDecoder : IconDecoder {
4242
else -> bitmap
4343
}
4444
} catch (e: OutOfMemoryError) {
45-
return null
45+
null
4646
}
47-
}
4847

4948
@VisibleForTesting(otherwise = PRIVATE)
5049
internal fun decodeBitmap(data: ByteArray): Bitmap? {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
package mozilla.components.browser.icons.processor
6+
7+
import android.content.Context
8+
import androidx.palette.graphics.Palette
9+
import mozilla.components.browser.icons.Icon
10+
import mozilla.components.browser.icons.IconRequest
11+
12+
/**
13+
* [IconProcessor] implementation to extract the dominant color from the icon.
14+
*/
15+
class ColorProcessor : IconProcessor {
16+
17+
override fun process(context: Context, request: IconRequest, resource: IconRequest.Resource?, icon: Icon): Icon {
18+
if (icon.bitmap == null || icon.color != null) return icon
19+
20+
val swatch = Palette.from(icon.bitmap).generate().dominantSwatch
21+
return swatch?.run { icon.copy(color = rgb) } ?: icon
22+
}
23+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
package mozilla.components.browser.icons.processor
6+
7+
import android.graphics.Bitmap
8+
import android.graphics.Color
9+
import mozilla.components.browser.icons.Icon
10+
import mozilla.components.support.test.any
11+
import mozilla.components.support.test.mock
12+
import org.junit.Assert.assertEquals
13+
import org.junit.Assert.assertNotNull
14+
import org.junit.Test
15+
import org.junit.runner.RunWith
16+
import org.mockito.Mockito.anyInt
17+
import org.mockito.Mockito.doAnswer
18+
import org.mockito.Mockito.doReturn
19+
import org.robolectric.RobolectricTestRunner
20+
21+
@RunWith(RobolectricTestRunner::class)
22+
class ColorProcessorTest {
23+
@Test
24+
fun `test extracting color`() {
25+
val icon = Icon(mockRedBitmap(1), source = Icon.Source.DISK)
26+
val processed = ColorProcessor().process(mock(), mock(), mock(), icon)
27+
28+
assertEquals(icon.bitmap, processed.bitmap)
29+
assertNotNull(processed.color)
30+
}
31+
32+
@Test
33+
fun `test extracting color from larger bitmap`() {
34+
val icon = Icon(mockRedBitmap(3), source = Icon.Source.DISK)
35+
val processed = ColorProcessor().process(mock(), mock(), mock(), icon)
36+
37+
assertEquals(icon.bitmap, processed.bitmap)
38+
assertNotNull(processed.color)
39+
}
40+
41+
private fun mockRedBitmap(size: Int): Bitmap {
42+
val bitmap: Bitmap = mock()
43+
doReturn(size).`when`(bitmap).height
44+
doReturn(size).`when`(bitmap).width
45+
46+
doAnswer {
47+
val pixels: IntArray = it.getArgument(0)
48+
for (i in 0 until pixels.size) {
49+
pixels[i] = Color.RED
50+
}
51+
null
52+
}.`when`(bitmap).getPixels(any(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt())
53+
54+
return bitmap
55+
}
56+
}

0 commit comments

Comments
 (0)