Can I Snapshot a SKNode? - objective-c

I have a SKScene and I want to snapshot a specific layer. It can be a layer displayed on a white UIView with a SKView in it. But in the end I want to take a snap from the state of this layer only.
Thanks in advance!

That's what worked for me:
var tempView = UIView(frame: CGRectMake(0, 0, 500, 500))
var tempSKView = SKView(frame: tempView.frame)
var tempScene = SKScene(size: tempSKView.bounds.size)
var lineTexture = SKTexture()
lineTexture = skView.textureFromNode(scene.lineLayer)
var lineLayerCopy = SKSpriteNode(texture: lineTexture)
lineLayerCopy.anchorPoint = CGPoint(x: 0.0, y: 0.0)
lineLayerCopy.size = CGSizeMake(500, 500)
tempScene.addChild(lineLayerCopy)
tempScene.backgroundColor = UIColor.whiteColor()
tempSKView.presentScene(tempScene)
tempView.addSubview(tempSKView)
self.view.addSubview(tempView)
Note: the anchorPoint is essential. It forces the scene to position in the center and so you have the perfect image.

Related

CARenderer draws nothing into bound texture

I'm trying to setup a CARenderer to draw into a mtlTexture, but all my attempts to get this working in a playground don't draw anything.
The resulting image is solid red, the yellow layer doesn't seem to be rendered at all.
Here's the simplest version of what I've tried:
import QuartzCore
import Metal
let layerTest = CATextLayer()
layerTest.frame = .init(origin: .zero, size: .init(width: 1920, height: 1080))
layerTest.string = "TEST"
layerTest.foregroundColor = .black
layerTest.backgroundColor = CGColor(red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0)
layerTest.position = CGPoint(x:0.0, y:0.0)
layerTest.anchorPoint = CGPoint(x:0.0, y:0.0)
layerTest.masksToBounds = true
let device = MTLCreateSystemDefaultDevice()!
let context = CIContext(mtlDevice: device)
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm, width: 1920, height: 1080, mipmapped: false)
textureDescriptor.usage = [.unknown]
textureDescriptor.storageMode = .private
let bytes = UnsafeMutablePointer<UInt8>.allocate(capacity: 1920 * 1080 * 4)
//fill the buffer with red
let pattern = UnsafeMutablePointer<UInt8>.allocate(capacity: 4)
(pattern + 0).initialize(to: 255)
(pattern + 1).initialize(to: 0)
(pattern + 2).initialize(to: 0)
(pattern + 3).initialize(to: 255)
memset_pattern4(bytes, pattern, 1920 * 1080 * 4)
let mtlBuffer = device.makeBuffer(bytes: bytes, length: 1920 * 1080 * 4)!
let mtlTexture = mtlBuffer.makeTexture(descriptor: textureDescriptor, offset: 0, bytesPerRow: 1920 * 4)!
let render = CARenderer(mtlTexture: mtlTexture)
render.bounds = layerTest.frame
render.layer = layerTest
render.setDestination(mtlTexture)
render.beginFrame(atTime: CACurrentMediaTime(), timeStamp: nil)
render.addUpdate(render.bounds)
render.render()
render.endFrame()
let ciImage = CIImage(mtlTexture: mtlTexture)!
let cgImage: CGImage = context.createCGImage(ciImage, from: ciImage.extent)! //<- this is just red frame
I submitted this to apple DTS and got a reply:
Setting the root layer of the CARenderer requires one implicit CATransaction to transfer ownership of the layer tree to the CARenderer’s context. CARenderer frame render methods will not work correctly until this ownership transfer is complete.
To complete the current transaction we call flush() and then commit() it:
render.layer = layerTest
CATransaction.flush()
CATransaction.commit()
Adding these two lines resolved the issue for me.

Fastest way to draw offscreen CALayer content

I'm looking for the fastest way to draw offscreen CALayer content (no alpha needed) on macOS. Note, that these examples aren't threaded, but the point is (and why I'm not just using CALayer.setNeedsDisplay) because I'm doing this drawing on a background thread.
My original code did this:
let bounds = layer.bounds.size
let contents = NSImage(size: size)
contents.lockFocusFlipped(true)
let context = NSGraphicsContext.current()!.cgContext
layer.draw(in: context)
contents.unlockFocus()
layer.contents = contents
My current best is quite a bit faster:
let contentsScale = layer.contentsScale
let width = Int(bounds.width * contentsScale)
let height = Int(bounds.height * contentsScale)
let bytesPerRow = width * 4
let alignedBytesPerRow = ((bytesPerRow + (64 - 1)) / 64) * 64
let context = CGContext(
data: nil,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: alignedBytesPerRow,
space: NSScreen.main()?.colorSpace?.cgColorSpace ?? CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue
)!
context.scaleBy(x: contentsScale, y: contentsScale)
layer.draw(in: context)
layer.contents = context.makeImage()
Tips and recommendations for making it better/faster are welcome.

How to add a overlay text on captured video/image in swift?

I want to add a overlay text like "Company watermark" on captured video or image programatically in swift. any help will be appreciated.
// create text Layer
let titleLayer = CATextLayer()
titleLayer.backgroundColor = UIColor.whiteColor().CGColor
titleLayer.string = "Company watermark"
titleLayer.font = UIFont(name: "Helvetica", size: 28)
titleLayer.shadowOpacity = 0.5
titleLayer.alignmentMode = kCAAlignmentCenter
titleLayer.frame = CGRectMake(0, 50, size.width, size.height / 6)
yourView.layer.addSublayer(titleLayer)
Hope it will help you for adding text in video.

How do I get the frame of visible content from SKCropNode?

It appears that, in SpriteKit, when I use a mask in a SKCropNode to hide some content, it fails to change the frame calculated by calculateAccumulatedFrame. I'm wondering if there's any way to calculate the visible frame.
A quick example:
import SpriteKit
let par = SKCropNode()
let bigShape = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 100, height: 100))
bigShape.fillColor = UIColor.redColor()
bigShape.strokeColor = UIColor.clearColor()
par.addChild(bigShape)
let smallShape = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 20, height: 20))
smallShape.fillColor = UIColor.greenColor()
smallShape.strokeColor = UIColor.clearColor()
par.maskNode = smallShape
par.calculateAccumulatedFrame() // returns (x=0, y=0, width=100, height=100)
I expected par.calculateAccumulatedFrame() to return (x=0, y=0, width=20, height=20) based on the crop node mask.
I thought maybe I could code the function myself as an extension that basically reimplements calculateAccumulatedFrame with support for checking for SKCropNodes and their masks, but it occurred to me that I would need to consider the alpha of that mask to determine if there's actual content that grows the frame. Sounds difficult.
Is there an easy way to calculate this?

How to resize Subview with Autolayout

I have a Tableview view, by some resson, i have to add this tableview in a Scrollview.
I used autolayout but this tableview not auto resize when i rotate the screen to landscape.
How to make this tableview including the same size scrollview when i rotate the sreen to landscape?
Here is my code:
scrollViewMain = UIScrollView()
scrollViewMain.contentInset = UIEdgeInsets(top: 34, left: 0, bottom: 52, right: 0)
scrollViewMain.scrollIndicatorInsets = UIEdgeInsets(top: 34, left: 0, bottom: 52, right: 0)
scrollViewMain.delegate = self
scrollViewMain.scrollEnabled = true
scrollViewMain.showsHorizontalScrollIndicator = false
scrollViewMain.showsVerticalScrollIndicator = true
scrollViewMain.indicatorStyle = UIScrollViewIndicatorStyle.White
scrollViewMain.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(scrollViewMain)
view.backgroundColor = BACKGROUND_COLOR
headerView = UIView(frame: CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_WIDTH / 3))
headerView.backgroundColor = UIColor.clearColor()
headerView.setTranslatesAutoresizingMaskIntoConstraints(false)
headerView.layer.masksToBounds = true
tableViewMain = UITableView()
tableViewMain.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
tableViewMain.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
tableViewMain.delegate = self
tableViewMain.dataSource = self
tableViewMain.backgroundColor = UIColor.clearColor()
tableViewMain.backgroundView = nil
tableViewMain.opaque = false
tableViewMain.separatorColor = UIColor(rgb: 0x3e3e3e)
tableViewMain.indicatorStyle = UIScrollViewIndicatorStyle.White
tableViewMain.scrollEnabled = false
tableViewMain.setTranslatesAutoresizingMaskIntoConstraints(false)
scrollViewMain.addSubview(tableViewMain)
tableViewMain.registerClass(MoviesTableViewCell.self, forCellReuseIdentifier: "MoviesTableViewCell")
tableViewMain.tableHeaderView = headerView
let viewDictionary = ["scrollViewMain": scrollViewMain, "tableViewMain": tableViewMain, "headerView": headerView]
let scrollViewMainConstraint_Horizontal:NSArray = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-0-[scrollViewMain]-0-|",
options: nil,
metrics: nil,
views: viewDictionary
)
let scrollViewMainConstraint_Vertical:NSArray = NSLayoutConstraint.constraintsWithVisualFormat(
"V:|-0-[scrollViewMain]-0-|",
options: nil,
metrics: nil,
views: viewDictionary
)
view.addConstraints(scrollViewMainConstraint_Horizontal as! [AnyObject])
view.addConstraints(scrollViewMainConstraint_Vertical as! [AnyObject])
I think the issue is that the table view doesn't have any constraints, so it doesn't know what rules to follow when the screen rotates. I don't know exactly what constraints you want, but assuming that you want the table view to fill up the whole scroll view, you could add constraints very similar to the ones you used for the scroll view (pin leading, trailing, top and bottom space to superview).