diff --git a/capture-sdk/sdk/src/test/java/net/gini/android/capture/internal/textrecognition/CropToCameraFrameTextRecognizerTest.kt b/capture-sdk/sdk/src/test/java/net/gini/android/capture/internal/textrecognition/CropToCameraFrameTextRecognizerTest.kt new file mode 100644 index 0000000000..f8cf161c1c --- /dev/null +++ b/capture-sdk/sdk/src/test/java/net/gini/android/capture/internal/textrecognition/CropToCameraFrameTextRecognizerTest.kt @@ -0,0 +1,176 @@ +package net.gini.android.capture.internal.textrecognition + +import android.graphics.Rect +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertWithMessage +import com.nhaarman.mockitokotlin2.mock +import net.gini.android.capture.internal.textrecognition.test.RecognizedTextFixtures +import net.gini.android.capture.internal.textrecognition.test.TextRecognizerStub +import net.gini.android.capture.internal.util.Size +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Cannot use `@RunWith(JUnitParamsRunner::class)` because android.graphics.Rect needs the implementation provided by Robolectric. + * Without Robolectric "method not mocked" exceptions are thrown: + * > `java.lang.RuntimeException: Method contains in android.graphics.Rect not mocked. See https://developer.android.com/r/studio-ui/build/not-mocked for details.` + */ +@RunWith(AndroidJUnit4::class) +internal class CropToCameraFrameTextRecognizerTest { + + /** + * Cannot use `@Parameters(method = "textCroppingTestParameterValues")`. See class comment above. + */ + @Test + fun `processImage() crops text to the camera frame and maintains text layout`() { + val testParameters = textCroppingTestParameterValues() + for (testParameter in testParameters) { + val testDescription = testParameter[0] as String + val recognizedText = testParameter[1] as RecognizedText + val expectedText = testParameter[2] as RecognizedText + val cameraPreviewSize = testParameter[3] as Size? + val imageSize = testParameter[4] as Size? + val imageRotation = testParameter[5] as Int + val cameraFrame = testParameter[6] as Rect? + + // Given + val textRecognizer = CropToCameraFrameTextRecognizer(TextRecognizerStub(recognizedText)) + cameraPreviewSize?.let { textRecognizer.cameraPreviewSize = it } + imageSize?.let { textRecognizer.setImageSizeAndRotation(imageSize, imageRotation) } + cameraFrame?.let { textRecognizer.cameraFrameRect = it } + + // When + textRecognizer.processImage(mock(), 100, 200, 0, doneCallback = { text -> + // Then + assertWithMessage(""" + Test: $testDescription + Camera preview size: $cameraPreviewSize + Image size: $imageSize + Image rotation: $imageRotation + Camera frame: $cameraFrame + """.trimIndent()) + .that(text).isEqualTo(expectedText) + }, cancelledCallback = { + }) + } + } + + /** + * Cannot use `@Parameters(method = "textCroppingTestParameterValues")`. See class comment above. + */ + @Test + fun `processByteArray() crops text to the camera frame and maintains text layout`() { + val testParameters = textCroppingTestParameterValues() + for (testParameter in testParameters) { + val testDescription = testParameter[0] as String + val recognizedText = testParameter[1] as RecognizedText + val expectedText = testParameter[2] as RecognizedText + val cameraPreviewSize = testParameter[3] as Size? + val imageSize = testParameter[4] as Size? + val imageRotation = testParameter[5] as Int + val cameraFrame = testParameter[6] as Rect? + + // Given + val textRecognizer = CropToCameraFrameTextRecognizer(TextRecognizerStub(recognizedText)) + cameraPreviewSize?.let { textRecognizer.cameraPreviewSize = it } + imageSize?.let { textRecognizer.setImageSizeAndRotation(imageSize, imageRotation) } + cameraFrame?.let { textRecognizer.cameraFrameRect = it } + + // When + textRecognizer.processByteArray(ByteArray(0), 100, 200, 0, doneCallback = { text -> + // Then + assertWithMessage(""" + Test: $testDescription + Camera preview size: $cameraPreviewSize + Image size: $imageSize + Image rotation: $imageRotation + Camera frame: $cameraFrame + """.trimIndent()) + .that(text).isEqualTo(expectedText) + }, cancelledCallback = { + }) + } + } + + private fun textCroppingTestParameterValues(): List> = listOf( + // testDescription, recognizedText, expectedText, cameraPreviewSize, imageSize, imageRotation, cameraFrame + listOf("Don't crop if some sizes are null", RecognizedTextFixtures.fixture1, RecognizedTextFixtures.fixture1, null, null, 0, null), + listOf("Don't crop if some sizes are null", RecognizedTextFixtures.fixture1, RecognizedTextFixtures.fixture1, null, null, 0, Rect(0, 0, 1000, 1000)), + listOf("Don't crop if some sizes are null", RecognizedTextFixtures.fixture1, RecognizedTextFixtures.fixture1, null, Size(1000, 1000), 0, null), + listOf("Don't crop if some sizes are null", RecognizedTextFixtures.fixture1, RecognizedTextFixtures.fixture1, null, Size(1000, 1000), 0, Rect(0, 0, 1000, 1000)), + listOf("Don't crop if some sizes are null", RecognizedTextFixtures.fixture1, RecognizedTextFixtures.fixture1, Size(1000, 1000), null, 0, null), + listOf("Don't crop if some sizes are null", RecognizedTextFixtures.fixture1, RecognizedTextFixtures.fixture1, Size(1000, 1000), null, 0, Rect(0, 0, 1000, 1000)), + listOf("Don't crop if some sizes are null", RecognizedTextFixtures.fixture1, RecognizedTextFixtures.fixture1, Size(1000, 1000), Size(1000, 1000), 0, null), + listOf("Don't crop if the camera frame contains all the text", + RecognizedTextFixtures.fixture1, RecognizedTextFixtures.fixture1, Size(1000, 1000), Size(1000, 1000), 0, Rect(0, 0, 1000, 1000)), + listOf("Don't crop if the camera frame contains all the text and the image size is different from the camera preview size", + RecognizedTextFixtures.fixture2, RecognizedTextFixtures.fixture2, Size(1000, 1000), Size(100, 100), 0, Rect(0, 0, 1000, 1000)), + listOf("Crop elements which exceed the left edge of the camera frame", + RecognizedTextFixtures.fixture1, fixture1CroppedLeft, Size(1000, 1000), Size(1000, 1000), 0, Rect(200, 0, 1000, 1000)), + listOf("Crop elements which exceed the left edge of the camera frame and the image size is different from the camera preview size", + RecognizedTextFixtures.fixture2, fixture2CroppedLeft, Size(1000, 1000), Size(100, 100), 0, Rect(80, 0, 1000, 1000)), + listOf("Crop elements which exceed the top edge of the camera frame", + RecognizedTextFixtures.fixture1, fixture1CroppedTop, Size(1000, 1000), Size(1000, 1000), 0, Rect(0, 140, 1000, 1000)), + // TODO: + // Crop elements which exceed the top edge of the camera frame and the image size is different from the camera preview size + // Crop elements which exceed the right edge of the camera frame + // Crop elements which exceed the right edge of the camera frame and the image size is different from the camera preview size + // Crop elements which exceed the bottom edge of the camera frame + // Crop elements which exceed the bottom edge of the camera frame and the image size is different from the camera preview size + ) + + private val fixture1CroppedLeft = RecognizedText( + """ + SE + Bank + """.trimIndent(), + blocks = listOf( + RecognizedTextBlock( + listOf( + RecognizedTextLine( + listOf( + RecognizedTextElement("SE", Rect(240, 90, 260, 100)), + ) + ), + ) + ), + RecognizedTextBlock( + listOf( + RecognizedTextLine( + listOf( + RecognizedTextElement("Bank", Rect(200, 130, 240, 140)), + ) + ), + ) + ), + ) + ) + + private val fixture2CroppedLeft = RecognizedText("DE86210700200123010101", + blocks = listOf( + RecognizedTextBlock( + listOf( + RecognizedTextLine( + listOf( + RecognizedTextElement("DE86210700200123010101", Rect(11, 7, 33, 8)), + ) + ), + ) + ), + ) + ) + + private val fixture1CroppedTop = RecognizedText("Verwendungszweck:", + blocks = listOf( + RecognizedTextBlock( + listOf( + RecognizedTextLine( + listOf( + RecognizedTextElement("Verwendungszweck:", Rect(50, 150, 170, 160)), + ) + ), + ) + ), + ) + ) +} \ No newline at end of file diff --git a/capture-sdk/sdk/src/test/java/net/gini/android/capture/internal/textrecognition/test/RecognizedTextFixtures.kt b/capture-sdk/sdk/src/test/java/net/gini/android/capture/internal/textrecognition/test/RecognizedTextFixtures.kt new file mode 100644 index 0000000000..857a3c48b2 --- /dev/null +++ b/capture-sdk/sdk/src/test/java/net/gini/android/capture/internal/textrecognition/test/RecognizedTextFixtures.kt @@ -0,0 +1,100 @@ +package net.gini.android.capture.internal.textrecognition.test + +import android.graphics.Rect +import net.gini.android.capture.internal.textrecognition.RecognizedText +import net.gini.android.capture.internal.textrecognition.RecognizedTextBlock +import net.gini.android.capture.internal.textrecognition.RecognizedTextElement +import net.gini.android.capture.internal.textrecognition.RecognizedTextLine + +internal object RecognizedTextFixtures { + + // For image size: 1000 x 1000 + val fixture1 = RecognizedText(""" + Bankverbindung + IBAN: DE86210700200123010101 + Empfänger: Zalando SE + BIC: DEUTEDEHH210 + Bank: Deutsche Bank + Verwendungszweck: + """.trimIndent(), + blocks = listOf( + RecognizedTextBlock( + listOf( + RecognizedTextLine( + listOf( + RecognizedTextElement("Bankverbindung", Rect(50, 50, 190, 60)), + ) + ), + RecognizedTextLine( + listOf( + RecognizedTextElement("IBAN:", Rect(50, 70, 100, 80)), + RecognizedTextElement("DE86210700200123010101", Rect(110, 70, 330, 80)), + ) + ), + RecognizedTextLine( + listOf( + RecognizedTextElement("Empfänger:", Rect(50, 90, 150, 100)), + RecognizedTextElement("Zalando", Rect(160, 90, 230, 100)), + RecognizedTextElement("SE", Rect(240, 90, 260, 100)), + ) + ), + ) + ), + RecognizedTextBlock( + listOf( + RecognizedTextLine( + listOf( + RecognizedTextElement("BIC:", Rect(50, 110, 90, 120)), + RecognizedTextElement("DEUTEDEHH210", Rect(50, 110, 170, 120)), + ) + ), + ) + ), + RecognizedTextBlock( + listOf( + RecognizedTextLine( + listOf( + RecognizedTextElement("Bank:", Rect(50, 130, 100, 140)), + RecognizedTextElement("Deutsche", Rect(110, 130, 190, 140)), + RecognizedTextElement("Bank", Rect(200, 130, 240, 140)), + ) + ), + ) + ), + RecognizedTextBlock( + listOf( + RecognizedTextLine( + listOf( + RecognizedTextElement("Verwendungszweck:", Rect(50, 150, 170, 160)), + ) + ), + ) + ), + ) + ) + + // For image size: 100 x 100 + val fixture2 = RecognizedText(""" + Bankverbindung + IBAN: DE86210700200123010101 + """.trimIndent(), + blocks = listOf( + RecognizedTextBlock( + listOf( + RecognizedTextLine( + listOf( + RecognizedTextElement("Bankverbindung", Rect(5, 5, 19, 6)), + ) + ), + RecognizedTextLine( + listOf( + RecognizedTextElement("IBAN:", Rect(5, 7, 10, 8)), + RecognizedTextElement("DE86210700200123010101", Rect(11, 7, 33, 8)), + ) + ), + ) + ), + ) + ) + +} \ No newline at end of file