SmartToolFactory / Jetpack-Compose-Tutorials

๐Ÿš€๐Ÿงจ๐Ÿ“ Series of Tutorials to learn about Jetpack Compose with subjects Material Widgets, Layout, SubcomposeLayout, custom layouts, State, custom rememberable, recomposition, LaunchedEffect, side-effects, Gesture, Animation, Navigation, Canvas, UIs like whatsapp and others.
Apache License 2.0
3.01k stars 312 forks source link

Bitmap erasing #9

Closed T8RIN closed 1 year ago

T8RIN commented 1 year ago

Hi, i want to add an Background remover tool to my ImageToolbox app, which you are definitely know, and i used your compose tutorial 6-11 as reference, the problem is that when i use your zoom modifier then in portrait all works fine, but in landscape orientation erase area draws not as expected i.e. in different place, how to workaround this issue?

SmartToolFactory commented 1 year ago

Hi. It's most likely the difference between dimensions of physical file and Composable you draw that file in. https://stackoverflow.com/questions/76930764/display-only-part-of-bitmap-in-image-composable/76931412#76931412 I explained it here. Let's say you have a 4000x4000 px file you draw inside a 1000x1000px Image composable. When you draw something at (3000, 3000) in Canvas or on physical file you need to translate it to (750, 750) to draw on Image Composable, right? Same thing applies to erasing as well. And when you draw something using touch you draw it on Composable dimensions, then you need to convert these to file dimensions. A shape at (500, 500) on Composable translates to (2000,2000) for file.

SmartToolFactory commented 1 year ago

I did this in image cropper using Matrix for path.

T8RIN commented 1 year ago

Thanks for the explanation!! You are amazing, keep doing!

SmartToolFactory commented 1 year ago

You are welcome. In the example in tutorial i set the Canvas or physical file size as big as Image composable using BoxWithConstraints dimensions via

    val imageWidth = constraints.maxWidth
    val imageHeight = constraints.maxHeight

    val drawImageBitmap = remember {
        Bitmap.createScaledBitmap(imageBitmap.asAndroidBitmap(), imageWidth, imageHeight, false)
            .asImageBitmap()
    }

That's why you have no issue in that example.

T8RIN commented 1 year ago

Got it :)

SmartToolFactory commented 1 year ago

This is one of the reasons i wrote ImageWithConstraints to get correct touch positions when you need to change aspect ratio. It returns the correct touch area or position no matter which ContentScale you select. If the Image composable has some spaces due to ContentScale or aspect ratio you also take into consideration that if top left of Composable (0,0) is in black area(no draw). You can test this with ImageWithThumbnail with an image with spaces on side when you use ContentScale.FillHeight or something

SmartToolFactory commented 1 year ago

Anyway, i hope it helps. If you still have the issue feel free to post code and image.

T8RIN commented 1 year ago

Anyway, i hope it helps. If you still have the issue feel free to post code and image.

Thanks a lot!

T8RIN commented 1 year ago

This is one of the reasons i wrote ImageWithConstraints to get correct touch positions when you need to change aspect ratio. It returns the correct touch area or position no matter which ContentScale you select. If the Image composable has some spaces due to ContentScale or aspect ratio you also take into consideration that if top left of Composable (0,0) is in black area(no draw). You can test this with ImageWithThumbnail with an image with spaces on side when you use ContentScale.FillHeight or something

Found the issue, problem was in aspect ratio modifier

T8RIN commented 1 year ago

I have another question, if i implemented fake erasing on canvas through BlendMode then how can i create "anti eraser" to recover image in some places?

SmartToolFactory commented 1 year ago

Easiest and most performant way is to do it on Composable until user decides to save file, when file is being saved do it as i do in CropAgent in Cropper applying BlendMode with paths to actual file.

val paths = remember { mutableStateListOf<Pair<Path, PathProperties>>() }

Create paths like this and do it as in my Drawing app or as in Tutorial6_4_2. When done draw these paths on Canvas(bitmap)

SmartToolFactory commented 1 year ago

If you don't need the properties only the Paths no need to have a Pair, mutableStateListOf<Path>() would suffice to undo any erase action

SmartToolFactory commented 1 year ago

and if you want to have redo you create another one val pathsUndone = remember { mutableStateListOf<Pair<Path, PathProperties>>() }

T8RIN commented 1 year ago

I've already implemented this here

But i want to implement anti eraser feature, when i will draw on empty image parts, if those pixels was erased before then i want to cancel BlendMode.clear

T8RIN commented 1 year ago

https://github.com/SmartToolFactory/Jetpack-Compose-Tutorials/assets/52178347/475fd0c5-c0fe-4549-a9ef-2bd72843dbec

Here i erased some parts of the image, but i want to apply "image paint" and restore the image in erased places by drawing, like Photoshop's recovery brush.

Maybe i should create an overlay on top of the first image and then with some different brush make it visible mb with BlendMode.SrcATop?

T8RIN commented 1 year ago

Did this trick using ImageShader ๐Ÿธ

https://github.com/SmartToolFactory/Jetpack-Compose-Tutorials/assets/52178347/a9939c00-6a3b-4792-9a25-0eaa189a9ce3