Skip to content

Commit 5d2ef5b

Browse files
authored
Merge pull request #25 from PatilShreyas/release-1.0.3
Release v1.0.3
2 parents 1c81e53 + 113f132 commit 5d2ef5b

File tree

26 files changed

+772
-664
lines changed

26 files changed

+772
-664
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ In `build.gradle` of app module, include this dependency
2929

3030
```gradle
3131
dependencies {
32-
implementation "dev.shreyaspatil:capturable:1.0.2"
32+
implementation "dev.shreyaspatil:capturable:1.0.3"
3333
}
3434
```
3535

@@ -101,6 +101,10 @@ captureController.capture(Bitmap.Config.ALPHA_8)
101101
102102
That's all needed!
103103

104+
#### ⚠️ Precaution
105+
106+
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.
107+
104108
## 📄 API Documentation
105109

106110
[**Visit the API documentation of this library**](https://patilshreyas.github.io/Capturable) to get more information in detail.

build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// Top-level build file where you can add configuration options common to all sub-projects/modules.
22
buildscript {
33
ext {
4-
agpVersion = '7.0.4'
5-
kotlinVersion = '1.5.31'
4+
agpVersion = '7.1.1'
5+
kotlinVersion = '1.6.10'
66
coroutinesVersion = '1.6.0'
77
androidCoreVersion = '1.7.0'
8-
composeVersion = '1.0.5'
8+
composeVersion = '1.1.0'
99
jUnitVersion = '4.13.2'
1010
androidJUnitTestVersion = '1.1.3'
1111
spotlessVersion = '6.3.0'

capturable/build.gradle

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ android {
1010
defaultConfig {
1111
minSdk 21
1212
targetSdk 31
13-
versionCode VERSION_CODE.toInteger()
14-
versionName VERSION_NAME
1513

1614
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
1715
consumerProguardFiles "consumer-rules.pro"

capturable/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ POM_PACKAGING=aar
66
POM_INCEPTION_YEAR=2022
77

88
GROUP=dev.shreyaspatil
9-
VERSION_NAME=1.0.2
9+
VERSION_NAME=1.0.3
1010
VERSION_CODE=3
1111

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

capturable/src/androidTest/java/dev/shreyaspatil/capturable/CapturableTest.kt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import dev.shreyaspatil.capturable.controller.CaptureController
3535
import org.junit.Assert.assertEquals
3636
import org.junit.Rule
3737
import org.junit.Test
38+
import java.math.RoundingMode
3839

3940
class CapturableTest {
4041

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

74-
val expectedHeight = with(composeTestRule.density) { contentHeight.toPx() }
75-
val expectedWidth = with(composeTestRule.density) { contentWidth.toPx() }
75+
val expectedHeight = with(composeTestRule.density) { contentHeight.toPx() }.roundToInt()
76+
val expectedWidth = with(composeTestRule.density) { contentWidth.toPx() }.roundToInt()
7677

77-
val actualHeight = bitmap.height.toFloat()
78-
val actualWidth = bitmap.width.toFloat()
78+
val actualHeight = bitmap.height
79+
val actualWidth = bitmap.width
7980

80-
assertEquals(actualHeight, expectedHeight)
81-
assertEquals(actualWidth, expectedWidth)
81+
assertEquals(expectedHeight, actualHeight)
82+
assertEquals(expectedWidth, actualWidth)
8283
}
84+
85+
/**
86+
* Converts float value to the integer value by rounding up to ceiling.
87+
*/
88+
private fun Float.roundToInt(): Int = toBigDecimal().setScale(0, RoundingMode.CEILING).toInt()
8389
}

capturable/src/main/java/dev/shreyaspatil/capturable/Capturable.kt

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ package dev.shreyaspatil.capturable
2626

2727
import android.app.Activity
2828
import android.content.Context
29+
import android.content.ContextWrapper
2930
import android.graphics.Bitmap
3031
import android.graphics.Rect
3132
import android.os.Build
@@ -124,23 +125,28 @@ private inline fun ComposeView.applyCapturability(
124125
private suspend fun View.drawToBitmapPostLaidOut(context: Context, config: Bitmap.Config): Bitmap {
125126
return suspendCoroutine { continuation ->
126127
doOnLayout { view ->
127-
// For device with API version O(26) and above should draw Bitmap using PixelCopy API.
128-
// The reason behind this is it throws IllegalArgumentException saying
129-
// "Software rendering doesn't support hardware bitmaps"
130-
// See this issue for the reference: https://github.com/PatilShreyas/Capturable/issues/7
131-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
132-
val window = (context as? Activity)?.window
133-
?: error("Can't get window from the Context")
134-
135-
drawBitmapWithPixelCopy(
136-
view = view,
137-
window = window,
138-
config = config,
139-
onDrawn = { bitmap -> continuation.resume(bitmap) },
140-
onError = { error -> continuation.resumeWithException(error) }
141-
)
142-
} else {
128+
try {
129+
// Initially, try to capture bitmap using drawToBitmap extension function
143130
continuation.resume(view.drawToBitmap(config))
131+
} catch (e: IllegalArgumentException) {
132+
// For device with API version O(26) and above should draw Bitmap using PixelCopy
133+
// API. The reason behind this is it throws IllegalArgumentException saying
134+
// "Software rendering doesn't support hardware bitmaps"
135+
// See this issue for the reference:
136+
// https://github.com/PatilShreyas/Capturable/issues/7
137+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
138+
val window = context.findActivity().window
139+
140+
drawBitmapWithPixelCopy(
141+
view = view,
142+
window = window,
143+
config = config,
144+
onDrawn = { bitmap -> continuation.resume(bitmap) },
145+
onError = { error -> continuation.resumeWithException(error) }
146+
)
147+
} else {
148+
continuation.resumeWithException(e)
149+
}
144150
}
145151
}
146152
}
@@ -180,3 +186,15 @@ private fun drawBitmapWithPixelCopy(
180186
Handler(Looper.getMainLooper())
181187
)
182188
}
189+
190+
/**
191+
* Traverses through this [Context] and finds [Activity] wrapped inside it.
192+
*/
193+
internal fun Context.findActivity(): Activity {
194+
var context = this
195+
while (context is ContextWrapper) {
196+
if (context is Activity) return context
197+
context = context.baseContext
198+
}
199+
throw IllegalStateException("Unable to retrieve Activity from the current context")
200+
}

docs/capturable/dev.shreyaspatil.capturable.controller/-capture-controller/capture.html

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,34 @@
22
<head>
33
<meta name="viewport" content="width=device-width, initial-scale=1" charset="UTF-8">
44
<title>capture</title>
5-
<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>
5+
<link href="../../../images/logo-icon.svg" rel="icon" type="image/svg"><script>var pathToRoot = "../../../";</script> <script>const storage = localStorage.getItem("dokka-dark-mode")
6+
const savedDarkMode = storage ? JSON.parse(storage) : false
7+
if(savedDarkMode === true){
8+
document.getElementsByTagName("html")[0].classList.add("theme-dark")
9+
}</script>
10+
<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>
611
<body>
12+
<div class="navigation-wrapper" id="navigation-wrapper">
13+
<div id="leftToggler"><span class="icon-toggler"></span></div>
14+
<div class="library-name"><a href="../../../index.html">capturable</a></div>
15+
<div></div>
16+
<div class="pull-right d-flex"><button id="theme-toggle-button"><span id="theme-toggle"></span></button>
17+
<div id="searchBar"></div>
18+
</div>
19+
</div>
720
<div id="container">
8-
<div id="leftColumn"><a href="../../../index.html">
9-
<div id="logo"></div>
10-
</a>
11-
<div id="paneSearch"></div>
21+
<div id="leftColumn">
1222
<div id="sideMenu"></div>
1323
</div>
1424
<div id="main">
15-
<div id="leftToggler"><span class="icon-toggler"></span></div>
16-
<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">
17-
<div class="navigation-wrapper" id="navigation-wrapper">
18-
<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>
19-
<div class="pull-right d-flex">
20-
<div id="searchBar"></div>
21-
</div>
22-
</div>
25+
<div class="main-content" id="content" pageIds="capturable::dev.shreyaspatil.capturable.controller/CaptureController/capture/#android.graphics.Bitmap.Config/PointingToDeclaration//936064587">
26+
<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>
2327
<div class="cover ">
2428
<h1 class="cover"><span><span>capture</span></span></h1>
2529
</div>
26-
<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>
27-
28-
<div>
29-
<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>
30-
</div>
31-
<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>
30+
<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>
3231
</div>
33-
<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>
32+
<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>
3433
</div>
3534
</div>
3635
</body>

0 commit comments

Comments
 (0)