diff --git a/app/src/androidTest/java/com/example/cahier/developer/brushgraph/data/BrushGraphRepositoryTest.kt b/app/src/androidTest/java/com/example/cahier/developer/brushgraph/data/BrushGraphRepositoryTest.kt index 53e962dc..493584e5 100644 --- a/app/src/androidTest/java/com/example/cahier/developer/brushgraph/data/BrushGraphRepositoryTest.kt +++ b/app/src/androidTest/java/com/example/cahier/developer/brushgraph/data/BrushGraphRepositoryTest.kt @@ -18,7 +18,7 @@ package com.example.cahier.developer.brushgraph.data import android.content.Context import androidx.ink.brush.StockBrushes -import androidx.ink.storage.AndroidBrushFamilySerialization +import androidx.ink.storage.encode import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.example.cahier.core.ui.CahierTextureBitmapStore @@ -78,7 +78,7 @@ class BrushGraphRepositoryTest { val family = StockBrushes.marker() val outputStream = FileOutputStream(tempFile) - AndroidBrushFamilySerialization.encode(family, outputStream, mockTextureStore) + family.encode(outputStream, mockTextureStore) outputStream.close() val uriString = android.net.Uri.fromFile(tempFile).toString() diff --git a/app/src/androidTest/java/com/example/cahier/developer/brushgraph/viewmodel/BrushGraphViewModelTest.kt b/app/src/androidTest/java/com/example/cahier/developer/brushgraph/viewmodel/BrushGraphViewModelTest.kt index cbaa27ca..ba40fd49 100644 --- a/app/src/androidTest/java/com/example/cahier/developer/brushgraph/viewmodel/BrushGraphViewModelTest.kt +++ b/app/src/androidTest/java/com/example/cahier/developer/brushgraph/viewmodel/BrushGraphViewModelTest.kt @@ -16,6 +16,7 @@ package com.example.cahier.developer.brushgraph.viewmodel +import androidx.ink.storage.encode import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.example.cahier.core.ui.CahierTextureBitmapStore @@ -442,7 +443,7 @@ class BrushGraphViewModelTest { 0.1f ).family val baos = ByteArrayOutputStream() - androidx.ink.storage.AndroidBrushFamilySerialization.encode(family, baos, mockTextureStore) + family.encode(baos, mockTextureStore) val entity = CustomBrushEntity(name = brushName, brushBytes = baos.toByteArray()) viewModel.loadFromPalette(entity) diff --git a/app/src/main/java/com/example/cahier/core/data/OfflineNotesRepository.kt b/app/src/main/java/com/example/cahier/core/data/OfflineNotesRepository.kt index 6fe9b72e..49097f4a 100644 --- a/app/src/main/java/com/example/cahier/core/data/OfflineNotesRepository.kt +++ b/app/src/main/java/com/example/cahier/core/data/OfflineNotesRepository.kt @@ -19,9 +19,10 @@ package com.example.cahier.core.data import android.content.Context +import android.graphics.Bitmap +import androidx.ink.brush.BrushFamily import androidx.ink.brush.Version -import androidx.ink.storage.AndroidBrushFamilySerialization -import androidx.ink.storage.BrushFamilyDecodeCallback +import androidx.ink.storage.decode import androidx.ink.strokes.Stroke import com.example.cahier.core.ui.CahierTextureBitmapStore import com.example.cahier.core.ui.Converters @@ -59,17 +60,19 @@ class OfflineNotesRepository( val dbBrushes = dbEntities.mapNotNull { entity -> try { ByteArrayInputStream(entity.brushBytes).use { inputStream -> - val family = AndroidBrushFamilySerialization.decode( + val family = BrushFamily.decode( inputStream, - maxVersion = Version.DEVELOPMENT, - BrushFamilyDecodeCallback { id, bitmap -> - if (bitmap != null && textureStore[id] == null) textureStore.loadTexture( - id, - bitmap - ) - id + maxVersion = Version.DEVELOPMENT + ) { id: String, bitmap: Bitmap? -> + bitmap?.let { + if (textureStore[id] == null) + textureStore.loadTexture( + id, + bitmap + ) } - ) + id + } CustomBrush(entity.name, com.example.cahier.R.drawable.edit_24px, family, true) } } catch (e: Exception) { diff --git a/app/src/main/java/com/example/cahier/developer/brushdesigner/viewmodel/BrushDesignerViewModel.kt b/app/src/main/java/com/example/cahier/developer/brushdesigner/viewmodel/BrushDesignerViewModel.kt index cf2c9719..057bd98d 100644 --- a/app/src/main/java/com/example/cahier/developer/brushdesigner/viewmodel/BrushDesignerViewModel.kt +++ b/app/src/main/java/com/example/cahier/developer/brushdesigner/viewmodel/BrushDesignerViewModel.kt @@ -93,7 +93,7 @@ class BrushDesignerViewModel @Inject constructor( BrushFamily.decode( inputStream, maxVersion = Version.DEVELOPMENT - ) { textureId, bitmap -> + ) { textureId: String, bitmap: Bitmap? -> bitmap?.let { textureStore.loadTexture(textureId, it) } textureId } diff --git a/app/src/main/java/com/example/cahier/developer/brushgraph/data/BrushGraphRepository.kt b/app/src/main/java/com/example/cahier/developer/brushgraph/data/BrushGraphRepository.kt index 989091d5..82fbdbf1 100644 --- a/app/src/main/java/com/example/cahier/developer/brushgraph/data/BrushGraphRepository.kt +++ b/app/src/main/java/com/example/cahier/developer/brushgraph/data/BrushGraphRepository.kt @@ -21,8 +21,8 @@ import android.net.Uri import android.util.Log import androidx.ink.brush.BrushFamily import androidx.ink.brush.Version -import androidx.ink.storage.AndroidBrushFamilySerialization -import androidx.ink.storage.BrushFamilyDecodeCallback +import androidx.ink.storage.decode +import androidx.ink.storage.encode import com.example.cahier.R import com.example.cahier.core.di.ApplicationScope import com.example.cahier.core.ui.CahierTextureBitmapStore @@ -102,7 +102,7 @@ class DefaultBrushGraphRepository @Inject constructor( try { val family = BrushFamilyConverter.convert(graph) val baos = ByteArrayOutputStream() - AndroidBrushFamilySerialization.encode(family, baos, textureStore) + family.encode(baos, textureStore) customBrushDao.saveCustomBrush( com.example.cahier.developer.brushdesigner.data.CustomBrushEntity( AUTOSAVE_KEY, @@ -184,16 +184,14 @@ class DefaultBrushGraphRepository @Inject constructor( val decodedBytes = entity.brushBytes return try { val bais = ByteArrayInputStream(decodedBytes) - val family = AndroidBrushFamilySerialization.decode( + val family = BrushFamily.decode( bais, - maxVersion = Version.DEVELOPMENT, - BrushFamilyDecodeCallback { id: String, bitmap: Bitmap? -> - if (bitmap != null) { - textureStore.loadTexture(id, bitmap) - } - id - } - ) + maxVersion = Version.DEVELOPMENT + ) { id: String, bitmap: Bitmap? -> + bitmap?.let { textureStore.loadTexture(id, it) } + id + } + loadBrushFamily(family) true } catch (e: Exception) { @@ -232,16 +230,15 @@ class DefaultBrushGraphRepository @Inject constructor( val bais = ByteArrayInputStream(bytes) val family = try { - AndroidBrushFamilySerialization.decode( + BrushFamily.decode( bais, - maxVersion = Version.DEVELOPMENT, - BrushFamilyDecodeCallback { id, bitmap -> - if (bitmap != null) { - textureStore.loadTexture(id, bitmap) - } - id + maxVersion = Version.DEVELOPMENT + ) { id: String, bitmap: Bitmap? -> + bitmap?.let { + textureStore.loadTexture(id, bitmap) } - ) + id + } } catch (e: Exception) { android.util.Log.e( "DefaultBrushGraphRepository", diff --git a/app/src/main/java/com/example/cahier/developer/brushgraph/ui/BrushGraphScreen.kt b/app/src/main/java/com/example/cahier/developer/brushgraph/ui/BrushGraphScreen.kt index 8c201a05..9be9a365 100644 --- a/app/src/main/java/com/example/cahier/developer/brushgraph/ui/BrushGraphScreen.kt +++ b/app/src/main/java/com/example/cahier/developer/brushgraph/ui/BrushGraphScreen.kt @@ -56,10 +56,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.hilt.navigation.compose.hiltViewModel +import androidx.ink.brush.BrushFamily import androidx.ink.brush.Version import androidx.ink.rendering.android.canvas.CanvasStrokeRenderer -import androidx.ink.storage.AndroidBrushFamilySerialization -import androidx.ink.storage.BrushFamilyDecodeCallback +import androidx.ink.storage.decode +import androidx.ink.storage.encode import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.window.core.layout.WindowHeightSizeClass import androidx.window.core.layout.WindowWidthSizeClass @@ -165,20 +166,17 @@ fun BrushGraphScreen( val family = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) { context.contentResolver.openInputStream(it)?.use { stream -> try { - AndroidBrushFamilySerialization.decode( + BrushFamily.decode( stream, - maxVersion = Version.DEVELOPMENT, - BrushFamilyDecodeCallback { id: String, bitmap: Bitmap? -> - if (bitmap != null) { - viewModel.loadTexture(id, bitmap) - } - id - } - ) + maxVersion = Version.DEVELOPMENT + ) { id: String, bitmap: Bitmap? -> + bitmap?.let { viewModel.loadTexture(id, it) } + id + } } catch (e: Exception) { Log.d( "BrushGraphWidget", - "Failed to decode with AndroidBrushFamilySerialization, trying legacy fallback" + "Failed to load brush." ) null } @@ -186,10 +184,6 @@ fun BrushGraphScreen( } if (family == null) { - Log.d( - "BrushGraphWidget", - "Failed to decode with AndroidBrushFamilySerialization, and legacy fallback is disabled." - ) viewModel.postDebug(DisplayText.Resource(R.string.bg_err_load_brush)) } else { viewModel.loadBrushFamily(family) @@ -215,8 +209,7 @@ fun BrushGraphScreen( scope.launch { try { context.contentResolver.openOutputStream(it)?.use { outputStream -> - AndroidBrushFamilySerialization.encode( - viewModel.brush.value.family, + viewModel.brush.value.family.encode( outputStream, textureStore ) diff --git a/app/src/main/java/com/example/cahier/developer/brushgraph/viewmodel/BrushGraphViewModel.kt b/app/src/main/java/com/example/cahier/developer/brushgraph/viewmodel/BrushGraphViewModel.kt index feaee2fb..f007629f 100644 --- a/app/src/main/java/com/example/cahier/developer/brushgraph/viewmodel/BrushGraphViewModel.kt +++ b/app/src/main/java/com/example/cahier/developer/brushgraph/viewmodel/BrushGraphViewModel.kt @@ -25,8 +25,8 @@ import androidx.ink.brush.StockBrushes import androidx.ink.brush.Version import androidx.ink.brush.compose.composeColor import androidx.ink.brush.compose.createWithComposeColor -import androidx.ink.storage.AndroidBrushFamilySerialization -import androidx.ink.storage.BrushFamilyDecodeCallback +import androidx.ink.storage.decode +import androidx.ink.storage.encode import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.cahier.core.ui.CahierTextureBitmapStore @@ -564,7 +564,7 @@ class BrushGraphViewModel @Inject constructor( viewModelScope.launch(Dispatchers.IO) { try { val baos = ByteArrayOutputStream() - AndroidBrushFamilySerialization.encode(brush.value.family, baos, textureStore) + brush.value.family.encode(baos, textureStore) val finalCompressedBytes = baos.toByteArray() customBrushDao.saveCustomBrush( @@ -595,16 +595,14 @@ class BrushGraphViewModel @Inject constructor( fun loadFromPalette(entity: CustomBrushEntity) { viewModelScope.launch(Dispatchers.IO) { try { - val family = AndroidBrushFamilySerialization.decode( + val family = BrushFamily.decode( ByteArrayInputStream(entity.brushBytes), - maxVersion = Version.DEVELOPMENT, - BrushFamilyDecodeCallback { id, bitmap -> - if (bitmap != null) { - loadTexture(id, bitmap) - } - id - } - ) + maxVersion = Version.DEVELOPMENT + ) { id: String, bitmap: Bitmap? -> + bitmap?.let { loadTexture(id, it) } + id + } + withContext(Dispatchers.Main) { loadBrushFamily(family) } diff --git a/app/src/main/java/com/example/cahier/features/drawing/CustomBrushes.kt b/app/src/main/java/com/example/cahier/features/drawing/CustomBrushes.kt index b53db861..275b8e18 100644 --- a/app/src/main/java/com/example/cahier/features/drawing/CustomBrushes.kt +++ b/app/src/main/java/com/example/cahier/features/drawing/CustomBrushes.kt @@ -17,9 +17,11 @@ package com.example.cahier.features.drawing import android.content.Context +import android.graphics.Bitmap import android.util.Log +import androidx.ink.brush.BrushFamily import androidx.ink.brush.Version -import androidx.ink.storage.AndroidBrushFamilySerialization +import androidx.ink.storage.decode import com.example.cahier.R import com.example.cahier.core.data.CustomBrush import com.example.cahier.core.ui.CahierTextureBitmapStore @@ -59,12 +61,11 @@ object CustomBrushes { val (resourceId, icon) = pair try { val brushFamily = context.resources.openRawResource(resourceId).use { inputStream -> - AndroidBrushFamilySerialization.decode( + BrushFamily.decode( inputStream, maxVersion = Version.DEVELOPMENT - ) { id, bitmap -> - if (bitmap != null) - textureStore.loadTexture(id, bitmap) + ) { id: String, bitmap: Bitmap? -> + bitmap?.let { textureStore.loadTexture(id, bitmap) } id } } diff --git a/app/src/main/java/com/example/cahier/features/drawing/viewmodel/DrawingCanvasViewModel.kt b/app/src/main/java/com/example/cahier/features/drawing/viewmodel/DrawingCanvasViewModel.kt index 3671bbe8..efc2b63f 100644 --- a/app/src/main/java/com/example/cahier/features/drawing/viewmodel/DrawingCanvasViewModel.kt +++ b/app/src/main/java/com/example/cahier/features/drawing/viewmodel/DrawingCanvasViewModel.kt @@ -18,6 +18,7 @@ package com.example.cahier.features.drawing.viewmodel import android.annotation.SuppressLint import android.content.Context +import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Canvas import android.net.Uri @@ -40,7 +41,8 @@ import androidx.ink.geometry.MutableParallelogram import androidx.ink.geometry.MutableSegment import androidx.ink.geometry.MutableVec import androidx.ink.rendering.android.canvas.CanvasStrokeRenderer -import androidx.ink.storage.AndroidBrushFamilySerialization +import androidx.ink.storage.decode +import androidx.ink.storage.encode import androidx.ink.strokes.Stroke import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel @@ -475,8 +477,7 @@ class DrawingCanvasViewModel @Inject constructor( withContext(Dispatchers.IO) { try { val stream = java.io.ByteArrayOutputStream() - AndroidBrushFamilySerialization.encode( - _selectedBrush.value.family, + _selectedBrush.value.family.encode( stream, textureStore ) @@ -529,12 +530,11 @@ class DrawingCanvasViewModel @Inject constructor( ByteArrayInputStream(entity.brushBytes).use { inputStream -> val family = - AndroidBrushFamilySerialization.decode( + BrushFamily.decode( inputStream, maxVersion = Version.DEVELOPMENT - ) { id, bitmap -> - if (bitmap != null) - textureStore.loadTexture(id, bitmap) + ) { id: String, bitmap: Bitmap? -> + bitmap?.let { textureStore.loadTexture(id, bitmap) } id } CustomBrush( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 67cbeb35..73e6a811 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ coilCompose = "3.4.0" foundation = "1.10.5" hiltAndroid = "2.59.2" hiltNavigationCompose = "1.3.0" -ink = "1.1.0-alpha03" +ink = "1.1.0-alpha04" kotlin = "2.3.20" coreKtx = "1.18.0" junit = "4.13.2" diff --git a/ink-proto/src/main/proto/brush_family.proto b/ink-proto/src/main/proto/brush_family.proto index 5dbb150b..310337e8 100644 --- a/ink-proto/src/main/proto/brush_family.proto +++ b/ink-proto/src/main/proto/brush_family.proto @@ -126,7 +126,8 @@ message BrushCoat { // Was `paint`; use `paint_preferences` instead. reserved 2; - // If empty, the default `BrushPaint` will be used. + // If empty, the default `BrushPaint` will be used, which has no + // `TextureLayer`s or `ColorFunction`s, and uses `SELF_OVERLAP_ANY`. repeated BrushPaint paint_preferences = 3 [(ink.proto.field_min_version) = 0]; } @@ -475,6 +476,14 @@ message BrushPaint { // opacity variations (e.g. with // `BrushBehavior::Target::HUE_OFFSET_IN_RADIANS`), or complex textures (e.g. // with `BrushPaint::StampingTexture`). + // + // That being said, SELF_OVERLAP_DISCARD is incompatible on the same + // `BrushCoat` with `BrushBehavior`s which target opacity or color, and with + // `BrushPaint::StampingTexture`. This is because SELF_OVERLAP_DISCARD forces + // usage of the path renderer, and the aforementioned features require the + // mesh renderer. In the case where both are specified on the same + // `BrushCoat`, the effects of the `BrushBehavior`/`TextureLayer`(s) will not + // be rendered. enum SelfOverlap { SELF_OVERLAP_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = 0]; SELF_OVERLAP_ANY = 1 [(ink.proto.enum_value_min_version) = 0]; @@ -900,13 +909,18 @@ message BrushBehavior { // Moves the brush tip by the target modifier times the brush size in the // direction of the modeled stroke input's velocity (the opposite direction - // if the value is negative). + // if the value is negative). For example, if a stroke is moving from left + // to right, a positive modifier will shift the brush tip to the right, + // and a negative modifier will shift the brush tip to the left. TARGET_POSITION_OFFSET_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE = 14 [(ink.proto.enum_value_min_version) = 0]; // Moves the brush tip by the target modifier times the brush size // perpendicular to the modeled stroke input's velocity, rotated 90 degrees - // in the direction from the positive x-axis to the positive y-axis. + // in the direction from the positive x-axis to the positive y-axis. For + // example, if positive-Y is up, and a stroke is moving from left to right, + // a positive modifier will shift the brush tip up, and a negative modifier + // will shift the brush tip down. TARGET_POSITION_OFFSET_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE = 15 [(ink.proto.enum_value_min_version) = 0]; @@ -1069,7 +1083,8 @@ message BrushBehavior { // To be valid: // * `source` must be a valid `Source` enumerator. // * `source_out_of_range_behavior` must be a valid `OutOfRange` enumerator. - // * The endpoints of `source_value_range` must be finite and distinct. + // * `source_value_range_start` and `source_value_range_end` must be finite + // and distinct. message SourceNode { option (ink.proto.message_min_version) = 0; @@ -1241,7 +1256,8 @@ message BrushBehavior { // // To be valid: // * `integrate_over` must be a valid `ProgressDomain` enumerator. - // * The endpoints of `integral_value_range` must be finite and distinct. + // * `integral_value_range_start` and `integral_value_range_end` must be + // finite and distinct. // * `integral_out_of_range_behavior` must be a valid `OutOfRange` // enumerator. message IntegralNode { @@ -1279,7 +1295,8 @@ message BrushBehavior { // // To be valid: // * `target` must be a valid `Target` enumerator. - // * The endpoints of `target_modifier_range` must be finite and distinct. + // * `target_modifier_range_start` and `target_modifier_range_end` must be + // finite and distinct. message TargetNode { option (ink.proto.message_min_version) = 0; @@ -1308,8 +1325,9 @@ message BrushBehavior { // // To be valid: // * `target` must be a valid `PolarTarget` enumerator. - // * The endpoints of `angle_range` and of `magnitude_range` must be finite - // and distinct. + // * `angle_range_start` and `angle_range_end` must be finite and distinct. + // * `magnitude_range_start` and `magnitude_range_end` must be finite and + // distinct. message PolarTargetNode { option (ink.proto.message_min_version) = 0;