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

Commit

Permalink
Faster BlurFilter + filter optimizations, fixes and improvements (#505)
Browse files Browse the repository at this point in the history
  • Loading branch information
soywiz authored Mar 20, 2022
1 parent 1920b9c commit af62ad9
Show file tree
Hide file tree
Showing 34 changed files with 749 additions and 216 deletions.
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

0 comments on commit af62ad9

Please sign in to comment.