From 00b655f519084badd7ad07c3ca0f09ceeeda665f Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Tue, 21 Sep 2021 04:27:20 -0400 Subject: [PATCH] Add clipping mask support --- SilicaViewer/Document.swift | 115 ++++++++++++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 4 deletions(-) diff --git a/SilicaViewer/Document.swift b/SilicaViewer/Document.swift index b24c51c..cd343be 100644 --- a/SilicaViewer/Document.swift +++ b/SilicaViewer/Document.swift @@ -1,6 +1,7 @@ import Cocoa import ZIPFoundation import CoreFoundation +import Accelerate struct SilicaChunk { var x: Int = 0 @@ -18,6 +19,7 @@ struct SilicaLayerData { struct SilicaLayer { var data: SilicaLayerData = SilicaLayerData() var mask: SilicaLayerData? + var clipped: Bool = false } struct SilicaDocument { @@ -140,6 +142,9 @@ class Document: NSDocument { layer.data.blendMode = (dict["blend"] as? NSNumber)!.intValue layer.data.opacity = (dict["opacity"] as? NSNumber)!.doubleValue layer.data.hidden = (dict["hidden"] as? Bool)! + layer.clipped = (dict["clipped"] as? Bool)! + + dump(dict, indent: 2) if maskClassID != 0 { layer.mask = parseSilicaLayer(archive: archive, dict: maskClass as! NSDictionary, isMask: true)?.data @@ -240,7 +245,7 @@ class Document: NSDocument { let strokeClassID = getClassID(id: strokeClassKey) let strokeCount = objectsArray[strokeClassID] as! NSNumber - info.strokeCount = Int(strokeCount) + info.strokeCount = Int(truncating: strokeCount) let nameClassKey = dict[NameKey] let nameClassID = getClassID(id: nameClassKey) @@ -347,8 +352,12 @@ class Document: NSDocument { ccgContext?.setFillColor(.white) ccgContext?.fill(CGRect(origin: .zero, size: CGSize(width: info.width, height: info.height))) - - for layer in info.layers.reversed() { + + var previousImage: CGImage? + + for (index, layer) in info.layers.reversed().enumerated() { + dump(layer, indent: 5) + // 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) @@ -409,14 +418,112 @@ class Document: NSDocument { ccgContext?.setAlpha(1.0) ccgContext?.setBlendMode(.sourceAtop) - if layer.mask != nil { + if layer.clipped { + let previousLayer = info.layers.reversed()[index - 1] + + var format: vImage_CGImageFormat = { + guard + let format = vImage_CGImageFormat(cgImage: previousImage!) else { + fatalError("Unable to create format.") + } + + return format + }() + + var sourceBuffer: vImage_Buffer = { + guard + var sourceImageBuffer = try? vImage_Buffer(cgImage: previousImage!, + format: format), + + var scaledBuffer = try? vImage_Buffer(width: Int(sourceImageBuffer.height / 3), + height: Int(sourceImageBuffer.width / 3), + bitsPerPixel: format.bitsPerPixel) else { + fatalError("Unable to create source buffers.") + } + + defer { + sourceImageBuffer.free() + } + + vImageScale_ARGB8888(&sourceImageBuffer, + &scaledBuffer, + nil, + vImage_Flags(kvImageNoFlags)) + + return scaledBuffer + }() + + /* + The 1-channel, 8-bit vImage buffer used as the operation destination. + */ + var destinationBuffer: vImage_Buffer = { + guard var destinationBuffer = try? vImage_Buffer(width: Int(sourceBuffer.width), + height: Int(sourceBuffer.height), + bitsPerPixel: 8) else { + fatalError("Unable to create destination buffers.") + } + + return destinationBuffer + }() + + + let redCoefficient: Float = 0.2126 + let greenCoefficient: Float = 0.7152 + let blueCoefficient: Float = 0.0722 + + let divisor: Int32 = 0x1000 + let fDivisor = Float(divisor) + + var coefficientsMatrix = [ + Int16(redCoefficient * fDivisor), + Int16(greenCoefficient * fDivisor), + Int16(blueCoefficient * fDivisor) + ] + + // Use the matrix of coefficients to compute the scalar luminance by + // returning the dot product of each RGB pixel and the coefficients + // matrix. + let preBias: [Int16] = [0, 0, 0, 0] + let postBias: Int32 = 0 + + vImageMatrixMultiply_ARGB8888ToPlanar8(&sourceBuffer, + &destinationBuffer, + &coefficientsMatrix, + divisor, + preBias, + postBias, + vImage_Flags(kvImageNoFlags)) + + // Create a 1-channel, 8-bit grayscale format that's used to + // generate a displayable image. + let monoFormat = vImage_CGImageFormat( + bitsPerComponent: 8, + bitsPerPixel: 8, + colorSpace: CGColorSpaceCreateDeviceGray(), + bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue), + renderingIntent: .defaultIntent)! + + // Create a Core Graphics image from the grayscale destination buffer. + let result = (try? destinationBuffer.createCGImage(format: monoFormat))! + + let newImage = layerImage.masking(result)! + + previousImage = newImage + + ccgContext?.draw(newImage, in: CGRect(x: 0, y: 0, width: info.width, height: info.height)) + } else if layer.mask != nil { let maskImage = (maskContext?.makeImage())! let newImage = layerImage.masking(maskImage)! + previousImage = newImage + ccgContext?.draw(newImage, in: CGRect(x: 0, y: 0, width: info.width, height: info.height)) } else { + previousImage = layerImage + 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))