I wrote the below code to draw a red rectangle, but apparently it is wrong :( I did not get any error, in same time did not get anything drawn, how can I fix it
private fun updateTransform() {
val matrix = Matrix()
// Compute the center of the view finder
val centerX = viewFinder.width / 2f
val centerY = viewFinder.height / 2f
// Correct preview output to account for display rotation
val rotationDegrees = when(viewFinder.display.rotation) {
Surface.ROTATION_0 -> 0
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
else -> return
}
matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY)
val bmp = Bitmap.createBitmap(1425, 1425, Bitmap.Config.ARGB_8888)
val paint = Paint().apply {
isAntiAlias = true
style = Paint.Style.STROKE
color = Color.RED
strokeWidth = 10f
}
// x: {559, y: 1901}, height: 1425, width: 1425
var canvas = Canvas(bmp)
canvas.apply {
drawRect(
25.toFloat(), // faceRectangle.left,
25.toFloat(), //faceRectangle.top,
250.toFloat(),
250.toFloat(),
paint
)
}
viewFinder.unlockCanvasAndPost(canvas)
viewFinder.setTransform(matrix)
}
I found a work around to do this, not sure if this is the only way, anyhow, I'll post my way here in case it is helpful for someone in the future:
The solution is to create an image view, with the same dimension, and location (constrains) of the cameeraX texture view, toact as overlay, and do all the drawings on it, you just need to be sure that this over lay is above not under the texture view, i.e. following it in the layout xml file.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextureView
android:id="#+id/view_finder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:visibility="visible" />
<ImageView
android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="#string/pic_desc"
android:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
And the drawing code is:
lateinit var overlay: Bitmap
private fun startCamera() {
val analyzerConfig = ImageAnalysisConfig.Builder().apply {
setImageReaderMode(
ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
}.build()
val analyzerUseCase = ImageAnalysis(analyzerConfig).apply {
analyzer = ImageAnalysis.Analyzer { image, rotationDegrees ->
val bitmap = view_finder.bitmap ?: return#Analyzer
overlay = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
scope.launch(Dispatchers.Unconfined) {
val mat = Mat()
Utils.bitmapToMat(bitmap!!, mat)
val detectedFaces = FaceDetection.detectFaces(bitmap!!)
if (detectedFaces.toArray().isNotEmpty()) {
val paint = Paint().apply {
isAntiAlias = true
style = Paint.Style.STROKE
color = Color.RED
strokeWidth = 10f
}
for (rect in detectedFaces.toArray()) {
var canvas = Canvas(overlay)
canvas.drawRect(
rect.x.toFloat(),
rect.y.toFloat(),
rect.x.toFloat() + rect.width,
rect.y.toFloat() + rect.height,
paint
)
overlay?.let { Canvas(it) }?.apply {
canvas
}
}
}
}
runOnUiThread {
imageView.setImageBitmap(overlay)
}
}
}
CameraX.bindToLifecycle(this, preview, imageCapture, analyzerUseCase)
}
Below is an output of my app as of now showing this drawing:
Related
I would like to create a watermark effect in my app using text as shown in the picture below.
I achieved this by using canvas and bitmap, is there any other reliable way to do this?
Here is my composable function
#Composable
fun WaterMark(
modifier: Modifier = Modifier,
content: (#Composable BoxScope.() -> Unit)? = null,
) {
val watermarkText: String = "some mutable text"
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.textSize = LocalContext.current.dpToPx(24).toFloat()
paint.color = PSCoreColours.psCoreColours.onSurface.hashCode()
paint.textAlign = Paint.Align.LEFT
paint.alpha = (255 * 0.25).toInt()
val baseline: Float = -paint.ascent()
val image: Bitmap = Bitmap.createBitmap(paint.measureText(watermarkText).toInt(),
(baseline + paint.descent()).toInt(),
Bitmap.Config.ARGB_8888)
val canvas = android.graphics.Canvas(image)
canvas.drawText(watermarkText, 0f, baseline, paint)
val rotationMatrix: Matrix = Matrix().apply { postRotate(-45f) }
val rotatedImage: Bitmap = Bitmap.createBitmap(image, 0, 0, image.width, image.height, rotationMatrix, true)
val pattern: ImageBitmap = rotatedImage.asImageBitmap()
Box {
content?.let { it() }
Canvas(
modifier = modifier
) {
val totalWidth = size.width / pattern.width
val totalHeight = size.height / pattern.height
var x = 0f
var y = 0f
for (i in 0..totalHeight.toInt()) {
y = (i * pattern.height).toFloat()
for (j in 0..totalWidth.toInt()) {
x = (j * pattern.width).toFloat()
drawImage(
pattern,
colorFilter = null,
topLeft = Offset(x, y),
)
}
}
}
}
}
You can do custom layouts in compose for this
private const val SPACING = 100
#Composable
fun Watermark(
content: #Composable BoxScope.() -> Unit,
) {
Box {
content()
Layout(
content = {
// Repeating the placeables, 6 should do for now but we should be able to calculate this too
repeat(6) {
Text(
text = watermarkText,
..
)
}
}
) { measurables, constraints ->
// Measuring all the placables
val placeables: List<Placeable> = measurables
.map { measurable -> measurable.measure(constraints) }
layout(constraints.maxWidth, constraints.maxHeight) {
// Calculating the max width of a placable
val maxWidth: Double = placeables.maxOf { it.width }.toDouble()
// Calculating the max width of a tile given the text is rotated
val tileSize: Int = (constraints.maxWidth / atan(maxWidth)).toInt()
placeables
.chunked(2) // Placing 2 columns
.forEachIndexed { index, (first, second) ->
val indexedTileSize: Int = index * tileSize
first.placeRelativeWithLayer(-SPACING, indexedTileSize + SPACING) { rotationZ = -45f }
second.placeRelativeWithLayer(tileSize, indexedTileSize) { rotationZ = -45f }
}
}
}
}
}
Watermark function creates instance of Paint and Bitmap on each recomposition. You should wrap them with remember as in this answer.
However you might, i think, do what you do fully Compose way without Paint and Bitmap either using Modifier.drawWithContent{} and drawText function of DrawScope and using translate or rotate inside DrawScope.
This is a drawText sample to understand how you can create and store TextLayoutResult remember.
And another sample using Modifier.drawWithContent
You can also try using Modifier.drawWithCache to cache TextLayoutResult in layout phase instead of composition phase which is suggested by Google Compose developer works on Text here
I am developing an android app using:
Jetpack Lifecycle (ViewModel)
Jetpack Navigation
Coil (Image Loader)
I am trying to customize the BottomNavigationMenu.
But one thing is very hard...
The last tab is the User's profile Image with Border.
If the user's profile image background color is white, then the ui is weird.
So I should show the border.
class MainActivity {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initBottomNav(binding.bottomNav)
vm.initProfileBottomIcon()
}
private fun initBottomNav(bottomNav: BottomNavigationView) {
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
bottomNav.setupWithNavController(navHostFragment.navController)
bottomNav.itemIconTintList = null
vm.profileImgUrl.observe(this) { url ->
bottomNav.menu.findItem(R.id.profileFragment).load(this, url) {
transformations(CircleCropTransformation())
}
}
}
}
This code draws the profile image on the BottomNavigationMenu.
But not draw the border.
When I googling, there is no support for CircleCrop with Border on Coil (even Glide).
So I tried the below code, But it doesn't work well..
vm.profileImg.observe(this) { imgBitmap ->
val layerBorder = ResourcesCompat.getDrawable(resources, R.drawable.oval_trans_border1_red, null)
val layerIcon = BitmapDrawable(Resources.getSystem(), imgBitmap)
val layerDrawable = LayerDrawable(arrayOf(layerIcon, layerBorder))
val bottomNavProfile = bottomNav.menu.findItem(R.id.profileFragment)
val request = ImageRequest.Builder(this)
.data(layerDrawable)
.target {
bottomNavProfile.icon = it
}
.apply {
transformations(CircleCropTransformation())
}
.build()
imageLoader.enqueue(request)
}
Somebody help me please?
You can write your own Transformation with border like this:
class BorderedCircleCropTransformation(
private val borderSize: Float = 0f,
#ColorInt private val borderColor: Int = Color.BLUE
) : Transformation {
override fun key(): String = BorderedCircleCropTransformation::class.java.name
override suspend fun transform(pool: BitmapPool, input: Bitmap, size: Size): Bitmap {
val borderOffset = (borderSize * 2).toInt()
val halfWidth = input.width / 2
val halfHeight = input.height / 2
val circleRadius = Math.min(halfWidth, halfHeight).toFloat()
val newBitmap = Bitmap.createBitmap(
input.width + borderOffset,
input.height + borderOffset,
Bitmap.Config.ARGB_8888
)
// Center coordinates of the image
val centerX = halfWidth + borderSize
val centerY = halfHeight + borderSize
val paint = Paint()
val canvas = Canvas(newBitmap).apply {
// Set transparent initial area
drawARGB(0, 0, 0, 0)
}
// Draw the transparent initial area
paint.isAntiAlias = true
paint.style = Paint.Style.FILL
canvas.drawCircle(centerX, centerY, circleRadius, paint)
// Draw the image
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
canvas.drawBitmap(input, borderSize, borderSize, paint)
// Draw the createBitmapWithBorder
paint.xfermode = null
paint.style = Paint.Style.STROKE
paint.color = borderColor
paint.strokeWidth = borderSize
canvas.drawCircle(centerX, centerY, circleRadius, paint)
return newBitmap
}
override fun equals(other: Any?) = other is BorderedCircleCropTransformation
override fun hashCode() = javaClass.hashCode()
override fun toString() = "BorderedCircleCropTransformation()"
private companion object {
val XFERMODE = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
}
}
internal val Bitmap.safeConfig: Bitmap.Config
get() = config ?: Bitmap.Config.ARGB_8888
then pass it as a transformation to coil and it will draw what you want.
I use this code for drawing border.
I'm trying to create a rounded triangle using Canvas in Jetpack Compose.
I try this code for drawing triangle:
#Composable
fun RoundedTriangle() {
Canvas(modifier = Modifier.size(500.dp)) {
val trianglePath = Path().apply {
val height = size.height
val width = size.width
moveTo(width / 2.0f, 0f)
lineTo(width, height)
lineTo(0f, height)
}
drawPath(trianglePath, color = Color.Blue)
}
}
But I don't know how to round the triangle corners. I also tried to use arcTo, but I was unable to get a suitable result.
How can I draw something like the figure below?
For Stroke you can specify rounding like this:
drawPath(
...
style = Stroke(
width = 2.dp.toPx(),
pathEffect = PathEffect.cornerPathEffect(4.dp.toPx())
)
)
Yet Fill seems lack of support rounding. I've created a feature request, please star it.
But Canvas has drawOutline function, which accepts both Outline, which can wrap a Path, and Paint, for which you can specify pathEffect:
Canvas(modifier = Modifier.fillMaxWidth().aspectRatio(1f)) {
val rect = Rect(Offset.Zero, size)
val trianglePath = Path().apply {
moveTo(rect.topCenter)
lineTo(rect.bottomRight)
lineTo(rect.bottomLeft)
close()
}
drawIntoCanvas { canvas ->
canvas.drawOutline(
outline = Outline.Generic(trianglePath),
paint = Paint().apply {
color = Color.Black
pathEffect = PathEffect.cornerPathEffect(rect.maxDimension / 3)
}
)
}
}
Path helpers:
fun Path.moveTo(offset: Offset) = moveTo(offset.x, offset.y)
fun Path.lineTo(offset: Offset) = lineTo(offset.x, offset.y)
Result:
Based on #philip-dukhov answer, if anyone is interested in appliying this to a square
#Composable
fun SquirclePath(
modifier: Modifier,
smoothingFactor: Int = 60,
color: Color,
strokeWidth: Float,
) {
Canvas(
modifier = modifier
) {
val rect = Rect(Offset.Zero, size)
val percent = smoothingFactor.percentOf(rect.minDimension)
val squirclePath = Path().apply {
with(rect) {
lineTo(topRight)
lineTo(bottomRight)
lineTo(bottomLeft)
lineTo(topLeft)
// this is where the path is finally linked together
close()
}
}
drawIntoCanvas { canvas ->
canvas.drawOutline(
outline = Outline.Generic(squirclePath),
paint = Paint().apply {
this.color = color
this.style = PaintingStyle.Fill
this.strokeWidth = strokeWidth
pathEffect = PathEffect.cornerPathEffect(percent)
}
)
}
}
}
fun Int.percentOf(target:Float) = (this.toFloat() / 100) * target
What is the Android Compose approach to tile an image to fill my background with a small pattern?
A naive approach for Bitmaps without rotation could be like this:
#Composable
fun TileImage() {
val pattern = ImageBitmap.imageResource(R.drawable.pattern_bitmap)
Canvas(modifier = Modifier.fillMaxSize()) {
// rotate(degrees = -15f) { // The rotation does not produce the desired effect
val totalWidth = size.width / pattern.width
val totalHeight = size.height / pattern.height
var x = 0f
var y = 0f
for (i in 0..totalHeight.toInt()) {
y = (i * pattern.height).toFloat()
for (j in 0..totalWidth.toInt()) {
x = (j * pattern.width).toFloat()
drawImage(
pattern,
colorFilter = giftColorFilter,
topLeft = Offset(x, y)
)
}
}
// }
}
}
In Android XML you can easily create XML to repeat a bitmap
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="#drawable/pattern_bitmap"
android:tileMode="repeat" />
Or if you need to tile a vector you can use a custom Drawable class to achieve your goal
TileDrawable(AppCompatResources.getDrawable(context, R.drawable.pattern_vector), Shader.TileMode.REPEAT)
class TileDrawable(drawable: Drawable, tileMode: Shader.TileMode, private val angle: Float? = null) : Drawable() {
private val paint: Paint = Paint().apply {
shader = BitmapShader(getBitmap(drawable), tileMode, tileMode)
}
override fun draw(canvas: Canvas) {
angle?.let {
canvas.rotate(it)
}
canvas.drawPaint(paint)
}
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
}
override fun getOpacity() = PixelFormat.TRANSLUCENT
override fun setColorFilter(colorFilter: ColorFilter?) {
paint.colorFilter = colorFilter
}
private fun getBitmap(drawable: Drawable): Bitmap {
if (drawable is BitmapDrawable) {
return drawable.bitmap
}
val bmp = Bitmap.createBitmap(
drawable.intrinsicWidth, drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val c = Canvas(bmp)
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
drawable.draw(c)
return bmp
}
}
If you want to use native canvas you can do something like this in jetpack compose.
Canvas(
modifier = Modifier
.fillMaxSize()
) {
val paint = Paint().asFrameworkPaint().apply {
isAntiAlias = true
shader = ImageShader(pattern, TileMode.Repeated, TileMode.Repeated)
}
drawIntoCanvas {
it.nativeCanvas.drawPaint(paint)
}
paint.reset()
}
And If you want to limit your repetition to a certain height and width you can use the clip modifier in canvas like below otherwise it will fill the entire screen.
Canvas(
modifier = Modifier
.width(300.dp)
.height(200.dp)
.clip(RectangleShape)
) {
----
}
Based on Rafiul's answer, I was able to come up with something a bit more succinct. Here's hoping Compose comes up with something built-in to make this simpler in the future.
val image = ImageBitmap.imageResource(R.drawable.my_image)
val brush = remember(image) { ShaderBrush(ImageShader(image, TileMode.Repeated, TileMode.Repeated)) }
Box(Modifier
.fillMaxSize()
.background(brush)) {
}
I want to create Custom Image View for avatars which can work in two modes:
If android:src is empty display circle with first letter of name
Else display circle avatar
At now if I use android:src my drawable is not in center, how can i make central crop of it?
Example
Here is code of custom view:
class AvatarImageView : AppCompatImageView{
private var letter = ""
private var avatarBackgroundColor = 0
private var avatarLetterColor = 0
private val paintBackground = Paint()
private val paintLetter = Paint(Paint.LINEAR_TEXT_FLAG)
private val paintAvatarImage = Paint()
private var circleX = 0f
private var circleY = 0f
private var circleRadius = 0f
private var letterX = 0f
private var letterY = 0f
private val avatarImageRect = RectF(0f, 0f, 0f, 0f)
constructor(context : Context)
: this(context, null)
constructor(context: Context, attrs : AttributeSet?)
: this(context, attrs, 0)
constructor(context: Context, attrs : AttributeSet?, defStyleAttr : Int)
: super(context, attrs, defStyleAttr){
attrs?.let{
val array = context.obtainStyledAttributes(it, R.styleable.AvatarImageView)
letter = array.getString(R.styleable.AvatarImageView_signature)?.trim()?.substring(0..0)?.toUpperCase(Locale.ROOT) ?: ""
avatarBackgroundColor = array.getColor(R.styleable.AvatarImageView_background_color, Color.BLUE)
avatarLetterColor = array.getColor(R.styleable.AvatarImageView_letter_color, Color.WHITE)
array.recycle()
}
init()
}
private fun init(){
paintBackground.style = Paint.Style.FILL
paintBackground.color = avatarBackgroundColor
paintLetter.color = avatarLetterColor
paintLetter.textAlign = Paint.Align.CENTER
paintLetter.isAntiAlias = true
paintLetter.style = Paint.Style.FILL
drawable?.let{
paintAvatarImage.isAntiAlias = true
val shader = BitmapShader(getBitmapFromDrawable(it), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
paintAvatarImage.shader = shader
}
}
private fun getBitmapFromDrawable(drawable: Drawable): Bitmap{
if (drawable is BitmapDrawable) {
drawable.bitmap?.let {
return drawable.bitmap
}
}
val bitmap: Bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
return bitmap
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
val availableWidth = width
val availableHeight = height
circleX = availableWidth / 2f
circleY = availableHeight / 2f
circleRadius = min(availableWidth, availableHeight) / 2f
paintLetter.textSize = availableHeight / 2f
letterX = availableWidth / 2f
letterY = availableHeight / 2f - ((paintLetter.descent() + paintLetter.ascent()) / 2f)
avatarImageRect.right = width.toFloat()
avatarImageRect.bottom = height.toFloat()
}
override fun onDraw(canvas: Canvas?) {
drawable?.let{
canvas?.drawRoundRect(avatarImageRect, width.toFloat(), height.toFloat(), paintAvatarImage)
} ?:run {
canvas?.drawCircle(circleX, circleY, circleRadius, paintBackground)
canvas?.drawText(letter, letterX, letterY, paintLetter)
}
}
}
My xml file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:padding="16dp"
tools:context=".ui.AvatarsFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.test.test.views.AvatarImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="#drawable/avatar"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
style="#style/TextAppearance.AppCompat.Medium"
android:text="Ivan Ivanov"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="10dp">
<com.test.test.views.AvatarImageView
android:layout_width="32dp"
android:layout_height="32dp"
app:signature="Ivan Ivanov"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
style="#style/TextAppearance.AppCompat.Medium"
android:text="Ivan Ivanov"/>
</LinearLayout>
</LinearLayout>
UPDATED: I know about other libraries, but I want to do it without them.
I used a CircleImageView to display images and picasso to load images into the CircleImageView
Picasso centers and crops the image
It worked for me...
SOLVED:
I`m now using this code for crop my image from https://stackoverflow.com/a/12089127/12209565:
private fun getCircleBitmap(bitmap: Bitmap): Bitmap {
val output = Bitmap.createBitmap(
bitmap.width,
bitmap.height,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(output)
val color = -0xbdbdbe
val paint = Paint()
val rect = Rect(0, 0, bitmap.width, bitmap.height)
paint.isAntiAlias = true
canvas.drawARGB(0, 0, 0, 0)
paint.color = color
canvas.drawCircle(
bitmap.width / 2f, bitmap.height / 2f,
bitmap.width / 2f, paint
)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
canvas.drawBitmap(bitmap, rect, rect, paint)
return output
}