Skip to content
This repository has been archived by the owner on Jul 8, 2022. It is now read-only.

Faster BlurFilter + filter optimizations, fixes and improvements #505

Merged
merged 1 commit into from
Mar 20, 2022
Merged
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
Binary file added e2e-test/references/FiltersE2ETestCase.alt3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 1 addition & 4 deletions korge-sandbox/src/commonMain/kotlin/FiltersSample.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ import com.soywiz.klock.*
import com.soywiz.korge.time.*
import com.soywiz.korge.view.*
import com.soywiz.korge.view.filter.*
import com.soywiz.korim.bitmap.*
import com.soywiz.korim.color.*
import com.soywiz.korim.format.*
import com.soywiz.korio.async.*
import com.soywiz.korio.file.std.*
import com.soywiz.korio.lang.*

Expand Down Expand Up @@ -51,7 +48,7 @@ object FiltersE2ETestCase : E2ETestCase() {
val bitmap = resourcesVfs["korge.png"].readBitmap().toBMP32().premultiplied()
println("PREPARING VIEWS...")
image(bitmap).scale(.5).position(0, 0).addFilter(WaveFilter(time = 0.5.seconds))
image(bitmap).scale(.5).position(256, 0).addFilter(BlurFilter(initialRadius = 6.0))
image(bitmap).scale(.5).position(256, 0).addFilter(BlurFilter(radius = 6.0))
//image(bitmap).scale(.5).position(256, 0).addFilter(BlurFilter(initialRadius = 4.0))
image(bitmap).scale(.5).position(512, 0).addFilter(TransitionFilter(TransitionFilter.Transition.SWEEP, reversed = false, smooth = true, ratio = 0.5))
image(bitmap).scale(.5).position(0, 256).addFilter(PageFilter(hratio = 0.5, hamplitude1 = 20.0))
Expand Down
4 changes: 3 additions & 1 deletion korge-sandbox/src/commonMain/kotlin/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ import kotlin.random.*
suspend fun main() = Korge(bgcolor = Colors.DARKCYAN.mix(Colors.BLACK, 0.8), clipBorders = false
//, debugAg = true
) {
mainCustomSolidRectShader()
//mainFiltersRenderToBitmap()
mainBlur()
//mainCustomSolidRectShader()
//mainMipmaps()
//mainColorTransformFilter()
//mainExifTest()
Expand Down
160 changes: 160 additions & 0 deletions korge-sandbox/src/commonMain/kotlin/MainBlur.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import com.soywiz.kmem.*
import com.soywiz.kmem.clamp
import com.soywiz.korge.input.*
import com.soywiz.korge.ui.*
import com.soywiz.korge.view.*
import com.soywiz.korge.view.filter.*
import com.soywiz.korim.color.*
import com.soywiz.korim.format.*
import com.soywiz.korim.text.*
import com.soywiz.korio.async.*
import com.soywiz.korio.file.std.*
import com.soywiz.korma.geom.*
import com.soywiz.korma.math.*

suspend fun Stage.mainBlur() {
solidRect(stage.width, stage.height, Colors.WHITE)
val bitmap = resourcesVfs["korim.png"].readBitmap()

val initialBlur = 6.0
var filterScale = 1.0

val blur0a = DirectionalBlurFilter(angle = 0.degrees, radius = initialBlur)
val blur0b = DirectionalBlurFilter(angle = 90.degrees, radius = initialBlur)
val blur0c = DirectionalBlurFilter(angle = 45.degrees, radius = initialBlur)
val blur1 = BlurFilter(initialBlur)
//val blur1 = DirectionalBlurFilter(angle = 0.degrees, radius = 32.0)
//val blur1 = DirectionalBlurFilter(angle = 90.degrees, radius = 32.0)
val blur2 = OldBlurFilter(initialBlur)

val image0b = image(bitmap).xy(700, 100).filters(blur0b)
val image0a = image(bitmap).xy(700, 400).filters(blur0a)
val image0c = image(bitmap).xy(900, 100).filters(blur0c)

val image1 = image(bitmap)
.xy(100, 100)
.filters(blur1)

val rotatedBitmap = image(bitmap)
.xy(150, 300)
.scale(0.75)
.anchor(Anchor.CENTER)
.rotation(45.degrees)
.filters(blur1)

val image2 = image(bitmap)
//solidRect(128, 128, Colors.RED)
.xy(300, 100)
.filters(blur2)
//.visible(false)

val dropshadowFilter = DropshadowFilter(blurRadius = 1.0, shadowColor = Colors.RED.withAd(0.3))
val image3 = image(bitmap).xy(500, 100).filters(dropshadowFilter)

val colorMatrixFilter = ColorMatrixFilter(ColorMatrixFilter.SEPIA_MATRIX, blendRatio = 0.5)
val image4 = image(bitmap).xy(500, 250).filters(colorMatrixFilter)

val transitionFilter = TransitionFilter(TransitionFilter.Transition.CIRCULAR, reversed = false, ratio = 0.5)
val image4b = image(bitmap).xy(370, 250).filters(transitionFilter)

val pageFilter = PageFilter()
val image5 = image(bitmap).xy(500, 450).filters(pageFilter)

val waveFilter = WaveFilter()
val image6 = image(bitmap).xy(500, 600).filters(waveFilter)

val flagFilter = FlagFilter()
val image7 = image(bitmap).xy(700, 600).filters(flagFilter)

val image8 = image(bitmap).xy(900, 600).filters(blur1, waveFilter, blur1, pageFilter)

uiVerticalStack(padding = 2.0, width = 370.0) {
xy(50, 400)
uiHorizontalFill {
uiText("Blur radius").apply { textColor = Colors.BLACK }
uiSlider(value = initialBlur, max = 32, step = 0.1).changed { blur1.radius = it.toDouble() }
}
uiHorizontalFill {
uiText("Drop radius").apply { textColor = Colors.BLACK }
uiSlider(value = dropshadowFilter.blurRadius.toInt(), max = 32).changed { dropshadowFilter.blurRadius = it.toDouble() }
}
uiHorizontalFill {
uiText("Drop X").apply { textColor = Colors.BLACK }
uiSlider(value = dropshadowFilter.dropX.toInt(), min = -32, max = +32).changed { dropshadowFilter.dropX = it.toDouble() }
}
uiHorizontalFill {
uiText("Drop Y").apply { textColor = Colors.BLACK }
uiSlider(value = dropshadowFilter.dropY.toInt(), min = -32, max = +32).changed { dropshadowFilter.dropY = it.toDouble() }
}
uiHorizontalFill {
uiButton("black").clicked { dropshadowFilter.shadowColor = Colors.BLACK.withAd(dropshadowFilter.shadowColor.ad) }
uiButton("red").clicked { dropshadowFilter.shadowColor = Colors.RED.withAd(dropshadowFilter.shadowColor.ad) }
uiButton("green").clicked { dropshadowFilter.shadowColor = Colors.GREEN.withAd(dropshadowFilter.shadowColor.ad) }
uiButton("blue").clicked { dropshadowFilter.shadowColor = Colors.BLUE.withAd(dropshadowFilter.shadowColor.ad) }
}
uiHorizontalFill {
uiText("Drop Alpha").apply { textColor = Colors.BLACK }
uiSlider(value = dropshadowFilter.shadowColor.a, min = 0, max = 255).changed { dropshadowFilter.shadowColor = dropshadowFilter.shadowColor.withA(it.toInt()) }
}
uiHorizontalFill {
uiText("Rotation").apply { textColor = Colors.BLACK }
uiSlider(value = rotatedBitmap.rotation.degrees.toInt(), min = 0, max = 360).changed { rotatedBitmap.rotation = it.degrees }
}
uiHorizontalFill {
uiButton("circular").clicked { transitionFilter.transition = TransitionFilter.Transition.CIRCULAR }
uiButton("diagonal1").clicked { transitionFilter.transition = TransitionFilter.Transition.DIAGONAL1 }
uiButton("diagonal2").clicked { transitionFilter.transition = TransitionFilter.Transition.DIAGONAL2 }
uiButton("sweep").clicked { transitionFilter.transition = TransitionFilter.Transition.SWEEP }
uiButton("horizontal").clicked { transitionFilter.transition = TransitionFilter.Transition.HORIZONTAL }
uiButton("vertical").clicked { transitionFilter.transition = TransitionFilter.Transition.VERTICAL }
}
uiHorizontalFill {
uiText("Blend").apply { textColor = Colors.BLACK }
uiSlider(value = 0.5, min = 0.0, max = 1.0, step = 0.1).changed {
colorMatrixFilter.blendRatio = it
pageFilter.hamplitude0 = it
transitionFilter.ratio = it
pageFilter.hratio = it
waveFilter.timeSeconds = it
flagFilter.timeSeconds = it
}
}
uiHorizontalFill {
uiText("Filter Scale").apply { textColor = Colors.BLACK }
uiSlider(value = 1.0, min = 0.2, max = 2.0, step = 0.1).changed {
filterScale = it
}
}
}

addUpdater {
//blur2.radius = blur1.radius
blur2.radius = blur1.radius / 2.0
blur0a.radius = blur1.radius
blur0b.radius = blur1.radius
blur0c.radius = blur1.radius

image0a.filterScale = filterScale
image0b.filterScale = filterScale
image0c.filterScale = filterScale
image1.filterScale = filterScale
rotatedBitmap.filterScale = filterScale
image2.filterScale = filterScale
image3.filterScale = filterScale
image4.filterScale = filterScale
image5.filterScale = filterScale
image6.filterScale = filterScale
image4b.filterScale = filterScale
image7.filterScale = filterScale
image8.filterScale = filterScale

//println(blur1.radius)
}

/*
while (true) {
tween(blur1::radius[0.0], time = 1.seconds)
tween(blur1::radius[32.0], time = 1.seconds)
}
*/
}
2 changes: 1 addition & 1 deletion korge-sandbox/src/commonMain/kotlin/MainExifTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ suspend fun Stage.mainExifTest() {
val info = file.readBitmapInfo()
image(file.readBitmapSliceWithOrientation())
.scale(0.2)
.filters(BlurFilter())
.filters(OldBlurFilter())
//println(info)
}

6 changes: 2 additions & 4 deletions korge-sandbox/src/commonMain/kotlin/MainFilterScale.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ suspend fun Stage.mainFilterScale() {
//.filters(BlurFilter())
//.filters(WaveFilter())

val combo = uiComboBox(items = listOf(0.0, 0.01, 0.05, 0.075, 0.125, 0.25, 0.44, 0.5, 0.75, 0.95, 0.99, 1.0)).xy(400, 100)
combo.onSelectionUpdate {
image.filterScale = it.selectedItem ?: 1.0
}
val combo = uiSlider(value = 1.0, max = 1.0, step = 0.01).xy(400, 100).changed { image.filterScale = it }
//val combo = uiComboBox(items = listOf(0.0, 0.01, 0.05, 0.075, 0.125, 0.25, 0.44, 0.5, 0.75, 0.95, 0.99, 1.0)).xy(400, 100).onSelectionUpdate { image.filterScale = it.selectedItem ?: 1.0 }

// This reproduces a bug (black right and bottom border) at least on macOS with M1
image.filterScale = 0.99
Expand Down
27 changes: 27 additions & 0 deletions korge-sandbox/src/commonMain/kotlin/MainFiltersRenderToBitmap.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import com.soywiz.klock.*
import com.soywiz.korge.view.*
import com.soywiz.korge.view.filter.*
import com.soywiz.korim.format.*
import com.soywiz.korio.file.std.*

suspend fun Stage.mainFiltersRenderToBitmap() {
println("LOADING IMAGE...")
val bitmap = resourcesVfs["korge.png"].readBitmap()
val container = FixedSizeContainer(width, height).apply {
//scale(2.0, 2.0)
println("PREPARING VIEWS...")
image(bitmap).scale(.5).position(0, 0).addFilter(WaveFilter(time = 0.5.seconds))
//image(bitmap).scale(.5).position(256, 0).addFilter(DirectionalBlurFilter(radius = 32.0))
image(bitmap).scale(.5).position(256, 0).addFilter(BlurFilter(radius = 32.0))
image(bitmap).scale(.5).position(512, 0).addFilter(TransitionFilter(TransitionFilter.Transition.SWEEP, reversed = false, smooth = true, ratio = 0.5))
image(bitmap).scale(.5).position(0, 256).addFilter(PageFilter(hratio = 0.5, hamplitude1 = 20.0))
image(bitmap).scale(.5).position(256, 256).addFilter(Convolute3Filter(Convolute3Filter.KERNEL_SHARPEN))
image(bitmap).scale(.5).position(512, 256).addFilter(SwizzleColorsFilter("bgga"))
println("VIEWS PREPARED")
}

//image(stage.renderToBitmap(views)).scale(0.4).xy(800, 50)
addChild(container)
image(container.renderToBitmap(views)).scale(1.0).xy(50, 50)
//container.visible = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ class RenderContext constructor(
/** Pool of [Point] objects that could be used temporarily by renders */
val pointPool = Pool(reset = { it.setTo(0, 0) }, preallocate = 8) { Point() }

val tempMargin: MutableMarginInt = MutableMarginInt()
val tempMatrix: Matrix = Matrix()

val identityMatrix = Matrix()

/**
* Allows to toggle whether stencil-based masks are enabled or not.
*/
Expand Down
19 changes: 15 additions & 4 deletions korge/src/commonMain/kotlin/com/soywiz/korge/render/Texture.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ class Texture(
/** Wether the texture is multiplied or not */
val premultiplied get() = base.premultiplied
/** Left position of the region of the texture in pixels */
val x = left
val x: Int get() = left
/** Top position of the region of the texture in pixels */
val y = top
val y: Int get() = top
/** Width of this texture region in pixels */
override val width = right - left
override val width: Int get() = right - left
/** Height of this texture region in pixels */
override val height = bottom - top
override val height: Int get() = bottom - top

/** Left coord of the texture region as a ratio (a value between 0 and 1) */
val x0: Float = (left).toFloat() / base.width.toFloat()
Expand Down Expand Up @@ -105,6 +105,14 @@ class Texture(
return Texture(base, tleft, ttop, tright, tbottom)
}

fun sliceBoundsUnclamped(left: Int, top: Int, right: Int, bottom: Int): Texture {
val tleft = (this.x + left)
val tright = (this.x + right)
val ttop = (this.y + top)
val tbottom = (this.y + bottom)
return Texture(base, tleft, ttop, tright, tbottom)
}

companion object {
/**
* Creates a [Texture] from a texture [agBase] and its wanted size [width], [height].
Expand All @@ -126,6 +134,9 @@ class Texture(
override fun close() = base.close()

override fun toString(): String = "Texture($base, (x=$x, y=$y, width=$width, height=$height))"

fun xcoord(x: Int): Float = (this.x + x).toFloat() / base.width.toFloat()
fun ycoord(y: Int): Float = (this.y + y).toFloat() / base.height.toFloat()
}

//suspend fun VfsFile.readTexture(ag: AG, imageFormats: ImageFormats, mipmaps: Boolean = true): Texture {
Expand Down
35 changes: 12 additions & 23 deletions korge/src/commonMain/kotlin/com/soywiz/korge/view/View.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package com.soywiz.korge.view
import com.soywiz.kds.*
import com.soywiz.kds.iterators.*
import com.soywiz.klock.*
import com.soywiz.kmem.*
import com.soywiz.korev.*
import com.soywiz.korge.baseview.*
import com.soywiz.korge.component.*
Expand Down Expand Up @@ -844,44 +845,29 @@ abstract class View internal constructor(

/** Usually a value between [0.0, 1.0] */
var filterScale: Double = 1.0
set(value) {
field = value.clamp(0.03125, 1.5)
}

fun renderFiltered(ctx: RenderContext, filter: Filter) {
val bounds = getLocalBoundsOptimizedAnchored()

val borderEffect = filter.border
ctx.matrixPool.alloc { tempMat2d ->
val tryFilterScale = filterScale
val texWidthNoBorder = (bounds.width * tryFilterScale).toInt().coerceAtLeast(1)
val texHeightNoBorder = (bounds.height * tryFilterScale).toInt().coerceAtLeast(1)

val realFilterScale = (texWidthNoBorder.toDouble() / bounds.width)

val texWidth = texWidthNoBorder + borderEffect * 2
val texHeight = texHeightNoBorder + borderEffect * 2
val texWidth = texWidthNoBorder
val texHeight = texHeightNoBorder

val addx = -bounds.x + borderEffect
val addy = -bounds.y + borderEffect
val addx = -bounds.x
val addy = -bounds.y

//println("FILTER: $texWidth, $texHeight : $globalMatrixInv, $globalMatrix, addx=$addx, addy=$addy, renderColorAdd=$renderColorAdd, renderColorMulInt=$renderColorMulInt, blendMode=$blendMode")
//println("FILTER($this): $texWidth, $texHeight : bounds=${bounds} addx=$addx, addy=$addy, renderColorAdd=$renderColorAdd, renderColorMul=$renderColorMul, blendMode=$blendMode")

/*
run {
val bmp = ctx.renderToBitmap(texWidth, texHeight) {
tempMat2d.copyFrom(globalMatrixInv)
tempMat2d.translate(addx, addy)
//println("globalMatrixInv:$globalMatrixInv, tempMat2d=$tempMat2d")
//println("texWidth=$texWidth, texHeight=$texHeight, $bounds, addx=$addx, addy=$addy, globalMatrix=$globalMatrix, globalMatrixInv:$globalMatrixInv, tempMat2d=$tempMat2d")
ctx.batch.setViewMatrixTemp(tempMat2d) {
renderInternal(ctx)
}
}
com.soywiz.korio.async.launchImmediately(ctx.coroutineContext) {
bmp.writeTo("/tmp/bitmap.png".uniVfs, PNG)
}
}
*/

ctx.renderToTexture(texWidth, texHeight, render = {
tempMat2d.copyFrom(globalMatrixInv)
//tempMat2d.copyFrom(globalMatrix)
Expand All @@ -891,9 +877,11 @@ abstract class View internal constructor(
//println("texWidth=$texWidth, texHeight=$texHeight, $bounds, addx=$addx, addy=$addy, globalMatrix=$globalMatrix, globalMatrixInv:$globalMatrixInv, tempMat2d=$tempMat2d")
@Suppress("DEPRECATION")
ctx.batch.setViewMatrixTemp(tempMat2d) {
// @TODO: Set blendMode to normal, colorMul to WHITE, colorAdd to NEUTRAL
renderInternal(ctx)
}
}) { texture ->
//println("texWidthHeight=$texWidth,$texHeight")
tempMat2d.copyFrom(globalMatrix)
tempMat2d.pretranslate(-addx, -addy)
tempMat2d.prescale(1.0 / realFilterScale)
Expand All @@ -905,7 +893,8 @@ abstract class View internal constructor(
texHeight,
renderColorAdd,
renderColorMul,
blendMode
blendMode,
realFilterScale
)
}
}
Expand Down
Loading