1

I'm doing a drawing app, which allows users to draw on video frames, so when drawing on the UIImageView, i add the Bezierpath to a CAShapeLayer and set a shadow path to it, and it works great, the thing is when i want to convert the layer to a cg or uiimage which will be eventually ciimage(to compose to a video frame), UIGraphicContext drops the shadow saturation, like, the line in CAShapeLayer doesn't have shadow path, but just a simple shadow with much less saturation this is the image while drawing with shadow

enter image description here

this image is the path with the same shadow after converting to image

enter image description here

i have tried lots of different methods that i could find in SOF or any other place on the internet, so basically there are two ways to convert a path to image(ci,ui,cg) which all of them using somehow same practice which is UIGraphicContext(if there is any other way PLEASE tell me), so i tried to convert a layer to uiimage(by rendering it on CGContext) and also tried to draw path directly on the context, drawing path directly gave me (just)a little improvement than rendering layer on the context. this is the code for directly drawing the path on the context:

    UIGraphicsBeginImageContextWithOptions(size, false, 0)
    let context = UIGraphicsGetCurrentContext()!
    //// Shadow Declarations
    let shadow = UIColor.yellow.withAlphaComponent(1)
    let shadowOffset = CGSize(width: 0, height: 0) //offset is the same as when drawing a path on CAShapeLayer
    let shadowBlurRadius: CGFloat = 20

    //// Bezier 2 Drawing
    context.saveGState()
    context.addPath(path.cgPath)
    context.setShadow(offset: shadowOffset, blur: shadowBlurRadius, color: (shadow as UIColor).cgColor)
    path.lineCapStyle = .round
    path.lineCapStyle = .round
    UIColor.white.setStroke()
    path.lineWidth = 10
    path.stroke(with: .color, alpha: 1.0)
    //context.strokePath()
    context.restoreGState()
    let image = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()
    return image

i would really appreciate any help or hint that i could get the same shadow result after converting.

Updated:

here is the code which generates the shadow on layer which works fine on the view (the first picture) I need the same result after rendering or drawing on the context.

private func setTheLayer(layer: CAShapeLayer, size: Int, path: CGPath, glowing: Bool, color: CGColor) {
    if glowing {
        layer.path = path
        layer.fillColor = nil
        layer.opacity = 1.0
        layer.lineWidth = CGFloat(size)
        layer.lineCap = .round
        layer.lineJoin = .round
        layer.strokeColor = UIColor.white.cgColor
        // drawing the glow shadow
        layer.shadowPath = path.copy(strokingWithWidth: CGFloat(size) * 4, lineCap: .round, lineJoin: .round, miterLimit: 0)
        layer.shadowOffset = CGSize(width: 0, height: 0)
        layer.shadowColor = color
        layer.shadowRadius = 5.0
        layer.shadowOpacity = 0.7
    } else {
        layer.path = path
        layer.fillColor = nil
        layer.opacity = 1.0
        layer.lineWidth = CGFloat(size)
        layer.lineCap = .round
        layer.lineJoin = .round
        layer.strokeColor = color
    }
}
koen
  • 5,383
  • 7
  • 50
  • 89
Mostafa
  • 48
  • 1
  • 8

1 Answers1

1

For reasons I don't understand, shadows are not rendered correctly when you use a layer's render(in:) method.

However, if your layers are added to a view, you can use that view's drawHierarchy(in: afterScreenUpdates:), which will faithfully render the shadow.

This playground code:

import UIKit
import PlaygroundSupport

let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
view.backgroundColor = .black
PlaygroundPage.current.liveView = view

let layer = CAShapeLayer()
let path = UIBezierPath()
path.move(to: CGPoint(x: 20, y: 50))
path.addLine(to: CGPoint(x: 180, y: 50))
layer.path = path.cgPath
layer.lineCap = .round
layer.fillColor = nil
layer.lineWidth = 4
layer.strokeColor = UIColor.white.cgColor
layer.shadowPath = path.cgPath.copy(strokingWithWidth: 16, lineCap: .round, lineJoin: .round, miterLimit: 0)
layer.shadowOffset = .zero
layer.shadowColor = UIColor.yellow.cgColor
layer.shadowRadius = 5
layer.shadowOpacity = 0.7
layer.frame = CGRect(x:0, y: 0, width: 200, height: 100)
view.layer.addSublayer(layer)
layer.shouldRasterize = true
let renderer = UIGraphicsImageRenderer(bounds: layer.bounds)
let image = renderer.image { context in
    view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
}
let imageView = UIImageView(image: image)
view.addSubview(imageView)
imageView.frame.origin.y = 100

Gives you this output:

enter image description here

I'd love to know why shadows don't render properly, though.

jrturton
  • 118,105
  • 32
  • 252
  • 268
  • thanks for the response, yeah i already did, as i said in the question rendering in the context was my first attempt, but less shadow saturation from drawing it directly on context, i tried different shadowing methods with and without path, and in the ui its ok, just when rendering on the context, its somehow omitted, as the pictures depicted – Mostafa Jul 14 '20 at 18:48
  • OK, I see the same thing as you, how interesting. I'll investigate further – jrturton Jul 15 '20 at 20:50