Skip to content

Commit

Permalink
Merge pull request #25 from PatilShreyas/release-1.0.3
Browse files Browse the repository at this point in the history
Release v1.0.3
  • Loading branch information
PatilShreyas authored Mar 20, 2022
2 parents 1c81e53 + 113f132 commit 5d2ef5b
Show file tree
Hide file tree
Showing 26 changed files with 772 additions and 664 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ In `build.gradle` of app module, include this dependency

```gradle
dependencies {
implementation "dev.shreyaspatil:capturable:1.0.2"
implementation "dev.shreyaspatil:capturable:1.0.3"
}
```

Expand Down Expand Up @@ -101,6 +101,10 @@ captureController.capture(Bitmap.Config.ALPHA_8)
That's all needed!

#### ⚠️ Precaution

While capturing the content on the devices having Android OS **version O and above (API 26+)** and having network images like Coil, Picasso, Glide, etc it may throw error like `java.lang.IllegalArgumentException: Software rendering doesn't support hardware bitmaps`. To overcome such issues, this library uses [`PixelCopy`](https://developer.android.com/reference/android/view/PixelCopy) API to capture Bitmap as a fallback mechanism. `PixelCopy` has some limitations such as it can't generate bitmap if composable content is clipped inside app's Window, beyond or above screen i.e. due to scrolling, etc. So make sure not to include any UI content inside `Composable` which uses hardware bitmaps.

## 📄 API Documentation

[**Visit the API documentation of this library**](https://patilshreyas.github.io/Capturable) to get more information in detail.
Expand Down
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
agpVersion = '7.0.4'
kotlinVersion = '1.5.31'
agpVersion = '7.1.1'
kotlinVersion = '1.6.10'
coroutinesVersion = '1.6.0'
androidCoreVersion = '1.7.0'
composeVersion = '1.0.5'
composeVersion = '1.1.0'
jUnitVersion = '4.13.2'
androidJUnitTestVersion = '1.1.3'
spotlessVersion = '6.3.0'
Expand Down
2 changes: 0 additions & 2 deletions capturable/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ android {
defaultConfig {
minSdk 21
targetSdk 31
versionCode VERSION_CODE.toInteger()
versionName VERSION_NAME

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
Expand Down
2 changes: 1 addition & 1 deletion capturable/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ POM_PACKAGING=aar
POM_INCEPTION_YEAR=2022

GROUP=dev.shreyaspatil
VERSION_NAME=1.0.2
VERSION_NAME=1.0.3
VERSION_CODE=3

POM_URL=https://github.com/PatilShreyas/Capturable/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import dev.shreyaspatil.capturable.controller.CaptureController
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import java.math.RoundingMode

class CapturableTest {

Expand Down Expand Up @@ -71,13 +72,18 @@ class CapturableTest {
// Then: Dimension of bitmap should be same as content's dimension
val bitmap = bitmaps.first()

val expectedHeight = with(composeTestRule.density) { contentHeight.toPx() }
val expectedWidth = with(composeTestRule.density) { contentWidth.toPx() }
val expectedHeight = with(composeTestRule.density) { contentHeight.toPx() }.roundToInt()
val expectedWidth = with(composeTestRule.density) { contentWidth.toPx() }.roundToInt()

val actualHeight = bitmap.height.toFloat()
val actualWidth = bitmap.width.toFloat()
val actualHeight = bitmap.height
val actualWidth = bitmap.width

assertEquals(actualHeight, expectedHeight)
assertEquals(actualWidth, expectedWidth)
assertEquals(expectedHeight, actualHeight)
assertEquals(expectedWidth, actualWidth)
}

/**
* Converts float value to the integer value by rounding up to ceiling.
*/
private fun Float.roundToInt(): Int = toBigDecimal().setScale(0, RoundingMode.CEILING).toInt()
}
50 changes: 34 additions & 16 deletions capturable/src/main/java/dev/shreyaspatil/capturable/Capturable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ package dev.shreyaspatil.capturable

import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.graphics.Bitmap
import android.graphics.Rect
import android.os.Build
Expand Down Expand Up @@ -124,23 +125,28 @@ private inline fun ComposeView.applyCapturability(
private suspend fun View.drawToBitmapPostLaidOut(context: Context, config: Bitmap.Config): Bitmap {
return suspendCoroutine { continuation ->
doOnLayout { view ->
// 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 {
try {
// Initially, try to capture bitmap using drawToBitmap extension function
continuation.resume(view.drawToBitmap(config))
} catch (e: IllegalArgumentException) {
// 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.findActivity().window

drawBitmapWithPixelCopy(
view = view,
window = window,
config = config,
onDrawn = { bitmap -> continuation.resume(bitmap) },
onError = { error -> continuation.resumeWithException(error) }
)
} else {
continuation.resumeWithException(e)
}
}
}
}
Expand Down Expand Up @@ -180,3 +186,15 @@ private fun drawBitmapWithPixelCopy(
Handler(Looper.getMainLooper())
)
}

/**
* Traverses through this [Context] and finds [Activity] wrapped inside it.
*/
internal fun Context.findActivity(): Activity {
var context = this
while (context is ContextWrapper) {
if (context is Activity) return context
context = context.baseContext
}
throw IllegalStateException("Unable to retrieve Activity from the current context")
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,34 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" charset="UTF-8">
<title>capture</title>
<link href="../../../images/logo-icon.svg" rel="icon" type="image/svg"><script>var pathToRoot = "../../../";</script><script type="text/javascript" src="../../../scripts/sourceset_dependencies.js" async="async"></script><link href="../../../styles/style.css" rel="Stylesheet"><link href="../../../styles/logo-styles.css" rel="Stylesheet"><link href="../../../styles/jetbrains-mono.css" rel="Stylesheet"><link href="../../../styles/main.css" rel="Stylesheet"><script type="text/javascript" src="../../../scripts/clipboard.js" async="async"></script><script type="text/javascript" src="../../../scripts/navigation-loader.js" async="async"></script><script type="text/javascript" src="../../../scripts/platform-content-handler.js" async="async"></script><script type="text/javascript" src="../../../scripts/main.js" async="async"></script> </head>
<link href="../../../images/logo-icon.svg" rel="icon" type="image/svg"><script>var pathToRoot = "../../../";</script> <script>const storage = localStorage.getItem("dokka-dark-mode")
const savedDarkMode = storage ? JSON.parse(storage) : false
if(savedDarkMode === true){
document.getElementsByTagName("html")[0].classList.add("theme-dark")
}</script>
<script type="text/javascript" src="../../../scripts/sourceset_dependencies.js" async="async"></script><link href="../../../styles/style.css" rel="Stylesheet"><link href="../../../styles/jetbrains-mono.css" rel="Stylesheet"><link href="../../../styles/main.css" rel="Stylesheet"><link href="../../../styles/prism.css" rel="Stylesheet"><link href="../../../styles/logo-styles.css" rel="Stylesheet"><script type="text/javascript" src="../../../scripts/clipboard.js" async="async"></script><script type="text/javascript" src="../../../scripts/navigation-loader.js" async="async"></script><script type="text/javascript" src="../../../scripts/platform-content-handler.js" async="async"></script><script type="text/javascript" src="../../../scripts/main.js" defer="defer"></script><script type="text/javascript" src="../../../scripts/prism.js" async="async"></script> </head>
<body>
<div class="navigation-wrapper" id="navigation-wrapper">
<div id="leftToggler"><span class="icon-toggler"></span></div>
<div class="library-name"><a href="../../../index.html">capturable</a></div>
<div></div>
<div class="pull-right d-flex"><button id="theme-toggle-button"><span id="theme-toggle"></span></button>
<div id="searchBar"></div>
</div>
</div>
<div id="container">
<div id="leftColumn"><a href="../../../index.html">
<div id="logo"></div>
</a>
<div id="paneSearch"></div>
<div id="leftColumn">
<div id="sideMenu"></div>
</div>
<div id="main">
<div id="leftToggler"><span class="icon-toggler"></span></div>
<script type="text/javascript" src="../../../scripts/main.js"></script> <div class="main-content" id="content" pageIds="capturable::dev.shreyaspatil.capturable.controller/CaptureController/capture/#android.graphics.Bitmap.Config/PointingToDeclaration//936064587">
<div class="navigation-wrapper" id="navigation-wrapper">
<div class="breadcrumbs"><a href="../../../index.html">capturable</a>/<a href="../index.html">dev.shreyaspatil.capturable.controller</a>/<a href="index.html">CaptureController</a>/<a href="capture.html">capture</a></div>
<div class="pull-right d-flex">
<div id="searchBar"></div>
</div>
</div>
<div class="main-content" id="content" pageIds="capturable::dev.shreyaspatil.capturable.controller/CaptureController/capture/#android.graphics.Bitmap.Config/PointingToDeclaration//936064587">
<div class="breadcrumbs"><a href="../../../index.html">capturable</a>/<a href="../index.html">dev.shreyaspatil.capturable.controller</a>/<a href="index.html">CaptureController</a>/<a href="capture.html">capture</a></div>
<div class="cover ">
<h1 class="cover"><span><span>capture</span></span></h1>
</div>
<div class="divergent-group" data-filterable-current=":capturable:dokkaHtml/release" data-filterable-set=":capturable:dokkaHtml/release"><div class="with-platform-tags"><span class="pull-right"></span></div>

<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">fun <a href="capture.html">capture</a>(config: <a href="https://developer.android.com/reference/kotlin/android/graphics/Bitmap.Config.html">Bitmap.Config</a> = Bitmap.Config.ARGB_8888)<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">Creates and send a Bitmap capture request with specified <a href="capture.html">config</a>.</p><p class="paragraph">Make sure to call this method as a part of callback function and not as a part of the <span data-unresolved-link="androidx.compose.runtime/Composable///PointingToDeclaration/">Composable</span> function itself.</p><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>config</span></span></span></div><div><div class="title"><div data-togglable="Parameters"><p class="paragraph">Bitmap config of the desired bitmap. Defaults to <a href="https://developer.android.com/reference/kotlin/android/graphics/Bitmap.Config.html#ARGB_8888">Bitmap.Config.ARGB_8888</a></p></div></div></div></div></div></div></div></div></div></div></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"><span class="token keyword"></span><span class="token keyword">fun </span><a href="capture.html"><span class="token function">capture</span></a><span class="token punctuation">(</span>config<span class="token operator">: </span><a href="https://developer.android.com/reference/kotlin/android/graphics/Bitmap.Config.html">Bitmap.Config</a><span class="token operator"> = </span>Bitmap.Config.ARGB_8888<span class="token punctuation">)</span><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><p class="paragraph">Creates and send a Bitmap capture request with specified <a href="capture.html">config</a>.</p><p class="paragraph">Make sure to call this method as a part of callback function and not as a part of the <span data-unresolved-link="androidx.compose.runtime/Composable///PointingToDeclaration/">Composable</span> function itself.</p><h2 class="">Parameters</h2><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"><div><span><span>config</span></span></div></span></div><div><div class="title"><div data-togglable="Parameters"><p class="paragraph">Bitmap config of the desired bitmap. Defaults to <a href="https://developer.android.com/reference/kotlin/android/graphics/Bitmap.Config.html#ARGB_8888">Bitmap.Config.ARGB_8888</a></p></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 class="footer"><span class="go-to-top-icon"><a href="#content" id="go-to-top-link"></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>
</div>
</body>
Expand Down
Loading

0 comments on commit 5d2ef5b

Please sign in to comment.