Try to add flood fill (works ugly and slow)

This commit is contained in:
T8RIN
2024-05-08 21:47:51 +03:00
parent 6f0b1204f6
commit 26860970c3
12 changed files with 154 additions and 155 deletions

View File

@ -1082,4 +1082,6 @@
<string name="draw_regular_star_sub">Draw star which will be regular instead of free form</string>
<string name="antialias">Antialias</string>
<string name="antialias_sub">Enables antialiasing to prevent sharp edges</string>
<string name="flood_fill_sub">Fills the area with given color</string>
<string name="flood_fill">Flood Fill</string>
</resources>

View File

@ -39,6 +39,7 @@ import androidx.compose.ui.graphics.toArgb
import androidx.core.content.res.ResourcesCompat
import androidx.exifinterface.media.ExifInterface
import dagger.hilt.android.qualifiers.ApplicationContext
import ru.tech.imageresizershrinker.core.domain.dispatchers.DispatchersHolder
import ru.tech.imageresizershrinker.core.domain.image.ImageGetter
import ru.tech.imageresizershrinker.core.domain.image.ImageTransformer
import ru.tech.imageresizershrinker.core.domain.model.IntegerSize
@ -59,8 +60,9 @@ internal class AndroidImageDrawApplier @Inject constructor(
@ApplicationContext private val context: Context,
private val imageTransformer: ImageTransformer<Bitmap>,
private val imageGetter: ImageGetter<Bitmap, ExifInterface>,
private val filterProvider: FilterProvider<Bitmap>
) : ImageDrawApplier<Bitmap, Path, Color> {
private val filterProvider: FilterProvider<Bitmap>,
private val dispatchersHolder: DispatchersHolder
) : ImageDrawApplier<Bitmap, Path, Color>, DispatchersHolder by dispatchersHolder {
override suspend fun applyDrawToImage(
drawBehavior: DrawBehavior,
@ -112,21 +114,9 @@ internal class AndroidImageDrawApplier @Inject constructor(
currentSize = canvasSize,
oldSize = size
)
val isRect = listOf(
DrawPathMode.OutlinedRect,
DrawPathMode.OutlinedOval,
DrawPathMode.Rect,
DrawPathMode.Oval
).any { pathMode::class.isInstance(it) }
val isRect = pathMode.isRect
val isFilled = listOf(
DrawPathMode.Rect,
DrawPathMode.Oval,
DrawPathMode.Lasso,
DrawPathMode.Triangle,
DrawPathMode.Polygon(),
DrawPathMode.Star()
).any { pathMode::class.isInstance(it) }
val isFilled = !pathMode.isStroke
if (drawMode is DrawMode.PathEffect && !isErasing) {
val shaderSource = imageTransformer.transform(

View File

@ -59,15 +59,26 @@ sealed class DrawPathMode(open val ordinal: Int) {
val isRegular: Boolean = false
) : DrawPathMode(15)
data object FloodFill : DrawPathMode(16)
val isStroke: Boolean
get() = listOf(Lasso, Rect, Oval, Triangle, Polygon(), Star()).all {
get() = listOf(Lasso, Rect, Oval, Triangle, Polygon(), Star(), FloodFill).all {
!it::class.isInstance(this)
}
val isRect: Boolean
get() = listOf(
OutlinedRect,
OutlinedOval,
Rect,
Oval
).any { it::class.isInstance(this) }
companion object {
val entries by lazy {
listOf(
Free,
FloodFill,
Line,
PointingArrow,
DoublePointingArrow,

View File

@ -342,7 +342,7 @@ fun DrawScreen(
}
)
AnimatedVisibility(
visible = drawPathMode.isStroke,
visible = drawPathMode.isStroke || drawPathMode is DrawPathMode.FloodFill,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
@ -354,6 +354,8 @@ fun DrawScreen(
),
title = if (drawMode is DrawMode.Text) {
stringResource(R.string.font_size)
} else if (drawPathMode is DrawPathMode.FloodFill) {
stringResource(R.string.tolerance)
} else stringResource(R.string.line_width),
valueRange = if (drawMode is DrawMode.Image) {
10f..120f

View File

@ -62,6 +62,7 @@ import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import com.smarttoolfactory.gesture.MotionEvent
import com.smarttoolfactory.gesture.pointerMotionEvents
import jp.co.cyberagent.android.gpuimage.GPUImageNativeLibrary
import kotlinx.coroutines.launch
import net.engawapg.lib.zoomable.ZoomState
import net.engawapg.lib.zoomable.ZoomableDefaults.defaultZoomOnDoubleTap
@ -320,7 +321,9 @@ fun BitmapDrawer(
MotionEvent.Down -> {
if (currentDrawPosition.isSpecified) {
onDrawStart()
drawPath.moveTo(currentDrawPosition.x, currentDrawPosition.y)
if (drawPathMode !is DrawPathMode.FloodFill) {
drawPath.moveTo(currentDrawPosition.x, currentDrawPosition.y)
}
previousDrawPosition = currentDrawPosition
pathWithoutTransformations = drawPath.copy()
} else {
@ -332,105 +335,135 @@ fun BitmapDrawer(
}
MotionEvent.Move -> {
drawHelper.drawPath(
onDrawFreeArrow = {
if (previousDrawPosition.isUnspecified && currentDrawPosition.isSpecified) {
drawPath = Path().apply {
moveTo(
if (drawPathMode !is DrawPathMode.FloodFill) {
drawHelper.drawPath(
onDrawFreeArrow = {
if (previousDrawPosition.isUnspecified && currentDrawPosition.isSpecified) {
drawPath = Path().apply {
moveTo(
currentDrawPosition.x,
currentDrawPosition.y
)
}
pathWithoutTransformations = drawPath.copy()
previousDrawPosition = currentDrawPosition
}
if (previousDrawPosition.isSpecified && currentDrawPosition.isSpecified) {
drawPath = pathWithoutTransformations
drawPath.quadraticTo(
previousDrawPosition.x,
previousDrawPosition.y,
(previousDrawPosition.x + currentDrawPosition.x) / 2,
(previousDrawPosition.y + currentDrawPosition.y) / 2
)
previousDrawPosition = currentDrawPosition
pathWithoutTransformations = drawPath.copy()
drawArrowsIfNeeded(drawPath)
}
},
onBaseDraw = {
if (previousDrawPosition.isUnspecified && currentDrawPosition.isSpecified) {
drawPath.moveTo(
currentDrawPosition.x,
currentDrawPosition.y
)
previousDrawPosition = currentDrawPosition
}
if (currentDrawPosition.isSpecified && previousDrawPosition.isSpecified) {
drawPath.quadraticTo(
previousDrawPosition.x,
previousDrawPosition.y,
(previousDrawPosition.x + currentDrawPosition.x) / 2,
(previousDrawPosition.y + currentDrawPosition.y) / 2
)
}
pathWithoutTransformations = drawPath.copy()
previousDrawPosition = currentDrawPosition
}
if (previousDrawPosition.isSpecified && currentDrawPosition.isSpecified) {
drawPath = pathWithoutTransformations
drawPath.quadraticTo(
previousDrawPosition.x,
previousDrawPosition.y,
(previousDrawPosition.x + currentDrawPosition.x) / 2,
(previousDrawPosition.y + currentDrawPosition.y) / 2
)
previousDrawPosition = currentDrawPosition
pathWithoutTransformations = drawPath.copy()
drawArrowsIfNeeded(drawPath)
}
},
onBaseDraw = {
if (previousDrawPosition.isUnspecified && currentDrawPosition.isSpecified) {
drawPath.moveTo(currentDrawPosition.x, currentDrawPosition.y)
previousDrawPosition = currentDrawPosition
}
if (currentDrawPosition.isSpecified && previousDrawPosition.isSpecified) {
drawPath.quadraticTo(
previousDrawPosition.x,
previousDrawPosition.y,
(previousDrawPosition.x + currentDrawPosition.x) / 2,
(previousDrawPosition.y + currentDrawPosition.y) / 2
)
}
previousDrawPosition = currentDrawPosition
}
)
)
}
motionEvent = MotionEvent.Idle
}
MotionEvent.Up -> {
if (currentDrawPosition.isSpecified && drawDownPosition.isSpecified) {
drawHelper.drawPath(
onDrawFreeArrow = {
drawPath = pathWithoutTransformations
PathMeasure().apply {
setPath(drawPath, false)
}.let {
it.getPosition(it.length)
}.let { lastPoint ->
if (!lastPoint.isSpecified) {
drawPath.moveTo(
if (drawPathMode !is DrawPathMode.FloodFill) {
drawHelper.drawPath(
onDrawFreeArrow = {
drawPath = pathWithoutTransformations
PathMeasure().apply {
setPath(drawPath, false)
}.let {
it.getPosition(it.length)
}.let { lastPoint ->
if (!lastPoint.isSpecified) {
drawPath.moveTo(
currentDrawPosition.x,
currentDrawPosition.y
)
}
drawPath.lineTo(
currentDrawPosition.x,
currentDrawPosition.y
)
}
drawPath.lineTo(
currentDrawPosition.x,
currentDrawPosition.y
)
}
drawArrowsIfNeeded(drawPath)
},
onBaseDraw = {
PathMeasure().apply {
setPath(drawPath, false)
}.let {
it.getPosition(it.length)
}.takeOrElse { currentDrawPosition }.let { lastPoint ->
drawPath.moveTo(lastPoint.x, lastPoint.y)
drawPath.lineTo(
currentDrawPosition.x,
currentDrawPosition.y
drawArrowsIfNeeded(drawPath)
},
onBaseDraw = {
PathMeasure().apply {
setPath(drawPath, false)
}.let {
it.getPosition(it.length)
}.takeOrElse { currentDrawPosition }.let { lastPoint ->
drawPath.moveTo(lastPoint.x, lastPoint.y)
drawPath.lineTo(
currentDrawPosition.x,
currentDrawPosition.y
)
}
}
)
onAddPath(
UiPathPaint(
path = drawPath,
strokeWidth = strokeWidth,
brushSoftness = brushSoftness,
drawColor = drawColor,
isErasing = isEraserOn,
drawMode = drawMode,
canvasSize = canvasSize,
drawPathMode = drawPathMode
)
)
} else {
LaunchedEffect(drawDownPosition) {
GPUImageNativeLibrary.floodFill(
srcBitmap = drawImageBitmap.overlay(drawBitmap)
.asAndroidBitmap(),
startX = drawDownPosition.x.toInt(),
startY = drawDownPosition.y.toInt(),
tolerance = strokeWidth.value,
fillColor = drawColor.toArgb()
)?.asComposePath()?.let { path ->
onAddPath(
UiPathPaint(
path = path,
strokeWidth = strokeWidth,
brushSoftness = brushSoftness,
drawColor = drawColor,
isErasing = isEraserOn,
drawMode = drawMode,
canvasSize = canvasSize,
drawPathMode = drawPathMode
)
)
}
}
)
onAddPath(
UiPathPaint(
path = drawPath,
strokeWidth = strokeWidth,
brushSoftness = brushSoftness,
drawColor = drawColor,
isErasing = isEraserOn,
drawMode = drawMode,
canvasSize = canvasSize,
drawPathMode = drawPathMode
)
)
}
}
currentDrawPosition = Offset.Unspecified

View File

@ -35,6 +35,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank
import androidx.compose.material.icons.rounded.Circle
import androidx.compose.material.icons.rounded.FormatColorFill
import androidx.compose.material.icons.rounded.RadioButtonUnchecked
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material.icons.rounded.StarOutline
@ -486,6 +487,7 @@ private fun DrawPathMode.getSubtitle(): Int = when (this) {
is DrawPathMode.OutlinedPolygon -> R.string.outlined_polygon_sub
is DrawPathMode.OutlinedStar -> R.string.outlined_star_sub
is DrawPathMode.Star -> R.string.star_sub
DrawPathMode.FloodFill -> R.string.flood_fill_sub
}
private fun DrawPathMode.getTitle(): Int = when (this) {
@ -506,6 +508,7 @@ private fun DrawPathMode.getTitle(): Int = when (this) {
is DrawPathMode.OutlinedPolygon -> R.string.outlined_polygon
is DrawPathMode.OutlinedStar -> R.string.outlined_star
is DrawPathMode.Star -> R.string.star
DrawPathMode.FloodFill -> R.string.flood_fill
}
private fun DrawPathMode.getIcon(): ImageVector = when (this) {
@ -526,4 +529,5 @@ private fun DrawPathMode.getIcon(): ImageVector = when (this) {
is DrawPathMode.OutlinedPolygon -> Icons.Outlined.Polygon
is DrawPathMode.OutlinedStar -> Icons.Rounded.StarOutline
is DrawPathMode.Star -> Icons.Rounded.Star
is DrawPathMode.FloodFill -> Icons.Rounded.FormatColorFill
}

View File

@ -107,22 +107,9 @@ fun rememberPaint(
context
) {
derivedStateOf {
val isRect = listOf(
DrawPathMode.OutlinedRect,
DrawPathMode.OutlinedOval,
DrawPathMode.Rect,
DrawPathMode.Oval,
DrawPathMode.Lasso
).any { drawPathMode::class.isInstance(it) }
val isRect = drawPathMode.isRect
val isFilled = listOf(
DrawPathMode.Rect,
DrawPathMode.Oval,
DrawPathMode.Lasso,
DrawPathMode.Triangle,
DrawPathMode.Polygon(),
DrawPathMode.Star()
).any { drawPathMode::class.isInstance(it) }
val isFilled = !drawPathMode.isStroke
Paint().apply {
blendMode = if (!isEraserOn) blendMode else BlendMode.Clear
@ -185,22 +172,9 @@ fun pathEffectPaint(
drawPathMode: DrawPathMode,
canvasSize: IntegerSize,
): NativePaint {
val isRect = listOf(
DrawPathMode.OutlinedRect,
DrawPathMode.OutlinedOval,
DrawPathMode.Rect,
DrawPathMode.Oval,
DrawPathMode.Lasso
).any { drawPathMode::class.isInstance(it) }
val isRect = drawPathMode.isRect
val isFilled = listOf(
DrawPathMode.Rect,
DrawPathMode.Oval,
DrawPathMode.Lasso,
DrawPathMode.Triangle,
DrawPathMode.Polygon(),
DrawPathMode.Star()
).any { drawPathMode::class.isInstance(it) }
val isFilled = !drawPathMode.isStroke
return Paint().apply {
if (isFilled) {

View File

@ -331,6 +331,8 @@ data class PathHelper(
DrawPathMode.Free,
DrawPathMode.Lasso -> onBaseDraw()
DrawPathMode.FloodFill -> Unit
}
} else onBaseDraw()

View File

@ -37,7 +37,6 @@ import ru.tech.imageresizershrinker.core.domain.image.ImageTransformer
import ru.tech.imageresizershrinker.core.domain.model.IntegerSize
import ru.tech.imageresizershrinker.core.filters.domain.FilterProvider
import ru.tech.imageresizershrinker.core.filters.domain.model.Filter
import ru.tech.imageresizershrinker.feature.draw.domain.DrawPathMode
import ru.tech.imageresizershrinker.feature.draw.domain.PathPaint
import ru.tech.imageresizershrinker.feature.filters.domain.FilterMask
import ru.tech.imageresizershrinker.feature.filters.domain.FilterMaskApplier
@ -97,19 +96,9 @@ internal class AndroidFilterMaskApplier @Inject constructor(
oldSize = pathPaint.canvasSize
)
val pathMode = pathPaint.drawPathMode
val isRect = listOf(
DrawPathMode.OutlinedRect,
DrawPathMode.OutlinedOval,
DrawPathMode.Rect,
DrawPathMode.Oval,
DrawPathMode.Lasso
).any { pathMode::class.isInstance(it) }
val isRect = pathMode.isRect
val isFilled = listOf(
DrawPathMode.Rect,
DrawPathMode.Oval,
DrawPathMode.Lasso
).any { pathMode::class.isInstance(it) }
val isFilled = !pathMode.isStroke
drawPath(
path,

View File

@ -84,19 +84,9 @@ fun PathPaintPreview(
.strokeWidth
.toPx(currentSize)
val isRect = listOf(
ru.tech.imageresizershrinker.feature.draw.domain.DrawPathMode.OutlinedRect,
ru.tech.imageresizershrinker.feature.draw.domain.DrawPathMode.OutlinedOval,
ru.tech.imageresizershrinker.feature.draw.domain.DrawPathMode.Rect,
ru.tech.imageresizershrinker.feature.draw.domain.DrawPathMode.Oval,
ru.tech.imageresizershrinker.feature.draw.domain.DrawPathMode.Lasso
).any { pathPaint.drawPathMode::class.isInstance(it) }
val isRect = pathPaint.drawPathMode.isRect
val isFilled = listOf(
ru.tech.imageresizershrinker.feature.draw.domain.DrawPathMode.Rect,
ru.tech.imageresizershrinker.feature.draw.domain.DrawPathMode.Oval,
ru.tech.imageresizershrinker.feature.draw.domain.DrawPathMode.Lasso
).any { pathPaint.drawPathMode::class.isInstance(it) }
val isFilled = !pathPaint.drawPathMode.isStroke
canvas.drawPath(
pathPaint.path

View File

@ -226,7 +226,7 @@ fun DrawEditOption(
}
)
AnimatedVisibility(
visible = drawPathMode.isStroke,
visible = drawPathMode.isStroke || drawPathMode is DrawPathMode.FloodFill,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
@ -238,6 +238,8 @@ fun DrawEditOption(
),
title = if (drawMode is DrawMode.Text) {
stringResource(R.string.font_size)
} else if (drawPathMode is DrawPathMode.FloodFill) {
stringResource(R.string.tolerance)
} else stringResource(R.string.line_width),
valueRange = if (drawMode is DrawMode.Image) {
10f..120f

View File

@ -9,7 +9,7 @@ versionCode = "138"
jvmTarget = "17"
compose-compiler = "1.5.13"
imageToolboxLibs = "1.4.8"
imageToolboxLibs = "1.5.0"
avifCoderCoil = "1.7.5"
avifCoder = "1.7.5"