Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,46 +46,55 @@ internal class ImageDiffer {
val minWidth = min(expectedWidth, actualWidth)
val minHeight = min(expectedHeight, actualHeight)

// Create canvas for composite image (expected + delta + actual)
// Create canvas for composite image (expected + delta + actual).
val canvas = document.createElement("canvas") as HTMLCanvasElement
val ctx = canvas.getContext("2d") as CanvasRenderingContext2D
canvas.width = maxWidth * 3 // Three sections of maxWidth
canvas.width = maxWidth * 3 // Three sections of maxWidth.
canvas.height = maxHeight

// Draw expected image on the left
// Draw expected image on the left.
ctx.drawImage(expectedImage, 0.0, 0.0)
val expectedData = ctx.getImageData(0.0, 0.0, maxWidth.toDouble(), maxHeight.toDouble())

// Draw actual image on the right
// Draw actual image on the right.
ctx.drawImage(actualImage, maxWidth * 2.0, 0.0)
val actualData =
ctx.getImageData(maxWidth * 2.0, 0.0, maxWidth.toDouble(), maxHeight.toDouble())

// Create delta image data
// Create delta image data.
val deltaData = ctx.createImageData(maxWidth.toDouble(), maxHeight.toDouble())
val deltaArray = deltaData.data.asDynamic()

var differentPixels = (maxWidth.toLong() * maxHeight) - (minWidth.toLong() * minHeight)
var differentPixels = 0L
var deltaRGB = 0L
var deltaA = (differentPixels * 255)
var deltaA = 0L

// Compare pixels
for (y in 0 until minWidth) {
for (x in 0 until minHeight) {
// Compare pixels.
for (y in 0 until maxHeight) {
for (x in 0 until maxWidth) {
val i = (y * maxWidth + x) * 4

val expectedR = expectedData.data[i].toInt()
val expectedG = expectedData.data[i + 1].toInt()
val expectedB = expectedData.data[i + 2].toInt()
val expectedA = expectedData.data[i + 3].toInt()
// Check if pixel exists in image.
val hasExpected = x < expectedWidth && y < expectedHeight
val hasActual = x < actualWidth && y < actualHeight

val actualR = actualData.data[i].toInt()
val actualG = actualData.data[i + 1].toInt()
val actualB = actualData.data[i + 2].toInt()
val actualA = actualData.data[i + 3].toInt()
// Skip if neither image has a pixel at this location.
if (!hasExpected && !hasActual) {
continue
}

val expectedR = if (hasExpected) expectedData.data[i].toInt() else 0
val expectedG = if (hasExpected) expectedData.data[i + 1].toInt() else 0
val expectedB = if (hasExpected) expectedData.data[i + 2].toInt() else 0
val expectedA = if (hasExpected) expectedData.data[i + 3].toInt() else 0

// If pixels are identical, make it transparent
if (actualR == expectedR && actualG == expectedG && actualB == expectedB && actualA == expectedA) {
val actualR = if (hasActual) actualData.data[i].toInt() else 0
val actualG = if (hasActual) actualData.data[i + 1].toInt() else 0
val actualB = if (hasActual) actualData.data[i + 2].toInt() else 0
val actualA = if (hasActual) actualData.data[i + 3].toInt() else 0

// If pixels are identical, make it transparent.
if (hasExpected && hasActual && actualR == expectedR && actualG == expectedG && actualB == expectedB && actualA == expectedA) {
deltaArray[i] = expectedR
deltaArray[i + 1] = expectedG
deltaArray[i + 2] = expectedB
Expand All @@ -95,27 +104,34 @@ internal class ImageDiffer {

differentPixels++

// Visualize differences with red pixel
// Visualize differences with red pixel.
deltaArray[i] = 255
deltaArray[i + 1] = 0
deltaArray[i + 2] = 0
deltaArray[i + 3] = 255

deltaRGB += abs(actualR - expectedR).toLong()
deltaRGB += abs(actualG - expectedG).toLong()
deltaRGB += abs(actualB - expectedB).toLong()
deltaA += abs(actualA - expectedA).toLong()
// For missing pixels, treat as maximum difference.
if (!hasExpected || !hasActual) {
deltaRGB += 255L * 3
deltaA += 255L
} else {
// For actual pixel differences, use real deltas.
deltaRGB += abs(actualR - expectedR).toLong()
deltaRGB += abs(actualG - expectedG).toLong()
deltaRGB += abs(actualB - expectedB).toLong()
deltaA += abs(actualA - expectedA).toLong()
}
}
}

if (differentPixels == 0L) {
return DiffResult(isDifferent = false)
}

// Draw delta image in the middle
// Draw delta image in the middle.
ctx.putImageData(deltaData, maxWidth.toDouble(), 0.0)

// Calculate percentage difference
// Calculate percentage difference.
val totalPixels = maxHeight.toLong() * maxWidth.toLong()
val percentDifference =
(deltaRGB * 100 / (totalPixels * 3L * 255L).toDouble()).toFloat().takeIf { it != 0f }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ internal class ImageDifferTest {
/**
* Compare a 100x200 transparent image against a 200x100 transparent image. We consider any pixel
* that isn't in the bounds of the other image to be different, so these two rectangles differ by
* 75%.
* 50%.
*/
@Test
fun fullAlphaSizeMismatch() = runTest {
Expand All @@ -140,7 +140,7 @@ internal class ImageDifferTest {

val diffResult = ImageDiffer().compare(transparent200x100, transparent100x200)
assertThat(diffResult.isDifferent).isEqualTo(true)
assertThat(diffResult.percentDifference).isEqualTo(75f)
assertThat(diffResult.percentDifference).isEqualTo(50f)
}

internal suspend fun Element.toBlob(): Blob = DomSnapshotter().snapshot(this, Frame.None, false).images.first()!!
Expand Down
Loading