Unverified Commit 8d260f5a authored by Igor Demin's avatar Igor Demin Committed by GitHub

Fix CubicResampler (#176)

Made similar to https://github.com/JetBrains/skija/blob/8057d11ea2b34e92c36188236200cfef58bf8d76/shared/java/CubicResampler.java#L31

BufferedImage.toBitmap() is copied from Compose
parent 9fcf9c10
package org.jetbrains.skia
import org.jetbrains.skia.impl.NativePointer
/**
*
......@@ -25,7 +24,7 @@ import org.jetbrains.skia.impl.NativePointer
*/
class CubicResampler(internal val b: Float, internal val c: Float) : SamplingMode {
override fun _pack(): Long = ((b.toBits().toULong() shl 32) or c.toBits().toULong()).toLong()
override fun _pack(): Long = (0x8L shl 60) or ((b.toBits().toULong() shl 32) or c.toBits().toULong()).toLong()
override fun equals(o: Any?): Boolean {
if (o === this) return true
......
package org.jetbrains.skia
import org.jetbrains.skia.impl.NativePointer
class FilterMipmap constructor(
internal val filterMode: FilterMode,
internal val mipmapMode: MipmapMode = MipmapMode.NONE
......
package org.jetbrains.skiko
import org.jetbrains.skia.Bitmap
import org.jetbrains.skia.ColorAlphaType
import org.jetbrains.skia.ColorType
import org.jetbrains.skia.ImageInfo
import org.jetbrains.skia.Image
import java.awt.Transparency
import java.awt.color.ColorSpace
import java.awt.image.BufferedImage
import java.awt.image.ComponentColorModel
import java.awt.image.DataBuffer
import java.awt.image.Raster
import java.io.ByteArrayOutputStream
import java.awt.image.*
import java.nio.ByteBuffer
import javax.imageio.ImageIO
private class DirectDataBuffer(val backing: ByteBuffer): DataBuffer(TYPE_BYTE, backing.limit()) {
override fun getElem(bank: Int, index: Int): Int {
......@@ -49,12 +46,30 @@ fun Bitmap.toBufferedImage(): BufferedImage {
}
fun BufferedImage.toBitmap(): Bitmap {
return Bitmap.makeFromImage(this.toImage())
val bytesPerPixel = 4
val pixels = ByteArray(width * height * bytesPerPixel)
var k = 0
for (y in 0 until height) {
for (x in 0 until width) {
val argb = getRGB(x, y)
val a = (argb shr 24) and 0xff
val r = (argb shr 16) and 0xff
val g = (argb shr 8) and 0xff
val b = (argb shr 0) and 0xff
pixels[k++] = b.toByte()
pixels[k++] = g.toByte()
pixels[k++] = r.toByte()
pixels[k++] = a.toByte()
}
}
val bitmap = Bitmap()
bitmap.allocPixels(ImageInfo.makeS32(width, height, ColorAlphaType.UNPREMUL))
bitmap.installPixels(pixels)
return bitmap
}
fun BufferedImage.toImage(): Image {
val bos = ByteArrayOutputStream()
ImageIO.write(this, "png", bos)
val data = bos.toByteArray()
return Image.makeFromEncoded(data)
return Image.makeFromBitmap(toBitmap())
}
\ No newline at end of file
......@@ -6,8 +6,14 @@ enum class OS(val id: String) {
MacOS("macos")
;
val isLinux
get() = this == Linux
val isWindows
get() = this == Windows
val isMacOS
get() = this == MacOS
}
enum class Arch(val id: String) {
......
package org.jetbrains.skiko
import org.jetbrains.skia.*
import org.jetbrains.skiko.util.ScreenshotTestRule
import org.jetbrains.skiko.util.loadResourceImage
import org.junit.Assume.assumeTrue
import org.junit.Rule
import org.junit.Test
class PaintTest {
@get:Rule
val screenshots = ScreenshotTestRule()
@Test
fun filterQuality() {
// macOs has different results
assumeTrue(hostOs.isWindows || hostOs.isLinux)
val surface = Surface.makeRasterN32Premul(16, 16)
surface.canvas.drawImageRect(
image = loadResourceImage("test.png"),
src = Rect.makeXYWH(0f, 2f, 2f, 4f),
dst = Rect.makeXYWH(0f, 4f, 4f, 12f),
samplingMode = FilterMipmap(FilterMode.NEAREST, MipmapMode.NONE),
Paint(),
true
)
surface.canvas.drawImageRect(
image = loadResourceImage("test.png"),
src = Rect.makeXYWH(0f, 2f, 2f, 4f),
dst = Rect.makeXYWH(4f, 4f, 4f, 12f),
samplingMode = FilterMipmap(FilterMode.LINEAR, MipmapMode.NONE),
Paint(),
true
)
surface.canvas.drawImageRect(
image = loadResourceImage("test.png"),
src = Rect.makeXYWH(0f, 2f, 2f, 4f),
dst = Rect.makeXYWH(8f, 4f, 4f, 12f),
samplingMode = CubicResampler(1 / 3.0f, 1 / 3.0f),
Paint(),
true
)
screenshots.assert(surface.makeImageSnapshot())
}
}
\ No newline at end of file
package org.jetbrains.skiko.util
import org.jetbrains.skia.Image
import org.jetbrains.skia.Pixmap
import java.awt.Color
import java.awt.image.BufferedImage
import java.io.InputStream
import kotlin.math.abs
fun isContentSame(img1: BufferedImage, img2: BufferedImage, sensitivity: Double): Boolean {
fun isContentSame(img1: Image, img2: Image, sensitivity: Double): Boolean {
require(sensitivity in 0.0..1.0)
val sensitivity255 = (sensitivity * 255).toInt()
if (img1.width == img2.width && img1.height == img2.height) {
for (x in 0 until img1.width) {
for (y in 0 until img1.height) {
val color1 = Color(img1.getRGB(x, y))
val color2 = Color(img2.getRGB(x, y))
val pixMap1 = Pixmap()
val pixMap2 = Pixmap()
img1.readPixels(pixMap1, 0, 0, false)
img2.readPixels(pixMap2, 0, 0, false)
for (y in 0 until img1.height) {
for (x in 0 until img1.width) {
val color1 = Color(pixMap1.getColor(x, y))
val color2 = Color(pixMap2.getColor(x, y))
if (abs(color1.red - color2.red) > sensitivity255) {
return false
}
......@@ -30,4 +36,21 @@ fun isContentSame(img1: BufferedImage, img2: BufferedImage, sensitivity: Double)
return false
}
return true
}
\ No newline at end of file
}
fun loadResourceImage(path: String) = useResource(path, ::loadImage)
inline fun <T> useResource(
resourcePath: String,
block: (InputStream) -> T
): T = openResource(resourcePath).use(block)
fun openResource(resourcePath: String): InputStream {
val classLoader = Thread.currentThread().contextClassLoader!!
return requireNotNull(classLoader.getResourceAsStream(resourcePath)) {
"Resource $resourcePath not found"
}
}
fun loadImage(inputStream: InputStream): Image =
Image.makeFromEncoded(inputStream.readAllBytes())
package org.jetbrains.skiko.util
import org.jetbrains.skia.Image
import org.jetbrains.skiko.OS
import org.jetbrains.skiko.hostOs
import org.jetbrains.skiko.toImage
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
......@@ -41,6 +43,10 @@ class ScreenshotTestRule : TestRule {
fun assert(rectangle: Rectangle, id: String = "") {
val actual = robot.createScreenCapture(rectangle)
assert(actual.toImage(), id)
}
fun assert(actual: Image, id: String = "") {
val name = if (id.isNotEmpty()) "${testIdentifier}_$id" else testIdentifier
val actualFile = File(screenshotsDir, "${name}_actual.png")
val expectedFile = File(screenshotsDir, "$name.png")
......@@ -48,16 +54,16 @@ class ScreenshotTestRule : TestRule {
actualFile.delete()
}
if (expectedFile.exists()) {
val expected = ImageIO.read(expectedFile)
val expected = Image.makeFromEncoded(expectedFile.readBytes())
// macOs screenshots can have different color on different configurations
if (!isContentSame(expected, actual, sensitivity = 0.25)) {
ImageIO.write(actual, "png", actualFile)
actualFile.writeBytes(actual.encodeToData()!!.bytes)
throw AssertionError(
"Image mismatch! Expected image ${expectedFile.absolutePath}, actual: ${actualFile.absolutePath}"
)
}
} else {
ImageIO.write(actual, "png", actualFile)
actualFile.writeBytes(actual.encodeToData()!!.bytes)
throw AssertionError(
"Missing screenshot image " +
"${actualFile.absolutePath}. " +
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment