Skip to content

Commit 6d5fd70

Browse files
authored
Share Playback Stories in 9:16 aspect ratio (#3345)
1 parent 0ff4314 commit 6d5fd70

File tree

3 files changed

+86
-3
lines changed

3 files changed

+86
-3
lines changed

modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/StoryCaptureController.kt

+5-3
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import androidx.compose.ui.graphics.asAndroidBitmap
1212
import androidx.compose.ui.graphics.toArgb
1313
import androidx.compose.ui.platform.LocalContext
1414
import androidx.core.graphics.applyCanvas
15+
import androidx.core.graphics.createBitmap
1516
import androidx.core.graphics.drawable.toBitmap
1617
import androidx.core.graphics.scale
1718
import au.com.shiftyjelly.pocketcasts.endofyear.ui.backgroundColor
1819
import au.com.shiftyjelly.pocketcasts.models.to.Story
20+
import au.com.shiftyjelly.pocketcasts.utils.fitToAspectRatio
1921
import au.com.shiftyjelly.pocketcasts.utils.log.LogBuffer
2022
import au.com.shiftyjelly.pocketcasts.utils.log.LogBuffer.TAG_CRASH
2123
import dev.shreyaspatil.capturable.controller.CaptureController
@@ -77,7 +79,7 @@ internal fun rememberStoryCaptureController(): StoryCaptureController {
7779
delay(50) // A small delay to settle stories animations before capturing a screenshot
7880
val controller = captureController(story)
7981
val file = runCatching {
80-
val bitmap: Bitmap = withContext(Dispatchers.Default) {
82+
val bitmap = withContext(Dispatchers.Default) {
8183
val background = controller.captureAsync().await()
8284
.asAndroidBitmap()
8385
.copy(Bitmap.Config.ARGB_8888, false)
@@ -88,7 +90,7 @@ internal fun rememberStoryCaptureController(): StoryCaptureController {
8890
.toBitmap()
8991
.scale(width = logoWidth, height = logoHeight)
9092

91-
Bitmap.createBitmap(background.width, background.height, Bitmap.Config.ARGB_8888).applyCanvas {
93+
createBitmap(background.width, background.height).applyCanvas {
9294
// Draw captured bitmap
9395
drawBitmap(background, 0f, 0f, null)
9496
// Hide bottom button behind an empty rect
@@ -110,7 +112,7 @@ internal fun rememberStoryCaptureController(): StoryCaptureController {
110112
(height - buttonHeightPx + (buttonHeightPx - pcLogo.height) / 2).toFloat(),
111113
null,
112114
)
113-
}
115+
}.fitToAspectRatio(9f / 16)
114116
}
115117
withContext(Dispatchers.IO) {
116118
val file = File(context.cacheDir, "pocket-casts-playback-screenshot.png")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package au.com.shiftyjelly.pocketcasts.utils
2+
3+
import android.graphics.Bitmap
4+
import android.graphics.Color
5+
import android.graphics.Paint
6+
import androidx.annotation.ColorInt
7+
import androidx.core.graphics.applyCanvas
8+
import kotlin.math.abs
9+
import kotlin.math.roundToInt
10+
11+
fun Bitmap.fitToAspectRatio(
12+
aspectRatio: Float,
13+
@ColorInt backgroundColor: Int = Color.BLACK,
14+
bitmapConfig: Bitmap.Config = Bitmap.Config.ARGB_8888,
15+
): Bitmap {
16+
val (newWidth, newHeight) = calculateNearestSize(width, height, aspectRatio)
17+
return Bitmap.createBitmap(newWidth, newHeight, bitmapConfig).applyCanvas {
18+
val paint = Paint().apply {
19+
isDither = true
20+
color = backgroundColor
21+
}
22+
drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
23+
drawBitmap(
24+
this@fitToAspectRatio,
25+
abs(width - this@fitToAspectRatio.width).toFloat() / 2,
26+
abs(height - this@fitToAspectRatio.height).toFloat() / 2,
27+
null,
28+
)
29+
}
30+
}
31+
32+
internal fun calculateNearestSize(
33+
width: Int,
34+
height: Int,
35+
aspectRatio: Float,
36+
): Pair<Int, Int> {
37+
check(aspectRatio > 0) { "Aspect ratio must be a positive number: $aspectRatio" }
38+
return when (width.toFloat() / height) {
39+
aspectRatio -> width to height
40+
in 0f..aspectRatio -> (height * aspectRatio).roundToInt() to height
41+
else -> width to (width / aspectRatio).roundToInt()
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package au.com.shiftyjelly.pocketcasts.utils
2+
3+
import org.junit.Assert.assertEquals
4+
import org.junit.Test
5+
6+
class BitmapUtilTest {
7+
@Test
8+
fun `fail for zero aspect ratio`() {
9+
try {
10+
calculateNearestSize(0, 0, aspectRatio = 0f)
11+
} catch (e: IllegalStateException) {
12+
assertEquals("Aspect ratio must be a positive number: 0.0", e.message)
13+
}
14+
}
15+
16+
@Test
17+
fun `fail for negative aspect ratio`() {
18+
try {
19+
calculateNearestSize(0, 0, aspectRatio = -1f)
20+
} catch (e: IllegalStateException) {
21+
assertEquals("Aspect ratio must be a positive number: -1.0", e.message)
22+
}
23+
}
24+
25+
@Test
26+
fun `calculate to fit horizontally`() {
27+
val size = calculateNearestSize(width = 100, height = 200, aspectRatio = 9f / 16)
28+
29+
assertEquals(113 to 200, size)
30+
}
31+
32+
@Test
33+
fun `calculate to fit vertically`() {
34+
val size = calculateNearestSize(width = 200, height = 100, aspectRatio = 9f / 16)
35+
36+
assertEquals(200 to 356, size)
37+
}
38+
}

0 commit comments

Comments
 (0)