From 7cbc10725483aaac44dfdf820035a7f1991012d1 Mon Sep 17 00:00:00 2001 From: takahirom Date: Tue, 7 Nov 2023 10:13:53 +0900 Subject: [PATCH 01/11] Add text and grid for compare canvas --- .../takahirom/roborazzi/RoborazziDesktop.kt | 9 ++- .../takahirom/roborazzi/AwtRoboCanvas.kt | 75 +++++++++++++++++-- .../github/takahirom/roborazzi/Roborazzi.kt | 16 +++- .../roborazzi/sample/FirstFragment.kt | 4 +- .../roborazzi/sample/MainActivity.kt | 2 +- 5 files changed, 90 insertions(+), 16 deletions(-) diff --git a/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt b/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt index 01737e2a..c5292e30 100644 --- a/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt +++ b/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt @@ -111,10 +111,11 @@ fun processOutputImageAndReportWithDefaults( }, comparisonCanvasFactory = { goldenCanvas, actualCanvas, resizeScale, bufferedImageType -> AwtRoboCanvas.generateCompareCanvas( - goldenCanvas = goldenCanvas as AwtRoboCanvas, - newCanvas = actualCanvas as AwtRoboCanvas, - newCanvasResize = resizeScale, - bufferedImageType = bufferedImageType + goldenCanvas = goldenCanvas as AwtRoboCanvas, + newCanvas = actualCanvas as AwtRoboCanvas, + newCanvasResize = resizeScale, + bufferedImageType = bufferedImageType, + oneDpPx = null ) } ) diff --git a/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt b/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt index 1def463f..91064204 100644 --- a/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt +++ b/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt @@ -16,7 +16,7 @@ import java.io.File import javax.imageio.ImageIO -class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: Int): RoboCanvas { +class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: Int) : RoboCanvas { private val bufferedImage = BufferedImage(width, height, bufferedImageType) override val width: Int get() = bufferedImage.width override val height: Int get() = bufferedImage.height @@ -205,7 +205,11 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: ) } - override fun differ(other: RoboCanvas, resizeScale: Double, imageComparator: ImageComparator): ImageComparator.ComparisonResult { + override fun differ( + other: RoboCanvas, + resizeScale: Double, + imageComparator: ImageComparator + ): ImageComparator.ComparisonResult { other as AwtRoboCanvas val otherImage = other.bufferedImage return imageComparator.compare( @@ -256,21 +260,76 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: goldenCanvas: AwtRoboCanvas, newCanvas: AwtRoboCanvas, newCanvasResize: Double, - bufferedImageType: Int + bufferedImageType: Int, + oneDpPx: Float? ): AwtRoboCanvas { newCanvas.drawPendingDraw() val image1 = goldenCanvas.bufferedImage val image2 = newCanvas.bufferedImage.scale(newCanvasResize) val diff = generateDiffImage(image1, image2) - val width = image1.width + diff.width + image2.width - val height = image1.height.coerceAtLeast(diff.height).coerceAtLeast(image2.height) + val margin = ((oneDpPx?:1F) * 16).toInt() + val width = image1.width + diff.width + image2.width + margin * 2 + val height = + image1.height.coerceAtLeast(diff.height).coerceAtLeast(image2.height) + margin * 2 val combined = BufferedImage(width, height, bufferedImageType) val g = combined.createGraphics() - g.drawImage(image1, 0, 0, null) - g.drawImage(diff, image1.width, 0, null) - g.drawImage(image2, image1.width + diff.width, 0, null) + // Grid lines with 16 and 4 px spacing + val oneDpPx = oneDpPx ?: 1F + g.stroke = BasicStroke(1F) + g.color = Color(0x33777777, true) + for (y in 0 until height step (4 * oneDpPx).toInt()) { + g.drawLine(0, y, width, y) + } + for (x in 0 until (margin + image1.width) step (4 * oneDpPx).toInt()) { + g.drawLine(x, 0, x, height) + } + for (x in (margin + image1.width) until (margin + image1.width + image2.width) step (4 * oneDpPx).toInt()) { + g.drawLine(x, 0, x, height) + } + for (x in (margin + image1.width + image2.width) until width step (4 * oneDpPx).toInt()) { + g.drawLine(x, 0, x, height) + } + + g.color = Color(0x99777777.toInt(), true) + for (y in 0 until height step (16 * oneDpPx).toInt()) { + g.drawLine(0, y, width, y) + } + + for (x in 0 until (margin + image1.width) step (16 * oneDpPx).toInt()) { + g.drawLine(x, 0, x, height) + } + for (x in (margin + image1.width) until (margin + image1.width + image2.width) step (16 * oneDpPx).toInt()) { + g.drawLine(x, 0, x, height) + } + for (x in (margin + image1.width + image2.width) until width step (16 * oneDpPx).toInt()) { + g.drawLine(x, 0, x, height) + } + + g.font = Font("Courier New", Font.BOLD, 12) + // draw rect for texts + fun drawStringWithBackgroundRect(text: String, x: Int, y: Int) { + val textLayout = TextLayout(text, g.font, g.fontRenderContext) + val bounds = textLayout.bounds + val rect = Rectangle( + x, + y - bounds.height.toInt(), + bounds.width.toInt(), + bounds.height.toInt() + ) + g.color = Color(0x55999999, true) + g.fillRect(rect.x - 4, rect.y - 4, rect.width + 8, rect.height + 8) + g.color = Color.BLACK + g.drawString(text, x, y) + } + drawStringWithBackgroundRect("Reference", margin, margin - 12) + drawStringWithBackgroundRect("Diff", image1.width + margin, margin - 12) + drawStringWithBackgroundRect("New", image1.width + diff.width + margin, margin - 12) + + g.drawImage(image1, margin, margin, null) + g.drawImage(diff, image1.width + margin, margin, null) + g.drawImage(image2, image1.width + diff.width + margin, margin, null) g.dispose() return AwtRoboCanvas( width = width, diff --git a/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt b/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt index 6cafd864..50183567 100644 --- a/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt +++ b/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt @@ -4,11 +4,13 @@ import android.app.Activity import android.app.Application import android.content.Context import android.content.ContextWrapper +import android.content.res.Resources import android.graphics.Bitmap import android.graphics.Rect import android.os.Bundle import android.os.Handler import android.os.Looper +import android.util.TypedValue import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver @@ -16,6 +18,7 @@ import androidx.compose.runtime.snapshots.Snapshot import androidx.compose.ui.test.SemanticsNodeInteraction import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.core.view.drawToBitmap +import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.Espresso.onIdle import androidx.test.espresso.Espresso.onView import androidx.test.espresso.NoActivityResumedException @@ -30,6 +33,7 @@ import org.hamcrest.core.IsEqual import java.io.File import java.util.Locale + fun ViewInteraction.captureRoboImage( filePath: String = DefaultFileNameGenerator.generateFilePath("png"), roborazziOptions: RoborazziOptions = provideRoborazziContext().options, @@ -591,7 +595,17 @@ fun processOutputImageAndReportWithDefaults( goldenCanvas as AwtRoboCanvas, actualCanvas as AwtRoboCanvas, resizeScale, - bufferedImageType + bufferedImageType, + oneDpPx = run { + val dip = 1f + val r: Resources = ApplicationProvider.getApplicationContext().resources + val px = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + dip, + r.getDisplayMetrics() + ) + px + } ) } ) diff --git a/sample-android/src/main/java/com/github/takahirom/roborazzi/sample/FirstFragment.kt b/sample-android/src/main/java/com/github/takahirom/roborazzi/sample/FirstFragment.kt index 6958f5e3..2b69afc9 100644 --- a/sample-android/src/main/java/com/github/takahirom/roborazzi/sample/FirstFragment.kt +++ b/sample-android/src/main/java/com/github/takahirom/roborazzi/sample/FirstFragment.kt @@ -70,7 +70,7 @@ fun SampleComposableFunction() { Modifier .testTag("MyComposeButton") .background(Color.Gray) - .size(50.dp) + .size(64.dp) .clickable { count++ } @@ -80,7 +80,7 @@ fun SampleComposableFunction() { Modifier .background(Color.Red) .testTag("child:$it") - .size(30.dp) + .size(32.dp) ) } } diff --git a/sample-android/src/main/java/com/github/takahirom/roborazzi/sample/MainActivity.kt b/sample-android/src/main/java/com/github/takahirom/roborazzi/sample/MainActivity.kt index a2382cf9..90f5b0c6 100644 --- a/sample-android/src/main/java/com/github/takahirom/roborazzi/sample/MainActivity.kt +++ b/sample-android/src/main/java/com/github/takahirom/roborazzi/sample/MainActivity.kt @@ -18,7 +18,7 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { // for making diff - // setTheme(com.google.android.material.R.style.Theme_Material3_Dark_NoActionBar) + setTheme(com.google.android.material.R.style.Theme_Material3_Dark_NoActionBar) super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) From a5d26735b94c30491e91a7cb112b622d927f47bb Mon Sep 17 00:00:00 2001 From: takahirom Date: Tue, 7 Nov 2023 10:43:22 +0900 Subject: [PATCH 02/11] Fix text calculation for comparison --- .../takahirom/roborazzi/AwtRoboCanvas.kt | 27 ++++++++++++------- .../github/takahirom/roborazzi/Roborazzi.kt | 2 +- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt b/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt index 91064204..7981eb5e 100644 --- a/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt +++ b/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt @@ -307,10 +307,13 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: g.drawLine(x, 0, x, height) } - g.font = Font("Courier New", Font.BOLD, 12) // draw rect for texts - fun drawStringWithBackgroundRect(text: String, x: Int, y: Int) { - val textLayout = TextLayout(text, g.font, g.fontRenderContext) + fun drawStringWithBackgroundRect(text: String, x: Int, y: Int, fontSize: Int) { + // fill with 4dp margin + val textMargin = (4 * oneDpPx).toInt() + // Set size to 12dp + val font = Font("Courier New", Font.BOLD, fontSize) + val textLayout = TextLayout(text, font, g.fontRenderContext) val bounds = textLayout.bounds val rect = Rectangle( x, @@ -318,14 +321,20 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: bounds.width.toInt(), bounds.height.toInt() ) - g.color = Color(0x55999999, true) - g.fillRect(rect.x - 4, rect.y - 4, rect.width + 8, rect.height + 8) + g.color = Color(0x55999999.toInt(), true) + g.fillRect( + rect.x, + rect.y - textMargin * 2, + rect.width + textMargin * 2, + rect.height + textMargin * 2 + ) g.color = Color.BLACK - g.drawString(text, x, y) + textLayout.draw(g, x.toFloat() + textMargin, y.toFloat() - textMargin) } - drawStringWithBackgroundRect("Reference", margin, margin - 12) - drawStringWithBackgroundRect("Diff", image1.width + margin, margin - 12) - drawStringWithBackgroundRect("New", image1.width + diff.width + margin, margin - 12) + val fontSize = (12 * oneDpPx).toInt() + drawStringWithBackgroundRect("Reference", margin, margin, fontSize) + drawStringWithBackgroundRect("Diff", image1.width + margin, margin, fontSize) + drawStringWithBackgroundRect("New", image1.width + diff.width + margin, margin, fontSize) g.drawImage(image1, margin, margin, null) g.drawImage(diff, image1.width + margin, margin, null) diff --git a/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt b/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt index 50183567..ec65d7b3 100644 --- a/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt +++ b/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt @@ -604,7 +604,7 @@ fun processOutputImageAndReportWithDefaults( dip, r.getDisplayMetrics() ) - px + (px * resizeScale).toFloat() } ) } From 648e1045d88b8fd5693d7d75c82190c28e648031 Mon Sep 17 00:00:00 2001 From: takahirom Date: Wed, 8 Nov 2023 10:05:42 +0900 Subject: [PATCH 03/11] Add changes to desktop sample --- sample-compose-desktop-jvm/src/main/kotlin/Main.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sample-compose-desktop-jvm/src/main/kotlin/Main.kt b/sample-compose-desktop-jvm/src/main/kotlin/Main.kt index 255769fd..ead4146c 100644 --- a/sample-compose-desktop-jvm/src/main/kotlin/Main.kt +++ b/sample-compose-desktop-jvm/src/main/kotlin/Main.kt @@ -15,13 +15,13 @@ import androidx.compose.ui.window.application @Composable @Preview fun App() { - var text by remember { mutableStateOf("Hello, World!") } + var text by remember { mutableStateOf("■■■■■ World!") } MaterialTheme { Button( modifier = Modifier.testTag("button"), onClick = { - text = "Hello, Desktop!" + text = "■■■■■ Desktop!" }) { Text( style = MaterialTheme.typography.h2, From 7f5c0cca97b527edb5fe4777b32f0226af79199e Mon Sep 17 00:00:00 2001 From: takahirom Date: Fri, 10 Nov 2023 09:00:04 +0900 Subject: [PATCH 04/11] Fix diff --- .../github/takahirom/roborazzi/sample/MainActivity.kt | 2 +- sample-compose-desktop-jvm/src/main/kotlin/Main.kt | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/sample-android/src/main/java/com/github/takahirom/roborazzi/sample/MainActivity.kt b/sample-android/src/main/java/com/github/takahirom/roborazzi/sample/MainActivity.kt index 90f5b0c6..a2382cf9 100644 --- a/sample-android/src/main/java/com/github/takahirom/roborazzi/sample/MainActivity.kt +++ b/sample-android/src/main/java/com/github/takahirom/roborazzi/sample/MainActivity.kt @@ -18,7 +18,7 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { // for making diff - setTheme(com.google.android.material.R.style.Theme_Material3_Dark_NoActionBar) + // setTheme(com.google.android.material.R.style.Theme_Material3_Dark_NoActionBar) super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) diff --git a/sample-compose-desktop-jvm/src/main/kotlin/Main.kt b/sample-compose-desktop-jvm/src/main/kotlin/Main.kt index ead4146c..1e0ac74c 100644 --- a/sample-compose-desktop-jvm/src/main/kotlin/Main.kt +++ b/sample-compose-desktop-jvm/src/main/kotlin/Main.kt @@ -1,12 +1,9 @@ + import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.material.Button import androidx.compose.material.MaterialTheme import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.window.Window @@ -15,13 +12,13 @@ import androidx.compose.ui.window.application @Composable @Preview fun App() { - var text by remember { mutableStateOf("■■■■■ World!") } + var text by remember { mutableStateOf("Hello, World!") } MaterialTheme { Button( modifier = Modifier.testTag("button"), onClick = { - text = "■■■■■ Desktop!" + text = "Hello, Desktop!" }) { Text( style = MaterialTheme.typography.h2, From b974bb529dbf3a76834a1520d1e0c15c7cce0d87 Mon Sep 17 00:00:00 2001 From: takahirom Date: Fri, 10 Nov 2023 09:09:31 +0900 Subject: [PATCH 05/11] Fix desktop --- .../takahirom/roborazzi/RoborazziDesktop.kt | 33 +++++++++++++++---- .../roborazzi/sample/FirstFragment.kt | 2 +- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt b/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt index c5292e30..7c9dac65 100644 --- a/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt +++ b/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt @@ -5,9 +5,14 @@ import androidx.compose.ui.graphics.toAwtImage import androidx.compose.ui.test.DesktopComposeUiTest import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.dp import com.github.takahirom.roborazzi.* +import java.awt.GraphicsConfiguration +import java.awt.GraphicsEnvironment import java.awt.image.BufferedImage import java.io.File +import java.util.* context(DesktopComposeUiTest) @OptIn(ExperimentalTestApi::class) @@ -111,12 +116,28 @@ fun processOutputImageAndReportWithDefaults( }, comparisonCanvasFactory = { goldenCanvas, actualCanvas, resizeScale, bufferedImageType -> AwtRoboCanvas.generateCompareCanvas( - goldenCanvas = goldenCanvas as AwtRoboCanvas, - newCanvas = actualCanvas as AwtRoboCanvas, - newCanvasResize = resizeScale, - bufferedImageType = bufferedImageType, - oneDpPx = null + goldenCanvas = goldenCanvas as AwtRoboCanvas, + newCanvas = actualCanvas as AwtRoboCanvas, + newCanvasResize = resizeScale, + bufferedImageType = bufferedImageType, + oneDpPx = GlobalDensity.run { 1.dp.toPx() } ) } ) -} \ No newline at end of file +} + +/** + * From compose-multiplatform's density.kt + */ +private val GlobalDensity: Density + get() = GraphicsEnvironment.getLocalGraphicsEnvironment() + .defaultScreenDevice + .defaultConfiguration + .density + +private val GraphicsConfiguration.density: Density + get() = Density( + defaultTransform.scaleX.toFloat(), + fontScale = 1f + ) + diff --git a/sample-android/src/main/java/com/github/takahirom/roborazzi/sample/FirstFragment.kt b/sample-android/src/main/java/com/github/takahirom/roborazzi/sample/FirstFragment.kt index d0938040..d5a9ddfe 100644 --- a/sample-android/src/main/java/com/github/takahirom/roborazzi/sample/FirstFragment.kt +++ b/sample-android/src/main/java/com/github/takahirom/roborazzi/sample/FirstFragment.kt @@ -85,7 +85,7 @@ fun SampleComposableFunction() { Box( Modifier .background(Color.Red) - .testTag("child:$it") + .testTag("child:$index") .size(32.dp) ){ Text( From 40be51efe815bab4017e65d290743935c4961610 Mon Sep 17 00:00:00 2001 From: takahirom Date: Fri, 10 Nov 2023 09:09:59 +0900 Subject: [PATCH 06/11] Add test diff --- sample-compose-desktop-jvm/src/main/kotlin/Main.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample-compose-desktop-jvm/src/main/kotlin/Main.kt b/sample-compose-desktop-jvm/src/main/kotlin/Main.kt index 1e0ac74c..cf5d1252 100644 --- a/sample-compose-desktop-jvm/src/main/kotlin/Main.kt +++ b/sample-compose-desktop-jvm/src/main/kotlin/Main.kt @@ -18,7 +18,7 @@ fun App() { Button( modifier = Modifier.testTag("button"), onClick = { - text = "Hello, Desktop!" + text = "Hello, Desktop! test" }) { Text( style = MaterialTheme.typography.h2, From 9cc3c271518c4aa283268e90c0b2619b194fdf3c Mon Sep 17 00:00:00 2001 From: takahirom Date: Fri, 10 Nov 2023 09:36:04 +0900 Subject: [PATCH 07/11] Quit using desktop density --- .../takahirom/roborazzi/RoborazziDesktop.kt | 33 +---- .../takahirom/roborazzi/AwtRoboCanvas.kt | 130 ++++++++++-------- 2 files changed, 75 insertions(+), 88 deletions(-) diff --git a/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt b/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt index 7c9dac65..c5292e30 100644 --- a/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt +++ b/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt @@ -5,14 +5,9 @@ import androidx.compose.ui.graphics.toAwtImage import androidx.compose.ui.test.DesktopComposeUiTest import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.SemanticsNodeInteraction -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.dp import com.github.takahirom.roborazzi.* -import java.awt.GraphicsConfiguration -import java.awt.GraphicsEnvironment import java.awt.image.BufferedImage import java.io.File -import java.util.* context(DesktopComposeUiTest) @OptIn(ExperimentalTestApi::class) @@ -116,28 +111,12 @@ fun processOutputImageAndReportWithDefaults( }, comparisonCanvasFactory = { goldenCanvas, actualCanvas, resizeScale, bufferedImageType -> AwtRoboCanvas.generateCompareCanvas( - goldenCanvas = goldenCanvas as AwtRoboCanvas, - newCanvas = actualCanvas as AwtRoboCanvas, - newCanvasResize = resizeScale, - bufferedImageType = bufferedImageType, - oneDpPx = GlobalDensity.run { 1.dp.toPx() } + goldenCanvas = goldenCanvas as AwtRoboCanvas, + newCanvas = actualCanvas as AwtRoboCanvas, + newCanvasResize = resizeScale, + bufferedImageType = bufferedImageType, + oneDpPx = null ) } ) -} - -/** - * From compose-multiplatform's density.kt - */ -private val GlobalDensity: Density - get() = GraphicsEnvironment.getLocalGraphicsEnvironment() - .defaultScreenDevice - .defaultConfiguration - .density - -private val GraphicsConfiguration.density: Density - get() = Density( - defaultTransform.scaleX.toFloat(), - fontScale = 1f - ) - +} \ No newline at end of file diff --git a/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt b/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt index 7981eb5e..9b1f3a60 100644 --- a/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt +++ b/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt @@ -267,7 +267,7 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: val image1 = goldenCanvas.bufferedImage val image2 = newCanvas.bufferedImage.scale(newCanvasResize) val diff = generateDiffImage(image1, image2) - val margin = ((oneDpPx?:1F) * 16).toInt() + val margin = ((oneDpPx ?: 1F) * 16).toInt() val width = image1.width + diff.width + image2.width + margin * 2 val height = image1.height.coerceAtLeast(diff.height).coerceAtLeast(image2.height) + margin * 2 @@ -275,70 +275,78 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: val combined = BufferedImage(width, height, bufferedImageType) val g = combined.createGraphics() - // Grid lines with 16 and 4 px spacing - val oneDpPx = oneDpPx ?: 1F - g.stroke = BasicStroke(1F) - g.color = Color(0x33777777, true) - for (y in 0 until height step (4 * oneDpPx).toInt()) { - g.drawLine(0, y, width, y) - } - for (x in 0 until (margin + image1.width) step (4 * oneDpPx).toInt()) { - g.drawLine(x, 0, x, height) - } - for (x in (margin + image1.width) until (margin + image1.width + image2.width) step (4 * oneDpPx).toInt()) { - g.drawLine(x, 0, x, height) - } - for (x in (margin + image1.width + image2.width) until width step (4 * oneDpPx).toInt()) { - g.drawLine(x, 0, x, height) - } + if (oneDpPx != null) { + // Grid lines with 16 and 4 px spacing + val oneDpPx = oneDpPx ?: 1F + g.stroke = BasicStroke(1F) + g.color = Color(0x33777777, true) + for (y in 0 until height step (4 * oneDpPx).toInt()) { + g.drawLine(0, y, width, y) + } + for (x in 0 until (margin + image1.width) step (4 * oneDpPx).toInt()) { + g.drawLine(x, 0, x, height) + } + for (x in (margin + image1.width) until (margin + image1.width + image2.width) step (4 * oneDpPx).toInt()) { + g.drawLine(x, 0, x, height) + } + for (x in (margin + image1.width + image2.width) until width step (4 * oneDpPx).toInt()) { + g.drawLine(x, 0, x, height) + } - g.color = Color(0x99777777.toInt(), true) - for (y in 0 until height step (16 * oneDpPx).toInt()) { - g.drawLine(0, y, width, y) - } + g.color = Color(0x99777777.toInt(), true) + for (y in 0 until height step (16 * oneDpPx).toInt()) { + g.drawLine(0, y, width, y) + } - for (x in 0 until (margin + image1.width) step (16 * oneDpPx).toInt()) { - g.drawLine(x, 0, x, height) - } - for (x in (margin + image1.width) until (margin + image1.width + image2.width) step (16 * oneDpPx).toInt()) { - g.drawLine(x, 0, x, height) - } - for (x in (margin + image1.width + image2.width) until width step (16 * oneDpPx).toInt()) { - g.drawLine(x, 0, x, height) - } + for (x in 0 until (margin + image1.width) step (16 * oneDpPx).toInt()) { + g.drawLine(x, 0, x, height) + } + for (x in (margin + image1.width) until (margin + image1.width + image2.width) step (16 * oneDpPx).toInt()) { + g.drawLine(x, 0, x, height) + } + for (x in (margin + image1.width + image2.width) until width step (16 * oneDpPx).toInt()) { + g.drawLine(x, 0, x, height) + } - // draw rect for texts - fun drawStringWithBackgroundRect(text: String, x: Int, y: Int, fontSize: Int) { - // fill with 4dp margin - val textMargin = (4 * oneDpPx).toInt() - // Set size to 12dp - val font = Font("Courier New", Font.BOLD, fontSize) - val textLayout = TextLayout(text, font, g.fontRenderContext) - val bounds = textLayout.bounds - val rect = Rectangle( - x, - y - bounds.height.toInt(), - bounds.width.toInt(), - bounds.height.toInt() - ) - g.color = Color(0x55999999.toInt(), true) - g.fillRect( - rect.x, - rect.y - textMargin * 2, - rect.width + textMargin * 2, - rect.height + textMargin * 2 - ) - g.color = Color.BLACK - textLayout.draw(g, x.toFloat() + textMargin, y.toFloat() - textMargin) + // draw rect for texts + fun drawStringWithBackgroundRect(text: String, x: Int, y: Int, fontSize: Int) { + // fill with 4dp margin + val textMargin = (4 * oneDpPx).toInt() + // Set size to 12dp + val font = Font("Courier New", Font.BOLD, fontSize) + val textLayout = TextLayout(text, font, g.fontRenderContext) + val bounds = textLayout.bounds + val rect = Rectangle( + x, + y - bounds.height.toInt(), + bounds.width.toInt(), + bounds.height.toInt() + ) + g.color = Color(0x55999999.toInt(), true) + g.fillRect( + rect.x, + rect.y - textMargin * 2, + rect.width + textMargin * 2, + rect.height + textMargin * 2 + ) + g.color = Color.BLACK + textLayout.draw(g, x.toFloat() + textMargin, y.toFloat() - textMargin) + } + + val fontSize = (12 * oneDpPx).toInt() + drawStringWithBackgroundRect("Reference", margin, margin, fontSize) + drawStringWithBackgroundRect("Diff", image1.width + margin, margin, fontSize) + drawStringWithBackgroundRect("New", image1.width + diff.width + margin, margin, fontSize) + + + g.drawImage(image1, margin, margin, null) + g.drawImage(diff, image1.width + margin, margin, null) + g.drawImage(image2, image1.width + diff.width + margin, margin, null) + } else { + g.drawImage(image1, 0, 0, null) + g.drawImage(diff, image1.width, 0, null) + g.drawImage(image2, image1.width + diff.width, 0, null) } - val fontSize = (12 * oneDpPx).toInt() - drawStringWithBackgroundRect("Reference", margin, margin, fontSize) - drawStringWithBackgroundRect("Diff", image1.width + margin, margin, fontSize) - drawStringWithBackgroundRect("New", image1.width + diff.width + margin, margin, fontSize) - - g.drawImage(image1, margin, margin, null) - g.drawImage(diff, image1.width + margin, margin, null) - g.drawImage(image2, image1.width + diff.width + margin, margin, null) g.dispose() return AwtRoboCanvas( width = width, From 0772ea55ee866a07b2a4e059aeea90a92d15b19a Mon Sep 17 00:00:00 2001 From: takahirom Date: Sun, 26 Nov 2023 12:16:17 +0900 Subject: [PATCH 08/11] Add options --- gradle/android.gradle | 6 +- include-build/roborazzi-core/build.gradle | 4 +- .../takahirom/roborazzi/RoborazziOptions.kt | 11 + .../roborazzi/RoborazziGradleProject.kt | 6 +- .../projects/app/build.gradle.kts | 6 +- .../takahirom/roborazzi/RoborazziDesktop.kt | 22 +- roborazzi-junit-rule/build.gradle | 6 +- roborazzi-painter/build.gradle | 6 + .../takahirom/roborazzi/AwtRoboCanvas.kt | 289 ++++++++++++------ .../github/takahirom/roborazzi/Roborazzi.kt | 33 +- sample-android-without-compose/build.gradle | 6 +- sample-android/build.gradle | 6 +- 12 files changed, 269 insertions(+), 132 deletions(-) diff --git a/gradle/android.gradle b/gradle/android.gradle index 608cca57..6f080955 100644 --- a/gradle/android.gradle +++ b/gradle/android.gradle @@ -17,11 +17,11 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '11' } buildFeatures { } diff --git a/include-build/roborazzi-core/build.gradle b/include-build/roborazzi-core/build.gradle index 5a514694..e6423090 100644 --- a/include-build/roborazzi-core/build.gradle +++ b/include-build/roborazzi-core/build.gradle @@ -87,8 +87,8 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } buildFeatures { } diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt index e9c7a14b..7253e441 100644 --- a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt +++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt @@ -131,8 +131,19 @@ data class RoborazziOptions( data class CompareOptions( val outputDirectoryPath: String = DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH, val imageComparator: ImageComparator = DefaultImageComparator, + val comparisonImageLayoutFormat: ComparisonImageLayoutFormat = ComparisonImageLayoutFormat.Grid(), val resultValidator: (result: ImageComparator.ComparisonResult) -> Boolean = DefaultResultValidator, ) { + sealed interface ComparisonImageLayoutFormat { + data class Grid( + val bigLineSpaceDp: Int? = 16, + val smallLineSpaceDp: Int? = 4, + val hasLabel: Boolean = true + ) : ComparisonImageLayoutFormat + + object Simple : ComparisonImageLayoutFormat + } + constructor( outputDirectoryPath: String = DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH, /** diff --git a/include-build/roborazzi-gradle-plugin/src/integrationTest/java/io/github/takahirom/roborazzi/RoborazziGradleProject.kt b/include-build/roborazzi-gradle-plugin/src/integrationTest/java/io/github/takahirom/roborazzi/RoborazziGradleProject.kt index e9b51ab8..dbbd2a34 100644 --- a/include-build/roborazzi-gradle-plugin/src/integrationTest/java/io/github/takahirom/roborazzi/RoborazziGradleProject.kt +++ b/include-build/roborazzi-gradle-plugin/src/integrationTest/java/io/github/takahirom/roborazzi/RoborazziGradleProject.kt @@ -199,11 +199,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "11" } buildFeatures { // compose = true diff --git a/include-build/roborazzi-gradle-plugin/src/integrationTest/projects/app/build.gradle.kts b/include-build/roborazzi-gradle-plugin/src/integrationTest/projects/app/build.gradle.kts index 77494556..5ce137ae 100644 --- a/include-build/roborazzi-gradle-plugin/src/integrationTest/projects/app/build.gradle.kts +++ b/include-build/roborazzi-gradle-plugin/src/integrationTest/projects/app/build.gradle.kts @@ -29,11 +29,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "11" } buildFeatures { // compose = true diff --git a/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt b/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt index c5292e30..ddc23cc4 100644 --- a/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt +++ b/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt @@ -5,6 +5,8 @@ import androidx.compose.ui.graphics.toAwtImage import androidx.compose.ui.test.DesktopComposeUiTest import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.dp import com.github.takahirom.roborazzi.* import java.awt.image.BufferedImage import java.io.File @@ -33,6 +35,7 @@ fun SemanticsNodeInteraction.captureRoboImage( if (!roborazziEnabled()) { return } + val density = this.fetchSemanticsNode().layoutInfo.density val awtImage = this.captureToImage().toAwtImage() val canvas = AwtRoboCanvas( width = awtImage.width, @@ -46,7 +49,8 @@ fun SemanticsNodeInteraction.captureRoboImage( processOutputImageAndReportWithDefaults( canvas = canvas, goldenFile = file, - roborazziOptions = roborazziOptions + roborazziOptions = roborazziOptions, + density = density ) canvas.release() } @@ -84,7 +88,8 @@ fun ImageBitmap.captureRoboImage( processOutputImageAndReportWithDefaults( canvas = canvas, goldenFile = file, - roborazziOptions = roborazziOptions + roborazziOptions = roborazziOptions, + density = null ) canvas.release() } @@ -93,6 +98,7 @@ fun processOutputImageAndReportWithDefaults( canvas: RoboCanvas, goldenFile: File, roborazziOptions: RoborazziOptions, + density: Density?, ) { processOutputImageAndReport( newRoboCanvas = canvas, @@ -111,11 +117,13 @@ fun processOutputImageAndReportWithDefaults( }, comparisonCanvasFactory = { goldenCanvas, actualCanvas, resizeScale, bufferedImageType -> AwtRoboCanvas.generateCompareCanvas( - goldenCanvas = goldenCanvas as AwtRoboCanvas, - newCanvas = actualCanvas as AwtRoboCanvas, - newCanvasResize = resizeScale, - bufferedImageType = bufferedImageType, - oneDpPx = null + AwtRoboCanvas.Companion.ComparisonFormat.create( + goldenCanvas = goldenCanvas as AwtRoboCanvas, + newCanvas = actualCanvas as AwtRoboCanvas, + newCanvasResize = resizeScale, + bufferedImageType = bufferedImageType, + oneDpPx = density?.run { 1.dp.toPx() } + ) ) } ) diff --git a/roborazzi-junit-rule/build.gradle b/roborazzi-junit-rule/build.gradle index 10657245..2c2b6487 100644 --- a/roborazzi-junit-rule/build.gradle +++ b/roborazzi-junit-rule/build.gradle @@ -25,11 +25,11 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '11' } buildFeatures { } diff --git a/roborazzi-painter/build.gradle b/roborazzi-painter/build.gradle index 2905ae27..5938a420 100644 --- a/roborazzi-painter/build.gradle +++ b/roborazzi-painter/build.gradle @@ -5,6 +5,12 @@ if (System.getenv("INTEGRATION_TEST") != "true") { pluginManager.apply("com.vanniktech.maven.publish") } +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } +} + dependencies { // Please see settings.gradle api "io.github.takahirom.roborazzi:roborazzi-core:$VERSION_NAME" diff --git a/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt b/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt index 9b1f3a60..4d2a278d 100644 --- a/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt +++ b/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt @@ -6,7 +6,12 @@ import android.graphics.Bitmap import android.graphics.Paint import android.graphics.Rect import com.dropbox.differ.ImageComparator -import java.awt.* +import java.awt.BasicStroke +import java.awt.Color +import java.awt.Font +import java.awt.Graphics2D +import java.awt.Rectangle +import java.awt.RenderingHints import java.awt.font.FontRenderContext import java.awt.font.TextLayout import java.awt.geom.AffineTransform @@ -177,13 +182,13 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: ) } - var baseDrawList = mutableListOf<() -> Unit>() + private var baseDrawList = mutableListOf<() -> Unit>() fun addBaseDraw(baseDraw: () -> Unit) { baseDrawList.add(baseDraw) } - var pendingDrawList = mutableListOf<() -> Unit>() + private var pendingDrawList = mutableListOf<() -> Unit>() fun addPendingDraw(pendingDraw: () -> Unit) { pendingDrawList.add(pendingDraw) } @@ -256,105 +261,209 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: const val TRANSPARENT_MEDIUM = 0x88 shl 56 const val TRANSPARENT_STRONG = 0x66 shl 56 + sealed interface ComparisonFormat { + val goldenCanvas: AwtRoboCanvas + val newCanvas: AwtRoboCanvas + val newCanvasResize: Double + val bufferedImageType: Int + val comparisonImageWidth: Int + val comparisonImageHeight: Int + + data class Grid( + override val goldenCanvas: AwtRoboCanvas, + override val newCanvas: AwtRoboCanvas, + override val newCanvasResize: Double, + override val bufferedImageType: Int, + val oneDpPx: Float, + val bigLineSpaceDp: Int? = 16, + val smallLineSpaceDp: Int? = 4, + val hasLabel: Boolean = true + ) : ComparisonFormat { + override val comparisonImageWidth: Int + get() = goldenCanvas.width + newCanvas.width + 2 * oneDpPx.toInt() + + override val comparisonImageHeight: Int + get() = goldenCanvas.height.coerceAtLeast(newCanvas.height) + 2 * oneDpPx.toInt() + + val margin = (16 * oneDpPx).toInt() + } + + data class Simple( + override val goldenCanvas: AwtRoboCanvas, + override val newCanvas: AwtRoboCanvas, + override val newCanvasResize: Double, + override val bufferedImageType: Int + ) : ComparisonFormat { + override val comparisonImageWidth: Int + get() = goldenCanvas.width + newCanvas.width + + override val comparisonImageHeight: Int + get() = goldenCanvas.height.coerceAtLeast(newCanvas.height) + } + + companion object { + fun create( + goldenCanvas: AwtRoboCanvas, + newCanvas: AwtRoboCanvas, + newCanvasResize: Double, + bufferedImageType: Int, + oneDpPx: Float?, + comparisonComparisonImageLayoutFormat: RoborazziOptions.CompareOptions.ComparisonImageLayoutFormat, + ): ComparisonFormat { + return if (comparisonComparisonImageLayoutFormat is RoborazziOptions.CompareOptions.ComparisonImageLayoutFormat.Grid && oneDpPx != null) { + Grid( + goldenCanvas, + newCanvas, + newCanvasResize, + bufferedImageType, + oneDpPx, + comparisonComparisonImageLayoutFormat.bigLineSpaceDp, + comparisonComparisonImageLayoutFormat.smallLineSpaceDp, + comparisonComparisonImageLayoutFormat.hasLabel + ) + } else { + if (comparisonComparisonImageLayoutFormat is RoborazziOptions.CompareOptions.ComparisonImageLayoutFormat.Grid) { + debugLog { "Roborazzi can't find the oneDpPx, so fall back to CompareOptions.Format.Simple comparison format" } + } + Simple( + goldenCanvas, + newCanvas, + newCanvasResize, + bufferedImageType + ) + } + } + } + } + fun generateCompareCanvas( - goldenCanvas: AwtRoboCanvas, - newCanvas: AwtRoboCanvas, - newCanvasResize: Double, - bufferedImageType: Int, - oneDpPx: Float? + comparisonFormat: ComparisonFormat ): AwtRoboCanvas { - newCanvas.drawPendingDraw() - val image1 = goldenCanvas.bufferedImage - val image2 = newCanvas.bufferedImage.scale(newCanvasResize) - val diff = generateDiffImage(image1, image2) - val margin = ((oneDpPx ?: 1F) * 16).toInt() - val width = image1.width + diff.width + image2.width + margin * 2 - val height = - image1.height.coerceAtLeast(diff.height).coerceAtLeast(image2.height) + margin * 2 - - val combined = BufferedImage(width, height, bufferedImageType) - - val g = combined.createGraphics() - if (oneDpPx != null) { - // Grid lines with 16 and 4 px spacing - val oneDpPx = oneDpPx ?: 1F - g.stroke = BasicStroke(1F) - g.color = Color(0x33777777, true) - for (y in 0 until height step (4 * oneDpPx).toInt()) { - g.drawLine(0, y, width, y) - } - for (x in 0 until (margin + image1.width) step (4 * oneDpPx).toInt()) { - g.drawLine(x, 0, x, height) - } - for (x in (margin + image1.width) until (margin + image1.width + image2.width) step (4 * oneDpPx).toInt()) { - g.drawLine(x, 0, x, height) - } - for (x in (margin + image1.width + image2.width) until width step (4 * oneDpPx).toInt()) { - g.drawLine(x, 0, x, height) - } + comparisonFormat.newCanvas.drawPendingDraw() + val referenceImage = comparisonFormat.goldenCanvas.bufferedImage + val newImage = + comparisonFormat.newCanvas.bufferedImage.scale(comparisonFormat.newCanvasResize) + val diffImage = generateDiffImage(referenceImage, newImage) + val comparisonImageWidth = comparisonFormat.comparisonImageWidth + val comparisonImageHeight = + comparisonFormat.comparisonImageHeight + + val comparisonImage = + BufferedImage( + comparisonImageWidth, + comparisonImageHeight, + comparisonFormat.bufferedImageType + ) - g.color = Color(0x99777777.toInt(), true) - for (y in 0 until height step (16 * oneDpPx).toInt()) { - g.drawLine(0, y, width, y) - } + val comparisonImageGraphics = comparisonImage.createGraphics() + when (comparisonFormat) { + is ComparisonFormat.Grid -> { + val margin = comparisonFormat.margin + // Grid lines with 16 and 4 px spacing + val oneDpPx = comparisonFormat.oneDpPx + comparisonImageGraphics.stroke = BasicStroke(1F) + comparisonImageGraphics.color = Color(0x33777777, true) + comparisonFormat.smallLineSpaceDp?.let { smallSpaceDp -> + for (y in 0 until comparisonImageHeight step (smallSpaceDp * oneDpPx).toInt()) { + comparisonImageGraphics.drawLine(0, y, comparisonImageWidth, y) + } + for (x in 0 until (margin + referenceImage.width) step (smallSpaceDp * oneDpPx).toInt()) { + comparisonImageGraphics.drawLine(x, 0, x, comparisonImageHeight) + } + for (x in (margin + referenceImage.width) until (margin + referenceImage.width + newImage.width) step (smallSpaceDp * oneDpPx).toInt()) { + comparisonImageGraphics.drawLine(x, 0, x, comparisonImageHeight) + } + for (x in (margin + referenceImage.width + newImage.width) until comparisonImageWidth step (smallSpaceDp * oneDpPx).toInt()) { + comparisonImageGraphics.drawLine(x, 0, x, comparisonImageHeight) + } + } + + comparisonImageGraphics.color = Color(0x99777777.toInt(), true) + comparisonFormat.bigLineSpaceDp?.let { bigSpaceDp -> + for (y in 0 until comparisonImageHeight step (bigSpaceDp * oneDpPx).toInt()) { + comparisonImageGraphics.drawLine(0, y, comparisonImageWidth, y) + } + + for (x in 0 until (margin + referenceImage.width) step (bigSpaceDp * oneDpPx).toInt()) { + comparisonImageGraphics.drawLine(x, 0, x, comparisonImageHeight) + } + for (x in (margin + referenceImage.width) until (margin + referenceImage.width + newImage.width) step (bigSpaceDp * oneDpPx).toInt()) { + comparisonImageGraphics.drawLine(x, 0, x, comparisonImageHeight) + } + for (x in (margin + referenceImage.width + newImage.width) until comparisonImageWidth step (bigSpaceDp * oneDpPx).toInt()) { + comparisonImageGraphics.drawLine(x, 0, x, comparisonImageHeight) + } + } + if (comparisonFormat.hasLabel) { + // draw rect for texts + fun drawStringWithBackgroundRect(text: String, x: Int, y: Int, fontSize: Int) { + // fill with 4dp margin + val textMargin = (4 * oneDpPx).toInt() + // Set size to 12dp + val font = Font("Courier New", Font.BOLD, fontSize) + val textLayout = TextLayout(text, font, comparisonImageGraphics.fontRenderContext) + val bounds = textLayout.bounds + val rect = Rectangle( + x, + y - bounds.height.toInt(), + bounds.width.toInt(), + bounds.height.toInt() + ) + comparisonImageGraphics.color = Color(0x55999999, true) + comparisonImageGraphics.fillRect( + rect.x, + rect.y - textMargin * 2, + rect.width + textMargin * 2, + rect.height + textMargin * 2 + ) + comparisonImageGraphics.color = Color.BLACK + textLayout.draw( + comparisonImageGraphics, + x.toFloat() + textMargin, + y.toFloat() - textMargin + ) + } + + val fontSize = (12 * oneDpPx).toInt() + drawStringWithBackgroundRect("Reference", margin, margin, fontSize) + drawStringWithBackgroundRect("Diff", referenceImage.width + margin, margin, fontSize) + drawStringWithBackgroundRect( + "New", + referenceImage.width + diffImage.width + margin, + margin, + fontSize + ) + } + comparisonImageGraphics.drawImage(referenceImage, margin, margin, null) + comparisonImageGraphics.drawImage(diffImage, referenceImage.width + margin, margin, null) + comparisonImageGraphics.drawImage( + newImage, + referenceImage.width + diffImage.width + margin, + margin, + null + ) - for (x in 0 until (margin + image1.width) step (16 * oneDpPx).toInt()) { - g.drawLine(x, 0, x, height) - } - for (x in (margin + image1.width) until (margin + image1.width + image2.width) step (16 * oneDpPx).toInt()) { - g.drawLine(x, 0, x, height) - } - for (x in (margin + image1.width + image2.width) until width step (16 * oneDpPx).toInt()) { - g.drawLine(x, 0, x, height) } - // draw rect for texts - fun drawStringWithBackgroundRect(text: String, x: Int, y: Int, fontSize: Int) { - // fill with 4dp margin - val textMargin = (4 * oneDpPx).toInt() - // Set size to 12dp - val font = Font("Courier New", Font.BOLD, fontSize) - val textLayout = TextLayout(text, font, g.fontRenderContext) - val bounds = textLayout.bounds - val rect = Rectangle( - x, - y - bounds.height.toInt(), - bounds.width.toInt(), - bounds.height.toInt() + is ComparisonFormat.Simple -> { + comparisonImageGraphics.drawImage(referenceImage, 0, 0, null) + comparisonImageGraphics.drawImage(diffImage, referenceImage.width, 0, null) + comparisonImageGraphics.drawImage( + newImage, + referenceImage.width + diffImage.width, + 0, + null ) - g.color = Color(0x55999999.toInt(), true) - g.fillRect( - rect.x, - rect.y - textMargin * 2, - rect.width + textMargin * 2, - rect.height + textMargin * 2 - ) - g.color = Color.BLACK - textLayout.draw(g, x.toFloat() + textMargin, y.toFloat() - textMargin) } - - val fontSize = (12 * oneDpPx).toInt() - drawStringWithBackgroundRect("Reference", margin, margin, fontSize) - drawStringWithBackgroundRect("Diff", image1.width + margin, margin, fontSize) - drawStringWithBackgroundRect("New", image1.width + diff.width + margin, margin, fontSize) - - - g.drawImage(image1, margin, margin, null) - g.drawImage(diff, image1.width + margin, margin, null) - g.drawImage(image2, image1.width + diff.width + margin, margin, null) - } else { - g.drawImage(image1, 0, 0, null) - g.drawImage(diff, image1.width, 0, null) - g.drawImage(image2, image1.width + diff.width, 0, null) } - g.dispose() + comparisonImageGraphics.dispose() return AwtRoboCanvas( - width = width, - height = height, + width = comparisonImageWidth, + height = comparisonImageHeight, filled = true, - bufferedImageType = bufferedImageType + bufferedImageType = comparisonFormat.bufferedImageType ).apply { - drawImage(combined) + drawImage(comparisonImage) } } diff --git a/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt b/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt index 36c3356b..85a91b57 100644 --- a/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt +++ b/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt @@ -31,7 +31,7 @@ import org.hamcrest.Matcher import org.hamcrest.Matchers import org.hamcrest.core.IsEqual import java.io.File -import java.util.* +import java.util.Locale fun ViewInteraction.captureRoboImage( @@ -594,20 +594,23 @@ fun processOutputImageAndReportWithDefaults( }, comparisonCanvasFactory = { goldenCanvas, actualCanvas, resizeScale, bufferedImageType -> AwtRoboCanvas.generateCompareCanvas( - goldenCanvas as AwtRoboCanvas, - actualCanvas as AwtRoboCanvas, - resizeScale, - bufferedImageType, - oneDpPx = run { - val dip = 1f - val r: Resources = ApplicationProvider.getApplicationContext().resources - val px = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - dip, - r.getDisplayMetrics() - ) - (px * resizeScale).toFloat() - } + AwtRoboCanvas.Companion.ComparisonFormat.create( + goldenCanvas = goldenCanvas as AwtRoboCanvas, + newCanvas = actualCanvas as AwtRoboCanvas, + newCanvasResize = resizeScale, + bufferedImageType = bufferedImageType, + oneDpPx = run { + val dip = 1f + val r: Resources = ApplicationProvider.getApplicationContext().resources + val px = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + dip, + r.getDisplayMetrics() + ) + (px * resizeScale).toFloat() + }, + comparisonComparisonImageLayoutFormat = roborazziOptions.compareOptions.comparisonImageLayoutFormat + ) ) } ) diff --git a/sample-android-without-compose/build.gradle b/sample-android-without-compose/build.gradle index 00fd0eef..37ea6357 100644 --- a/sample-android-without-compose/build.gradle +++ b/sample-android-without-compose/build.gradle @@ -24,11 +24,11 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = "11" } buildFeatures { viewBinding true diff --git a/sample-android/build.gradle b/sample-android/build.gradle index 92981757..12a3dcc6 100644 --- a/sample-android/build.gradle +++ b/sample-android/build.gradle @@ -24,11 +24,11 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = "11" } buildFeatures { viewBinding true From 0fd10091b3578f8aab8052d53676e74f3627dcfe Mon Sep 17 00:00:00 2001 From: takahirom Date: Sun, 26 Nov 2023 12:35:10 +0900 Subject: [PATCH 09/11] Fix desktop --- .../takahirom/roborazzi/RoborazziOptions.kt | 8 +-- .../takahirom/roborazzi/RoborazziDesktop.kt | 5 +- .../takahirom/roborazzi/AwtRoboCanvas.kt | 52 +++++++++---------- .../github/takahirom/roborazzi/Roborazzi.kt | 4 +- 4 files changed, 35 insertions(+), 34 deletions(-) diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt index 7253e441..e52fe743 100644 --- a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt +++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt @@ -131,17 +131,17 @@ data class RoborazziOptions( data class CompareOptions( val outputDirectoryPath: String = DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH, val imageComparator: ImageComparator = DefaultImageComparator, - val comparisonImageLayoutFormat: ComparisonImageLayoutFormat = ComparisonImageLayoutFormat.Grid(), + val comparisonStyle: ComparisonStyle = ComparisonStyle.Grid(), val resultValidator: (result: ImageComparator.ComparisonResult) -> Boolean = DefaultResultValidator, ) { - sealed interface ComparisonImageLayoutFormat { + sealed interface ComparisonStyle { data class Grid( val bigLineSpaceDp: Int? = 16, val smallLineSpaceDp: Int? = 4, val hasLabel: Boolean = true - ) : ComparisonImageLayoutFormat + ) : ComparisonStyle - object Simple : ComparisonImageLayoutFormat + object Simple : ComparisonStyle } constructor( diff --git a/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt b/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt index ddc23cc4..7f6f422a 100644 --- a/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt +++ b/roborazzi-compose-desktop/src/commonMain/kotlin/io/github/takahirom/roborazzi/RoborazziDesktop.kt @@ -117,12 +117,13 @@ fun processOutputImageAndReportWithDefaults( }, comparisonCanvasFactory = { goldenCanvas, actualCanvas, resizeScale, bufferedImageType -> AwtRoboCanvas.generateCompareCanvas( - AwtRoboCanvas.Companion.ComparisonFormat.create( + AwtRoboCanvas.Companion.ComparisonCanvasParameters.create( goldenCanvas = goldenCanvas as AwtRoboCanvas, newCanvas = actualCanvas as AwtRoboCanvas, newCanvasResize = resizeScale, bufferedImageType = bufferedImageType, - oneDpPx = density?.run { 1.dp.toPx() } + oneDpPx = density?.run { 1.dp.toPx() }, + comparisonComparisonStyle = roborazziOptions.compareOptions.comparisonStyle, ) ) } diff --git a/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt b/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt index 4d2a278d..566f1396 100644 --- a/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt +++ b/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt @@ -261,7 +261,7 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: const val TRANSPARENT_MEDIUM = 0x88 shl 56 const val TRANSPARENT_STRONG = 0x66 shl 56 - sealed interface ComparisonFormat { + sealed interface ComparisonCanvasParameters { val goldenCanvas: AwtRoboCanvas val newCanvas: AwtRoboCanvas val newCanvasResize: Double @@ -278,7 +278,7 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: val bigLineSpaceDp: Int? = 16, val smallLineSpaceDp: Int? = 4, val hasLabel: Boolean = true - ) : ComparisonFormat { + ) : ComparisonCanvasParameters { override val comparisonImageWidth: Int get() = goldenCanvas.width + newCanvas.width + 2 * oneDpPx.toInt() @@ -293,7 +293,7 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: override val newCanvas: AwtRoboCanvas, override val newCanvasResize: Double, override val bufferedImageType: Int - ) : ComparisonFormat { + ) : ComparisonCanvasParameters { override val comparisonImageWidth: Int get() = goldenCanvas.width + newCanvas.width @@ -308,21 +308,21 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: newCanvasResize: Double, bufferedImageType: Int, oneDpPx: Float?, - comparisonComparisonImageLayoutFormat: RoborazziOptions.CompareOptions.ComparisonImageLayoutFormat, - ): ComparisonFormat { - return if (comparisonComparisonImageLayoutFormat is RoborazziOptions.CompareOptions.ComparisonImageLayoutFormat.Grid && oneDpPx != null) { + comparisonComparisonStyle: RoborazziOptions.CompareOptions.ComparisonStyle, + ): ComparisonCanvasParameters { + return if (comparisonComparisonStyle is RoborazziOptions.CompareOptions.ComparisonStyle.Grid && oneDpPx != null) { Grid( goldenCanvas, newCanvas, newCanvasResize, bufferedImageType, oneDpPx, - comparisonComparisonImageLayoutFormat.bigLineSpaceDp, - comparisonComparisonImageLayoutFormat.smallLineSpaceDp, - comparisonComparisonImageLayoutFormat.hasLabel + comparisonComparisonStyle.bigLineSpaceDp, + comparisonComparisonStyle.smallLineSpaceDp, + comparisonComparisonStyle.hasLabel ) } else { - if (comparisonComparisonImageLayoutFormat is RoborazziOptions.CompareOptions.ComparisonImageLayoutFormat.Grid) { + if (comparisonComparisonStyle is RoborazziOptions.CompareOptions.ComparisonStyle.Grid) { debugLog { "Roborazzi can't find the oneDpPx, so fall back to CompareOptions.Format.Simple comparison format" } } Simple( @@ -337,33 +337,33 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: } fun generateCompareCanvas( - comparisonFormat: ComparisonFormat + comparisonCanvasParameters: ComparisonCanvasParameters ): AwtRoboCanvas { - comparisonFormat.newCanvas.drawPendingDraw() - val referenceImage = comparisonFormat.goldenCanvas.bufferedImage + comparisonCanvasParameters.newCanvas.drawPendingDraw() + val referenceImage = comparisonCanvasParameters.goldenCanvas.bufferedImage val newImage = - comparisonFormat.newCanvas.bufferedImage.scale(comparisonFormat.newCanvasResize) + comparisonCanvasParameters.newCanvas.bufferedImage.scale(comparisonCanvasParameters.newCanvasResize) val diffImage = generateDiffImage(referenceImage, newImage) - val comparisonImageWidth = comparisonFormat.comparisonImageWidth + val comparisonImageWidth = comparisonCanvasParameters.comparisonImageWidth val comparisonImageHeight = - comparisonFormat.comparisonImageHeight + comparisonCanvasParameters.comparisonImageHeight val comparisonImage = BufferedImage( comparisonImageWidth, comparisonImageHeight, - comparisonFormat.bufferedImageType + comparisonCanvasParameters.bufferedImageType ) val comparisonImageGraphics = comparisonImage.createGraphics() - when (comparisonFormat) { - is ComparisonFormat.Grid -> { - val margin = comparisonFormat.margin + when (comparisonCanvasParameters) { + is ComparisonCanvasParameters.Grid -> { + val margin = comparisonCanvasParameters.margin // Grid lines with 16 and 4 px spacing - val oneDpPx = comparisonFormat.oneDpPx + val oneDpPx = comparisonCanvasParameters.oneDpPx comparisonImageGraphics.stroke = BasicStroke(1F) comparisonImageGraphics.color = Color(0x33777777, true) - comparisonFormat.smallLineSpaceDp?.let { smallSpaceDp -> + comparisonCanvasParameters.smallLineSpaceDp?.let { smallSpaceDp -> for (y in 0 until comparisonImageHeight step (smallSpaceDp * oneDpPx).toInt()) { comparisonImageGraphics.drawLine(0, y, comparisonImageWidth, y) } @@ -379,7 +379,7 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: } comparisonImageGraphics.color = Color(0x99777777.toInt(), true) - comparisonFormat.bigLineSpaceDp?.let { bigSpaceDp -> + comparisonCanvasParameters.bigLineSpaceDp?.let { bigSpaceDp -> for (y in 0 until comparisonImageHeight step (bigSpaceDp * oneDpPx).toInt()) { comparisonImageGraphics.drawLine(0, y, comparisonImageWidth, y) } @@ -394,7 +394,7 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: comparisonImageGraphics.drawLine(x, 0, x, comparisonImageHeight) } } - if (comparisonFormat.hasLabel) { + if (comparisonCanvasParameters.hasLabel) { // draw rect for texts fun drawStringWithBackgroundRect(text: String, x: Int, y: Int, fontSize: Int) { // fill with 4dp margin @@ -445,7 +445,7 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: } - is ComparisonFormat.Simple -> { + is ComparisonCanvasParameters.Simple -> { comparisonImageGraphics.drawImage(referenceImage, 0, 0, null) comparisonImageGraphics.drawImage(diffImage, referenceImage.width, 0, null) comparisonImageGraphics.drawImage( @@ -461,7 +461,7 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: width = comparisonImageWidth, height = comparisonImageHeight, filled = true, - bufferedImageType = comparisonFormat.bufferedImageType + bufferedImageType = comparisonCanvasParameters.bufferedImageType ).apply { drawImage(comparisonImage) } diff --git a/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt b/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt index 85a91b57..ead85fbc 100644 --- a/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt +++ b/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt @@ -594,7 +594,7 @@ fun processOutputImageAndReportWithDefaults( }, comparisonCanvasFactory = { goldenCanvas, actualCanvas, resizeScale, bufferedImageType -> AwtRoboCanvas.generateCompareCanvas( - AwtRoboCanvas.Companion.ComparisonFormat.create( + AwtRoboCanvas.Companion.ComparisonCanvasParameters.create( goldenCanvas = goldenCanvas as AwtRoboCanvas, newCanvas = actualCanvas as AwtRoboCanvas, newCanvasResize = resizeScale, @@ -609,7 +609,7 @@ fun processOutputImageAndReportWithDefaults( ) (px * resizeScale).toFloat() }, - comparisonComparisonImageLayoutFormat = roborazziOptions.compareOptions.comparisonImageLayoutFormat + comparisonComparisonStyle = roborazziOptions.compareOptions.comparisonStyle ) ) } From ecace653fc89e6d214d912af572cfcfef528df07 Mon Sep 17 00:00:00 2001 From: takahirom Date: Sun, 26 Nov 2023 13:28:10 +0900 Subject: [PATCH 10/11] Fix width --- .../takahirom/roborazzi/AwtRoboCanvas.kt | 88 +++++++++++-------- 1 file changed, 50 insertions(+), 38 deletions(-) diff --git a/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt b/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt index 566f1396..0d9548cd 100644 --- a/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt +++ b/roborazzi-painter/src/main/java/com/github/takahirom/roborazzi/AwtRoboCanvas.kt @@ -6,12 +6,7 @@ import android.graphics.Bitmap import android.graphics.Paint import android.graphics.Rect import com.dropbox.differ.ImageComparator -import java.awt.BasicStroke -import java.awt.Color -import java.awt.Font -import java.awt.Graphics2D -import java.awt.Rectangle -import java.awt.RenderingHints +import java.awt.* import java.awt.font.FontRenderContext import java.awt.font.TextLayout import java.awt.geom.AffineTransform @@ -262,37 +257,44 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: const val TRANSPARENT_STRONG = 0x66 shl 56 sealed interface ComparisonCanvasParameters { - val goldenCanvas: AwtRoboCanvas - val newCanvas: AwtRoboCanvas - val newCanvasResize: Double - val bufferedImageType: Int - val comparisonImageWidth: Int - val comparisonImageHeight: Int + abstract val referenceImage: BufferedImage + abstract val newImage: BufferedImage + abstract val diffImage: BufferedImage + abstract val newCanvasResize: Double + abstract val bufferedImageType: Int + abstract val comparisonImageWidth: Int + abstract val comparisonImageHeight: Int data class Grid( - override val goldenCanvas: AwtRoboCanvas, - override val newCanvas: AwtRoboCanvas, + val goldenCanvas: AwtRoboCanvas, + val newCanvas: AwtRoboCanvas, override val newCanvasResize: Double, override val bufferedImageType: Int, + override val referenceImage: BufferedImage, + override val newImage: BufferedImage, + override val diffImage: BufferedImage, val oneDpPx: Float, val bigLineSpaceDp: Int? = 16, val smallLineSpaceDp: Int? = 4, val hasLabel: Boolean = true ) : ComparisonCanvasParameters { + + val margin = (16 * oneDpPx).toInt() override val comparisonImageWidth: Int - get() = goldenCanvas.width + newCanvas.width + 2 * oneDpPx.toInt() + get() = goldenCanvas.width + newCanvas.width + diffImage.width + 2 * margin override val comparisonImageHeight: Int - get() = goldenCanvas.height.coerceAtLeast(newCanvas.height) + 2 * oneDpPx.toInt() - - val margin = (16 * oneDpPx).toInt() + get() = goldenCanvas.height.coerceAtLeast(newCanvas.height) + 2 * margin } data class Simple( - override val goldenCanvas: AwtRoboCanvas, - override val newCanvas: AwtRoboCanvas, + val goldenCanvas: AwtRoboCanvas, + val newCanvas: AwtRoboCanvas, override val newCanvasResize: Double, - override val bufferedImageType: Int + override val bufferedImageType: Int, + override val referenceImage: BufferedImage, + override val newImage: BufferedImage, + override val diffImage: BufferedImage, ) : ComparisonCanvasParameters { override val comparisonImageWidth: Int get() = goldenCanvas.width + newCanvas.width @@ -310,26 +312,38 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: oneDpPx: Float?, comparisonComparisonStyle: RoborazziOptions.CompareOptions.ComparisonStyle, ): ComparisonCanvasParameters { + newCanvas.drawPendingDraw() + + val referenceImage = goldenCanvas.bufferedImage + val newImage = + newCanvas.bufferedImage.scale(newCanvasResize) + val diffImage = generateDiffImage(referenceImage, newImage) return if (comparisonComparisonStyle is RoborazziOptions.CompareOptions.ComparisonStyle.Grid && oneDpPx != null) { Grid( - goldenCanvas, - newCanvas, - newCanvasResize, - bufferedImageType, - oneDpPx, - comparisonComparisonStyle.bigLineSpaceDp, - comparisonComparisonStyle.smallLineSpaceDp, - comparisonComparisonStyle.hasLabel + goldenCanvas = goldenCanvas, + newCanvas = newCanvas, + newCanvasResize = newCanvasResize, + bufferedImageType = bufferedImageType, + referenceImage = referenceImage, + newImage = newImage, + diffImage = diffImage, + oneDpPx = oneDpPx, + bigLineSpaceDp = comparisonComparisonStyle.bigLineSpaceDp, + smallLineSpaceDp = comparisonComparisonStyle.smallLineSpaceDp, + hasLabel = comparisonComparisonStyle.hasLabel ) } else { if (comparisonComparisonStyle is RoborazziOptions.CompareOptions.ComparisonStyle.Grid) { debugLog { "Roborazzi can't find the oneDpPx, so fall back to CompareOptions.Format.Simple comparison format" } } Simple( - goldenCanvas, - newCanvas, - newCanvasResize, - bufferedImageType + goldenCanvas = goldenCanvas, + newCanvas = newCanvas, + newCanvasResize = newCanvasResize, + bufferedImageType = bufferedImageType, + referenceImage = referenceImage, + newImage = newImage, + diffImage = diffImage, ) } } @@ -339,11 +353,9 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: fun generateCompareCanvas( comparisonCanvasParameters: ComparisonCanvasParameters ): AwtRoboCanvas { - comparisonCanvasParameters.newCanvas.drawPendingDraw() - val referenceImage = comparisonCanvasParameters.goldenCanvas.bufferedImage - val newImage = - comparisonCanvasParameters.newCanvas.bufferedImage.scale(comparisonCanvasParameters.newCanvasResize) - val diffImage = generateDiffImage(referenceImage, newImage) + val referenceImage = comparisonCanvasParameters.referenceImage + val newImage = comparisonCanvasParameters.newImage + val diffImage = comparisonCanvasParameters.diffImage val comparisonImageWidth = comparisonCanvasParameters.comparisonImageWidth val comparisonImageHeight = comparisonCanvasParameters.comparisonImageHeight From 6f44dfcfbc5c4bab6163a5ef4cf49c005414f224 Mon Sep 17 00:00:00 2001 From: takahirom Date: Sun, 26 Nov 2023 13:46:14 +0900 Subject: [PATCH 11/11] Add comparison style test --- .../takahirom/roborazzi/RoborazziOptions.kt | 2 + .../roborazzi/sample/ComparisonStyleTest.kt | 82 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 sample-android/src/test/java/com/github/takahirom/roborazzi/sample/ComparisonStyleTest.kt diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt index e52fe743..53ee6cea 100644 --- a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt +++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt @@ -134,7 +134,9 @@ data class RoborazziOptions( val comparisonStyle: ComparisonStyle = ComparisonStyle.Grid(), val resultValidator: (result: ImageComparator.ComparisonResult) -> Boolean = DefaultResultValidator, ) { + @ExperimentalRoborazziApi sealed interface ComparisonStyle { + @ExperimentalRoborazziApi data class Grid( val bigLineSpaceDp: Int? = 16, val smallLineSpaceDp: Int? = 4, diff --git a/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/ComparisonStyleTest.kt b/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/ComparisonStyleTest.kt new file mode 100644 index 00000000..5527c1ff --- /dev/null +++ b/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/ComparisonStyleTest.kt @@ -0,0 +1,82 @@ +package com.github.takahirom.roborazzi.sample + +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.core.app.ActivityScenario +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers +import com.github.takahirom.roborazzi.RoborazziOptions +import com.github.takahirom.roborazzi.captureRoboImage +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.annotation.Config +import org.robolectric.annotation.GraphicsMode + +@RunWith(AndroidJUnit4::class) +@GraphicsMode(GraphicsMode.Mode.NATIVE) +@Config( + sdk = [30], + qualifiers = RobolectricDeviceQualifiers.NexusOne +) +class ComparisonStyleTest { + @get:Rule + val composeTestRule = createAndroidComposeRule() + + @Test + fun diffDefault() { + ActivityScenario.launch(MainActivity::class.java) + + onView(ViewMatchers.isRoot()) + .captureRoboImage() + } + + @Test + fun diffNoLabel() { + ActivityScenario.launch(MainActivity::class.java) + + onView(ViewMatchers.isRoot()) + .captureRoboImage( + roborazziOptions = RoborazziOptions( + compareOptions = RoborazziOptions.CompareOptions( + comparisonStyle = RoborazziOptions.CompareOptions.ComparisonStyle.Grid( + hasLabel = false + ) + ) + ) + ) + } + + @Test + fun diffNoSmallLineSpace() { + ActivityScenario.launch(MainActivity::class.java) + + onView(ViewMatchers.isRoot()) + .captureRoboImage( + roborazziOptions = RoborazziOptions( + compareOptions = RoborazziOptions.CompareOptions( + comparisonStyle = RoborazziOptions.CompareOptions.ComparisonStyle.Grid( + smallLineSpaceDp = null + ) + ) + ) + ) + } + + @Test + fun diffNoBigLineSpace() { + ActivityScenario.launch(MainActivity::class.java) + + onView(ViewMatchers.isRoot()) + .captureRoboImage( + roborazziOptions = RoborazziOptions( + compareOptions = RoborazziOptions.CompareOptions( + comparisonStyle = RoborazziOptions.CompareOptions.ComparisonStyle.Grid( + bigLineSpaceDp = null + ) + ) + ) + ) + } +}