Skip to content

Commit

Permalink
Merge pull request #6 from alyssaburlton/image-comparison
Browse files Browse the repository at this point in the history
Image comparison
  • Loading branch information
alyssaruth authored Apr 17, 2023
2 parents b946c57 + 16287b2 commit a33dec4
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 21 deletions.
5 changes: 3 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ plugins {
}

group 'com.github.alexburlton'
version '2.0.0'
version '3.0.0'

repositories {
mavenCentral()
}

dependencies {
implementation 'com.github.romankh3:image-comparison:4.4.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0"
implementation 'io.kotest:kotest-assertions-core:5.5.4'
implementation "org.junit.jupiter:junit-jupiter-api:5.9.2"
Expand Down Expand Up @@ -70,7 +71,7 @@ publishing {
mavenJava(MavenPublication) {
groupId = 'com.github.alexburlton'
artifactId = 'swing-test'
version = '2.0.0'
version = '3.0.0'
from components.java

pom {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.github.alyssaburlton.swingtest

import com.github.romankh3.image.comparison.ImageComparison
import com.github.romankh3.image.comparison.model.ImageComparisonResult
import com.github.romankh3.image.comparison.model.ImageComparisonState
import io.kotest.assertions.fail
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Assumptions
Expand All @@ -15,12 +18,10 @@ const val ENV_SCREENSHOT_OS = "screenshotOs"
private const val DEFAULT_WIDTH = 200
private const val DEFAULT_HEIGHT = 200

fun Icon.shouldMatch(other: Icon)
{
fun Icon.shouldMatch(other: Icon) {
toBufferedImage().isEqual(other.toBufferedImage()) shouldBe true
}
private fun Icon.toBufferedImage(): BufferedImage
{
private fun Icon.toBufferedImage(): BufferedImage {
val bi = BufferedImage(iconWidth, iconHeight, BufferedImage.TYPE_INT_RGB)
val g = bi.createGraphics()
paintIcon(null, g, 0, 0)
Expand All @@ -31,7 +32,7 @@ private fun Icon.toBufferedImage(): BufferedImage
fun JComponent.toBufferedImage(): BufferedImage {
val width = getWidthForSnapshot()
val height = getHeightForSnapshot()

val img = BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR)
val g2 = img.createGraphics()
paint(g2)
Expand All @@ -50,13 +51,16 @@ private fun JComponent.getHeightForSnapshot(): Int = when {
else -> DEFAULT_HEIGHT
}

fun JComponent.shouldMatchImage(imageName: String) {
@JvmOverloads
fun JComponent.shouldMatchImage(imageName: String, pixelTolerance: Double = 0.01, failingPixelsThreshold: Double = 0.0) {
verifyOs()

val overwrite = System.getProperty(ENV_UPDATE_SNAPSHOT) == "true"
val img = toBufferedImage()

val callingSite = Throwable().stackTrace[1].className
val stackFrames = Throwable().stackTrace.toList()

val callingSite = stackFrames.first { it.className != "com.github.alyssaburlton.swingtest.SwingSnapshotsKt" }.className
val imgPath = "src/test/resources/__snapshots__/$callingSite"

val file = File("$imgPath/$imageName.png")
Expand All @@ -70,22 +74,40 @@ fun JComponent.shouldMatchImage(imageName: String) {
ImageIO.write(img, "png", file)
} else {
val savedImg = ImageIO.read(file)
val match = img.isEqual(savedImg)
if (!match) {
val result = ImageComparison(savedImg, img)
.setPixelToleranceLevel(pixelTolerance)
.setAllowingPercentOfDifferentPixels(failingPixelsThreshold)
.compareImages()

if (result.imageComparisonState != ImageComparisonState.MATCH) {
val failedFile = File("$imgPath/$imageName.failed.png")
ImageIO.write(img, "png", failedFile)
fail("Snapshot image did not match: $imgPath/$imageName.png. Run with system property -DupdateSnapshots=true to overwrite.")

val comparisonFile = File("$imgPath/$imageName.comparison.png")
ImageIO.write(result.result, "png", comparisonFile)

val message = "Snapshot image did not match: $imgPath/$imageName.png.\n" +
"A difference of ${result.differencePercentString()}% was detected\n" +
"See $imageName.failed.png and $imageName.comparison.png in the same directory for details.\n\n" +
"Run with system property -DupdateSnapshots=true to overwrite."

fail(message)
}
}
}

private fun ImageComparisonResult.differencePercentString(): String {
return "%.2f".format(100 * differencePercent)
}

private fun verifyOs() {
val osForScreenshots = (System.getProperty(ENV_SCREENSHOT_OS) ?: "").lowercase()

val os = System.getProperty("os.name").lowercase()
if (osForScreenshots.isNotEmpty()) {
Assumptions.assumeTrue(os.contains(osForScreenshots),
"Wrong OS for screenshot tests (wanted $osForScreenshots, found $os)"
Assumptions.assumeTrue(
os.contains(osForScreenshots),
"Wrong OS for screenshot tests (wanted $osForScreenshots, found $os)",
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.alyssaburlton.swingtest

import com.github.romankh3.image.comparison.ImageComparisonUtil
import io.kotest.assertions.throwables.shouldNotThrowAny
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.collections.shouldContainExactly
Expand Down Expand Up @@ -93,25 +94,32 @@ class SwingSnapshotsTest {

@Test
fun `Should fail and write out the correct comparison file`() {
val comp = makeComponent("Label A")
comp.createImageFile("Image")
val lblA = resourceAsLabel("/images/label-a.png")
lblA.createImageFile("Image")

val otherLabel = makeComponent("Label B")
val lblB = resourceAsLabel("/images/label-b.png")
val comparisonImage = ImageComparisonUtil.readImageFromResources("images/LabelComparisonResult.png")

val exception = shouldThrow<AssertionError> {
otherLabel.shouldMatchImage("Image")
lblB.shouldMatchImage("Image")
}

exception.message shouldBe "Snapshot image did not match: $resourceLocation/Image.png. Run with system property -DupdateSnapshots=true to overwrite."
exception.message shouldBe "Snapshot image did not match: $resourceLocation/Image.png.\n" +
"A difference of 6.69% was detected\n" +
"See Image.failed.png and Image.comparison.png in the same directory for details.\n\n" +
"Run with system property -DupdateSnapshots=true to overwrite."

val originalFile = File("$resourceLocation/Image.png")
val failedFile = File("$resourceLocation/Image.failed.png")
val comparisonFile = File("$resourceLocation/Image.comparison.png")

originalFile.shouldExist()
failedFile.shouldExist()
comparisonFile.shouldExist()

ImageIO.read(originalFile).isEqual(comp.toBufferedImage()) shouldBe true
ImageIO.read(failedFile).isEqual(otherLabel.toBufferedImage()) shouldBe true
ImageIO.read(originalFile).isEqual(lblA.toBufferedImage()) shouldBe true
ImageIO.read(failedFile).isEqual(lblB.toBufferedImage()) shouldBe true
ImageIO.read(comparisonFile).isEqual(comparisonImage) shouldBe true
}

@Test
Expand All @@ -128,6 +136,28 @@ class SwingSnapshotsTest {
File("$resourceLocation/Image.failed.png").shouldNotExist()
}

@Test
fun `Should have a built-in tolerance for subtle differences`() {
val dartboard1 = resourceAsLabel("/images/dartboard-1.png")
dartboard1.createImageFile("dartboard")

val dartboard2 = resourceAsLabel("/images/dartboard-2.png")
shouldNotThrowAny {
dartboard2.shouldMatchImage("dartboard")
}
}

@Test
fun `Should support precise matching`() {
val dartboard1 = resourceAsLabel("/images/dartboard-1.png")
dartboard1.createImageFile("dartboard")

val dartboard2 = resourceAsLabel("/images/dartboard-2.png")
shouldThrow<AssertionError> {
dartboard2.shouldMatchImage("dartboard", pixelTolerance = 0.0)
}
}

@Test
fun `Should write a snapshot of the correct size`() {
val labelA = JLabel("A")
Expand Down Expand Up @@ -197,6 +227,11 @@ class SwingSnapshotsTest {
}
}

private fun resourceAsLabel(resourcePath: String): JLabel {
val icon = ImageIcon(javaClass.getResource(resourcePath))
return JLabel(icon).also { it.size = Dimension(icon.iconWidth, icon.iconHeight) }
}

private fun JComponent.createImageFile(filename: String) {
System.setProperty(ENV_UPDATE_SNAPSHOT, "true")
shouldMatchImage(filename)
Expand Down
Binary file added src/test/resources/images/LabelComparisonResult.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/test/resources/images/dartboard-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/test/resources/images/dartboard-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/test/resources/images/label-a.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/test/resources/images/label-b.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit a33dec4

Please sign in to comment.