Skip to content

Commit 6aff5e0

Browse files
authored
Merge pull request #18 from maiatoday/feature/capture-to-bitmap
[feat] Adds bitmap rendering mode for patchables
2 parents 20a8dac + a8ea712 commit 6aff5e0

6 files changed

Lines changed: 155 additions & 35 deletions

File tree

app/src/main/java/de/berlindroid/zepatch/MainActivity.kt

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import android.os.Bundle
44
import androidx.activity.ComponentActivity
55
import androidx.activity.compose.setContent
66
import androidx.activity.enableEdgeToEdge
7-
import androidx.compose.foundation.border
87
import androidx.compose.foundation.layout.Arrangement
98
import androidx.compose.foundation.layout.Box
109
import androidx.compose.foundation.layout.Column
@@ -47,9 +46,10 @@ import androidx.compose.runtime.remember
4746
import androidx.compose.runtime.rememberCoroutineScope
4847
import androidx.compose.runtime.setValue
4948
import androidx.compose.ui.Modifier
50-
import androidx.compose.ui.graphics.Color
5149
import androidx.compose.ui.input.nestedscroll.nestedScroll
5250
import androidx.compose.ui.unit.dp
51+
import de.berlindroid.zepatch.ui.PatchableBoundingBox
52+
import de.berlindroid.zepatch.ui.PatchableToBitmap
5353
import de.berlindroid.zepatch.ui.theme.ZePatchTheme
5454
import kotlinx.coroutines.launch
5555

@@ -159,20 +159,6 @@ private fun PatchableList(
159159
}
160160
}
161161

162-
@Composable
163-
private fun PatchableBoundingBox(
164-
modifier: Modifier = Modifier,
165-
patchable: @Composable () -> Unit
166-
) {
167-
Box(
168-
modifier = modifier.border(
169-
width = 1.dp,
170-
color = Color.Black,
171-
),
172-
) {
173-
patchable()
174-
}
175-
}
176162

177163
@ExperimentalMaterial3Api
178164
@Composable
@@ -229,12 +215,12 @@ private fun PatchableDetail(
229215
}
230216
when (currentMode) {
231217
PatchablePreviewMode.COMPOSABLE -> PatchableBoundingBox(patchable = patchable)
232-
PatchablePreviewMode.TBD -> Text("Not implemented")
218+
PatchablePreviewMode.BITMAP -> PatchableToBitmap(patchable = patchable)
233219
}
234220
}
235221
}
236222
}
237223

238224
private enum class PatchablePreviewMode {
239-
COMPOSABLE, TBD
225+
COMPOSABLE, BITMAP
240226
}
Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package de.berlindroid.zepatch.patchable
22

3-
import androidx.compose.foundation.layout.Box
4-
import androidx.compose.foundation.layout.padding
53
import androidx.compose.material3.Text
64
import androidx.compose.runtime.Composable
75
import androidx.compose.ui.Modifier
86
import androidx.compose.ui.tooling.preview.Preview
7+
import de.berlindroid.zepatch.ui.SafeArea
98
import androidx.compose.ui.unit.dp
109
import de.berlindroid.zepatch.annotations.Patch
1110

@@ -24,22 +23,8 @@ fun Demo2() {
2423
Text("Another Demo")
2524
}
2625
}
27-
28-
// TODO: share this?
29-
@Composable
30-
fun SafeArea(
31-
modifier: Modifier = Modifier,
32-
content: @Composable () -> Unit,
33-
) {
34-
Box(
35-
modifier = modifier.padding(32.dp),
36-
) {
37-
content()
38-
}
39-
}
40-
4126
@Preview
4227
@Composable
4328
fun PreviewDemo() {
44-
29+
Demo()
4530
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package de.berlindroid.zepatch.ui
2+
3+
import androidx.compose.foundation.Image
4+
import androidx.compose.foundation.layout.Box
5+
import androidx.compose.foundation.layout.fillMaxWidth
6+
import androidx.compose.material3.CircularProgressIndicator
7+
import androidx.compose.runtime.Composable
8+
import androidx.compose.runtime.getValue
9+
import androidx.compose.runtime.mutableStateOf
10+
import androidx.compose.runtime.remember
11+
import androidx.compose.runtime.setValue
12+
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.graphics.ImageBitmap
14+
import de.berlindroid.zepatch.utils.CaptureToBitmap
15+
16+
/**
17+
* Renders the provided [patchable] into a Bitmap-like [ImageBitmap] and displays it via [Image].
18+
* Uses a composable utility that captures the composed content from a graphics layer.
19+
*/
20+
@Composable
21+
fun PatchableToBitmap(
22+
modifier: Modifier = Modifier,
23+
patchable: @Composable () -> Unit,
24+
) {
25+
var image by remember { mutableStateOf<ImageBitmap?>(null) }
26+
27+
Box(modifier = modifier.fillMaxWidth()) {
28+
// Compose the content through the composable utility; it will invoke the callback when ready.
29+
CaptureToBitmap(
30+
modifier = Modifier.fillMaxWidth(),
31+
onBitmap = { img -> image = img },
32+
content = patchable
33+
)
34+
35+
image?.let {
36+
Image(
37+
bitmap = it,
38+
contentDescription = "patch bitmap",
39+
modifier = Modifier.fillMaxWidth()
40+
)
41+
} ?: CircularProgressIndicator()
42+
43+
}
44+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package de.berlindroid.zepatch.ui
2+
3+
import androidx.compose.foundation.border
4+
import androidx.compose.foundation.layout.Box
5+
import androidx.compose.runtime.Composable
6+
import androidx.compose.ui.Modifier
7+
import androidx.compose.ui.graphics.Color
8+
import androidx.compose.ui.unit.dp
9+
10+
@Composable
11+
fun PatchableBoundingBox(
12+
modifier: Modifier = Modifier,
13+
patchable: @Composable () -> Unit
14+
) {
15+
Box(
16+
modifier = modifier.border(
17+
width = 1.dp,
18+
color = Color.Black,
19+
),
20+
) {
21+
patchable()
22+
}
23+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package de.berlindroid.zepatch.ui
2+
3+
import androidx.compose.foundation.layout.Box
4+
import androidx.compose.foundation.layout.padding
5+
import androidx.compose.runtime.Composable
6+
import androidx.compose.ui.Modifier
7+
import androidx.compose.ui.unit.dp
8+
9+
/**
10+
* Common SafeArea wrapper to provide consistent padding around patchable content.
11+
*/
12+
@Composable
13+
fun SafeArea(
14+
modifier: Modifier = Modifier,
15+
content: @Composable () -> Unit,
16+
) {
17+
Box(
18+
modifier = modifier.padding(32.dp),
19+
) {
20+
content()
21+
}
22+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package de.berlindroid.zepatch.utils
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.clickable
5+
import androidx.compose.foundation.layout.Box
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.runtime.LaunchedEffect
8+
import androidx.compose.runtime.getValue
9+
import androidx.compose.runtime.mutableStateOf
10+
import androidx.compose.runtime.remember
11+
import androidx.compose.runtime.rememberCoroutineScope
12+
import androidx.compose.runtime.setValue
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.draw.drawWithContent
15+
import androidx.compose.ui.graphics.Color
16+
import androidx.compose.ui.graphics.ImageBitmap
17+
import androidx.compose.ui.graphics.rememberGraphicsLayer
18+
import kotlinx.coroutines.launch
19+
20+
/**
21+
* Composable utility that renders [content] into a compositing graphics layer and can
22+
* capture it into an [ImageBitmap].
23+
*
24+
* How it works:
25+
* - We draw the composable's content into a remembered graphics layer using drawWithContent.
26+
* - We then draw that content normally so it appears on screen.
27+
* - We can capture the current pixels of that layer via graphicsLayer.toImageBitmap().
28+
*
29+
* Behavior:
30+
* - If [autoCapture] is true, it will capture once on first composition and invoke [onBitmap].
31+
* - It also supports manual capture by tapping the content (clickable), which will invoke [onBitmap].
32+
*/
33+
@Composable
34+
fun CaptureToBitmap(
35+
modifier: Modifier = Modifier,
36+
onBitmap: (ImageBitmap) -> Unit,
37+
content: @Composable () -> Unit,
38+
) {
39+
val coroutineScope = rememberCoroutineScope()
40+
val graphicsLayer = rememberGraphicsLayer()
41+
42+
Box(
43+
modifier = modifier
44+
.drawWithContent {
45+
// Record content drawing into the graphics layer
46+
graphicsLayer.record {
47+
this@drawWithContent.drawContent()
48+
}
49+
}
50+
.clickable {
51+
coroutineScope.launch {
52+
val bitmap = graphicsLayer.toImageBitmap()
53+
onBitmap(bitmap)
54+
}
55+
}
56+
.background(Color.White)
57+
) {
58+
content()
59+
}
60+
}

0 commit comments

Comments
 (0)