mirror of
https://github.com/T8RIN/ImageToolbox.git
synced 2025-05-17 05:26:02 +08:00
Try to add flood fill (works ugly and slow)
This commit is contained in:
@ -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>
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
@ -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) {
|
||||
|
@ -331,6 +331,8 @@ data class PathHelper(
|
||||
|
||||
DrawPathMode.Free,
|
||||
DrawPathMode.Lasso -> onBaseDraw()
|
||||
|
||||
DrawPathMode.FloodFill -> Unit
|
||||
}
|
||||
} else onBaseDraw()
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
Reference in New Issue
Block a user