Add mask rendering functionality
This commit is contained in:
parent
19188fd4e9
commit
af6890dca8
1 changed files with 120 additions and 29 deletions
|
@ -8,8 +8,16 @@ struct SilicaChunk {
|
||||||
var image: NSImage = NSImage()
|
var image: NSImage = NSImage()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SilicaLayer {
|
struct SilicaLayerData {
|
||||||
|
var blendMode: Int = 0
|
||||||
var chunks: [SilicaChunk] = []
|
var chunks: [SilicaChunk] = []
|
||||||
|
var opacity: Double = 1.0
|
||||||
|
var hidden: Bool = false
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SilicaLayer {
|
||||||
|
var data: SilicaLayerData = SilicaLayerData()
|
||||||
|
var mask: SilicaLayerData?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SilicaDocument {
|
struct SilicaDocument {
|
||||||
|
@ -112,16 +120,30 @@ class Document: NSDocument {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSilicaLayer(archive: Archive, dict: NSDictionary) {
|
func parseSilicaLayer(archive: Archive, dict: NSDictionary, isMask: Bool) -> SilicaLayer? {
|
||||||
let objectsArray = self.dict?["$objects"] as! NSArray
|
let objectsArray = self.dict?["$objects"] as! NSArray
|
||||||
|
|
||||||
if getDocumentClassName(dict: dict) == LayerClassName {
|
if getDocumentClassName(dict: dict) == LayerClassName {
|
||||||
var layer = SilicaLayer()
|
var layer = SilicaLayer()
|
||||||
|
|
||||||
|
dump(dict)
|
||||||
|
|
||||||
let UUIDKey = dict["UUID"]
|
let UUIDKey = dict["UUID"]
|
||||||
let UUIDClassID = getClassID(id: UUIDKey)
|
let UUIDClassID = getClassID(id: UUIDKey)
|
||||||
let UUIDClass = objectsArray[UUIDClassID] as! NSString
|
let UUIDClass = objectsArray[UUIDClassID] as! NSString
|
||||||
|
|
||||||
|
let maskKey = dict["mask"]
|
||||||
|
let maskClassID = getClassID(id: maskKey)
|
||||||
|
let maskClass = objectsArray[maskClassID]
|
||||||
|
|
||||||
|
layer.data.blendMode = (dict["blend"] as? NSNumber)!.intValue
|
||||||
|
layer.data.opacity = (dict["opacity"] as? NSNumber)!.doubleValue
|
||||||
|
layer.data.hidden = (dict["hidden"] as? Bool)!
|
||||||
|
|
||||||
|
if maskClassID != 0 {
|
||||||
|
layer.mask = parseSilicaLayer(archive: archive, dict: maskClass as! NSDictionary, isMask: true)?.data
|
||||||
|
}
|
||||||
|
|
||||||
var chunkPaths: [String] = []
|
var chunkPaths: [String] = []
|
||||||
|
|
||||||
archive.forEach { (entry: Entry) in
|
archive.forEach { (entry: Entry) in
|
||||||
|
@ -130,7 +152,7 @@ class Document: NSDocument {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layer.chunks = Array(repeating: SilicaChunk(), count: chunkPaths.count)
|
layer.data.chunks = Array(repeating: SilicaChunk(), count: chunkPaths.count)
|
||||||
|
|
||||||
let dispatchGroup = DispatchGroup()
|
let dispatchGroup = DispatchGroup()
|
||||||
let queue = DispatchQueue(label: "imageWork")
|
let queue = DispatchQueue(label: "imageWork")
|
||||||
|
@ -149,7 +171,13 @@ class Document: NSDocument {
|
||||||
}
|
}
|
||||||
|
|
||||||
let (width, height) = getTileSize(x: x, y: y)
|
let (width, height) = getTileSize(x: x, y: y)
|
||||||
let byteSize = width * height * 4
|
|
||||||
|
var numChannels = 4
|
||||||
|
if isMask {
|
||||||
|
numChannels = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
let byteSize = width * height * numChannels
|
||||||
|
|
||||||
let uncompressedMemory = UnsafeMutablePointer<UInt8>.allocate(capacity: byteSize)
|
let uncompressedMemory = UnsafeMutablePointer<UInt8>.allocate(capacity: byteSize)
|
||||||
|
|
||||||
|
@ -166,20 +194,28 @@ class Document: NSDocument {
|
||||||
let imageData = Data(bytes: uncompressedMemory, count: byteSize)
|
let imageData = Data(bytes: uncompressedMemory, count: byteSize)
|
||||||
|
|
||||||
let render: CGColorRenderingIntent = .defaultIntent
|
let render: CGColorRenderingIntent = .defaultIntent
|
||||||
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
|
var rgbColorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue).union(.byteOrder32Big)
|
|
||||||
|
if isMask {
|
||||||
|
rgbColorSpace = CGColorSpaceCreateDeviceGray()
|
||||||
|
}
|
||||||
|
|
||||||
|
var bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue).union(.byteOrder32Big)
|
||||||
|
if isMask {
|
||||||
|
bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue).union(.byteOrder32Big)
|
||||||
|
}
|
||||||
let providerRef: CGDataProvider? = CGDataProvider(data: imageData as CFData)
|
let providerRef: CGDataProvider? = CGDataProvider(data: imageData as CFData)
|
||||||
|
|
||||||
guard let cgimage = CGImage(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: width * 4, space: rgbColorSpace, bitmapInfo: bitmapInfo, provider: providerRef!, decode: nil, shouldInterpolate: false, intent: render) else {
|
guard let cgimage = CGImage(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 8 * numChannels, bytesPerRow: width * numChannels, space: rgbColorSpace, bitmapInfo: bitmapInfo, provider: providerRef!, decode: nil, shouldInterpolate: false, intent: render) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let image = NSImage(cgImage: cgimage, size: NSZeroSize)
|
let image = NSImage(cgImage: cgimage, size: NSZeroSize)
|
||||||
|
|
||||||
queue.async(flags: .barrier) {
|
queue.async(flags: .barrier) {
|
||||||
layer.chunks[i].image = image
|
layer.data.chunks[i].image = image
|
||||||
layer.chunks[i].x = x
|
layer.data.chunks[i].x = x
|
||||||
layer.chunks[i].y = y
|
layer.data.chunks[i].y = y
|
||||||
|
|
||||||
dispatchGroup.leave()
|
dispatchGroup.leave()
|
||||||
}
|
}
|
||||||
|
@ -187,8 +223,10 @@ class Document: NSDocument {
|
||||||
|
|
||||||
dispatchGroup.wait()
|
dispatchGroup.wait()
|
||||||
|
|
||||||
info.layers.append(layer)
|
return layer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSilicaDocument(archive: Archive, dict: NSDictionary) {
|
func parseSilicaDocument(archive: Archive, dict: NSDictionary) {
|
||||||
|
@ -233,7 +271,8 @@ class Document: NSDocument {
|
||||||
let layerClassID = getClassID(id: object)
|
let layerClassID = getClassID(id: object)
|
||||||
let layerClass = objectsArray[layerClassID] as! NSDictionary
|
let layerClass = objectsArray[layerClassID] as! NSDictionary
|
||||||
|
|
||||||
parseSilicaLayer(archive: archive, dict: layerClass)
|
guard let layer = parseSilicaLayer(archive: archive, dict: layerClass, isMask: false) else { return }
|
||||||
|
info.layers.append(layer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -283,14 +322,26 @@ class Document: NSDocument {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeComposite() -> NSImage {
|
func makeComposite() -> NSImage {
|
||||||
var image = NSImage(size: NSSize(width: info.width, height: info.height))
|
// create the final composite output image
|
||||||
image.lockFocus()
|
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue).union(.byteOrder32Big)
|
||||||
|
|
||||||
let color = NSColor.white
|
let ccgContext = CGContext(data: nil, width: info.width, height: info.height, bitsPerComponent: 8, bytesPerRow: info.width * 4, space: rgbColorSpace, bitmapInfo: bitmapInfo.rawValue)
|
||||||
color.drawSwatch(in: NSRect(origin: .zero, size: image.size))
|
|
||||||
|
ccgContext?.setFillColor(.white)
|
||||||
|
ccgContext?.fill(CGRect(origin: .zero, size: CGSize(width: info.width, height: info.height)))
|
||||||
|
|
||||||
for layer in info.layers.reversed() {
|
for layer in info.layers.reversed() {
|
||||||
for chunk in layer.chunks {
|
// start by creating a new layer composite image, needed for image masking
|
||||||
|
let layerContext = CGContext(data: nil, width: info.width, height: info.height, bitsPerComponent: 8, bytesPerRow: info.width * 4, space: rgbColorSpace, bitmapInfo: bitmapInfo.rawValue)
|
||||||
|
|
||||||
|
let grayColorSpace = CGColorSpaceCreateDeviceGray()
|
||||||
|
let maskBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue).union(.byteOrder16Big)
|
||||||
|
|
||||||
|
let maskContext = CGContext(data: nil, width: info.width, height: info.height, bitsPerComponent: 16, bytesPerRow: 0, space: grayColorSpace, bitmapInfo: maskBitmapInfo.rawValue)
|
||||||
|
|
||||||
|
if layer.mask != nil {
|
||||||
|
for chunk in layer.mask!.chunks {
|
||||||
let x = chunk.x
|
let x = chunk.x
|
||||||
var y = chunk.y
|
var y = chunk.y
|
||||||
|
|
||||||
|
@ -302,11 +353,51 @@ class Document: NSDocument {
|
||||||
|
|
||||||
let rect = NSRect(x: info.tileSize * x, y: info.height - (info.tileSize * y), width: width, height: height)
|
let rect = NSRect(x: info.tileSize * x, y: info.height - (info.tileSize * y), width: width, height: height)
|
||||||
|
|
||||||
chunk.image.draw(in: rect)
|
if !layer.data.hidden {
|
||||||
|
maskContext?.draw(chunk.image.cgImage(forProposedRect: nil, context: NSGraphicsContext.current, hints: nil)!, in: rect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for chunk in layer.data.chunks {
|
||||||
|
let x = chunk.x
|
||||||
|
var y = chunk.y
|
||||||
|
|
||||||
|
let (width, height) = getTileSize(x: x, y: y)
|
||||||
|
|
||||||
|
if y == rows {
|
||||||
|
y = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
let rect = NSRect(x: info.tileSize * x, y: info.height - (info.tileSize * y), width: width, height: height)
|
||||||
|
|
||||||
|
var op = CGBlendMode.copy
|
||||||
|
if layer.data.blendMode == 12 {
|
||||||
|
op = .hardLight
|
||||||
|
}
|
||||||
|
|
||||||
|
layerContext?.setAlpha(CGFloat(layer.data.opacity))
|
||||||
|
layerContext?.setBlendMode(op)
|
||||||
|
|
||||||
|
if !layer.data.hidden {
|
||||||
|
layerContext?.draw(chunk.image.cgImage(forProposedRect: nil, context: NSGraphicsContext.current, hints: nil)!, in: rect)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
image.unlockFocus()
|
var layerImage = (layerContext?.makeImage())!
|
||||||
|
if layer.mask != nil {
|
||||||
|
let maskImage = (maskContext?.makeImage())!
|
||||||
|
|
||||||
|
layerImage = layerImage.masking(maskImage)!
|
||||||
|
}
|
||||||
|
|
||||||
|
ccgContext?.setAlpha(1.0)
|
||||||
|
ccgContext?.setBlendMode(.sourceAtop)
|
||||||
|
|
||||||
|
ccgContext?.draw(layerImage, in: CGRect(x: 0, y: 0, width: info.width, height: info.height))
|
||||||
|
}
|
||||||
|
|
||||||
|
var image = NSImage(cgImage: (ccgContext?.makeImage())!, size: NSSize(width: info.width, height: info.height))
|
||||||
|
|
||||||
if info.orientation == 3 {
|
if info.orientation == 3 {
|
||||||
image = image.imageRotatedByDegreess(degrees: 90)
|
image = image.imageRotatedByDegreess(degrees: 90)
|
||||||
|
|
Reference in a new issue