From 2dabf4c5e55112cd44d846aeeb145c2ea497c57b Mon Sep 17 00:00:00 2001 From: takahirom Date: Fri, 1 Nov 2024 13:42:38 +0900 Subject: [PATCH 01/10] Support lossless WebP --- gradle/libs.versions.toml | 2 + include-build/roborazzi-core/build.gradle | 1 + .../github/takahirom/roborazzi/JvmImageIo.kt | 107 +++++++++++++++ .../takahirom/roborazzi/RoborazziOptions.kt | 1 + .../roborazzi/processOutputImageAndReport.kt | 9 +- .../roborazzi/PlatformRecordOptions.kt | 3 + .../github/takahirom/roborazzi/RoboCanvas.kt | 1 + .../takahirom/roborazzi/RoborazziDesktop.kt | 6 +- .../takahirom/roborazzi/AwtRoboCanvas.kt | 69 ++-------- roborazzi/build.gradle | 1 - .../github/takahirom/roborazzi/Roborazzi.kt | 11 +- sample-android/build.gradle | 1 + .../sample/boxed/LosslessWebpTest.kt | 122 ++++++++++++++++++ 13 files changed, 271 insertions(+), 63 deletions(-) create mode 100644 include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/JvmImageIo.kt create mode 100644 include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/PlatformRecordOptions.kt create mode 100644 sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 87117ac58..24bd9b064 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -42,6 +42,7 @@ kotlinx-io = "0.3.3" webjar-material-design-icons = "4.0.0" webjar-materialize = "1.0.0" webjars-locator-lite = "0.0.6" +webpImageio = "0.3.3" composable-preview-scanner = "0.3.2" @@ -99,3 +100,4 @@ kotlinx-io-core = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.re webjars-material-design-icons = { module = "org.webjars:material-design-icons", version.ref = "webjar-material-design-icons" } webjars-materialize = { module = "org.webjars:materializecss", version.ref = "webjar-materialize" } webjars-locator-lite = { module = "org.webjars:webjars-locator-lite", version.ref = "webjars-locator-lite" } +webp-imageio = { module = "io.github.darkxanter:webp-imageio", version.ref = "webpImageio" } diff --git a/include-build/roborazzi-core/build.gradle b/include-build/roborazzi-core/build.gradle index 726eb4f03..ad91d01b5 100644 --- a/include-build/roborazzi-core/build.gradle +++ b/include-build/roborazzi-core/build.gradle @@ -53,6 +53,7 @@ kotlin { commonJvmMain { dependencies { implementation libs.junit + compileOnly(libs.webp.imageio) } } commonJvmTest { diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/JvmImageIo.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/JvmImageIo.kt new file mode 100644 index 000000000..1d7f14909 --- /dev/null +++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/JvmImageIo.kt @@ -0,0 +1,107 @@ +package com.github.takahirom.roborazzi + +import com.luciad.imageio.webp.WebPWriteParam +import java.awt.image.BufferedImage +import java.awt.image.RenderedImage +import java.io.File +import javax.imageio.IIOImage +import javax.imageio.ImageIO +import javax.imageio.ImageTypeSpecifier +import javax.imageio.ImageWriteParam +import javax.imageio.ImageWriter +import javax.imageio.metadata.IIOMetadata +import javax.imageio.metadata.IIOMetadataFormatImpl +import javax.imageio.metadata.IIOMetadataNode +import javax.imageio.stream.FileImageOutputStream + +fun interface AwtImageWriter { + fun write( + dest: File, + contextData: Map, + bufferedImage: BufferedImage + ) +} + +fun interface AwtImageLoader { + fun load(file: File): BufferedImage +} + +open class JvmPlatformRecordOptions( + val awtImageWriter: AwtImageWriter = AwtImageWriter { file, contextData, bufferedImage -> + val imageExtension = file.extension.ifBlank { "png" } + if (contextData.isEmpty()) { + ImageIO.write( + bufferedImage, + imageExtension, + file + ) + return@AwtImageWriter + } + val writer = getWriter(bufferedImage, imageExtension) + val meta = writer.writeMetadata(contextData, bufferedImage) + writer.output = ImageIO.createImageOutputStream(file) + writer.write(IIOImage(bufferedImage, null, meta)) + }, + var awtImageLoader: AwtImageLoader = AwtImageLoader { ImageIO.read(it) } +) : PlatformRecordOptions + + +@ExperimentalRoborazziApi +fun getWriter(renderedImage: RenderedImage, extension: String): ImageWriter { + val typeSpecifier = ImageTypeSpecifier.createFromRenderedImage(renderedImage) + val iterator: Iterator<*> = ImageIO.getImageWriters(typeSpecifier, extension) + return if (iterator.hasNext()) { + iterator.next() as ImageWriter + } else { + throw IllegalArgumentException("No ImageWriter found for $extension") + } +} + +@ExperimentalRoborazziApi +fun ImageWriter.writeMetadata( + contextData: Map, + bufferedImage: BufferedImage, +): IIOMetadata? { + val meta = getDefaultImageMetadata(ImageTypeSpecifier(bufferedImage), null) ?: run { + // If we use WebP, it seems that we can't get the metadata + return null + } + + val root = IIOMetadataNode(IIOMetadataFormatImpl.standardMetadataFormatName) + contextData.forEach { (key, value) -> + val textEntry = IIOMetadataNode("TextEntry") + textEntry.setAttribute("keyword", key) + textEntry.setAttribute("value", value.toString()) + val text = IIOMetadataNode("Text") + text.appendChild(textEntry) + root.appendChild(text) + } + + meta.mergeTree(IIOMetadataFormatImpl.standardMetadataFormatName, root) + return meta +} + +/** + * Add testImplementation("io.github.darkxanter:webp-imageio:0.3.0") to use this + */ +fun losslessWebPSaver(): AwtImageWriter = + AwtImageWriter { file, context, bufferedImage -> + val writer: ImageWriter = + ImageIO.getImageWritersByMIMEType("image/webp").next() + try { + val writeParam = WebPWriteParam(writer.getLocale()) + writeParam.compressionMode = ImageWriteParam.MODE_EXPLICIT + writeParam.compressionType = + writeParam.getCompressionTypes() + .get(WebPWriteParam.LOSSLESS_COMPRESSION) + + + writer.setOutput(FileImageOutputStream(file)) + + writer.write(null, IIOImage(bufferedImage, null, null), writeParam) + } catch (e: NoClassDefFoundError) { + throw IllegalStateException("Add testImplementation(\"io.github.darkxanter:webp-imageio:0.3.0\") to use this") + } finally { + writer.dispose() + } + } \ No newline at end of file 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 273a7f3f6..24849d6b4 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 @@ -195,6 +195,7 @@ data class RoborazziOptions( val resizeScale: Double = roborazziDefaultResizeScale(), val applyDeviceCrop: Boolean = false, val pixelBitConfig: PixelBitConfig = PixelBitConfig.Argb8888, + val platformRecordOptions: PlatformRecordOptions = JvmPlatformRecordOptions(), ) enum class PixelBitConfig { 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 b2b09ae9d..edcd84eab 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 @@ -119,7 +119,8 @@ fun processOutputImageAndReport( .save( path = comparisonFile.absolutePath, resizeScale = resizeScale, - contextData = contextData + contextData = contextData, + platformRecordOptions = recordOptions.platformRecordOptions, ) debugLog { "processOutputImageAndReport(): compareCanvas is saved " + @@ -140,7 +141,8 @@ fun processOutputImageAndReport( .save( path = actualFile.absolutePath, resizeScale = resizeScale, - contextData = contextData + contextData = contextData, + platformRecordOptions = recordOptions.platformRecordOptions, ) debugLog { "processOutputImageAndReport(): actualCanvas is saved " + @@ -186,7 +188,8 @@ fun processOutputImageAndReport( newRoboCanvas.save( path = goldenFile.absolutePath, resizeScale = resizeScale, - contextData = contextData + contextData = contextData, + platformRecordOptions = recordOptions.platformRecordOptions, ) debugLog { "processOutputImageAndReport: \n" + diff --git a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/PlatformRecordOptions.kt b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/PlatformRecordOptions.kt new file mode 100644 index 000000000..108575b20 --- /dev/null +++ b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/PlatformRecordOptions.kt @@ -0,0 +1,3 @@ +package com.github.takahirom.roborazzi + +interface PlatformRecordOptions \ No newline at end of file diff --git a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/RoboCanvas.kt b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/RoboCanvas.kt index f24027be8..2346116a7 100644 --- a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/RoboCanvas.kt +++ b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/RoboCanvas.kt @@ -11,6 +11,7 @@ interface RoboCanvas { path: String, resizeScale: Double, contextData: Map, + platformRecordOptions: PlatformRecordOptions, ) fun differ(other: RoboCanvas, resizeScale: Double, imageComparator: ImageComparator): ImageComparator.ComparisonResult fun release() 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 edb7c371e..554f1336b 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 @@ -114,7 +114,11 @@ fun processOutputImageAndReportWithDefaults( ) }, canvasFactoryFromFile = { file, bufferedImageType -> - AwtRoboCanvas.load(file, bufferedImageType) + AwtRoboCanvas.load( + file = file, + bufferedImageType = bufferedImageType, + platformRecordOptions = roborazziOptions.recordOptions.platformRecordOptions + ) }, comparisonCanvasFactory = { goldenCanvas, actualCanvas, resizeScale, bufferedImageType -> AwtRoboCanvas.generateCompareCanvas( diff --git a/roborazzi-painter/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/AwtRoboCanvas.kt b/roborazzi-painter/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/AwtRoboCanvas.kt index 43d01636d..20191d711 100644 --- a/roborazzi-painter/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/AwtRoboCanvas.kt +++ b/roborazzi-painter/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/AwtRoboCanvas.kt @@ -18,15 +18,7 @@ import java.awt.font.TextLayout import java.awt.geom.AffineTransform import java.awt.image.AffineTransformOp import java.awt.image.BufferedImage -import java.awt.image.RenderedImage import java.io.File -import javax.imageio.IIOImage -import javax.imageio.ImageIO -import javax.imageio.ImageTypeSpecifier -import javax.imageio.ImageWriter -import javax.imageio.metadata.IIOMetadata -import javax.imageio.metadata.IIOMetadataFormatImpl -import javax.imageio.metadata.IIOMetadataNode class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: Int) : RoboCanvas { private val bufferedImage = BufferedImage(width, height, bufferedImageType) @@ -219,7 +211,12 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: pendingDrawList.add(pendingDraw) } - override fun save(path: String, resizeScale: Double, contextData: Map) { + override fun save( + path: String, + resizeScale: Double, + contextData: Map, + platformRecordOptions: PlatformRecordOptions + ) { val file = File(path) drawPendingDraw() val directory = file.parentFile @@ -231,51 +228,12 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: // ignore } val scaledBufferedImage = croppedImage.scale(resizeScale) - val imageExtension = file.extension.ifBlank { "png" } - if (contextData.isEmpty()) { - ImageIO.write( - scaledBufferedImage, - imageExtension, - file + (platformRecordOptions as JvmPlatformRecordOptions) + .awtImageWriter.write( + dest = file, + contextData = contextData, + bufferedImage = scaledBufferedImage ) - return - } - val writer = getWriter(croppedImage, imageExtension) - val meta = writer.writeMetadata(contextData) - writer.output = ImageIO.createImageOutputStream(file) - writer.write(IIOImage(scaledBufferedImage, null, meta)) - } - - private fun ImageWriter.writeMetadata( - contextData: Map - ): IIOMetadata? { - val meta = getDefaultImageMetadata(ImageTypeSpecifier(croppedImage), null) ?: run { - // If we use WebP, it seems that we can't get the metadata - return null - } - - val root = IIOMetadataNode(IIOMetadataFormatImpl.standardMetadataFormatName) - contextData.forEach { (key, value) -> - val textEntry = IIOMetadataNode("TextEntry") - textEntry.setAttribute("keyword", key) - textEntry.setAttribute("value", value.toString()) - val text = IIOMetadataNode("Text") - text.appendChild(textEntry) - root.appendChild(text) - } - - meta.mergeTree(IIOMetadataFormatImpl.standardMetadataFormatName, root) - return meta - } - - private fun getWriter(renderedImage: RenderedImage, extension: String): ImageWriter { - val typeSpecifier = ImageTypeSpecifier.createFromRenderedImage(renderedImage) - val iterator: Iterator<*> = ImageIO.getImageWriters(typeSpecifier, extension) - return if (iterator.hasNext()) { - iterator.next() as ImageWriter - } else { - throw IllegalArgumentException("No ImageWriter found for $extension") - } } override fun differ( @@ -312,8 +270,8 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: } companion object { - fun load(file: File, bufferedImageType: Int): AwtRoboCanvas { - val loadedImage: BufferedImage = ImageIO.read(file) + fun load(file: File, bufferedImageType: Int, platformRecordOptions: PlatformRecordOptions): AwtRoboCanvas { + val loadedImage: BufferedImage = (platformRecordOptions as JvmPlatformRecordOptions).awtImageLoader.load(file) val awtRoboCanvas = AwtRoboCanvas( loadedImage.width, height = loadedImage.height, @@ -599,3 +557,4 @@ private fun BufferedImage.graphics(block: (Graphics2D) -> T): T { graphics.dispose() return result } + diff --git a/roborazzi/build.gradle b/roborazzi/build.gradle index 1c1ef567f..416d73790 100644 --- a/roborazzi/build.gradle +++ b/roborazzi/build.gradle @@ -22,7 +22,6 @@ dependencies { compileOnly libs.androidx.compose.ui.test.junit4 compileOnly libs.robolectric - implementation libs.androidx.core.ktx api libs.dropbox.differ api libs.androidx.test.espresso.core 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 457ccba62..13cff5eb2 100644 --- a/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt +++ b/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt @@ -19,9 +19,14 @@ import androidx.compose.ui.test.SemanticsNodeInteraction import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.core.view.drawToBitmap import androidx.test.core.app.ApplicationProvider -import androidx.test.espresso.* +import androidx.test.espresso.Espresso import androidx.test.espresso.Espresso.onIdle import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.NoActivityResumedException +import androidx.test.espresso.Root +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction +import androidx.test.espresso.ViewInteraction import androidx.test.espresso.base.RootsOracle_Factory import androidx.test.platform.app.InstrumentationRegistry import com.dropbox.differ.ImageComparator @@ -29,7 +34,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( @@ -644,7 +649,7 @@ fun processOutputImageAndReportWithDefaults( ) }, canvasFactoryFromFile = { file, bufferedImageType -> - AwtRoboCanvas.load(file, bufferedImageType) + AwtRoboCanvas.load(file, bufferedImageType, roborazziOptions.recordOptions.platformRecordOptions) }, comparisonCanvasFactory = { goldenCanvas, actualCanvas, resizeScale, bufferedImageType -> AwtRoboCanvas.generateCompareCanvas( diff --git a/sample-android/build.gradle b/sample-android/build.gradle index 4a7f8e7bf..2bcf43c95 100644 --- a/sample-android/build.gradle +++ b/sample-android/build.gradle @@ -55,6 +55,7 @@ dependencies { testImplementation libs.androidx.compose.ui.test.junit4 debugImplementation libs.androidx.compose.ui.test.manifest implementation libs.androidx.activity.compose + testImplementation(libs.webp.imageio) implementation libs.androidx.core.ktx implementation libs.androidx.appcompat diff --git a/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt b/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt new file mode 100644 index 000000000..832025c01 --- /dev/null +++ b/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt @@ -0,0 +1,122 @@ +package com.github.takahirom.roborazzi.sample.boxed + +import android.widget.TextView +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.github.takahirom.roborazzi.DefaultFileNameGenerator +import com.github.takahirom.roborazzi.JvmPlatformRecordOptions +import com.github.takahirom.roborazzi.ROBORAZZI_DEBUG +import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers +import com.github.takahirom.roborazzi.RoborazziOptions +import com.github.takahirom.roborazzi.RoborazziTaskType +import com.github.takahirom.roborazzi.captureRoboImage +import com.github.takahirom.roborazzi.losslessWebPSaver +import com.github.takahirom.roborazzi.provideRoborazziContext +import com.github.takahirom.roborazzi.roborazziSystemPropertyOutputDirectory +import com.github.takahirom.roborazzi.sample.MainActivity +import com.github.takahirom.roborazzi.sample.R +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.annotation.Config +import org.robolectric.annotation.GraphicsMode +import java.io.File + + +@RunWith(AndroidJUnit4::class) +@GraphicsMode(GraphicsMode.Mode.NATIVE) +@Config( + sdk = [30], + qualifiers = RobolectricDeviceQualifiers.NexusOne +) +class LosslessWebpTest { + @get:Rule + val composeTestRule = createAndroidComposeRule() + + @Test + fun whenCompareSameImageTheCompareImageShouldNotBeGenerated() { + boxedEnvironment { + ROBORAZZI_DEBUG = true + provideRoborazziContext().setImageExtension("webp") + val prefix = + "${roborazziSystemPropertyOutputDirectory()}/${this::class.qualifiedName}.saveImage" + val expectedOutput = + File("$prefix.webp") + val expectedCompareOutput = File("${prefix}_compare.webp") + expectedOutput.delete() + try { + onView(ViewMatchers.withId(R.id.textview_first)) + .captureRoboImage( + filePath = expectedOutput.absolutePath, + roborazziOptions = RoborazziOptions( + taskType = RoborazziTaskType.Record, + recordOptions = RoborazziOptions.RecordOptions( + platformRecordOptions = JvmPlatformRecordOptions( + awtImageWriter = losslessWebPSaver() + ) + ) + ), + ) + composeTestRule.activity.findViewById(R.id.textview_first) + .text = "Hello, Roborazzi! This is a test for size change." + DefaultFileNameGenerator.reset() + + onView(ViewMatchers.withId(R.id.textview_first)) + .captureRoboImage( + roborazziOptions = RoborazziOptions( + taskType = RoborazziTaskType.Compare + ) + ) + println(expectedCompareOutput.absolutePath + ":" + expectedCompareOutput.exists()) + assert(!expectedCompareOutput.exists()) + } finally { + expectedCompareOutput.delete() + } + } + } + + + @Test + fun whenCompareDifferentImageTheCompareImageShouldBeGenerated() { + boxedEnvironment { + ROBORAZZI_DEBUG = true + provideRoborazziContext().setImageExtension("webp") + val prefix = + "${roborazziSystemPropertyOutputDirectory()}/${this::class.qualifiedName}.saveImage" + val expectedOutput = + File("$prefix.webp") + val expectedCompareOutput = File("${prefix}_compare.webp") + expectedOutput.delete() + try { + onView(ViewMatchers.withId(R.id.textview_first)) + .captureRoboImage( + filePath = expectedOutput.absolutePath, + roborazziOptions = RoborazziOptions( + taskType = RoborazziTaskType.Record, + recordOptions = RoborazziOptions.RecordOptions( + platformRecordOptions = JvmPlatformRecordOptions( + awtImageWriter = losslessWebPSaver() + ) + ) + ), + ) + composeTestRule.activity.findViewById(R.id.textview_first) + .text = "Hello, Roborazzi! This is a test for size change." + DefaultFileNameGenerator.reset() + + onView(ViewMatchers.withId(R.id.textview_first)) + .captureRoboImage( + roborazziOptions = RoborazziOptions( + taskType = RoborazziTaskType.Compare + ) + ) + println(expectedCompareOutput.absolutePath + ":" + expectedCompareOutput.exists()) + assert(expectedCompareOutput.exists()) + } finally { + expectedCompareOutput.delete() + } + } + } +} From 2d75231db37b53d161996252a077f1f375eb6bee Mon Sep 17 00:00:00 2001 From: takahirom Date: Fri, 1 Nov 2024 14:23:40 +0900 Subject: [PATCH 02/10] Fix test --- .../sample/boxed/LosslessWebpTest.kt | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt b/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt index 832025c01..0e57a576d 100644 --- a/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt +++ b/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.espresso.Espresso.onView import androidx.test.espresso.matcher.ViewMatchers import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.github.takahirom.roborazzi.DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH import com.github.takahirom.roborazzi.DefaultFileNameGenerator import com.github.takahirom.roborazzi.JvmPlatformRecordOptions import com.github.takahirom.roborazzi.ROBORAZZI_DEBUG @@ -13,8 +14,8 @@ import com.github.takahirom.roborazzi.RoborazziOptions import com.github.takahirom.roborazzi.RoborazziTaskType import com.github.takahirom.roborazzi.captureRoboImage import com.github.takahirom.roborazzi.losslessWebPSaver +import com.github.takahirom.roborazzi.nameWithoutExtension import com.github.takahirom.roborazzi.provideRoborazziContext -import com.github.takahirom.roborazzi.roborazziSystemPropertyOutputDirectory import com.github.takahirom.roborazzi.sample.MainActivity import com.github.takahirom.roborazzi.sample.R import org.junit.Rule @@ -34,6 +35,11 @@ import java.io.File class LosslessWebpTest { @get:Rule val composeTestRule = createAndroidComposeRule() + val recordOptions = RoborazziOptions.RecordOptions( + platformRecordOptions = JvmPlatformRecordOptions( + awtImageWriter = losslessWebPSaver() + ) + ) @Test fun whenCompareSameImageTheCompareImageShouldNotBeGenerated() { @@ -41,35 +47,32 @@ class LosslessWebpTest { ROBORAZZI_DEBUG = true provideRoborazziContext().setImageExtension("webp") val prefix = - "${roborazziSystemPropertyOutputDirectory()}/${this::class.qualifiedName}.saveImage" + DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH + "/" + DefaultFileNameGenerator.generateFilePath().nameWithoutExtension val expectedOutput = File("$prefix.webp") val expectedCompareOutput = File("${prefix}_compare.webp") expectedOutput.delete() + expectedCompareOutput.delete() try { + onView(ViewMatchers.withId(R.id.textview_first)) .captureRoboImage( filePath = expectedOutput.absolutePath, roborazziOptions = RoborazziOptions( taskType = RoborazziTaskType.Record, - recordOptions = RoborazziOptions.RecordOptions( - platformRecordOptions = JvmPlatformRecordOptions( - awtImageWriter = losslessWebPSaver() - ) - ) + recordOptions = recordOptions ), ) - composeTestRule.activity.findViewById(R.id.textview_first) - .text = "Hello, Roborazzi! This is a test for size change." DefaultFileNameGenerator.reset() onView(ViewMatchers.withId(R.id.textview_first)) .captureRoboImage( roborazziOptions = RoborazziOptions( - taskType = RoborazziTaskType.Compare + taskType = RoborazziTaskType.Compare, + recordOptions = recordOptions ) ) - println(expectedCompareOutput.absolutePath + ":" + expectedCompareOutput.exists()) + assert(expectedOutput.exists()) assert(!expectedCompareOutput.exists()) } finally { expectedCompareOutput.delete() @@ -84,22 +87,19 @@ class LosslessWebpTest { ROBORAZZI_DEBUG = true provideRoborazziContext().setImageExtension("webp") val prefix = - "${roborazziSystemPropertyOutputDirectory()}/${this::class.qualifiedName}.saveImage" + DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH + "/" + DefaultFileNameGenerator.generateFilePath().nameWithoutExtension val expectedOutput = File("$prefix.webp") val expectedCompareOutput = File("${prefix}_compare.webp") expectedOutput.delete() + expectedCompareOutput.delete() try { onView(ViewMatchers.withId(R.id.textview_first)) .captureRoboImage( filePath = expectedOutput.absolutePath, roborazziOptions = RoborazziOptions( taskType = RoborazziTaskType.Record, - recordOptions = RoborazziOptions.RecordOptions( - platformRecordOptions = JvmPlatformRecordOptions( - awtImageWriter = losslessWebPSaver() - ) - ) + recordOptions = recordOptions ), ) composeTestRule.activity.findViewById(R.id.textview_first) @@ -108,11 +108,13 @@ class LosslessWebpTest { onView(ViewMatchers.withId(R.id.textview_first)) .captureRoboImage( + filePath = expectedOutput.absolutePath, roborazziOptions = RoborazziOptions( - taskType = RoborazziTaskType.Compare + taskType = RoborazziTaskType.Compare, + recordOptions = recordOptions ) ) - println(expectedCompareOutput.absolutePath + ":" + expectedCompareOutput.exists()) + assert(expectedOutput.exists()) assert(expectedCompareOutput.exists()) } finally { expectedCompareOutput.delete() From 75953d6a6563415f56a5fe39a36df27c8ca543bb Mon Sep 17 00:00:00 2001 From: takahirom Date: Fri, 1 Nov 2024 14:23:40 +0900 Subject: [PATCH 03/10] Use val --- .../com/github/takahirom/roborazzi/JvmImageIo.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/JvmImageIo.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/JvmImageIo.kt index 1d7f14909..815da22b3 100644 --- a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/JvmImageIo.kt +++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/JvmImageIo.kt @@ -16,17 +16,17 @@ import javax.imageio.stream.FileImageOutputStream fun interface AwtImageWriter { fun write( - dest: File, + destFile: File, contextData: Map, - bufferedImage: BufferedImage + image: BufferedImage ) } fun interface AwtImageLoader { - fun load(file: File): BufferedImage + fun load(inputFile: File): BufferedImage } -open class JvmPlatformRecordOptions( +data class JvmPlatformRecordOptions( val awtImageWriter: AwtImageWriter = AwtImageWriter { file, contextData, bufferedImage -> val imageExtension = file.extension.ifBlank { "png" } if (contextData.isEmpty()) { @@ -42,7 +42,7 @@ open class JvmPlatformRecordOptions( writer.output = ImageIO.createImageOutputStream(file) writer.write(IIOImage(bufferedImage, null, meta)) }, - var awtImageLoader: AwtImageLoader = AwtImageLoader { ImageIO.read(it) } + val awtImageLoader: AwtImageLoader = AwtImageLoader { ImageIO.read(it) } ) : PlatformRecordOptions From 22ca0522a693f6aa16b160c16c33c9f6d1270205 Mon Sep 17 00:00:00 2001 From: takahirom Date: Fri, 1 Nov 2024 14:31:24 +0900 Subject: [PATCH 04/10] Change saver to writer --- .../kotlin/com/github/takahirom/roborazzi/JvmImageIo.kt | 2 +- .../takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/JvmImageIo.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/JvmImageIo.kt index 815da22b3..30e8afa4e 100644 --- a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/JvmImageIo.kt +++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/JvmImageIo.kt @@ -84,7 +84,7 @@ fun ImageWriter.writeMetadata( /** * Add testImplementation("io.github.darkxanter:webp-imageio:0.3.0") to use this */ -fun losslessWebPSaver(): AwtImageWriter = +fun losslessWebPWriter(): AwtImageWriter = AwtImageWriter { file, context, bufferedImage -> val writer: ImageWriter = ImageIO.getImageWritersByMIMEType("image/webp").next() diff --git a/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt b/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt index 0e57a576d..0ee38c3bf 100644 --- a/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt +++ b/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt @@ -13,7 +13,7 @@ import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers import com.github.takahirom.roborazzi.RoborazziOptions import com.github.takahirom.roborazzi.RoborazziTaskType import com.github.takahirom.roborazzi.captureRoboImage -import com.github.takahirom.roborazzi.losslessWebPSaver +import com.github.takahirom.roborazzi.losslessWebPWriter import com.github.takahirom.roborazzi.nameWithoutExtension import com.github.takahirom.roborazzi.provideRoborazziContext import com.github.takahirom.roborazzi.sample.MainActivity @@ -37,7 +37,7 @@ class LosslessWebpTest { val composeTestRule = createAndroidComposeRule() val recordOptions = RoborazziOptions.RecordOptions( platformRecordOptions = JvmPlatformRecordOptions( - awtImageWriter = losslessWebPSaver() + awtImageWriter = losslessWebPWriter() ) ) From ddbaeece71fdb100b00b3766f353f72a0e9e3f51 Mon Sep 17 00:00:00 2001 From: takahirom Date: Fri, 1 Nov 2024 14:39:23 +0900 Subject: [PATCH 05/10] Fix parameter name --- .../kotlin/com/github/takahirom/roborazzi/AwtRoboCanvas.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/roborazzi-painter/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/AwtRoboCanvas.kt b/roborazzi-painter/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/AwtRoboCanvas.kt index 20191d711..d0503c34c 100644 --- a/roborazzi-painter/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/AwtRoboCanvas.kt +++ b/roborazzi-painter/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/AwtRoboCanvas.kt @@ -230,9 +230,9 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: val scaledBufferedImage = croppedImage.scale(resizeScale) (platformRecordOptions as JvmPlatformRecordOptions) .awtImageWriter.write( - dest = file, + destFile = file, contextData = contextData, - bufferedImage = scaledBufferedImage + image = scaledBufferedImage ) } From 4bc482b3f095dc2a24e3b1a26c82aaf952014780 Mon Sep 17 00:00:00 2001 From: takahirom Date: Sat, 2 Nov 2024 01:57:19 +0900 Subject: [PATCH 06/10] Refactor naming --- .../{JvmImageIo.kt => ImageIoFormat.commonJvm.kt} | 15 +++++++++++++-- .../takahirom/roborazzi/RoborazziOptions.kt | 2 +- .../roborazzi/processOutputImageAndReport.kt | 6 +++--- .../github/takahirom/roborazzi/ImageIoFormat.kt | 8 ++++++++ .../takahirom/roborazzi/PlatformRecordOptions.kt | 3 --- .../com/github/takahirom/roborazzi/RoboCanvas.kt | 3 +-- .../takahirom/roborazzi/ImageIoFormat.ios.kt | 10 ++++++++++ .../takahirom/roborazzi/RoborazziDesktop.kt | 2 +- .../github/takahirom/roborazzi/AwtRoboCanvas.kt | 8 ++++---- .../com/github/takahirom/roborazzi/Roborazzi.kt | 2 +- .../roborazzi/sample/boxed/LosslessWebpTest.kt | 7 ++----- 11 files changed, 44 insertions(+), 22 deletions(-) rename include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/{JvmImageIo.kt => ImageIoFormat.commonJvm.kt} (92%) create mode 100644 include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.kt delete mode 100644 include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/PlatformRecordOptions.kt create mode 100644 include-build/roborazzi-core/src/iosMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.ios.kt diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/JvmImageIo.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt similarity index 92% rename from include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/JvmImageIo.kt rename to include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt index 30e8afa4e..c40cb3216 100644 --- a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/JvmImageIo.kt +++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt @@ -14,6 +14,17 @@ import javax.imageio.metadata.IIOMetadataFormatImpl import javax.imageio.metadata.IIOMetadataNode import javax.imageio.stream.FileImageOutputStream +@Suppress("FunctionName") +actual fun WebPImageIoFormat(): ImageIoFormat { + return JvmImageIoFormat( + awtImageWriter = losslessWebPWriter() + ) +} + +actual fun ImageIoFormat(): ImageIoFormat { + return JvmImageIoFormat() +} + fun interface AwtImageWriter { fun write( destFile: File, @@ -26,7 +37,7 @@ fun interface AwtImageLoader { fun load(inputFile: File): BufferedImage } -data class JvmPlatformRecordOptions( +data class JvmImageIoFormat( val awtImageWriter: AwtImageWriter = AwtImageWriter { file, contextData, bufferedImage -> val imageExtension = file.extension.ifBlank { "png" } if (contextData.isEmpty()) { @@ -43,7 +54,7 @@ data class JvmPlatformRecordOptions( writer.write(IIOImage(bufferedImage, null, meta)) }, val awtImageLoader: AwtImageLoader = AwtImageLoader { ImageIO.read(it) } -) : PlatformRecordOptions +) : ImageIoFormat @ExperimentalRoborazziApi 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 24849d6b4..e03c340c3 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 @@ -195,7 +195,7 @@ data class RoborazziOptions( val resizeScale: Double = roborazziDefaultResizeScale(), val applyDeviceCrop: Boolean = false, val pixelBitConfig: PixelBitConfig = PixelBitConfig.Argb8888, - val platformRecordOptions: PlatformRecordOptions = JvmPlatformRecordOptions(), + val imageIoFormat: ImageIoFormat = ImageIoFormat(), ) enum class PixelBitConfig { 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 edcd84eab..748ebee23 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 @@ -120,7 +120,7 @@ fun processOutputImageAndReport( path = comparisonFile.absolutePath, resizeScale = resizeScale, contextData = contextData, - platformRecordOptions = recordOptions.platformRecordOptions, + imageIoFormat = recordOptions.imageIoFormat, ) debugLog { "processOutputImageAndReport(): compareCanvas is saved " + @@ -142,7 +142,7 @@ fun processOutputImageAndReport( path = actualFile.absolutePath, resizeScale = resizeScale, contextData = contextData, - platformRecordOptions = recordOptions.platformRecordOptions, + imageIoFormat = recordOptions.imageIoFormat, ) debugLog { "processOutputImageAndReport(): actualCanvas is saved " + @@ -189,7 +189,7 @@ fun processOutputImageAndReport( path = goldenFile.absolutePath, resizeScale = resizeScale, contextData = contextData, - platformRecordOptions = recordOptions.platformRecordOptions, + imageIoFormat = recordOptions.imageIoFormat, ) debugLog { "processOutputImageAndReport: \n" + diff --git a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.kt b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.kt new file mode 100644 index 000000000..ad550584a --- /dev/null +++ b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.kt @@ -0,0 +1,8 @@ +package com.github.takahirom.roborazzi + +interface ImageIoFormat + +@Suppress("FunctionName") +expect fun WebPImageIoFormat() : ImageIoFormat + +expect fun ImageIoFormat() : ImageIoFormat \ No newline at end of file diff --git a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/PlatformRecordOptions.kt b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/PlatformRecordOptions.kt deleted file mode 100644 index 108575b20..000000000 --- a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/PlatformRecordOptions.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.github.takahirom.roborazzi - -interface PlatformRecordOptions \ No newline at end of file diff --git a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/RoboCanvas.kt b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/RoboCanvas.kt index 2346116a7..275a3c6de 100644 --- a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/RoboCanvas.kt +++ b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/RoboCanvas.kt @@ -11,10 +11,9 @@ interface RoboCanvas { path: String, resizeScale: Double, contextData: Map, - platformRecordOptions: PlatformRecordOptions, + imageIoFormat: ImageIoFormat, ) fun differ(other: RoboCanvas, resizeScale: Double, imageComparator: ImageComparator): ImageComparator.ComparisonResult fun release() - } diff --git a/include-build/roborazzi-core/src/iosMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.ios.kt b/include-build/roborazzi-core/src/iosMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.ios.kt new file mode 100644 index 000000000..66fddb8f0 --- /dev/null +++ b/include-build/roborazzi-core/src/iosMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.ios.kt @@ -0,0 +1,10 @@ +package com.github.takahirom.roborazzi + +@Suppress("FunctionName") +actual fun WebPImageIoFormat(): ImageIoFormat { + TODO("NOT IMPLEMENTED YET") +} + +actual fun ImageIoFormat(): ImageIoFormat { + TODO("NOT IMPLEMENTED YET") +} \ No newline at end of file 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 554f1336b..333f498c7 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,7 +117,7 @@ fun processOutputImageAndReportWithDefaults( AwtRoboCanvas.load( file = file, bufferedImageType = bufferedImageType, - platformRecordOptions = roborazziOptions.recordOptions.platformRecordOptions + imageIoFormat = roborazziOptions.recordOptions.imageIoFormat ) }, comparisonCanvasFactory = { goldenCanvas, actualCanvas, resizeScale, bufferedImageType -> diff --git a/roborazzi-painter/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/AwtRoboCanvas.kt b/roborazzi-painter/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/AwtRoboCanvas.kt index d0503c34c..05903cf08 100644 --- a/roborazzi-painter/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/AwtRoboCanvas.kt +++ b/roborazzi-painter/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/AwtRoboCanvas.kt @@ -215,7 +215,7 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: path: String, resizeScale: Double, contextData: Map, - platformRecordOptions: PlatformRecordOptions + imageIoFormat: ImageIoFormat, ) { val file = File(path) drawPendingDraw() @@ -228,7 +228,7 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: // ignore } val scaledBufferedImage = croppedImage.scale(resizeScale) - (platformRecordOptions as JvmPlatformRecordOptions) + (imageIoFormat as JvmImageIoFormat) .awtImageWriter.write( destFile = file, contextData = contextData, @@ -270,8 +270,8 @@ class AwtRoboCanvas(width: Int, height: Int, filled: Boolean, bufferedImageType: } companion object { - fun load(file: File, bufferedImageType: Int, platformRecordOptions: PlatformRecordOptions): AwtRoboCanvas { - val loadedImage: BufferedImage = (platformRecordOptions as JvmPlatformRecordOptions).awtImageLoader.load(file) + fun load(file: File, bufferedImageType: Int, imageIoFormat: ImageIoFormat): AwtRoboCanvas { + val loadedImage: BufferedImage = (imageIoFormat as JvmImageIoFormat).awtImageLoader.load(file) val awtRoboCanvas = AwtRoboCanvas( loadedImage.width, height = loadedImage.height, 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 13cff5eb2..336b0c126 100644 --- a/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt +++ b/roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt @@ -649,7 +649,7 @@ fun processOutputImageAndReportWithDefaults( ) }, canvasFactoryFromFile = { file, bufferedImageType -> - AwtRoboCanvas.load(file, bufferedImageType, roborazziOptions.recordOptions.platformRecordOptions) + AwtRoboCanvas.load(file, bufferedImageType, roborazziOptions.recordOptions.imageIoFormat) }, comparisonCanvasFactory = { goldenCanvas, actualCanvas, resizeScale, bufferedImageType -> AwtRoboCanvas.generateCompareCanvas( diff --git a/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt b/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt index 0ee38c3bf..23f1df75d 100644 --- a/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt +++ b/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt @@ -7,13 +7,12 @@ import androidx.test.espresso.matcher.ViewMatchers import androidx.test.ext.junit.runners.AndroidJUnit4 import com.github.takahirom.roborazzi.DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH import com.github.takahirom.roborazzi.DefaultFileNameGenerator -import com.github.takahirom.roborazzi.JvmPlatformRecordOptions import com.github.takahirom.roborazzi.ROBORAZZI_DEBUG import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers import com.github.takahirom.roborazzi.RoborazziOptions import com.github.takahirom.roborazzi.RoborazziTaskType +import com.github.takahirom.roborazzi.WebPImageIoFormat import com.github.takahirom.roborazzi.captureRoboImage -import com.github.takahirom.roborazzi.losslessWebPWriter import com.github.takahirom.roborazzi.nameWithoutExtension import com.github.takahirom.roborazzi.provideRoborazziContext import com.github.takahirom.roborazzi.sample.MainActivity @@ -36,9 +35,7 @@ class LosslessWebpTest { @get:Rule val composeTestRule = createAndroidComposeRule() val recordOptions = RoborazziOptions.RecordOptions( - platformRecordOptions = JvmPlatformRecordOptions( - awtImageWriter = losslessWebPWriter() - ) + imageIoFormat = WebPImageIoFormat(), ) @Test From 701136fd7d89d2d6066e0d0acaf5b9294fe3ed8b Mon Sep 17 00:00:00 2001 From: takahirom Date: Sat, 2 Nov 2024 16:59:46 +0900 Subject: [PATCH 07/10] Change naming of webp format --- .../com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt | 2 +- .../kotlin/com/github/takahirom/roborazzi/ImageIoFormat.kt | 2 +- .../com/github/takahirom/roborazzi/ImageIoFormat.ios.kt | 2 +- .../takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt index c40cb3216..1c2f1ae3f 100644 --- a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt +++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt @@ -15,7 +15,7 @@ import javax.imageio.metadata.IIOMetadataNode import javax.imageio.stream.FileImageOutputStream @Suppress("FunctionName") -actual fun WebPImageIoFormat(): ImageIoFormat { +actual fun LosslessWebPImageIoFormat(): ImageIoFormat { return JvmImageIoFormat( awtImageWriter = losslessWebPWriter() ) diff --git a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.kt b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.kt index ad550584a..2852346fd 100644 --- a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.kt +++ b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.kt @@ -3,6 +3,6 @@ package com.github.takahirom.roborazzi interface ImageIoFormat @Suppress("FunctionName") -expect fun WebPImageIoFormat() : ImageIoFormat +expect fun LosslessWebPImageIoFormat() : ImageIoFormat expect fun ImageIoFormat() : ImageIoFormat \ No newline at end of file diff --git a/include-build/roborazzi-core/src/iosMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.ios.kt b/include-build/roborazzi-core/src/iosMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.ios.kt index 66fddb8f0..fcd0d53d1 100644 --- a/include-build/roborazzi-core/src/iosMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.ios.kt +++ b/include-build/roborazzi-core/src/iosMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.ios.kt @@ -1,7 +1,7 @@ package com.github.takahirom.roborazzi @Suppress("FunctionName") -actual fun WebPImageIoFormat(): ImageIoFormat { +actual fun LosslessWebPImageIoFormat(): ImageIoFormat { TODO("NOT IMPLEMENTED YET") } diff --git a/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt b/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt index 23f1df75d..b87afce4d 100644 --- a/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt +++ b/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/LosslessWebpTest.kt @@ -11,7 +11,7 @@ import com.github.takahirom.roborazzi.ROBORAZZI_DEBUG import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers import com.github.takahirom.roborazzi.RoborazziOptions import com.github.takahirom.roborazzi.RoborazziTaskType -import com.github.takahirom.roborazzi.WebPImageIoFormat +import com.github.takahirom.roborazzi.LosslessWebPImageIoFormat import com.github.takahirom.roborazzi.captureRoboImage import com.github.takahirom.roborazzi.nameWithoutExtension import com.github.takahirom.roborazzi.provideRoborazziContext @@ -35,7 +35,7 @@ class LosslessWebpTest { @get:Rule val composeTestRule = createAndroidComposeRule() val recordOptions = RoborazziOptions.RecordOptions( - imageIoFormat = WebPImageIoFormat(), + imageIoFormat = LosslessWebPImageIoFormat(), ) @Test From 50f8338705042261b3d49082ef5b32f111c64ab5 Mon Sep 17 00:00:00 2001 From: takahirom Date: Mon, 4 Nov 2024 19:10:56 +0900 Subject: [PATCH 08/10] Add "Experimental WebP support and other image formats" --- docs/topics/how_to_use.md | 127 +++++------------- .../roborazzi/ImageIoFormat.commonJvm.kt | 5 + .../takahirom/roborazzi/ImageIoFormat.kt | 3 + .../roborazzi/RoborazziProperties.kt | 2 +- 4 files changed, 46 insertions(+), 91 deletions(-) diff --git a/docs/topics/how_to_use.md b/docs/topics/how_to_use.md index a0a6c1c41..2339b345e 100644 --- a/docs/topics/how_to_use.md +++ b/docs/topics/how_to_use.md @@ -369,7 +369,7 @@ fun captureRoboGifSample() { -### Automatically generate gif with test rule +### Generate gif with test rule > **Note** > You **don't need to use RoborazziRule** if you're using captureRoboImage(). @@ -409,7 +409,7 @@ class RuleTestWithOnlyFail { } ``` -### Automatically generate Jetpack Compose gif with test rule +### Generate Jetpack Compose gif with test rule Test target @@ -546,94 +546,6 @@ class RoborazziRule private constructor( } ``` -### Roborazzi options - -```kotlin -data class RoborazziOptions( - val captureType: CaptureType = if (isNativeGraphicsEnabled()) CaptureType.Screenshot() else CaptureType.Dump(), - val compareOptions: CompareOptions = CompareOptions(), - val recordOptions: RecordOptions = RecordOptions(), -) { - sealed interface CaptureType { - class Screenshot : CaptureType - - data class Dump( - val takeScreenShot: Boolean = isNativeGraphicsEnabled(), - val basicSize: Int = 600, - val depthSlideSize: Int = 30, - val query: ((RoboComponent) -> Boolean)? = null, - val explanation: ((RoboComponent) -> String?) = DefaultExplanation, - ) : CaptureType { - companion object { - val DefaultExplanation: ((RoboComponent) -> String) = { - it.text - } - val AccessibilityExplanation: ((RoboComponent) -> String) = { - it.accessibilityText - } - } - } - } - - data class CompareOptions( - val roborazziCompareReporter: RoborazziCompareReporter = RoborazziCompareReporter(), - val resultValidator: (result: ImageComparator.ComparisonResult) -> Boolean, - ) { - constructor( - roborazziCompareReporter: RoborazziCompareReporter = RoborazziCompareReporter(), - /** - * This value determines the threshold of pixel change at which the diff image is output or not. - * The value should be between 0 and 1 - */ - changeThreshold: Float = 0.01F, - ) : this(roborazziCompareReporter, ThresholdValidator(changeThreshold)) - } - - interface RoborazziCompareReporter { - fun report(compareReportCaptureResult: CompareReportCaptureResult) - - companion object { - operator fun invoke(): RoborazziCompareReporter { - ... - } - } - - class JsonOutputRoborazziCompareReporter : RoborazziCompareReporter { - ... - - override fun report(compareReportCaptureResult: CompareReportCaptureResult) { - ... - } - } - - class VerifyRoborazziCompareReporter : RoborazziCompareReporter { - override fun report(compareReportCaptureResult: CompareReportCaptureResult) { - ... - } - } - } - - data class RecordOptions( - val resizeScale: Double = roborazziDefaultResizeScale(), - val applyDeviceCrop: Boolean = false, - val pixelBitConfig: PixelBitConfig = PixelBitConfig.Argb8888, - ) - - enum class PixelBitConfig { - Argb8888, - Rgb565; - - fun toBitmapConfig(): Bitmap.Config { - ... - } - - fun toBufferedImageType(): Int { - ... - } - } -} -``` - #### Image comparator custom settings When comparing images, you may encounter differences due to minor changes related to antialiasing. You can use the options below to avoid this. ```kotlin @@ -654,8 +566,43 @@ val roborazziRule = RoborazziRule( ) ``` +### Experimental WebP support and other image formats + +You can set `roborazzi.record.image.extension` to `webp` in your `gradle.properties` file to generate WebP images. + +```kotlin +roborazzi.record.image.extension=webp +``` + +WebP is a lossy image format by default, making it challenging to manage image differences. To address this, we provide a lossless WebP image comparison feature. + +```kotlin +onView(ViewMatchers.withId(R.id.textview_first)) + .captureRoboImage( + roborazziOptions = RoborazziOptions( + recordOptions = RoborazziOptions.RecordOptions( + imageIoFormat = LosslessWebPImageIoFormat(), + ), + ) + ) +``` + +You can also use other image formats by implementing your own `AwtImageWriter` and `AwtImageLoader`. + +```kotlin +data class JvmImageIoFormat( + val awtImageWriter: AwtImageWriter, + val awtImageLoader: AwtImageLoader +) : ImageIoFormat + +``` + ### Dump mode If you are having trouble debugging your test, try Dump mode as follows. ![image](https://user-images.githubusercontent.com/1386930/226364158-a07a0fb0-d8e7-46b7-a495-8dd217faaadb.png) + +### Roborazzi options + +Please check out [RoborazziOptions](https://github.com/takahirom/roborazzi/blob/main/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt) for available Roborazzi options. diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt index 1c2f1ae3f..2943f7bc5 100644 --- a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt +++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt @@ -14,6 +14,7 @@ import javax.imageio.metadata.IIOMetadataFormatImpl import javax.imageio.metadata.IIOMetadataNode import javax.imageio.stream.FileImageOutputStream +@ExperimentalRoborazziApi @Suppress("FunctionName") actual fun LosslessWebPImageIoFormat(): ImageIoFormat { return JvmImageIoFormat( @@ -21,10 +22,12 @@ actual fun LosslessWebPImageIoFormat(): ImageIoFormat { ) } +@ExperimentalRoborazziApi actual fun ImageIoFormat(): ImageIoFormat { return JvmImageIoFormat() } +@ExperimentalRoborazziApi fun interface AwtImageWriter { fun write( destFile: File, @@ -33,10 +36,12 @@ fun interface AwtImageWriter { ) } +@ExperimentalRoborazziApi fun interface AwtImageLoader { fun load(inputFile: File): BufferedImage } +@ExperimentalRoborazziApi data class JvmImageIoFormat( val awtImageWriter: AwtImageWriter = AwtImageWriter { file, contextData, bufferedImage -> val imageExtension = file.extension.ifBlank { "png" } diff --git a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.kt b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.kt index 2852346fd..e13af6c23 100644 --- a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.kt +++ b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.kt @@ -1,8 +1,11 @@ package com.github.takahirom.roborazzi +@ExperimentalRoborazziApi interface ImageIoFormat +@ExperimentalRoborazziApi @Suppress("FunctionName") expect fun LosslessWebPImageIoFormat() : ImageIoFormat +@ExperimentalRoborazziApi expect fun ImageIoFormat() : ImageIoFormat \ No newline at end of file diff --git a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/RoborazziProperties.kt b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/RoborazziProperties.kt index e8465d819..b3874fb36 100644 --- a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/RoborazziProperties.kt +++ b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/RoborazziProperties.kt @@ -10,7 +10,7 @@ fun roborazziSystemPropertyOutputDirectory(): String { @ExperimentalRoborazziApi fun roborazziSystemPropertyImageExtension(): String { - return getSystemProperty("roborazzi.image.extension", "png") + return getSystemProperty("roborazzi.record.image.extension", "png") } @ExperimentalRoborazziApi From 557e310d4e6816597e40dfa226aa245beca87b50 Mon Sep 17 00:00:00 2001 From: takahirom Date: Tue, 5 Nov 2024 23:56:26 +0900 Subject: [PATCH 09/10] Add README docs about a WebP dependency --- README.md | 128 ++++++------------ docs/topics/how_to_use.md | 3 +- include-build/roborazzi-core/build.gradle | 2 + .../roborazzi/ImageIoFormat.commonJvm.kt | 2 +- 4 files changed, 43 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 9f30048d4..f487d3f74 100644 --- a/README.md +++ b/README.md @@ -692,7 +692,7 @@ fun captureRoboGifSample() { -### Automatically generate gif with test rule +### Generate gif with test rule > **Note** > You **don't need to use RoborazziRule** if you're using captureRoboImage(). @@ -732,7 +732,7 @@ class RuleTestWithOnlyFail { } ``` -### Automatically generate Jetpack Compose gif with test rule +### Generate Jetpack Compose gif with test rule Test target @@ -869,94 +869,6 @@ class RoborazziRule private constructor( } ``` -### Roborazzi options - -```kotlin -data class RoborazziOptions( - val captureType: CaptureType = if (isNativeGraphicsEnabled()) CaptureType.Screenshot() else CaptureType.Dump(), - val compareOptions: CompareOptions = CompareOptions(), - val recordOptions: RecordOptions = RecordOptions(), -) { - sealed interface CaptureType { - class Screenshot : CaptureType - - data class Dump( - val takeScreenShot: Boolean = isNativeGraphicsEnabled(), - val basicSize: Int = 600, - val depthSlideSize: Int = 30, - val query: ((RoboComponent) -> Boolean)? = null, - val explanation: ((RoboComponent) -> String?) = DefaultExplanation, - ) : CaptureType { - companion object { - val DefaultExplanation: ((RoboComponent) -> String) = { - it.text - } - val AccessibilityExplanation: ((RoboComponent) -> String) = { - it.accessibilityText - } - } - } - } - - data class CompareOptions( - val roborazziCompareReporter: RoborazziCompareReporter = RoborazziCompareReporter(), - val resultValidator: (result: ImageComparator.ComparisonResult) -> Boolean, - ) { - constructor( - roborazziCompareReporter: RoborazziCompareReporter = RoborazziCompareReporter(), - /** - * This value determines the threshold of pixel change at which the diff image is output or not. - * The value should be between 0 and 1 - */ - changeThreshold: Float = 0.01F, - ) : this(roborazziCompareReporter, ThresholdValidator(changeThreshold)) - } - - interface RoborazziCompareReporter { - fun report(compareReportCaptureResult: CompareReportCaptureResult) - - companion object { - operator fun invoke(): RoborazziCompareReporter { - ... - } - } - - class JsonOutputRoborazziCompareReporter : RoborazziCompareReporter { - ... - - override fun report(compareReportCaptureResult: CompareReportCaptureResult) { - ... - } - } - - class VerifyRoborazziCompareReporter : RoborazziCompareReporter { - override fun report(compareReportCaptureResult: CompareReportCaptureResult) { - ... - } - } - } - - data class RecordOptions( - val resizeScale: Double = roborazziDefaultResizeScale(), - val applyDeviceCrop: Boolean = false, - val pixelBitConfig: PixelBitConfig = PixelBitConfig.Argb8888, - ) - - enum class PixelBitConfig { - Argb8888, - Rgb565; - - fun toBitmapConfig(): Bitmap.Config { - ... - } - - fun toBufferedImageType(): Int { - ... - } - } -} -``` - #### Image comparator custom settings When comparing images, you may encounter differences due to minor changes related to antialiasing. You can use the options below to avoid this. ```kotlin @@ -977,12 +889,48 @@ val roborazziRule = RoborazziRule( ) ``` +### Experimental WebP support and other image formats + +You can set `roborazzi.record.image.extension` to `webp` in your `gradle.properties` file to generate WebP images. + +```kotlin +roborazzi.record.image.extension=webp +``` + +WebP is a lossy image format by default, which can make managing image differences challenging. To address this, we provide a lossless WebP image comparison feature. +To enable WebP support, add `testImplementation("io.github.darkxanter:webp-imageio:0.3.3")` to your `build.gradle.kts` file. + +```kotlin +onView(ViewMatchers.withId(R.id.textview_first)) + .captureRoboImage( + roborazziOptions = RoborazziOptions( + recordOptions = RoborazziOptions.RecordOptions( + imageIoFormat = LosslessWebPImageIoFormat(), + ), + ) + ) +``` + +You can also use other image formats by implementing your own `AwtImageWriter` and `AwtImageLoader`. + +```kotlin +data class JvmImageIoFormat( + val awtImageWriter: AwtImageWriter, + val awtImageLoader: AwtImageLoader +) : ImageIoFormat + +``` + ### Dump mode If you are having trouble debugging your test, try Dump mode as follows. ![image](https://user-images.githubusercontent.com/1386930/226364158-a07a0fb0-d8e7-46b7-a495-8dd217faaadb.png) +### Roborazzi options + +Please check out [RoborazziOptions](https://github.com/takahirom/roborazzi/blob/main/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt) for available Roborazzi options. +
diff --git a/docs/topics/how_to_use.md b/docs/topics/how_to_use.md index 2339b345e..e95be494a 100644 --- a/docs/topics/how_to_use.md +++ b/docs/topics/how_to_use.md @@ -574,7 +574,8 @@ You can set `roborazzi.record.image.extension` to `webp` in your `gradle.propert roborazzi.record.image.extension=webp ``` -WebP is a lossy image format by default, making it challenging to manage image differences. To address this, we provide a lossless WebP image comparison feature. +WebP is a lossy image format by default, which can make managing image differences challenging. To address this, we provide a lossless WebP image comparison feature. +To enable WebP support, add `testImplementation("io.github.darkxanter:webp-imageio:0.3.3")` to your `build.gradle.kts` file. ```kotlin onView(ViewMatchers.withId(R.id.textview_first)) diff --git a/include-build/roborazzi-core/build.gradle b/include-build/roborazzi-core/build.gradle index ad91d01b5..165fa51ac 100644 --- a/include-build/roborazzi-core/build.gradle +++ b/include-build/roborazzi-core/build.gradle @@ -53,6 +53,8 @@ kotlin { commonJvmMain { dependencies { implementation libs.junit + // The library is a little bit heavy, so we use compileOnly here. + // Users need to add this library to their dependencies. compileOnly(libs.webp.imageio) } } diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt index 2943f7bc5..a643470fb 100644 --- a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt +++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt @@ -98,7 +98,7 @@ fun ImageWriter.writeMetadata( } /** - * Add testImplementation("io.github.darkxanter:webp-imageio:0.3.0") to use this + * Add testImplementation("io.github.darkxanter:webp-imageio:0.3.3") to use this */ fun losslessWebPWriter(): AwtImageWriter = AwtImageWriter { file, context, bufferedImage -> From 085761409d1866226f0e937b92de67a76507a204 Mon Sep 17 00:00:00 2001 From: takahirom Date: Tue, 5 Nov 2024 23:59:29 +0900 Subject: [PATCH 10/10] Add private to losslessWebPWriter --- .../com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt index a643470fb..b2766d9b1 100644 --- a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt +++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ImageIoFormat.commonJvm.kt @@ -100,7 +100,7 @@ fun ImageWriter.writeMetadata( /** * Add testImplementation("io.github.darkxanter:webp-imageio:0.3.3") to use this */ -fun losslessWebPWriter(): AwtImageWriter = +private fun losslessWebPWriter(): AwtImageWriter = AwtImageWriter { file, context, bufferedImage -> val writer: ImageWriter = ImageIO.getImageWritersByMIMEType("image/webp").next()