Skip to content

Commit

Permalink
Fix exyte#123: Parse SVG filters to Macaw effects
Browse files Browse the repository at this point in the history
  • Loading branch information
f3dm76 committed May 16, 2018
1 parent 910845f commit de82612
Show file tree
Hide file tree
Showing 14 changed files with 330 additions and 187 deletions.
210 changes: 108 additions & 102 deletions Macaw.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Source/animation/layer_animation/FuncBounds.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func boundsWithDerivative(p0: Point, p1: Point, p2: Point, p3: Point) -> Rect? {
let cy = 3 * p1.y - 3 * p0.y
let sy = solveEquation(a: ay, b: by, c: cy)

let solutions = [0, 1, sx.s1, sx.s2, sy.s1, sy.s2].flatMap { $0 }
let solutions = [0, 1, sx.s1, sx.s2, sy.s1, sy.s2].compactMap { $0 }
var minX: Double? = .none
var minY: Double? = .none
var maxX: Double? = .none
Expand Down
8 changes: 4 additions & 4 deletions Source/animation/types/MorphingAnimation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ public extension AnimatableVariable where T: ContentsInterpolation {
}

// Shapes on same hierarhy level
let fromShapes = fromNode.contents.flatMap { $0 as? Shape }
let toShapes = to.flatMap { $0 as? Shape }
let fromShapes = fromNode.contents.compactMap { $0 as? Shape }
let toShapes = to.compactMap { $0 as? Shape }
let minPathsNumber = min(fromShapes.count, toShapes.count)

var animations = [Animation]()
Expand Down Expand Up @@ -117,8 +117,8 @@ public extension AnimatableVariable where T: ContentsInterpolation {
}

// Groups on same hierahy level
let fromGroups = fromNode.contents.flatMap { $0 as? Group }
let toGroups = to.flatMap { $0 as? Group }
let fromGroups = fromNode.contents.compactMap { $0 as? Group }
let toGroups = to.compactMap { $0 as? Group }
let minGroupsNumber = min(fromGroups.count, toGroups.count)
for i in 0..<minGroupsNumber {
let fromGroup = fromGroups[i]
Expand Down
2 changes: 2 additions & 0 deletions Source/model/draw/AlphaEffect.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
open class AlphaEffect: Effect {
}
16 changes: 0 additions & 16 deletions Source/model/draw/DropShadow.swift

This file was deleted.

5 changes: 3 additions & 2 deletions Source/model/draw/Effect.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import Foundation

open class Effect {
open let input: Effect?

public init() {
public init(input: Effect?) {
self.input = input
}

}
5 changes: 2 additions & 3 deletions Source/model/draw/GaussianBlur.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import Foundation
open class GaussianBlur: Effect {

open let radius: Double
open let input: Effect?

public init(radius: Double = 0, input: Effect? = nil) {
public init(radius: Double = 0, input: Effect?) {
self.radius = radius
self.input = input
super.init(input: input)
}
}
11 changes: 11 additions & 0 deletions Source/model/draw/OffsetEffect.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
open class OffsetEffect: Effect {

open let dx: Double
open let dy: Double

public init(dx: Double = 0, dy: Double = 0, input: Effect?) {
self.dx = dx
self.dy = dy
super.init(input: input)
}
}
2 changes: 1 addition & 1 deletion Source/model/scene/Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ open class Node: Drawable {
}

public func nodesBy(tag: String) -> [Node] {
return [nodeBy(tag: tag)].flatMap { $0 }
return [nodeBy(tag: tag)].compactMap { $0 }
}

// MARK: - Events
Expand Down
2 changes: 1 addition & 1 deletion Source/render/GroupRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class GroupRenderer: NodeRenderer {
renderers.forEach { $0.dispose() }
renderers.removeAll()

if let updatedRenderers = group?.contents.flatMap ({ child -> NodeRenderer? in
if let updatedRenderers = group?.contents.compactMap ({ child -> NodeRenderer? in
guard let interval = renderingInterval else {
return RenderUtils.createNodeRenderer(child, context: ctx, animationCache: animationCache)
}
Expand Down
3 changes: 2 additions & 1 deletion Source/render/RenderUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ class RenderUtils {
}

fileprivate class func pointsToPath(_ points: [Double]) -> MBezierPath {
let parts = stride(from: 0, to: points.count, by: 2).map { Array(points[$0 ..< $0 + 2]) }
let count = points.count / 2 * 2 // points count divisible by 2
let parts = stride(from: 0, to: count, by: 2).map { Array(points[$0 ..< $0 + 2]) }
let path = MBezierPath()
var first = true
for part in parts {
Expand Down
98 changes: 90 additions & 8 deletions Source/render/ShapeRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,104 @@ class ShapeRenderer: NodeRenderer {
observe(shape.strokeVar)
}

fileprivate func drawShape(in context: CGContext, opacity: Double) {
guard let shape = shape else { return }
setGeometry(shape.form, ctx: context)
var fillRule = FillRule.nonzero
if let path = shape.form as? Path {
fillRule = path.fillRule
}
drawPath(shape.fill, stroke: shape.stroke, ctx: context, opacity: opacity, fillRule: fillRule)
}

override func doRender(_ force: Bool, opacity: Double) {
guard let shape = shape else {
guard let shape = shape, let context = ctx.cgContext else { return }
if shape.fill == nil && shape.stroke == nil { return }

// no effects, just draw as usual
guard let effect = shape.effect else {
drawShape(in: context, opacity: opacity)
return
}

if shape.fill != nil || shape.stroke != nil {
setGeometry(shape.form, ctx: ctx.cgContext!)

var fillRule = FillRule.nonzero
if let path = shape.form as? Path {
fillRule = path.fillRule
var effects = [Effect]()
var next: Effect? = effect
while next != nil {
effects.append(next!)
next = next?.input
}

let offset = effects.filter { $0 is OffsetEffect }.first
let otherEffects = effects.filter { !($0 is OffsetEffect) }
if let offset = offset as? OffsetEffect {
let move = Transform(m11: 1, m12: 0, m21: 0, m22: 1, dx: offset.dx, dy: offset.dy)
context.concatenate(move.toCG())

if otherEffects.count == 0 {
// draw offset shape
drawShape(in: context, opacity: opacity)
} else {
// apply other effects to offset shape
applyEffects(otherEffects, opacity: opacity)
}
drawPath(shape.fill, stroke: shape.stroke, ctx: ctx.cgContext!, opacity: opacity, fillRule: fillRule)

// move back and draw the shape itself
context.concatenate(move.invert()!.toCG())
drawShape(in: context, opacity: opacity)
} else {
// draw the shape
drawShape(in: context, opacity: opacity)

// apply other effects to shape
applyEffects(otherEffects, opacity: opacity)
}
}

fileprivate func applyEffects(_ effects: [Effect], opacity: Double) {
guard let shape = shape, let context = ctx.cgContext else { return }
for effect in effects {
if let blur = effect as? GaussianBlur {
let shadowInset = min(blur.radius * 6 + 1, 150)
guard let shapeImage = saveToImage(shape: shape, shadowInset: shadowInset, opacity: opacity)?.cgImage else { return }

guard let filteredImage = applyBlur(shapeImage, blur: blur) else { return }

guard let bounds = shape.bounds() else { return }
context.draw(filteredImage, in: CGRect(x: bounds.x - shadowInset / 2, y: bounds.y - shadowInset / 2, width: bounds.w + shadowInset, height: bounds.h + shadowInset))
}
}
}

fileprivate func applyBlur(_ image: CGImage, blur: GaussianBlur) -> CGImage? {
let image = CIImage(cgImage: image)
guard let filter = CIFilter(name: "CIGaussianBlur") else { return .none }
filter.setDefaults()
filter.setValue(Int(blur.radius), forKey: kCIInputRadiusKey)
filter.setValue(image, forKey: kCIInputImageKey)

let context = CIContext(options: nil)
let imageRef = context.createCGImage(filter.outputImage!, from: image.extent)
return imageRef
}

fileprivate func saveToImage(shape: Shape, shadowInset: Double, opacity: Double) -> MImage? {
guard let size = shape.bounds() else { return .none }
MGraphicsBeginImageContextWithOptions(CGSize(width: size.w + shadowInset, height: size.h + shadowInset), false, 1)

guard let tempContext = MGraphicsGetCurrentContext() else { return .none }

if (shape.fill != nil || shape.stroke != nil) {
// flip y-axis and leave space for the blur
tempContext.translateBy(x: CGFloat(shadowInset / 2 - size.x), y: CGFloat(size.h + shadowInset / 2 + size.y))
tempContext.scaleBy(x: 1, y: -1)
drawShape(in: tempContext, opacity: opacity)
}

let img = MGraphicsGetImageFromCurrentImageContext()
MGraphicsEndImageContext()
return img
}

override func doFindNodeAt(location: CGPoint, ctx: CGContext) -> Node? {
guard let shape = shape else {
return .none
Expand Down
Loading

0 comments on commit de82612

Please sign in to comment.