Reputation: 43
I have created a a canvas application. A user can draw on canvas. Now I want to save the progress of it's drawing in the Room Database. But I'm getting error for Serialization/Deserialization of androidx.compose.ui.graphics.Path.
Here is my code for Serialization/Deserialization of the entity:
@Entity(tableName = "canvas_projects")
data class CanvasProject(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val name: String,
val ratio: Float,
@TypeConverters(BitmapConverter::class) val canvasBackground: Bitmap?,
@TypeConverters(PathListConverter::class) val paths: List<PathWithProperties>
)
data class PathWithProperties(
@TypeConverters(PathConverter::class) val path: Path,
val pathProperties: PathProperties
)
object PathConverter {
private val gson = Gson()
@TypeConverter
fun fromPath(path: Path) : String {
return gson.toJson(path)
}
@TypeConverter
fun toPath(data: String) : Path {
return gson.fromJson(data, Path::class.java)
}
}
data class PathProperties(
val strokeWidth: Float,
val color: String,
val alphas: Float,
val strokeCap: StrokeCap = StrokeCap.Round,
val strokeJoin: StrokeJoin = StrokeJoin.Round,
val eraseMode: Boolean,
val isBitmap: Boolean,
val bitmap: ImageBitmap? = null,
val bitmapRect: Rect? = null
)
object BitmapConverter {
@TypeConverter
fun fromBitmap(bitmap: Bitmap?): ByteArray? {
if (bitmap != null) {
val outputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
return outputStream.toByteArray()
} else return null
}
@TypeConverter
fun toBitmap(byteArray: ByteArray?): Bitmap? {
return if (byteArray != null) BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) else null
}
}
object PathListConverter {
private val gson = Gson()
@TypeConverter
fun fromPathList(pathList: List<PathWithProperties>): String {
return gson.toJson(pathList)
}
@TypeConverter
fun toPathList(data: String): List<PathWithProperties> {
val listType = object : TypeToken<List<PathWithProperties>>() {}.type
return gson.fromJson(data, listType)
}
}
But I'm getting the below error:
com.google.gson.JsonIOException: Interfaces can't be instantiated! Register an InstanceCreator or a TypeAdapter for this type. Interface name: androidx.compose.ui.graphics.Path
How can I solve this issue?
Upvotes: 2
Views: 112
Reputation: 67238
You can't serialize androidx.compose.ui.graphics.Path
but you can create a class, for instance SaveablePath
, that contains or is a Path and define operations like moveTo(x, y)
, lineTo(x, y)
in an Interface or UseCase
and serialize and deserialize them such as
PathDrawEntity(operationType, x, y, other serializable/parcelable properties)
and use this class to draw everything back when you read from database using path object
class SaveablePath(
private val internalPath: Path,
val saveUseCase: SaveUseCase,
):Path {
override fun moveTo(x: Float, y: Float) {
internalPath.moveTo(x, y)
saveUseCase.moveTo(x,y)
}
// Do samething with others
override fun relativeMoveTo(dx: Float, dy: Float) {
internalPath.rMoveTo(dx, dy)
}
override fun lineTo(x: Float, y: Float) {
internalPath.lineTo(x, y)
}
override fun relativeLineTo(dx: Float, dy: Float) {
internalPath.rLineTo(dx, dy)
}
override fun quadraticBezierTo(x1: Float, y1: Float, x2: Float, y2: Float) {
internalPath.quadTo(x1, y1, x2, y2)
}
override fun quadraticTo(x1: Float, y1: Float, x2: Float, y2: Float) {
internalPath.quadTo(x1, y1, x2, y2)
}
override fun relativeQuadraticBezierTo(dx1: Float, dy1: Float, dx2: Float, dy2: Float) {
internalPath.rQuadTo(dx1, dy1, dx2, dy2)
}
}
and a function to read saved operations and apply them to Path when read to draw on Canvas.
Basically you convert Path operations to serializable classes and after reading them from db you draw using these classes by mapping to Path operations.
@Immutable data class RelativeMoveTo(val dx: Float, val dy: Float) : PathNode()
@Immutable data class MoveTo(val x: Float, val y: Float) : PathNode()
@Immutable data class RelativeLineTo(val dx: Float, val dy: Float) : PathNode()
@Immutable data class LineTo(val x: Float, val y: Float) : PathNode()
@Immutable data class RelativeHorizontalTo(val dx: Float) : PathNode()
@Immutable data class HorizontalTo(val x: Float) : PathNode()
@Immutable data class RelativeVerticalTo(val dy: Float) : PathNode()
@Immutable data class VerticalTo(val y: Float) : PathNode()
@Immutable
data class RelativeCurveTo(
val dx1: Float,
val dy1: Float,
val dx2: Float,
val dy2: Float,
val dx3: Float,
val dy3: Float
) : PathNode(isCurve = true)
@Immutable
data class CurveTo(
val x1: Float,
val y1: Float,
val x2: Float,
val y2: Float,
val x3: Float,
val y3: Float
) : PathNode(isCurve = true)
@Immutable
data class RelativeReflectiveCurveTo(
val dx1: Float,
val dy1: Float,
val dx2: Float,
val dy2: Float
) : PathNode(isCurve = true)
Other option is to save Canvas
with GraphicsLayer
to convert to ImageBitmap then to Bitmap or Base64 string or ByteArray to save canvas in db or in a File.
@Preview
@Composable
fun GraphicsLayerToImageBitmapSample() {
val coroutineScope = rememberCoroutineScope()
val graphicsLayer = rememberGraphicsLayer()
var imageBitmap by remember {
mutableStateOf<ImageBitmap?>(null)
}
var touchPosition by remember {
mutableStateOf(Offset.Unspecified)
}
val painter = painterResource(R.drawable.avatar_2_raster)
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Canvas(
modifier = Modifier
.pointerInput(Unit) {
detectTapGestures { offset: Offset ->
touchPosition = offset
}
}
.drawWithContent {
drawContent()
graphicsLayer.record {
[email protected]()
}
}
.fillMaxWidth()
.aspectRatio(1f),
) {
with(painter) {
draw(size)
}
if (touchPosition != Offset.Unspecified) {
drawCircle(
color = Color.Blue,
radius = size.width * .1f,
center = touchPosition,
style = Stroke(
8.dp.toPx(), pathEffect = PathEffect.dashPathEffect(
floatArrayOf(20f, 20f)
)
)
)
}
}
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
coroutineScope.launch {
imageBitmap = graphicsLayer.toImageBitmap()
}
}
) {
Text("Convert graphicsLayer to ImageBitmap")
}
Text(text = "Screenshot of Composable", fontSize = 22.sp)
imageBitmap?.let {
Image(
bitmap = it,
modifier = Modifier
.fillMaxWidth(.7f)
.aspectRatio(1f),
contentDescription = null
)
}
}
}
Upvotes: 3