1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.ink.brush 18 19 import androidx.ink.brush.color.Color 20 import androidx.ink.brush.color.colorspace.ColorSpaces 21 import androidx.ink.brush.color.toArgb 22 import androidx.ink.nativeloader.UsedByNative 23 import com.google.common.truth.Truth.assertThat 24 import kotlin.test.assertFailsWith 25 import org.junit.Test 26 import org.junit.runner.RunWith 27 import org.junit.runners.JUnit4 28 29 @OptIn(ExperimentalInkCustomBrushApi::class) 30 @RunWith(JUnit4::class) 31 class BrushTest { 32 private val size = 10F 33 private val epsilon = 1F 34 private val color = Color(red = 230, green = 115, blue = 140, alpha = 255) 35 private val family = BrushFamily(clientBrushFamilyId = "/brush-family:inkpen:1") 36 37 @Test constructor_withValidArguments_returnsABrushnull38 fun constructor_withValidArguments_returnsABrush() { 39 val brush = Brush.createWithColorLong(family, color.value.toLong(), size, epsilon) 40 assertThat(brush).isNotNull() 41 assertThat(brush.family).isEqualTo(family) 42 assertThat(brush.colorLong).isEqualTo(color.value.toLong()) 43 assertThat(brush.colorIntArgb).isEqualTo(color.toArgb()) 44 assertThat(brush.colorLong).isEqualTo(color.value.toLong()) 45 assertThat(brush.size).isEqualTo(size) 46 assertThat(brush.epsilon).isEqualTo(epsilon) 47 } 48 49 @Test 50 @Suppress("Range") // Testing error cases. constructor_withBadSize_willThrownull51 fun constructor_withBadSize_willThrow() { 52 assertFailsWith<IllegalArgumentException> { 53 Brush(family, color, -2F, epsilon) // non-positive size. 54 } 55 56 assertFailsWith<IllegalArgumentException> { 57 Brush(family, color, Float.POSITIVE_INFINITY, epsilon) // non-finite size. 58 } 59 60 assertFailsWith<IllegalArgumentException> { 61 Brush(family, color, Float.NaN, epsilon) // non-finite size. 62 } 63 } 64 65 @Test 66 @Suppress("Range") // Testing error cases. constructor_withBadEpsilon_willThrownull67 fun constructor_withBadEpsilon_willThrow() { 68 assertFailsWith<IllegalArgumentException> { 69 Brush(family, color, size, -2F) // non-positive epsilon. 70 } 71 72 assertFailsWith<IllegalArgumentException> { 73 Brush(family, color, size, Float.POSITIVE_INFINITY) // non-finite epsilon. 74 } 75 76 assertFailsWith<IllegalArgumentException> { 77 Brush(family, color, size, Float.NaN) // non-finite epsilon. 78 } 79 } 80 81 @Test colorAccessors_areAllEquivalentnull82 fun colorAccessors_areAllEquivalent() { 83 val color = Color(red = 230, green = 115, blue = 140, alpha = 196) 84 val brush = Brush.createWithColorLong(family, color.value.toLong(), size, epsilon) 85 86 assertThat(brush.colorIntArgb).isEqualTo(color.toArgb()) 87 assertThat(brush.colorLong).isEqualTo(color.value.toLong()) 88 } 89 90 @Test withColorIntArgb_withLowAlpha_returnsBrushWithCorrectColornull91 fun withColorIntArgb_withLowAlpha_returnsBrushWithCorrectColor() { 92 val brush = Brush.createWithColorIntArgb(family, 0x12345678, size, epsilon) 93 assertThat(brush.colorIntArgb).isEqualTo(0x12345678) 94 } 95 96 @Test withColorIntArgb_withHighAlpha_returnsBrushWithCorrectColornull97 fun withColorIntArgb_withHighAlpha_returnsBrushWithCorrectColor() { 98 val brush = Brush.createWithColorIntArgb(family, 0xAA123456.toInt(), size, epsilon) 99 assertThat(brush.colorIntArgb).isEqualTo(0xAA123456.toInt()) 100 } 101 102 @Test withColorLong_returnsBrushWithCorrectColornull103 fun withColorLong_returnsBrushWithCorrectColor() { 104 val colorLong = Color(0.9f, 0.45f, 0.55f, 0.15f, ColorSpaces.DisplayP3).value.toLong() 105 val brush = Brush.createWithColorLong(family, colorLong, size, epsilon) 106 assertThat(brush.colorLong).isEqualTo(colorLong) 107 } 108 109 @Test withColorLong_inUnsupportedColorSpace_returnsBrushWithConvertedColornull110 fun withColorLong_inUnsupportedColorSpace_returnsBrushWithConvertedColor() { 111 val colorLong = Color(0.9f, 0.45f, 0.55f, 0.15f, ColorSpaces.AdobeRgb).value.toLong() 112 val brush = Brush.createWithColorLong(family, colorLong, size, epsilon) 113 114 val expectedColor = Color(colorLong.toULong()).convert(ColorSpaces.DisplayP3) 115 assertThat(brush.colorLong).isEqualTo(expectedColor.value.toLong()) 116 assertThat(brush.colorIntArgb).isEqualTo(expectedColor.toArgb()) 117 } 118 119 @Test equals_returnsTrueForIdenticalBrushesnull120 fun equals_returnsTrueForIdenticalBrushes() { 121 val brush = Brush(family, color, size, epsilon) 122 val otherBrush = Brush(family, color, size, epsilon) 123 assertThat(brush == brush).isTrue() 124 assertThat(brush == otherBrush).isTrue() 125 assertThat(otherBrush == brush).isTrue() 126 } 127 128 @Test hashCode_isEqualForIdenticalBrushesnull129 fun hashCode_isEqualForIdenticalBrushes() { 130 val brush = Brush(family, color, size, epsilon) 131 val otherBrush = Brush(family, color, size, epsilon) 132 assertThat(brush == brush).isTrue() 133 assertThat(brush == otherBrush).isTrue() 134 assertThat(otherBrush == brush).isTrue() 135 } 136 137 @Test equals_returnsFalseIfAnyFieldsDiffernull138 fun equals_returnsFalseIfAnyFieldsDiffer() { 139 val brush = Brush(family, color, size, epsilon) 140 141 val differentFamilyBrush = 142 Brush(BrushFamily(clientBrushFamilyId = "/brush-family:pencil:1"), color, size, epsilon) 143 assertThat(brush == differentFamilyBrush).isFalse() 144 assertThat(differentFamilyBrush == brush).isFalse() 145 assertThat(brush != differentFamilyBrush).isTrue() 146 assertThat(differentFamilyBrush != brush).isTrue() 147 148 val otherColor = 149 Color(red = 1F, green = 0F, blue = 0F, alpha = 1F, colorSpace = ColorSpaces.DisplayP3) 150 .value 151 .toLong() 152 val differentcolorBrush = Brush.createWithColorLong(family, otherColor, size, epsilon) 153 assertThat(brush == differentcolorBrush).isFalse() 154 assertThat(differentcolorBrush == brush).isFalse() 155 assertThat(brush != differentcolorBrush).isTrue() 156 assertThat(differentcolorBrush != brush).isTrue() 157 158 val differentSizeBrush = Brush(family, color, 9.0f, epsilon) 159 assertThat(brush == differentSizeBrush).isFalse() 160 assertThat(differentSizeBrush == brush).isFalse() 161 assertThat(brush != differentSizeBrush).isTrue() 162 assertThat(differentSizeBrush != brush).isTrue() 163 164 val differentEpsilonBrush = Brush(family, color, size, 1.1f) 165 assertThat(brush == differentEpsilonBrush).isFalse() 166 assertThat(differentEpsilonBrush == brush).isFalse() 167 assertThat(brush != differentEpsilonBrush).isTrue() 168 assertThat(differentEpsilonBrush != brush).isTrue() 169 } 170 171 @Test hashCode_differsIfAnyFieldsDiffernull172 fun hashCode_differsIfAnyFieldsDiffer() { 173 val brush = Brush(family, color, size, epsilon) 174 175 val differentFamilyBrush = 176 Brush(BrushFamily(clientBrushFamilyId = "/brush-family:pencil:1"), color, size, epsilon) 177 assertThat(differentFamilyBrush.hashCode()).isNotEqualTo(brush.hashCode()) 178 179 val otherColor = 180 Color(red = 1F, green = 0F, blue = 0F, alpha = 1F, colorSpace = ColorSpaces.DisplayP3) 181 .value 182 .toLong() 183 val differentcolorBrush = Brush.createWithColorLong(family, otherColor, size, epsilon) 184 assertThat(differentcolorBrush.hashCode()).isNotEqualTo(brush.hashCode()) 185 186 val differentSizeBrush = Brush(family, color, 9.0f, epsilon) 187 assertThat(differentSizeBrush.hashCode()).isNotEqualTo(brush.hashCode()) 188 189 val differentEpsilonBrush = Brush(family, color, size, 1.1f) 190 assertThat(differentEpsilonBrush.hashCode()).isNotEqualTo(brush.hashCode()) 191 } 192 193 @Test copy_returnsTheSameBrushnull194 fun copy_returnsTheSameBrush() { 195 val originalBrush = buildTestBrush() 196 197 val newBrush = originalBrush.copy() 198 199 // A pure copy returns `this`. 200 assertThat(newBrush).isSameInstanceAs(originalBrush) 201 } 202 203 @Test copy_withChangedBrushFamily_returnsCopyWithDifferentBrushFamilynull204 fun copy_withChangedBrushFamily_returnsCopyWithDifferentBrushFamily() { 205 val originalBrush = buildTestBrush() 206 207 val newBrush = originalBrush.copy(family = BrushFamily()) 208 209 assertThat(newBrush).isNotEqualTo(originalBrush) 210 assertThat(newBrush.family).isNotEqualTo(originalBrush.family) 211 212 // The new brush has the original color, size and epsilon. 213 assertThat(newBrush.colorLong).isEqualTo(originalBrush.colorLong) 214 assertThat(newBrush.size).isEqualTo(originalBrush.size) 215 assertThat(newBrush.epsilon).isEqualTo(originalBrush.epsilon) 216 } 217 218 @Test copyWithColorIntArgb_withLowAlpha_returnsCopyWithThatColornull219 fun copyWithColorIntArgb_withLowAlpha_returnsCopyWithThatColor() { 220 val originalBrush = buildTestBrush() 221 222 val newBrush = originalBrush.copyWithColorIntArgb(colorIntArgb = 0x12345678) 223 224 assertThat(newBrush).isNotEqualTo(originalBrush) 225 assertThat(newBrush.colorLong).isNotEqualTo(originalBrush.colorLong) 226 assertThat(newBrush.colorIntArgb).isEqualTo(0x12345678) 227 228 // The new brush has the original family, size and epsilon. 229 assertThat(newBrush.family).isSameInstanceAs(originalBrush.family) 230 assertThat(newBrush.size).isEqualTo(originalBrush.size) 231 assertThat(newBrush.epsilon).isEqualTo(originalBrush.epsilon) 232 } 233 234 @Test copyWithColorIntArgb_withHighAlpha_returnsCopyWithThatColornull235 fun copyWithColorIntArgb_withHighAlpha_returnsCopyWithThatColor() { 236 val originalBrush = buildTestBrush() 237 238 val newBrush = originalBrush.copyWithColorIntArgb(colorIntArgb = 0xAA123456.toInt()) 239 240 assertThat(newBrush).isNotEqualTo(originalBrush) 241 assertThat(newBrush.colorLong).isNotEqualTo(originalBrush.colorLong) 242 assertThat(newBrush.colorIntArgb).isEqualTo(0xAA123456.toInt()) 243 244 // The new brush has the original family, size and epsilon. 245 assertThat(newBrush.family).isSameInstanceAs(originalBrush.family) 246 assertThat(newBrush.size).isEqualTo(originalBrush.size) 247 assertThat(newBrush.epsilon).isEqualTo(originalBrush.epsilon) 248 } 249 250 @Test copyWithColorLong_withChangedColor_returnsCopyWithThatColornull251 fun copyWithColorLong_withChangedColor_returnsCopyWithThatColor() { 252 val originalBrush = buildTestBrush() 253 254 val newColor = Color(red = 255, green = 230, blue = 115, alpha = 140).value.toLong() 255 val newBrush = originalBrush.copyWithColorLong(colorLong = newColor) 256 257 assertThat(newBrush).isNotEqualTo(originalBrush) 258 assertThat(newBrush.colorLong).isNotEqualTo(originalBrush.colorLong) 259 assertThat(newBrush.colorLong).isEqualTo(newColor) 260 261 // The new brush has the original family, size and epsilon. 262 assertThat(newBrush.family).isSameInstanceAs(originalBrush.family) 263 assertThat(newBrush.size).isEqualTo(originalBrush.size) 264 assertThat(newBrush.epsilon).isEqualTo(originalBrush.epsilon) 265 } 266 267 @Test copyWithColorLong_inUnsupportedColorSpace_returnsCopyWithConvertedColornull268 fun copyWithColorLong_inUnsupportedColorSpace_returnsCopyWithConvertedColor() { 269 val originalBrush = buildTestBrush() 270 271 val newColor = Color(0.9f, 0.45f, 0.55f, 0.15f, ColorSpaces.AdobeRgb).value.toLong() 272 val newBrush = originalBrush.copyWithColorLong(colorLong = newColor) 273 274 val expectedColor = Color(newColor.toULong()).convert(ColorSpaces.DisplayP3) 275 assertThat(newBrush.colorLong).isEqualTo(expectedColor.value.toLong()) 276 assertThat(newBrush.colorIntArgb).isEqualTo(expectedColor.toArgb()) 277 } 278 279 @Test brushBuilderBuild_withColorIntWithLowAlpha_createsExpectedBrushnull280 fun brushBuilderBuild_withColorIntWithLowAlpha_createsExpectedBrush() { 281 val testBrush = buildTestBrush() 282 283 val builtBrush = 284 Brush.builder() 285 .setFamily(testBrush.family) 286 .setColorIntArgb(0x12345678) 287 .setSize(9f) 288 .setEpsilon(0.9f) 289 .build() 290 291 assertThat(builtBrush.family).isEqualTo(testBrush.family) 292 assertThat(builtBrush.colorIntArgb).isEqualTo(0x12345678) 293 assertThat(builtBrush.size).isEqualTo(9f) 294 assertThat(builtBrush.epsilon).isEqualTo(0.9f) 295 } 296 297 @Test brushBuilderBuild_withColorIntWithHighAlpha_createsExpectedBrushnull298 fun brushBuilderBuild_withColorIntWithHighAlpha_createsExpectedBrush() { 299 val testBrush = buildTestBrush() 300 301 val builtBrush = 302 Brush.builder() 303 .setFamily(testBrush.family) 304 .setColorIntArgb(0xAA123456.toInt()) 305 .setSize(9f) 306 .setEpsilon(0.9f) 307 .build() 308 309 assertThat(builtBrush.family).isEqualTo(testBrush.family) 310 assertThat(builtBrush.colorIntArgb).isEqualTo(0xAA123456.toInt()) 311 assertThat(builtBrush.size).isEqualTo(9f) 312 assertThat(builtBrush.epsilon).isEqualTo(0.9f) 313 } 314 315 @Test brushBuilderBuild_withColorLong_createsExpectedBrushnull316 fun brushBuilderBuild_withColorLong_createsExpectedBrush() { 317 val testBrush = buildTestBrush() 318 val testColorLong = Color(0.9f, 0.45f, 0.55f, 0.15f, ColorSpaces.DisplayP3).value.toLong() 319 320 val builtBrush = 321 Brush.builder() 322 .setFamily(testBrush.family) 323 .setColorLong(testColorLong) 324 .setSize(9f) 325 .setEpsilon(0.9f) 326 .build() 327 328 assertThat(builtBrush.family).isEqualTo(testBrush.family) 329 assertThat(builtBrush.colorLong).isEqualTo(testColorLong) 330 assertThat(builtBrush.size).isEqualTo(9f) 331 assertThat(builtBrush.epsilon).isEqualTo(0.9f) 332 } 333 334 @Test brushBuilderBuild_withUnsupportedColorSpace_createsBrushWithConvertedColornull335 fun brushBuilderBuild_withUnsupportedColorSpace_createsBrushWithConvertedColor() { 336 val testBrush = buildTestBrush() 337 val testColorLong = Color(0.9f, 0.45f, 0.55f, 0.15f, ColorSpaces.AdobeRgb).value.toLong() 338 339 val builtBrush = 340 Brush.builder() 341 .setFamily(testBrush.family) 342 .setColorLong(testColorLong) 343 .setSize(9f) 344 .setEpsilon(0.9f) 345 .build() 346 347 val expectedColor = Color(testColorLong.toULong()).convert(ColorSpaces.DisplayP3) 348 assertThat(builtBrush.family).isEqualTo(testBrush.family) 349 assertThat(builtBrush.colorLong).isEqualTo(expectedColor.value.toLong()) 350 assertThat(builtBrush.size).isEqualTo(9f) 351 assertThat(builtBrush.epsilon).isEqualTo(0.9f) 352 } 353 354 /** 355 * Creates an expected C++ Brush with default brush family/color and returns true if every 356 * property of the Kotlin Brush's JNI-created C++ counterpart is equivalent to the expected C++ 357 * Brush. 358 */ matchesDefaultBrushnull359 @UsedByNative private external fun matchesDefaultBrush(actualBrushNativePointer: Long): Boolean 360 361 /** 362 * Creates an expected C++ Brush with custom values and returns true if every property of the 363 * Kotlin Brush's JNI-created C++ counterpart is equivalent to the expected C++ Brush. 364 */ 365 @UsedByNative private external fun matchesCustomBrush(actualBrushNativePointer: Long): Boolean 366 367 /** Brush with every field different from default values. */ 368 private fun buildTestBrush(): Brush = 369 Brush( 370 BrushFamily( 371 tip = 372 BrushTip( 373 0.1f, 374 0.2f, 375 0.3f, 376 0.4f, 377 0.5f, 378 0.6f, 379 0.7f, 380 0.8f, 381 9L, 382 listOf( 383 BrushBehavior( 384 source = BrushBehavior.Source.TILT_IN_RADIANS, 385 target = BrushBehavior.Target.HEIGHT_MULTIPLIER, 386 sourceValueRangeStart = 0.2f, 387 sourceValueRangeEnd = .8f, 388 targetModifierRangeStart = 1.1f, 389 targetModifierRangeEnd = 1.7f, 390 sourceOutOfRangeBehavior = BrushBehavior.OutOfRange.MIRROR, 391 responseCurve = EasingFunction.Predefined.EASE_IN_OUT, 392 responseTimeMillis = 1L, 393 enabledToolTypes = setOf(InputToolType.STYLUS), 394 isFallbackFor = BrushBehavior.OptionalInputProperty.TILT_X_AND_Y, 395 ) 396 ), 397 ), 398 paint = BrushPaint(), 399 clientBrushFamilyId = "/brush-family:marker:1", 400 ), 401 color, 402 13F, 403 0.1234F, 404 ) 405 } 406