Created
October 2, 2019 13:10
-
-
Save vinaysshenoy/2d9968ee7e92978efa435e56b61ced8e to your computer and use it in GitHub Desktop.
Image similarity approvals for Java Approvals (https://github.com/approvals/ApprovalTests.Java)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.vinaysshenoy | |
import org.approvaltests.Approvals | |
import org.approvaltests.approvers.ApprovalApprover | |
import org.approvaltests.core.ApprovalFailureReporter | |
import org.approvaltests.core.ApprovalReporterWithCleanUp | |
import org.approvaltests.core.ApprovalWriter | |
import org.approvaltests.namer.ApprovalNamer | |
import org.approvaltests.writers.ImageApprovalWriter | |
import java.awt.image.BufferedImage | |
import java.io.File | |
import javax.imageio.ImageIO | |
class ImageApprover( | |
namer: ApprovalNamer, | |
private val writer: ApprovalWriter, | |
private val minimumSimilarity: Double | |
) : ApprovalApprover { | |
private val baseFilePath = "${namer.sourceFilePath}${namer.approvalName}" | |
private val received: File = File(writer.getReceivedFilename(baseFilePath)) | |
private val approved: File = File(writer.getApprovalFilename(baseFilePath)) | |
companion object { | |
fun create(image: BufferedImage, minimumSimilarity: Double): ImageApprover { | |
return ImageApprover( | |
namer = Approvals.createApprovalNamer(), | |
writer = ImageApprovalWriter(image), | |
minimumSimilarity = minimumSimilarity | |
) | |
} | |
} | |
override fun cleanUpAfterSuccess(reporter: ApprovalFailureReporter) { | |
received.delete() | |
(reporter as? ApprovalReporterWithCleanUp)?.cleanUp(received.absolutePath, approved.absolutePath) | |
} | |
override fun approve(): Boolean { | |
writer.writeReceivedFile(received.absolutePath) | |
if (!approved.exists() || !received.exists()) { | |
return false | |
} | |
val receivedImage = ImageIO.read(received) | |
val approvedImage = ImageIO.read(approved) | |
val similarity = ImageSimilarity.similarity(receivedImage, approvedImage) | |
return similarity >= minimumSimilarity | |
} | |
override fun reportFailure(reporter: ApprovalFailureReporter) { | |
reporter.report(received.absolutePath, approved.absolutePath) | |
} | |
override fun fail() { | |
throw Error("Failed Approval\n Approved:${approved.absolutePath}\n Received:${received.absolutePath}") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.vinaysshenoy | |
import java.awt.image.BufferedImage | |
import kotlin.math.abs | |
/** | |
* Method was copied from [RosettaCode](https://rosettacode.org/wiki/Percentage_difference_between_images#Kotlin) | |
**/ | |
object ImageSimilarity { | |
fun differenceBetween(img1: BufferedImage, img2: BufferedImage): Double { | |
val width = img1.width | |
val height = img1.height | |
val width2 = img2.width | |
val height2 = img2.height | |
if (width != width2 || height != height2) { | |
val f = "(%d,%d) vs. (%d,%d)".format(width, height, width2, height2) | |
throw IllegalArgumentException("Images must have the same dimensions: $f") | |
} | |
var diff = 0L | |
for (y in 0 until height) { | |
for (x in 0 until width) { | |
diff += pixelDiff(img1.getRGB(x, y), img2.getRGB(x, y)) | |
} | |
} | |
val maxDiff = 3L * 255 * width * height | |
return diff / maxDiff.toDouble() | |
} | |
fun similarity(img1: BufferedImage, img2: BufferedImage): Double { | |
return 1.0 - differenceBetween(img1, img2) | |
} | |
private fun pixelDiff(rgb1: Int, rgb2: Int): Int { | |
val r1 = (rgb1 shr 16) and 0xff | |
val g1 = (rgb1 shr 8) and 0xff | |
val b1 = rgb1 and 0xff | |
val r2 = (rgb2 shr 16) and 0xff | |
val g2 = (rgb2 shr 8) and 0xff | |
val b2 = rgb2 and 0xff | |
return abs(r1 - r2) + abs(g1 - g2) + abs(b1 - b2) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment