diff --git a/Dependencies/PSDWriter/Shared/PSDLayer.h b/Dependencies/PSDWriter/Shared/PSDLayer.h index 847517d..0a9696b 100755 --- a/Dependencies/PSDWriter/Shared/PSDLayer.h +++ b/Dependencies/PSDWriter/Shared/PSDLayer.h @@ -52,7 +52,23 @@ kPSDBlendModeLighter, kPSDBlendModeVividLight, - kPSDBlendModeHardLight + kPSDBlendModeHardLight, + + kPSDBlendModeColor, + kPSDBlendModeAdd, + kPSDBlendModeLighterColor, + kPSdBlendModeLinearLight, + kPSDBlendModePinLight, + kPSDBlendModeHardMix, + kPSDBlendModeDivide, + kPSDBlendModeOverlay, + kPSDBlendSoftLight, + kPSDBlendModeDifference, + kPSDBlendModeExclusion, + kPSDBlendModeSubtract, + kPSDBlendModeHue, + kPSDBlendModeSaturation, + kPSDBlendModeLuminosity, }; @property (nonatomic, assign) NSInteger blendMode; diff --git a/Dependencies/PSDWriter/Shared/PSDWriter.m b/Dependencies/PSDWriter/Shared/PSDWriter.m index c299551..7ea9d26 100755 --- a/Dependencies/PSDWriter/Shared/PSDWriter.m +++ b/Dependencies/PSDWriter/Shared/PSDWriter.m @@ -43,6 +43,19 @@ char blendModeLumKey[4] = {'l','u','m',' '}; char blendModevLiteKey[4] = {'v','L','i','t'}; char blendModehLiteKey[4] = {'h','L','i','t'}; +char blendModeAddKey[4] = {'l', 'd', 'd', 'g'}; +char blendModeLigColorKey[4] = {'l', 'g', 'C', 'l'}; +char blendModeLinColorKey[4] = {'l', 'L', 'i', 't'}; +char blendModePinLightKey[4] = {'p', 'L', 'i', 't'}; +char blendModeHardMixKey[4] = {'h', 'M', 'i', 'x'}; +char blendModeDivideKey[4] = {'f', 'd', 'i', 'v'}; +char blendModeOverlayKey[4] = {'o', 'v', 'e', 'r'}; +char blendModeSofLightKey[4] = {'s', 'L', 'i', 't'}; +char blendModeDiffKey[4] = {'d', 'i', 'f', 'f'}; +char blendModeExclKey[4] = {'s', 'm', 'u', 'd'}; +char blendModeSubKey[4] = {'f', 's', 'u', 'b'}; +char blendModeSatuKey[4] = {'s', 'a', 't', ' '}; +char blendModeLuminKey[4] = {'l', 'u', 'm', ' '}; char *blendModes[36] = { &blendModeNormKey, @@ -63,6 +76,22 @@ char *blendModes[36] = &blendModevLiteKey, &blendModehLiteKey, + &blendModeColKey, + &blendModeAddKey, + &blendModeLigColorKey, + &blendModeLinColorKey, + &blendModePinLightKey, + &blendModeHardMixKey, + &blendModeDivideKey, + &blendModeOverlayKey, + &blendModeSofLightKey, + &blendModeDiffKey, + &blendModeExclKey, + &blendModeSubKey, + &blendModeHueKey, + &blendModeSatuKey, + &blendModeLuminKey, + 0 }; diff --git a/SilicaViewer/AppDelegate.swift b/SilicaViewer/AppDelegate.swift index 464d49c..84ed381 100644 --- a/SilicaViewer/AppDelegate.swift +++ b/SilicaViewer/AppDelegate.swift @@ -66,67 +66,67 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations { return kPSDBlendModeScreen } if layer.data.blendMode == 13 { - return kPSDBlendModeHardLight + return kPSDBlendModeColor } if layer.data.blendMode == 9 { return kPSDBlendModeColorDodge } if layer.data.blendMode == 3 { - //blendMode = kPSDBlendModeSubtract + return kPSDBlendModeAdd } if layer.data.blendMode == 0 && layer.data.blendMode != layer.data.extendedBlend { if layer.data.extendedBlend == 25 { return kPSDBlendModeDarkerColor } if layer.data.extendedBlend == 24 { - //blendMode = kPSDBlendModeLighterColor + return kPSDBlendModeLighterColor } if layer.data.extendedBlend == 21 { return kPSDBlendModeVividLight } if layer.data.extendedBlend == 22 { - //blendMode = kPSdBlendModeLinearLight + return kPSdBlendModeLinearLight } if layer.data.extendedBlend == 23 { - //blendMode = kPSDBlendModePinLight + return kPSDBlendModePinLight } if layer.data.extendedBlend == 20 { - //blendMode = kPSDBlendModeHardMix + return kPSDBlendModeHardMix } if layer.data.extendedBlend == 26 { - //blendMode = kPSDBlendModeDivide + return kPSDBlendModeDivide } } if layer.data.blendMode == 11 { - //blendMode = kPSDBlendModeOverlay + return kPSDBlendModeOverlay } if layer.data.blendMode == 17 { - //return .softLight + return kPSDBlendSoftLight } if layer.data.blendMode == 12 { return kPSDBlendModeHardLight } if layer.data.blendMode == 6 { - //return .difference + return kPSDBlendModeDifference } if layer.data.blendMode == 5 { - //return .exclusion + return kPSDBlendModeExclusion } if layer.data.blendMode == 7 { - //return .componentAdd + return kPSDBlendModeSubtract } if layer.data.blendMode == 15 { - //return .hue + return kPSDBlendModeHue } if layer.data.blendMode == 16 { - //return .saturation + return kPSDBlendModeSaturation } if layer.data.blendMode == 13 { - //return .color + return kPSDBlendModeColor } if layer.data.blendMode == 14 { - //return .luminosity + return kPSDBlendModeLuminosity } return kPSDBlendModeNormal diff --git a/SilicaViewer/Document.swift b/SilicaViewer/Document.swift index 5a0a8c6..b9e1eab 100644 --- a/SilicaViewer/Document.swift +++ b/SilicaViewer/Document.swift @@ -18,7 +18,11 @@ struct SilicaLayerData { var hidden: Bool = false } -struct SilicaLayer { +struct SilicaLayer : Equatable { + static func == (lhs: SilicaLayer, rhs: SilicaLayer) -> Bool { + return lhs.name == rhs.name && lhs.clipped == rhs.clipped + } + var name: String = "" var data: SilicaLayerData = SilicaLayerData() var mask: SilicaLayerData? @@ -210,13 +214,13 @@ class Document: NSDocument { return .screen } if layer.data.blendMode == 13 { - return .hardLight + return .color } if layer.data.blendMode == 9 { return .colorDodge } if layer.data.blendMode == 3 { - return .subtract + return .componentAdd } if layer.data.blendMode == 0 && layer.data.blendMode != layer.data.extendedBlend { @@ -259,7 +263,7 @@ class Document: NSDocument { return .exclusion } if layer.data.blendMode == 7 { - return .componentAdd + return .subtract } if layer.data.blendMode == 15 { return .hue @@ -491,6 +495,7 @@ class Document: NSDocument { let layerClass = objectsArray[layerClassID] as! NSDictionary guard let layer = parseSilicaLayer(archive: archive, dict: layerClass, isMask: false) else { return } + info.layers.append(layer) } } @@ -574,6 +579,8 @@ class Document: NSDocument { let kernel = getBlendKernel(layer) + Swift.print(layer.name + " - " + kernel!.name) + for chunk in layer.data.chunks { layerContext?.setAlpha(CGFloat(layer.data.opacity)) layerContext?.setBlendMode(.normal) @@ -582,20 +589,34 @@ class Document: NSDocument { layerContext?.draw(chunk.image!, in: getChunkRect(chunk)) } } - - let layerImage = layerContext?.makeImage() - if layer.clipped && previousImage != nil { - let result = previousImage!.toGrayscale() - let newImage = layerImage!.masking(result!) - - previousImage = newImage - } else { - previousImage = layerImage - } + let layerImage = layerContext?.makeImage() // apply image - return kernel!.apply(foreground: CIImage(cgImage: previousImage!), background: CIImage(color: .clear), colorSpace: info.colorSpace)! + return kernel!.apply(foreground: CIImage(cgImage: layerImage!), background: previousImage == nil ? CIImage(color: .clear) : CIImage(cgImage: previousImage!), colorSpace: info.colorSpace)! + } + + // this returns all of the layers that are clipping onto this one + func getAllClippingLayers(layer: SilicaLayer) -> [SilicaLayer] { + var clippingLayers : [SilicaLayer] = [] + + let layers : [SilicaLayer] = info.layers.reversed() + let index = layers.firstIndex(of: layer)! + 1 + if index >= layers.count { + return clippingLayers + } + + for layerIndex in index...layers.count - 1 { + //Swift.print("checking " + layers[layerIndex].name + " is clipping = " + layers[layerIndex].clipped.description) + + if(layers[layerIndex].clipped) { + clippingLayers.append(layers[layerIndex]) + } else { + break + } + } + + return clippingLayers } func makeComposite() -> NSImage? { @@ -618,7 +639,7 @@ class Document: NSDocument { var previousImage: CGImage? = nil for layer in info.layers.reversed() { - if !layer.data.hidden { + if !layer.data.hidden && !layer.clipped { // 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: info.colorSpace, bitmapInfo: bitmapInfo.rawValue) @@ -626,8 +647,11 @@ class Document: NSDocument { var maskContext: CGContext? - let kernel = getBlendKernel(layer) - + var kernel = getBlendKernel(layer) + //kernel = .sourceOver + + Swift.print(layer.name + " - " + kernel!.name) + if layer.mask != nil { let grayColorSpace = CGColorSpaceCreateDeviceGray() let maskBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue).union(.byteOrder16Big) @@ -650,19 +674,41 @@ class Document: NSDocument { layerContext?.draw(chunk.image!, in: getChunkRect(chunk)) } } + + let clippingLayers = getAllClippingLayers(layer: layer) + if !clippingLayers.isEmpty { + let layerImage = layerContext?.makeImage() + + var clippedMaster: CGImage? = layerImage + for layer in clippingLayers { + Swift.print("- " + layer.name + " is clipping with us...") + + // so we if we want to clip, we want to gather all of the clipping layers in order first... + + Swift.print("processing clipped layer " + layer.name) + + let temporaryClippedMaster = blendLayer(layer, previousImage: &clippedMaster) + + clippedMaster = context.createCGImage(temporaryClippedMaster, from: info.cgRect, format: .RGBA8, colorSpace: info.colorSpace) + } + + Swift.print("clipping " + layer.name + "...") + + layerContext?.setAlpha(1.0) + layerContext?.setBlendMode(.sourceAtop) + + layerContext?.draw(clippedMaster!, in: info.cgRect) + } let layerImage = layerContext?.makeImage() - if layer.clipped && previousImage != nil { - let result = previousImage!.toGrayscale() - let newImage = layerImage!.masking(result!) - - previousImage = newImage - } else if layer.mask != nil && maskContext != nil { - let maskImage = (maskContext?.makeImage())! - let newImage = layerImage!.masking(maskImage)! + if layer.mask != nil && maskContext != nil { + Swift.print("masking " + layer.name + "...") + + let maskImage = (maskContext?.makeImage())! + let newImage = layerImage!.masking(maskImage)! - previousImage = newImage + previousImage = newImage } else { previousImage = layerImage } @@ -779,34 +825,52 @@ public extension NSImage { } } -public extension CGImage { - func toGrayscale() -> CGImage? { - let ciImage = CIImage(cgImage: self) - - let filter = CIFilter(name: "CIColorControls") - filter?.setValue(ciImage, forKey: kCIInputImageKey) - filter?.setValue(5.0, forKey: kCIInputBrightnessKey) - filter?.setValue(0.0, forKey: kCIInputSaturationKey) - filter?.setValue(1.1, forKey: kCIInputContrastKey) +class ColorFilter: CIFilter { + // 2 + var inputImage: CIImage? - guard let intermediateImage = filter?.outputImage else { - return nil - } - - guard let image = CIContext().createCGImage(intermediateImage, from: CGRect(origin: .zero, size: CGSize(width: self.width, height: self.height))) else { - return nil - } - + // 3 + static var kernel: CIKernel = { () -> CIColorKernel in + guard let url = Bundle.main.url(forResource: "default", + withExtension: "metallib"), + let data = try? Data(contentsOf: url) else { + fatalError("Unable to load metallib") + } + + guard let kernel = try? CIColorKernel( + functionName: "colorFilterKernel", + fromMetalLibraryData: data) else { + fatalError("Unable to create color kernel") + } + + return kernel + }() + + // 4 + override var outputImage: CIImage? { + guard let inputImage = inputImage else { return nil } + return ColorFilter.kernel.apply( + extent: inputImage.extent, + roiCallback: { _, rect in + return rect + }, + arguments: [inputImage]) + } +} + +extension CGImage { + func toGrayscale() -> CGImage? { + let rect = CGRect(x: 0, y: 0, width: width, height: height) let grayColorSpace = CGColorSpaceCreateDeviceGray() let maskBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue).union(.byteOrder16Big) - let maskContext = CGContext(data: nil, width: self.width, height: self.height, bitsPerComponent: 16, bytesPerRow: 0, space: grayColorSpace, bitmapInfo: maskBitmapInfo.rawValue) + let ccgContext = CGContext(data: nil, width: width, height: height, bitsPerComponent: 16, bytesPerRow: width * 2, space: grayColorSpace, bitmapInfo: maskBitmapInfo.rawValue) - maskContext?.setFillColor(.black) - maskContext?.fill(CGRect(origin: .zero, size: CGSize(width: self.width, height: self.height))) + ccgContext?.setFillColor(.black) + ccgContext?.fill(rect) - maskContext?.draw(image, in: CGRect(origin: .zero, size: CGSize(width: self.width, height: self.height))) - - return maskContext?.makeImage() + ccgContext?.draw(self, in: rect) + + return ccgContext?.makeImage() } }