From dee0d83270badb8b44b07cb51bbb3a1a5092a7aa Mon Sep 17 00:00:00 2001 From: Luigi Rosso Date: Tue, 26 Nov 2019 16:40:01 -0800 Subject: [PATCH] Getting drop shadow, blur, and inner shadows working. --- flare_dart/lib/actor_component.dart | 14 +- .../lib/actor_layer_effect_renderer.dart | 29 +++ flare_dart/lib/actor_node.dart | 30 ++- flare_flutter/lib/flare.dart | 204 ++++++++++++++++-- 4 files changed, 258 insertions(+), 19 deletions(-) diff --git a/flare_dart/lib/actor_component.dart b/flare_dart/lib/actor_component.dart index 0eaf830..12ea89d 100644 --- a/flare_dart/lib/actor_component.dart +++ b/flare_dart/lib/actor_component.dart @@ -4,7 +4,19 @@ import "stream_reader.dart"; abstract class ActorComponent { String _name = "Unnamed"; - ActorNode parent; + ActorNode _parent; + ActorNode get parent => _parent; + set parent(ActorNode value) { + if (_parent == value) { + return; + } + ActorNode from = _parent; + _parent = value; + onParentChanged(from, value); + } + + void onParentChanged(ActorNode from, ActorNode to) {} + ActorArtboard artboard; int _parentIdx = 0; int idx = 0; diff --git a/flare_dart/lib/actor_layer_effect_renderer.dart b/flare_dart/lib/actor_layer_effect_renderer.dart index 5f74903..c48602c 100644 --- a/flare_dart/lib/actor_layer_effect_renderer.dart +++ b/flare_dart/lib/actor_layer_effect_renderer.dart @@ -1,7 +1,12 @@ +import 'package:flare_dart/actor_drop_shadow.dart'; +import 'package:flare_dart/actor_node.dart'; import 'package:flare_dart/math/aabb.dart'; import 'actor_artboard.dart'; +import 'actor_blur.dart'; +import 'actor_component.dart'; import 'actor_drawable.dart'; +import 'actor_inner_shadow.dart'; import 'actor_mask.dart'; class ActorLayerEffectRendererMask { @@ -15,6 +20,13 @@ class ActorLayerEffectRenderer extends ActorDrawable { List get drawables => _drawables; final List _renderMasks = []; List get renderMasks => _renderMasks; + ActorBlur _blur; + List _dropShadows; + List _innerShadows; + + ActorBlur get blur => _blur; + List get dropShadows => _dropShadows; + List get innerShadows => _innerShadows; bool addDrawable(ActorDrawable drawable) { if (_drawables.contains(drawable)) { @@ -31,6 +43,13 @@ class ActorLayerEffectRenderer extends ActorDrawable { .sort((ActorDrawable a, ActorDrawable b) => a.drawOrder - b.drawOrder); } + @override + void onParentChanged(ActorNode from, ActorNode to) { + from?.findLayerEffect(); + to?.findLayerEffect(); + findEffects(); + } + @override int get blendModeId { return 0; @@ -52,6 +71,15 @@ class ActorLayerEffectRenderer extends ActorDrawable { return instanceNode; } + void findEffects() { + var blurs = parent.children.whereType(); + _blur = blurs.isNotEmpty ? blurs.first : null; + _dropShadows = + parent.children.whereType().toList(growable: false); + _innerShadows = + parent.children.whereType().toList(growable: false); + } + @override void completeResolve() { super.completeResolve(); @@ -68,6 +96,7 @@ class ActorLayerEffectRenderer extends ActorDrawable { }); sortDrawables(); computeMasks(); + findEffects(); } void computeMasks() { diff --git a/flare_dart/lib/actor_node.dart b/flare_dart/lib/actor_node.dart index efdeae5..5153f67 100644 --- a/flare_dart/lib/actor_node.dart +++ b/flare_dart/lib/actor_node.dart @@ -1,3 +1,5 @@ +import 'package:flare_dart/actor_layer_effect_renderer.dart'; + import "actor_artboard.dart"; import "actor_component.dart"; import "actor_constraint.dart"; @@ -30,6 +32,7 @@ class ActorNode extends ActorComponent { Vec2D _scale = Vec2D.fromValues(1.0, 1.0); double _opacity = 1.0; double _renderOpacity = 1.0; + ActorLayerEffectRenderer _layerEffect; bool _overrideWorldTransform = false; bool _isCollapsedVisibility = false; @@ -164,6 +167,25 @@ class ActorNode extends ActorComponent { return _renderOpacity; } + double get childOpacity { + return _layerEffect == null ? _renderOpacity : 1; + } + + // Helper that looks for layer effect, this is only called by + // ActorLayerEffectRenderer when the parent changes. This keeps it efficient + // so not every ActorNode has to look for layerEffects as most won't have it. + void findLayerEffect() { + var layerEffects = children?.whereType(); + var change = layerEffects != null && layerEffects.isNotEmpty + ? layerEffects.first + : null; + if (_layerEffect != change) { + _layerEffect = change; + // Force update the opacity. + markTransformDirty(); + } + } + bool get renderCollapsed { return _renderCollapsed; } @@ -222,7 +244,7 @@ class ActorNode extends ActorComponent { if (parent != null) { _renderCollapsed = _isCollapsedVisibility || parent._renderCollapsed; - _renderOpacity *= parent._renderOpacity; + _renderOpacity *= parent.childOpacity; if (!_overrideWorldTransform) { Mat2D.multiply(_worldTransform, parent._worldTransform, _transform); } @@ -260,9 +282,13 @@ class ActorNode extends ActorComponent { return node; } + void removeChild(ActorComponent component) { + _children?.remove(component); + } + void addChild(ActorComponent component) { if (component.parent != null) { - component.parent._children.remove(component); + component.parent.removeChild(component); } component.parent = this; _children ??= []; diff --git a/flare_flutter/lib/flare.dart b/flare_flutter/lib/flare.dart index 79cc82e..2ebc4fa 100644 --- a/flare_flutter/lib/flare.dart +++ b/flare_flutter/lib/flare.dart @@ -7,6 +7,7 @@ import 'dart:ui' as ui; import 'package:flare_dart/actor_flags.dart'; import 'package:flare_dart/actor_image.dart'; +import 'package:flare_dart/actor_mask.dart'; import 'package:flare_dart/math/aabb.dart'; import 'package:flutter/services.dart'; @@ -1308,12 +1309,134 @@ class FlutterActorLayerEffectRenderer extends ActorLayerEffectRenderer with FlutterActorDrawable { @override void draw(ui.Canvas canvas) { - drawPass(canvas, Paint()); - } - - void drawPass(ui.Canvas canvas, Paint layerPaint) { var aabb = artboard.artboardAABB(); Rect bounds = Rect.fromLTRB(aabb[0], aabb[1], aabb[2], aabb[3]); + + double baseBlurX = 0; + double baseBlurY = 0; + Paint layerPaint = Paint(); + Color layerColor = Colors.white.withOpacity(parent.renderOpacity); + layerPaint.color = layerColor; + if (blur?.isActive ?? false) { + baseBlurX = blur.blurX; + baseBlurY = blur.blurY; + layerPaint.imageFilter = + ui.ImageFilter.blur(sigmaX: baseBlurX, sigmaY: baseBlurY); + } + + if (dropShadows.isNotEmpty) { + for (final dropShadow in dropShadows) { + if (!dropShadow.isActive) { + continue; + } + // DropShadow: To draw a shadow we just draw the shape (with + // drawPass) with a custom color and image (blur) filter before + // drawing the main shape. + canvas.save(); + var color = dropShadow.color; + canvas.translate(dropShadow.offsetX, dropShadow.offsetY); + var shadowPaint = Paint() + ..color = layerColor + ..imageFilter = ui.ImageFilter.blur( + sigmaX: dropShadow.blurX + baseBlurX, + sigmaY: dropShadow.blurY + baseBlurY) + ..colorFilter = ui.ColorFilter.mode( + ui.Color.fromRGBO( + (color[0] * 255.0).round(), + (color[1] * 255.0).round(), + (color[2] * 255.0).round(), + color[3]), + ui.BlendMode.srcIn) + ..blendMode = ui.BlendMode.values[dropShadow.blendModeId]; + + drawPass(canvas, bounds, shadowPaint); + canvas.restore(); + canvas.restore(); + } + } + drawPass(canvas, bounds, layerPaint); + // Draw inner shadows on the main layer. + if (innerShadows.isNotEmpty) { + for (final innerShadow in innerShadows) { + if (!innerShadow.isActive) { + continue; + } + var blendMode = ui.BlendMode.values[innerShadow.blendModeId]; + bool extraBlendPass = blendMode != ui.BlendMode.srcOver; + if (extraBlendPass) { + // if we have a custom blend mode, then we can't just srcATop with + // what's already been drawn. We need to draw the contents as a mask + // to then draw the shadow on top of with srcIn to only show the + // shadow and finally composite with the desired blend mode requested + // here. + var extraLayerPaint = Paint()..blendMode = blendMode; + drawPass(canvas, bounds, extraLayerPaint); + } + + // because there's no way to compose image filters (use two filters in + // one) we have to use an extra layer to invert the alpha for the inner + // shadow before blurring. + + var color = innerShadow.color; + var shadowPaint = Paint() + ..color = layerColor + ..blendMode = + extraBlendPass ? ui.BlendMode.srcIn : ui.BlendMode.srcATop + ..imageFilter = ui.ImageFilter.blur( + sigmaX: innerShadow.blurX + baseBlurX, + sigmaY: innerShadow.blurY + baseBlurY) + ..colorFilter = ui.ColorFilter.mode( + ui.Color.fromRGBO( + (color[0] * 255.0).round(), + (color[1] * 255.0).round(), + (color[2] * 255.0).round(), + color[3]), + ui.BlendMode.srcIn); + + canvas.saveLayer(bounds, shadowPaint); + canvas.translate(innerShadow.offsetX, innerShadow.offsetY); + + // Invert the alpha to compute inner part. + var invertPaint = Paint() + ..colorFilter = const ui.ColorFilter.matrix([ + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + -1, + 255, + ]); + drawPass(canvas, bounds, invertPaint); + // restore draw pass (inverted aint) + canvas.restore(); + // restore save layer used to that blurs and colors the shadow + canvas.restore(); + + if (extraBlendPass) { + // Restore extra layer used to draw the contents to clip against (we + // clip by drawing with srcIn) + canvas.restore(); + } + } + } + canvas.restore(); + } + + void drawPass(ui.Canvas canvas, Rect bounds, Paint layerPaint) { canvas.saveLayer(bounds, layerPaint); for (final drawable in drawables) { if (drawable is FlutterActorDrawable) { @@ -1322,20 +1445,71 @@ class FlutterActorLayerEffectRenderer extends ActorLayerEffectRenderer } for (final renderMask in renderMasks) { - if (!renderMask.mask.isActive) { + var mask = renderMask.mask; + if (!mask.isActive) { continue; } - // const mat = CanvasKit.SkColorFilter.MakeMatrix(new Float32Array([ - // 0, 0, 0, 0, 0, - // 0, 0, 0, 0, 0, - // 0, 0, 0, 0, 0, - // 0, 0, 0, 1, 0 - // ])); - // maskPaint.setColorFilter(mat); var maskPaint = Paint(); - maskPaint.colorFilter = ui.ColorFilter.matrix( - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]); + switch (mask.maskType) { + case MaskType.invertedAlpha: + maskPaint.colorFilter = const ui.ColorFilter.matrix( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 1]); + break; + case MaskType.luminance: + maskPaint.colorFilter = const ui.ColorFilter.matrix([ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0.33, + 0.59, + 0.11, + 0, + 0 + ]); + break; + case MaskType.invertedLuminance: + maskPaint.colorFilter = const ui.ColorFilter.matrix([ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -0.33, + -0.59, + -0.11, + 0, + 1 + ]); + break; + case MaskType.alpha: + default: + maskPaint.colorFilter = const ui.ColorFilter.matrix( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]); + break; + } maskPaint.blendMode = BlendMode.dstIn; canvas.saveLayer(bounds, maskPaint); @@ -1351,8 +1525,6 @@ class FlutterActorLayerEffectRenderer extends ActorLayerEffectRenderer } canvas.restore(); } - - canvas.restore(); } @override