Skip to content

Commit

Permalink
Repro test
Browse files Browse the repository at this point in the history
  • Loading branch information
jrodbx committed Jun 18, 2024
1 parent 3339ae5 commit 3104599
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 8 deletions.
15 changes: 7 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 5

- name: Configure JDK
uses: actions/setup-java@v4
Expand All @@ -41,18 +43,15 @@ jobs:
- name: Setup Gradle
uses: gradle/gradle-build-action@v3

- name: Run All Tests
run: ./gradlew check
- name: Record Sample Snapshots
run: ./gradlew sample:recordPaparazziDebug --tests=RenderingIssuesTest

- name: Upload Test Failures
if: failure()
- name: Upload Sample Snapshots
uses: actions/upload-artifact@v4
with:
name: test-failures-${{ matrix.os }}-${{ matrix.java-version }}
name: sample-snapshots-${{ matrix.os }}-${{ matrix.java-version }}
path: |
**/build/reports/tests/*/
**/build/paparazzi/failures/
paparazzi-gradle-plugin/src/test/projects/**/build/reports/paparazzi/**/images/
sample/src/test/snapshots/**
publish:
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions sample-cli/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
apply plugin: 'org.jetbrains.kotlin.jvm'
160 changes: 160 additions & 0 deletions sample-cli/src/main/java/app/cash/paparazzi/sample/ImageComparer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package app.cash.paparazzi.sample

import java.awt.Color
import java.awt.image.BufferedImage
import java.awt.image.BufferedImage.TYPE_INT_ARGB
import java.io.File
import java.io.File.separatorChar
import javax.imageio.ImageIO

public fun main() {
val image1 = "sample-cli/images1/app.cash.paparazzi.sample_RenderingIssuesTest_example_mac-arm-7ca1390_.png"
val image2 = "sample-cli/images2/app.cash.paparazzi.sample_RenderingIssuesTest_example_mac-arm-7ca1390_.png"

compareImages(image1, image2)
}

private fun readImage(image: String): BufferedImage {
val file = File(image)
println("Reading: ${file.absolutePath}")
return ImageIO.read(file)
}

public fun compareImages(image1: String, image2: String) {
val goldenImage: BufferedImage = readImage(image1)
val image: BufferedImage = readImage(image2)

val goldenImageWidth = goldenImage.width
val goldenImageHeight = goldenImage.height

val imageWidth = image.width
val imageHeight = image.height

val deltaWidth = Math.max(goldenImageWidth, imageWidth)
val deltaHeight = Math.max(goldenImageHeight, imageHeight)

// Blur the images to account for the scenarios where there are pixel
// differences
// in where a sharp edge occurs
// goldenImage = blur(goldenImage, 6);
// image = blur(image, 6);
val width = goldenImageWidth + deltaWidth + imageWidth
val deltaImage = BufferedImage(width, deltaHeight, TYPE_INT_ARGB)
val g = deltaImage.graphics

// Compute delta map
var delta: Long = 0
for (y in 0 until deltaHeight) {
for (x in 0 until deltaWidth) {
val goldenRgb = if (x >= goldenImageWidth || y >= goldenImageHeight) {
0x00808080
} else {
goldenImage.getRGB(x, y)
}

val rgb = if (x >= imageWidth || y >= imageHeight) {
0x00808080
} else {
image.getRGB(x, y)
}

if (goldenRgb == rgb) {
deltaImage.setRGB(goldenImageWidth + x, y, 0x00808080)
continue
}

// If the pixels have no opacity, don't delta colors at all
if (goldenRgb and -0x1000000 == 0 && rgb and -0x1000000 == 0) {
deltaImage.setRGB(goldenImageWidth + x, y, 0x00808080)
continue
}

val deltaR = (rgb and 0xFF0000).ushr(16) - (goldenRgb and 0xFF0000).ushr(16)
val newR = 128 + deltaR and 0xFF
val deltaG = (rgb and 0x00FF00).ushr(8) - (goldenRgb and 0x00FF00).ushr(8)
val newG = 128 + deltaG and 0xFF
val deltaB = (rgb and 0x0000FF) - (goldenRgb and 0x0000FF)
val newB = 128 + deltaB and 0xFF

val avgAlpha =
((goldenRgb and -0x1000000).ushr(24) + (rgb and -0x1000000).ushr(24)) / 2 shl 24

val newRGB = avgAlpha or (newR shl 16) or (newG shl 8) or newB
deltaImage.setRGB(goldenImageWidth + x, y, newRGB)

delta += Math.abs(deltaR)
.toLong()
delta += Math.abs(deltaG)
.toLong()
delta += Math.abs(deltaB)
.toLong()
}
}

// 3 different colors, 256 color levels
val total = deltaHeight.toLong() * deltaWidth.toLong() * 3L * 256L
val percentDifference = (delta * 100 / total.toDouble()).toFloat()

var error: String? = null
val imageName = getName(image2)
if (percentDifference > 0.0) {
error = String.format("Images differ (by %f%%)", percentDifference)
} else if (Math.abs(goldenImageWidth - imageWidth) >= 2) {
error = "Widths differ too much for " + imageName + ": " +
goldenImageWidth + "x" + goldenImageHeight +
"vs" + imageWidth + "x" + imageHeight
} else if (Math.abs(goldenImageHeight - imageHeight) >= 2) {
error = "Heights differ too much for " + imageName + ": " +
goldenImageWidth + "x" + goldenImageHeight +
"vs" + imageWidth + "x" + imageHeight
}

if (error != null) {
// Expected on the left
// Golden on the right
g.drawImage(goldenImage, 0, 0, null)
g.drawImage(image, goldenImageWidth + deltaWidth, 0, null)

// Labels
if (deltaWidth > 80) {
g.color = Color.RED
g.drawString("Expected", 10, 20)
g.drawString("Actual", goldenImageWidth + deltaWidth + 10, 20)
}

val output = File(".", "delta-$imageName")
if (output.exists()) {
val deleted = output.delete()
println("existing output deleted?: $deleted")
}
ImageIO.write(deltaImage, "PNG", output)
error += " - see details in file://" + output.path + "\n"
error = run {
var initialMessage = error
val output1 = File(".", getName(image2))
if (output1.exists()) {
val deleted = output1.delete()
println("existing output deleted?: $deleted")
}
ImageIO.write(image, "PNG", output1)
initialMessage += "Thumbnail for current rendering stored at file://" + output1.path
// initialMessage += "\nRun the following command to accept the changes:\n";
// initialMessage += String.format("mv %1$s %2$s", output.getPath(),
// ImageUtils.class.getResource(relativePath).getPath());
// The above has been commented out, since the destination path returned is in out dir
// and it makes the tests pass without the code being actually checked in.
initialMessage
}
println(error)
throw AssertionError(error)
} else {
println("Images are identical!")
}

g.dispose()
}

private fun getName(relativePath: String): String {
return relativePath.substring(relativePath.lastIndexOf(separatorChar) + 1)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package app.cash.paparazzi.sample

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.material.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import app.cash.paparazzi.Paparazzi
import com.android.ide.common.rendering.api.SessionParams
import org.junit.Rule
import org.junit.Test

class RenderingIssuesTest {
@get:Rule
val paparazzi = Paparazzi(
renderingMode = SessionParams.RenderingMode.SHRINK,
)

@Test
fun example() {
paparazzi.snapshot("${osName()}-${gitShortSha()}") {
Box(
modifier = Modifier.background(Color(0xFF000033))
) {
Text("ExampleText", color = Color.White)
}
}
}

fun osName(): String {
val osName = System.getProperty("os.name")!!.lowercase()
return when {
osName.startsWith("windows") -> "win"
osName.startsWith("mac") -> {
val osArch = System.getProperty("os.arch")!!.lowercase()
if (osArch.startsWith("x86")) "mac-x86" else "mac-arm"
}

else -> "linux"
}
}

fun gitShortSha(): String {
val command = "git rev-parse --short HEAD~"
val p = ProcessBuilder().command(*command.split(" ").toTypedArray()).start()
val exit = p.waitFor()
if (exit == 0) {
return p.inputStream.bufferedReader().readText()
} else {
throw Exception(p.errorStream.bufferedReader().readText())
}
}
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ include ':paparazzi-preview-processor'
include ':paparazzi-gradle-plugin'

include ':sample'
include ':sample-cli'

enableFeaturePreview('TYPESAFE_PROJECT_ACCESSORS')

Expand Down

0 comments on commit 3104599

Please sign in to comment.