Skip to content

Commit

Permalink
change: add layer for backdrop filter
Browse files Browse the repository at this point in the history
  • Loading branch information
muedsa committed Jan 19, 2024
1 parent 76a2b36 commit a91fdd1
Show file tree
Hide file tree
Showing 23 changed files with 437 additions and 83 deletions.
8 changes: 5 additions & 3 deletions src/main/kotlin/com/muedsa/snapshot/Snapshot.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.muedsa.snapshot

import com.muedsa.geometry.Offset
import com.muedsa.snapshot.rendering.LayerPaintContext
import com.muedsa.snapshot.rendering.PaintingContext
import com.muedsa.snapshot.rendering.box.BoxConstraints
import com.muedsa.snapshot.widget.SingleWidgetBuilder
Expand Down Expand Up @@ -34,9 +35,10 @@ class Snapshot(
val surface =
Surface.makeRaster(ImageInfo.makeN32Premul(ceil(rootSize.width).toInt(), ceil(rootSize.height).toInt()))
surface.canvas.clear(background)
val context = PaintingContext(bounds = Offset.ZERO combine rootSize, debug = debug)
context.paintChild(rootRenderBox, Offset.ZERO)
surface.canvas.drawPicture(context.stopRecord())
val context =
PaintingContext.paintRoot(bounds = Offset.ZERO combine rootSize, renderBox = rootRenderBox, debug = debug)
val layerPaintContext = LayerPaintContext(canvas = surface.canvas)
context.containerLayer.paint(layerPaintContext)
surface.flush()
image = surface.makeImageSnapshot()
surface.close()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.muedsa.snapshot.rendering

import org.jetbrains.skia.BlendMode
import org.jetbrains.skia.ImageFilter
import org.jetbrains.skia.Paint
import org.jetbrains.skia.Rect

class BackdropFilterLayer(
val bounds: Rect,
val filter: ImageFilter,
val blendMode: BlendMode = BlendMode.SRC_OVER,
) : ContainerLayer() {

override fun paint(context: LayerPaintContext) {
context.pictures.lastOrNull()?.also {
context.canvas.drawPicture(it, paint = Paint().apply {
this@apply.imageFilter = this@BackdropFilterLayer.filter
this@apply.blendMode = this@BackdropFilterLayer.blendMode
})
}
super.paint(context)
}
}
17 changes: 17 additions & 0 deletions src/main/kotlin/com/muedsa/snapshot/rendering/ClipPathLayer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.muedsa.snapshot.rendering

import org.jetbrains.skia.Path

class ClipPathLayer(
val clipPath: Path,
val clipBehavior: ClipBehavior = ClipBehavior.ANTI_ALIAS,
) : ContainerLayer() {


override fun paint(context: LayerPaintContext) {
context.canvas.save()
context.canvas.clipPath(clipPath, antiAlias = clipBehavior == ClipBehavior.ANTI_ALIAS)
super.paint(context)
context.canvas.restore()
}
}
17 changes: 17 additions & 0 deletions src/main/kotlin/com/muedsa/snapshot/rendering/ClipRRectLayer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.muedsa.snapshot.rendering

import org.jetbrains.skia.RRect

class ClipRRectLayer(
val clipRRect: RRect,
val clipBehavior: ClipBehavior = ClipBehavior.HARD_EDGE,
) : ContainerLayer() {


override fun paint(context: LayerPaintContext) {
context.canvas.save()
context.canvas.clipRRect(clipRRect, antiAlias = clipBehavior == ClipBehavior.ANTI_ALIAS)
super.paint(context)
context.canvas.restore()
}
}
17 changes: 17 additions & 0 deletions src/main/kotlin/com/muedsa/snapshot/rendering/ClipRectLayer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.muedsa.snapshot.rendering

import org.jetbrains.skia.Rect

class ClipRectLayer(
val clipRect: Rect,
val clipBehavior: ClipBehavior = ClipBehavior.HARD_EDGE,
) : ContainerLayer() {


override fun paint(context: LayerPaintContext) {
context.canvas.save()
context.canvas.clipRect(clipRect, antiAlias = clipBehavior == ClipBehavior.ANTI_ALIAS)
super.paint(context)
context.canvas.restore()
}
}
18 changes: 18 additions & 0 deletions src/main/kotlin/com/muedsa/snapshot/rendering/ContainerLayer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.muedsa.snapshot.rendering

open class ContainerLayer : Layer() {

private val _children: MutableList<Layer> = mutableListOf()
val children: List<Layer> = _children

override fun paint(context: LayerPaintContext) {
children.forEach {
it.paint(context)
}
}

fun append(child: Layer) {
_children.add(child)
child.parent = this
}
}
8 changes: 8 additions & 0 deletions src/main/kotlin/com/muedsa/snapshot/rendering/Layer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.muedsa.snapshot.rendering

abstract class Layer {

var parent: Layer? = null

abstract fun paint(context: LayerPaintContext)
}
10 changes: 10 additions & 0 deletions src/main/kotlin/com/muedsa/snapshot/rendering/LayerPaintContext.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.muedsa.snapshot.rendering

import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Picture

class LayerPaintContext(
val canvas: Canvas,
) {
val pictures: MutableList<Picture> = mutableListOf()
}
186 changes: 124 additions & 62 deletions src/main/kotlin/com/muedsa/snapshot/rendering/PaintingContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,93 +6,165 @@ import com.muedsa.geometry.shift
import com.muedsa.snapshot.rendering.box.RenderBox
import org.jetbrains.skia.*

class PaintingContext(
val bounds: Rect,
class PaintingContext private constructor(
val containerLayer: ContainerLayer,
val estimatedBounds: Rect,
var debug: Boolean = false,
) : ClipContext() {

override val canvas: Canvas
get() = recorderCanvas

private lateinit var recorder: PictureRecorder
get() {
if (recorderCanvas == null) {
startRecording()
}
assert(currentLayer != null)
return recorderCanvas!!
}

private lateinit var recorderCanvas: Canvas
fun paintChild(child: RenderBox, offset: Offset) {
child.paint(this, offset)
if (debug) {
child.debugPaint(this, offset)
}
}

init {
record()
protected fun appendLayer(layer: Layer) {
assert(!isRecording)
containerLayer.append(layer)
}

fun stopRecord(): Picture = recorder.finishRecordingAsPicture()

fun record() {
private var currentLayer: PictureLayer? = null
private var recorder: PictureRecorder? = null
private var recorderCanvas: Canvas? = null

val isRecording: Boolean
get() {
val hasCanvas: Boolean = recorderCanvas != null
if (hasCanvas) {
assert(currentLayer != null)
assert(recorder != null)
assert(recorderCanvas != null)
} else {
assert(currentLayer == null)
assert(recorder == null)
assert(recorderCanvas == null)
}
return hasCanvas
}


protected fun startRecording() {
assert(!isRecording)
currentLayer = PictureLayer()
recorder = PictureRecorder()
recorderCanvas = recorder.beginRecording(bounds)
recorderCanvas = recorder!!.beginRecording(estimatedBounds)
containerLayer.append(currentLayer!!)
}

protected fun stopRecordingIfNeeded() {
if (!isRecording) {
return
}

// todo debug paint

fun paintChild(child: RenderBox, offset: Offset) {
child.paint(this, offset)
if (debug) {
child.debugPaint(this, offset)
}
currentLayer!!.picture = recorder!!.finishRecordingAsPicture()
currentLayer = null
recorder = null
recorderCanvas = null
}

fun doClipPath(
fun addLayer(layer: Layer) {
stopRecordingIfNeeded()
appendLayer(layer)
}

fun pushLayer(
childLayer: ContainerLayer,
painter: (PaintingContext, Offset) -> Unit,
offset: Offset,
childPaintBounds: Rect? = null,
) {
stopRecordingIfNeeded()
appendLayer(childLayer)
val childContext: PaintingContext = createChildContext(childLayer, childPaintBounds ?: estimatedBounds)
painter(childContext, offset)
childContext.stopRecordingIfNeeded()
}

protected fun createChildContext(childLayer: ContainerLayer, bounds: Rect): PaintingContext {
return PaintingContext(childLayer, bounds)
}

fun pushClipPath(
offset: Offset,
bounds: Rect,
clipPath: Path,
clipBehavior: ClipBehavior = ClipBehavior.ANTI_ALIAS,
painter: (PaintingContext, Offset) -> Unit,
) {
): ClipPathLayer? {
if (clipBehavior == ClipBehavior.NONE) {
painter(this, offset)
return
return null
}
val offsetBounds: Rect = bounds.shift(offset)
val offsetClipPath: Path = Path().also {
clipPath.offset(offset.x, offset.y, it)
}
clipPathAndPaint(offsetClipPath, clipBehavior, offsetBounds) {
painter(this, offset)
}
val layer = ClipPathLayer(
clipPath = offsetClipPath,
clipBehavior = clipBehavior
)
pushLayer(layer, painter, offset, childPaintBounds = offsetBounds)
return layer
}

fun doClipRect(
fun pushClipRect(
offset: Offset,
clipRect: Rect,
clipBehavior: ClipBehavior = ClipBehavior.HARD_EDGE,
painter: (PaintingContext, Offset) -> Unit,
) {
): ClipRectLayer? {
if (clipBehavior == ClipBehavior.NONE) {
painter(this, offset)
return
return null
}
val offsetClipRect: Rect = clipRect.shift(offset)
clipRectAndPaint(offsetClipRect, clipBehavior, offsetClipRect) {
painter(this, offset)
}
val layer = ClipRectLayer(
clipRect = offsetClipRect,
clipBehavior = clipBehavior
)
pushLayer(layer, painter, offset, childPaintBounds = offsetClipRect)
return layer
}

fun doClipRRect(
fun pushClipRRect(
offset: Offset,
bounds: Rect,
clipRRect: RRect,
clipBehavior: ClipBehavior = ClipBehavior.ANTI_ALIAS,
painter: (PaintingContext, Offset) -> Unit,
) {
): ClipRRectLayer? {
if (clipBehavior == ClipBehavior.NONE) {
painter(this, offset)
return
return null
}
val offsetBounds: Rect = bounds.shift(offset)
val offsetClipRRect: RRect = clipRRect.shift(offset)
clipRRectAndPaint(offsetClipRRect, clipBehavior, offsetBounds) {
painter(this, offset)
}
val layer = ClipRRectLayer(
clipRRect = offsetClipRRect,
clipBehavior = clipBehavior
)
pushLayer(layer, painter, offset, childPaintBounds = offsetBounds)
return layer
}

fun doTransform(offset: Offset, transform: Matrix44CMO, painter: (PaintingContext, Offset) -> Unit) {
fun pushTransform(
offset: Offset,
transform: Matrix44CMO,
painter: (PaintingContext, Offset) -> Unit,
): TransformLayer {
val effectiveTransform: Matrix44CMO = Matrix44CMO.translationValues(
x = offset.x,
y = offset.y,
Expand All @@ -101,34 +173,24 @@ class PaintingContext(
multiply(transform)
translate(-offset.x, -offset.y)
}
canvas.save()
// skia的Matrix44为行顺序
canvas.concat(effectiveTransform.toRMO())
painter(this, offset)
canvas.restore()
val layer = TransformLayer(
transform = effectiveTransform
)
pushLayer(layer, painter, offset, childPaintBounds = estimatedBounds)
return layer
}

fun pushBackdrop(
offset: Offset,
clipRect: Rect,
imageFilter: ImageFilter,
blendMode: BlendMode = BlendMode.SRC_OVER,
) {
val rect = clipRect.shift(offset)
val picture = stopRecord()
record()
canvas.clipRect(rect)
canvas.saveLayer(bounds = bounds, paint = Paint().apply {
this@apply.imageFilter = imageFilter
this@apply.blendMode = blendMode
this@apply.isAntiAlias = true
})
canvas.drawPicture(picture)
canvas.restore() // saveLayer
canvas.restore() // clipRect
val backdropPicture = stopRecord()
record()
canvas.drawPicture(picture = picture)
canvas.drawPicture(picture = backdropPicture)
companion object {

fun paintRoot(bounds: Rect, renderBox: RenderBox, debug: Boolean = false): PaintingContext {
val paintingContext = PaintingContext(
containerLayer = ContainerLayer(),
estimatedBounds = bounds,
debug = debug
)
paintingContext.paintChild(renderBox, Offset.ZERO)
paintingContext.stopRecordingIfNeeded()
return paintingContext
}
}
}
Loading

0 comments on commit a91fdd1

Please sign in to comment.