From 0b7285e5076258fe084b5845d175c9b303f6c519 Mon Sep 17 00:00:00 2001
From: Vlad Vamos <vladvamos@protonmail.com>
Date: Wed, 28 Aug 2024 18:14:29 +0300
Subject: [PATCH] implement diff percentage

---
 .../roborazzi/processOutputImageAndReport.kt       |  8 ++++++++
 .../github/takahirom/roborazzi/CaptureResult.kt    |  2 ++
 .../takahirom/roborazzi/CaptureResultTest.kt       | 14 +++++++++++---
 .../io/github/takahirom/roborazzi/RoborazziIos.kt  |  4 ++++
 4 files changed, 25 insertions(+), 3 deletions(-)

diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/processOutputImageAndReport.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/processOutputImageAndReport.kt
index 4627ddee1..f2a089d44 100644
--- a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/processOutputImageAndReport.kt
+++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/processOutputImageAndReport.kt
@@ -2,6 +2,7 @@ package com.github.takahirom.roborazzi
 
 import com.dropbox.differ.ImageComparator
 import java.io.File
+import kotlin.properties.Delegates
 
 fun interface EmptyCanvasFactory {
   operator fun invoke(
@@ -84,6 +85,10 @@ fun processOutputImageAndReport(
         bufferedImageType = recordOptions.pixelBitConfig.toBufferedImageType()
       )
     }
+
+    // Only used by CaptureResult.Changed
+    var diffPercentage by Delegates.notNull<Float>()
+
     val changed = if (height == goldenRoboCanvas.height && width == goldenRoboCanvas.width) {
       val comparisonResult: ImageComparator.ComparisonResult =
         newRoboCanvas.differ(
@@ -91,10 +96,12 @@ fun processOutputImageAndReport(
           resizeScale = resizeScale,
           imageComparator = roborazziOptions.compareOptions.imageComparator
         )
+      diffPercentage = comparisonResult.pixelDifferences.toFloat() / comparisonResult.pixelCount
       val changed = !roborazziOptions.compareOptions.resultValidator(comparisonResult)
       reportLog("${goldenFile.name} The differ result :$comparisonResult changed:$changed")
       changed
     } else {
+      diffPercentage = Float.NaN // diff. percentage is not defined if new canvas and golden canvas dimensions differ
       reportLog("${goldenFile.name} The image size is changed. actual = (${goldenRoboCanvas.width}, ${goldenRoboCanvas.height}), golden = (${newRoboCanvas.croppedWidth}, ${newRoboCanvas.croppedHeight})")
       true
     }
@@ -147,6 +154,7 @@ fun processOutputImageAndReport(
           actualFile = actualFile.absolutePath,
           goldenFile = goldenFile.absolutePath,
           timestampNs = System.nanoTime(),
+          diffPercentage = diffPercentage,
           contextData = contextData,
         )
       } else {
diff --git a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/CaptureResult.kt b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/CaptureResult.kt
index e9497a329..938f273e7 100644
--- a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/CaptureResult.kt
+++ b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/CaptureResult.kt
@@ -74,6 +74,8 @@ sealed interface CaptureResult {
     override val actualFile:@Contextual String,
     @SerialName("timestamp")
     override val timestampNs: Long,
+    @SerialName("diff_percentage")
+    val diffPercentage: Float,
     @SerialName("context_data")
     override val contextData: Map<String,@Contextual Any>
   ) : CaptureResult {
diff --git a/include-build/roborazzi-core/src/jvmTest/kotlin/io/github/takahirom/roborazzi/CaptureResultTest.kt b/include-build/roborazzi-core/src/jvmTest/kotlin/io/github/takahirom/roborazzi/CaptureResultTest.kt
index 7e852d5a7..efcf8741c 100644
--- a/include-build/roborazzi-core/src/jvmTest/kotlin/io/github/takahirom/roborazzi/CaptureResultTest.kt
+++ b/include-build/roborazzi-core/src/jvmTest/kotlin/io/github/takahirom/roborazzi/CaptureResultTest.kt
@@ -1,16 +1,15 @@
 package io.github.takahirom.roborazzi
 
 import com.github.takahirom.roborazzi.CaptureResult
-import com.github.takahirom.roborazzi.CaptureResults.Companion.json
 import com.github.takahirom.roborazzi.CaptureResults
+import com.github.takahirom.roborazzi.CaptureResults.Companion.json
 import com.github.takahirom.roborazzi.ResultSummary
-import com.github.takahirom.roborazzi.absolutePath
-import kotlinx.io.files.Path
 import kotlinx.serialization.json.JsonObject
 import kotlinx.serialization.json.boolean
 import kotlinx.serialization.json.double
 import kotlinx.serialization.json.doubleOrNull
 import kotlinx.serialization.json.encodeToJsonElement
+import kotlinx.serialization.json.float
 import kotlinx.serialization.json.int
 import kotlinx.serialization.json.intOrNull
 import kotlinx.serialization.json.jsonArray
@@ -47,6 +46,7 @@ class CaptureResultTest {
         goldenFile = "/golden_file",
         actualFile = "/actual_file",
         timestampNs = 123456789,
+        diffPercentage = 0.123f,
         contextData = mapOf("key" to Long.MAX_VALUE - 100),
       ),
       CaptureResult.Unchanged(
@@ -95,6 +95,12 @@ class CaptureResultTest {
         expectedCaptureResult.timestampNs,
         actualJsonResult["timestamp"]?.jsonPrimitive?.long
       )
+      if (expectedCaptureResult is CaptureResult.Changed) {
+        assertEquals(
+          expectedCaptureResult.diffPercentage,
+          actualJsonResult["diff_percentage"]?.jsonPrimitive?.float
+        )
+      }
       assertEquals(
         expectedCaptureResult.contextData.entries.map { it.key to it.value },
         (actualJsonResult["context_data"]?.jsonObject?.entries
@@ -154,6 +160,7 @@ class CaptureResultTest {
                     "actual_file_path": "actual_file",
                     "golden_file_path": "golden_file",
                     "timestamp": 123456789,
+                    "diff_percentage": 0.123,
                     "context_data": {
                         "key": 9223372036854775707
                     }
@@ -201,6 +208,7 @@ class CaptureResultTest {
     assertEquals("actual_file", actualChangedResult.actualFile)
     assertEquals("golden_file", actualChangedResult.goldenFile)
     assertEquals(123456789, actualChangedResult.timestampNs)
+    assertEquals(0.123f, actualChangedResult.diffPercentage)
     assertEquals(9223372036854775707, actualChangedResult.contextData["key"])
 
     val actualUnchangedResult = actualCaptureResultList[3] as CaptureResult.Unchanged
diff --git a/roborazzi-compose-ios/src/iosMain/kotlin/io/github/takahirom/roborazzi/RoborazziIos.kt b/roborazzi-compose-ios/src/iosMain/kotlin/io/github/takahirom/roborazzi/RoborazziIos.kt
index be1bfda6f..f9054981d 100644
--- a/roborazzi-compose-ios/src/iosMain/kotlin/io/github/takahirom/roborazzi/RoborazziIos.kt
+++ b/roborazzi-compose-ios/src/iosMain/kotlin/io/github/takahirom/roborazzi/RoborazziIos.kt
@@ -539,6 +539,7 @@ fun SemanticsNodeInteraction.captureRoboImage(
           actualFile = actualFilePath,
           goldenFile = goldenFilePath,
           timestampNs = getNanoTime(),
+          diffPercentage = Float.NaN,
           contextData = emptyMap()
         )
         writeJson(result, resultsDir, nameWithoutExtension)
@@ -581,6 +582,7 @@ fun SemanticsNodeInteraction.captureRoboImage(
           actualFile = actualFilePath,
           goldenFile = goldenFilePath,
           timestampNs = getNanoTime(),
+          diffPercentage = Float.NaN,
           contextData = emptyMap()
         )
         writeJson(result, resultsDir, nameWithoutExtension)
@@ -623,6 +625,7 @@ fun SemanticsNodeInteraction.captureRoboImage(
           actualFile = goldenFilePath,
           goldenFile = goldenFilePath,
           timestampNs = getNanoTime(),
+          diffPercentage = Float.NaN,
           contextData = emptyMap()
         )
         writeJson(result, resultsDir, nameWithoutExtension)
@@ -665,6 +668,7 @@ fun SemanticsNodeInteraction.captureRoboImage(
           actualFile = goldenFilePath,
           goldenFile = goldenFilePath,
           timestampNs = getNanoTime(),
+          diffPercentage = Float.NaN,
           contextData = emptyMap()
         )
         writeJson(result, resultsDir, nameWithoutExtension)