Skip to content

Commit

Permalink
Merge pull request #11 from PatilShreyas/v1.0.2-dev
Browse files Browse the repository at this point in the history
Release v1.0.2
  • Loading branch information
PatilShreyas committed Jan 23, 2022
2 parents fbc5567 + 219fe0c commit f75509d
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 11 deletions.
6 changes: 3 additions & 3 deletions capturable/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ POM_PACKAGING=aar
POM_INCEPTION_YEAR=2022

GROUP=dev.shreyaspatil
VERSION_NAME=1.0.1
VERSION_CODE=2
VERSION_NAME=1.0.2
VERSION_CODE=3

POM_URL=https://github.com/PatilShreyas/Capturable/

Expand All @@ -21,4 +21,4 @@ POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/PatilShreyas/Capturable.git

POM_DEVELOPER_ID=patilshreyas
POM_DEVELOPER_NAME=Shreyas Patil
POM_DEVELOPER_URL=https://github.com/PatilShreyas/
POM_DEVELOPER_URL=https://github.com/PatilShreyas/
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class CapturableTest {
composeTestRule.setContent {
Capturable(
controller = controller,
onCaptured = { bitmaps.add(it) }
onCaptured = { bitmap, _ -> bitmaps.add(bitmap!!) }
) {
Box(Modifier.size(contentWidth, contentHeight)) {
Text("Hello! Inside Capturable")
Expand All @@ -62,6 +62,9 @@ class CapturableTest {
// When: Content is captured
controller.capture()

// Wait for some time to get a callback
Thread.sleep(500)

// Then: Content should be get captured ONLY ONCE and stored as bitmap
assert(bitmaps.size == 1)

Expand Down
78 changes: 72 additions & 6 deletions capturable/src/main/java/dev/shreyaspatil/capturable/Capturable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,24 @@
*/
package dev.shreyaspatil.capturable

import android.app.Activity
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.view.PixelCopy
import android.view.View
import android.view.Window
import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.doOnLayout
import androidx.core.view.drawToBitmap
Expand All @@ -41,6 +51,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

/**
Expand All @@ -52,8 +63,9 @@ import kotlin.coroutines.suspendCoroutine
* val captureController = rememberCaptureController()
* Capturable(
* controller = captureController,
* onCaptured = { bitmap ->
* onCaptured = { bitmap, error ->
* // Do something with [bitmap]
* // Handle [error] if required
* }
* ) {
* // Composable content
Expand All @@ -78,8 +90,9 @@ fun Capturable(
onCaptured: (ImageBitmap?, Throwable?) -> Unit,
content: @Composable () -> Unit
) {
val context = LocalContext.current
AndroidView(
factory = { ComposeView(it).applyCapturability(controller, onCaptured, content) },
factory = { ComposeView(it).applyCapturability(controller, onCaptured, content, context) },
modifier = modifier
)
}
Expand All @@ -90,13 +103,14 @@ fun Capturable(
private inline fun ComposeView.applyCapturability(
controller: CaptureController,
noinline onCaptured: (ImageBitmap?, Throwable?) -> Unit,
crossinline content: @Composable () -> Unit
crossinline content: @Composable () -> Unit,
context: Context
) = apply {
setContent {
content()
LaunchedEffect(controller, onCaptured) {
controller.captureRequests
.mapNotNull { config -> drawToBitmapPostLaidOut(config) }
.mapNotNull { config -> drawToBitmapPostLaidOut(context, config) }
.onEach { bitmap -> onCaptured(bitmap.asImageBitmap(), null) }
.catch { error -> onCaptured(null, error) }
.launchIn(this)
Expand All @@ -107,10 +121,62 @@ private inline fun ComposeView.applyCapturability(
/**
* Waits till this [View] is laid off and then draws it to the [Bitmap] with specified [config].
*/
private suspend fun View.drawToBitmapPostLaidOut(config: Bitmap.Config): Bitmap {
private suspend fun View.drawToBitmapPostLaidOut(context: Context, config: Bitmap.Config): Bitmap {
return suspendCoroutine { continuation ->
doOnLayout { view ->
continuation.resume(view.drawToBitmap(config))
// For device with API version O(26) and above should draw Bitmap using PixelCopy API.
// The reason behind this is it throws IllegalArgumentException saying
// "Software rendering doesn't support hardware bitmaps"
// See this issue for the reference: https://github.com/PatilShreyas/Capturable/issues/7
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val window = (context as? Activity)?.window
?: error("Can't get window from the Context")

drawBitmapWithPixelCopy(
view = view,
window = window,
config = config,
onDrawn = { bitmap -> continuation.resume(bitmap) },
onError = { error -> continuation.resumeWithException(error) }
)
} else {
continuation.resume(view.drawToBitmap(config))
}
}
}
}

/**
* Draws a [view] to a [Bitmap] with [config] using a [PixelCopy] API.
* Gives callback [onDrawn] after successfully drawing Bitmap otherwise invokes [onError].
*/
@RequiresApi(Build.VERSION_CODES.O)
private fun drawBitmapWithPixelCopy(
view: View,
window: Window,
config: Bitmap.Config,
onDrawn: (Bitmap) -> Unit,
onError: (Throwable) -> Unit
) {
val width = view.width
val height = view.height

val bitmap = Bitmap.createBitmap(width, height, config)

val (x, y) = IntArray(2).apply { view.getLocationInWindow(this) }
val rect = Rect(x, y, x + width, y + height)

PixelCopy.request(
window,
rect,
bitmap,
{ copyResult ->
if (copyResult == PixelCopy.SUCCESS) {
onDrawn(bitmap)
} else {
onError(RuntimeException("Failed to draw bitmap"))
}
},
Handler(Looper.getMainLooper())
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ <h1 class="cover"><span><span>Capturable</span></span></h1>
<div>
<div class="platform-hinted " data-platform-hinted="data-platform-hinted"><div class="content sourceset-depenent-content" data-active="" data-togglable=":capturable:dokkaHtml/release"><div class="symbol monospace"><div class="block"><div class="block">@<span data-unresolved-link="androidx.compose.runtime/Composable///PointingToDeclaration/">Composable</span></div></div>fun <a href="-capturable.html">Capturable</a>(controller: <a href="../dev.shreyaspatil.capturable.controller/-capture-controller/index.html">CaptureController</a>, modifier: <span data-unresolved-link="androidx.compose.ui/Modifier///PointingToDeclaration/">Modifier</span> = Modifier, onCaptured: (<span data-unresolved-link="androidx.compose.ui.graphics/ImageBitmap///PointingToDeclaration/">ImageBitmap</span>?, <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-throwable/index.html">Throwable</a>?) -&gt; <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-unit/index.html">Unit</a>, content: <span>@<span data-unresolved-link="androidx.compose.runtime/Composable///PointingToDeclaration/">Composable</span> </span>() -&gt; <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-unit/index.html">Unit</a>)<span class="top-right-position"><span class="copy-icon"></span><div class="copy-popup-wrapper popup-to-left"><span class="copy-popup-icon"></span><span>Content copied to clipboard</span></div></span></div></div></div>
</div>
<p class="paragraph">A composable with <a href="-capturable.html">content</a> which supports to capture <span data-unresolved-link="androidx.compose.ui.graphics/ImageBitmap///PointingToDeclaration/">ImageBitmap</span> from a <a href="-capturable.html">content</a>.</p><p class="paragraph">Example usage:</p><div class="sample-container"><code class="" theme="idea"><pre>val captureController = rememberCaptureController()<br>Capturable(<br> controller = captureController,<br> onCaptured = { bitmap -&gt;<br> // Do something with [bitmap]<br> }<br>) {<br> // Composable content<br>}<br><br>Button(onClick = {<br> // Capture content<br> captureController.capture()<br>}) { ... }</pre></code></div><h2 class="">Parameters</h2><div data-togglable="Parameters"><div class="platform-hinted WithExtraAttributes" data-platform-hinted="data-platform-hinted" data-togglable="Parameters"><div class="content sourceset-depenent-content" data-active="" data-togglable=":capturable:dokkaHtml/release"><div data-togglable="Parameters"><div class="table" data-togglable="Parameters"><div class="table-row" data-filterable-current=":capturable:dokkaHtml/release" data-filterable-set=":capturable:dokkaHtml/release"><div class="main-subrow keyValue WithExtraAttributes"><div class=""><span class="inline-flex"><span><span>controller</span></span></span></div><div><div class="title"><div data-togglable="Parameters"><p class="paragraph">A <a href="../dev.shreyaspatil.capturable.controller/-capture-controller/index.html">CaptureController</a> which gives control to capture the <a href="-capturable.html">content</a>.</p></div></div></div></div></div><div class="table-row" data-filterable-current=":capturable:dokkaHtml/release" data-filterable-set=":capturable:dokkaHtml/release"><div class="main-subrow keyValue WithExtraAttributes"><div class=""><span class="inline-flex"><span><span>modifier</span></span></span></div><div><div class="title"><div data-togglable="Parameters"><p class="paragraph">The <span data-unresolved-link="androidx.compose.ui/Modifier///PointingToDeclaration/">Modifier</span> to be applied to the layout.</p></div></div></div></div></div><div class="table-row" data-filterable-current=":capturable:dokkaHtml/release" data-filterable-set=":capturable:dokkaHtml/release"><div class="main-subrow keyValue WithExtraAttributes"><div class=""><span class="inline-flex"><span>on</span><wbr></wbr><span><span>Captured</span></span></span></div><div><div class="title"><div data-togglable="Parameters"><p class="paragraph">The callback which gives back <span data-unresolved-link="androidx.compose.ui.graphics/ImageBitmap///PointingToDeclaration/">ImageBitmap</span> after composable is captured. If any error is occurred while capturing bitmap, <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-throwable/index.html">Throwable</a> is provided.</p></div></div></div></div></div><div class="table-row" data-filterable-current=":capturable:dokkaHtml/release" data-filterable-set=":capturable:dokkaHtml/release"><div class="main-subrow keyValue WithExtraAttributes"><div class=""><span class="inline-flex"><span><span>content</span></span></span></div><div><div class="title"><div data-togglable="Parameters"><p class="paragraph"><span data-unresolved-link="androidx.compose.runtime/Composable///PointingToDeclaration/">Composable</span> content to be captured.</p></div></div></div></div></div></div></div></div></div></div></div>
<p class="paragraph">A composable with <a href="-capturable.html">content</a> which supports to capture <span data-unresolved-link="androidx.compose.ui.graphics/ImageBitmap///PointingToDeclaration/">ImageBitmap</span> from a <a href="-capturable.html">content</a>.</p><p class="paragraph">Example usage:</p><div class="sample-container"><code class="" theme="idea"><pre>val captureController = rememberCaptureController()<br>Capturable(<br> controller = captureController,<br> onCaptured = { bitmap, error -&gt;<br> // Do something with [bitmap]<br> // Handle [error] if required<br> }<br>) {<br> // Composable content<br>}<br><br>Button(onClick = {<br> // Capture content<br> captureController.capture()<br>}) { ... }</pre></code></div><h2 class="">Parameters</h2><div data-togglable="Parameters"><div class="platform-hinted WithExtraAttributes" data-platform-hinted="data-platform-hinted" data-togglable="Parameters"><div class="content sourceset-depenent-content" data-active="" data-togglable=":capturable:dokkaHtml/release"><div data-togglable="Parameters"><div class="table" data-togglable="Parameters"><div class="table-row" data-filterable-current=":capturable:dokkaHtml/release" data-filterable-set=":capturable:dokkaHtml/release"><div class="main-subrow keyValue WithExtraAttributes"><div class=""><span class="inline-flex"><span><span>controller</span></span></span></div><div><div class="title"><div data-togglable="Parameters"><p class="paragraph">A <a href="../dev.shreyaspatil.capturable.controller/-capture-controller/index.html">CaptureController</a> which gives control to capture the <a href="-capturable.html">content</a>.</p></div></div></div></div></div><div class="table-row" data-filterable-current=":capturable:dokkaHtml/release" data-filterable-set=":capturable:dokkaHtml/release"><div class="main-subrow keyValue WithExtraAttributes"><div class=""><span class="inline-flex"><span><span>modifier</span></span></span></div><div><div class="title"><div data-togglable="Parameters"><p class="paragraph">The <span data-unresolved-link="androidx.compose.ui/Modifier///PointingToDeclaration/">Modifier</span> to be applied to the layout.</p></div></div></div></div></div><div class="table-row" data-filterable-current=":capturable:dokkaHtml/release" data-filterable-set=":capturable:dokkaHtml/release"><div class="main-subrow keyValue WithExtraAttributes"><div class=""><span class="inline-flex"><span>on</span><wbr></wbr><span><span>Captured</span></span></span></div><div><div class="title"><div data-togglable="Parameters"><p class="paragraph">The callback which gives back <span data-unresolved-link="androidx.compose.ui.graphics/ImageBitmap///PointingToDeclaration/">ImageBitmap</span> after composable is captured. If any error is occurred while capturing bitmap, <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-throwable/index.html">Throwable</a> is provided.</p></div></div></div></div></div><div class="table-row" data-filterable-current=":capturable:dokkaHtml/release" data-filterable-set=":capturable:dokkaHtml/release"><div class="main-subrow keyValue WithExtraAttributes"><div class=""><span class="inline-flex"><span><span>content</span></span></span></div><div><div class="title"><div data-togglable="Parameters"><p class="paragraph"><span data-unresolved-link="androidx.compose.runtime/Composable///PointingToDeclaration/">Composable</span> content to be captured.</p></div></div></div></div></div></div></div></div></div></div></div>
</div>
<div class="footer"><span class="go-to-top-icon"><a href="#content"></a></span><span>© 2022 Copyright</span><span class="pull-right"><span>Generated by </span><a href="https://github.com/Kotlin/dokka"><span>dokka</span><span class="padded-icon"></span></a></span></div>
</div>
Expand Down

0 comments on commit f75509d

Please sign in to comment.