Add the rest of the supported Photoshop blend modes, fix clipping
This is not the final blending code, it will go through another refactor but it's an improvement from before.
This commit is contained in:
parent
985241a8b3
commit
acda007eee
4 changed files with 176 additions and 67 deletions
18
Dependencies/PSDWriter/Shared/PSDLayer.h
vendored
18
Dependencies/PSDWriter/Shared/PSDLayer.h
vendored
|
@ -52,7 +52,23 @@
|
||||||
kPSDBlendModeLighter,
|
kPSDBlendModeLighter,
|
||||||
|
|
||||||
kPSDBlendModeVividLight,
|
kPSDBlendModeVividLight,
|
||||||
kPSDBlendModeHardLight
|
kPSDBlendModeHardLight,
|
||||||
|
|
||||||
|
kPSDBlendModeColor,
|
||||||
|
kPSDBlendModeAdd,
|
||||||
|
kPSDBlendModeLighterColor,
|
||||||
|
kPSdBlendModeLinearLight,
|
||||||
|
kPSDBlendModePinLight,
|
||||||
|
kPSDBlendModeHardMix,
|
||||||
|
kPSDBlendModeDivide,
|
||||||
|
kPSDBlendModeOverlay,
|
||||||
|
kPSDBlendSoftLight,
|
||||||
|
kPSDBlendModeDifference,
|
||||||
|
kPSDBlendModeExclusion,
|
||||||
|
kPSDBlendModeSubtract,
|
||||||
|
kPSDBlendModeHue,
|
||||||
|
kPSDBlendModeSaturation,
|
||||||
|
kPSDBlendModeLuminosity,
|
||||||
};
|
};
|
||||||
|
|
||||||
@property (nonatomic, assign) NSInteger blendMode;
|
@property (nonatomic, assign) NSInteger blendMode;
|
||||||
|
|
29
Dependencies/PSDWriter/Shared/PSDWriter.m
vendored
29
Dependencies/PSDWriter/Shared/PSDWriter.m
vendored
|
@ -43,6 +43,19 @@ char blendModeLumKey[4] = {'l','u','m',' '};
|
||||||
char blendModevLiteKey[4] = {'v','L','i','t'};
|
char blendModevLiteKey[4] = {'v','L','i','t'};
|
||||||
char blendModehLiteKey[4] = {'h','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] =
|
char *blendModes[36] =
|
||||||
{ &blendModeNormKey,
|
{ &blendModeNormKey,
|
||||||
|
@ -63,6 +76,22 @@ char *blendModes[36] =
|
||||||
&blendModevLiteKey,
|
&blendModevLiteKey,
|
||||||
&blendModehLiteKey,
|
&blendModehLiteKey,
|
||||||
|
|
||||||
|
&blendModeColKey,
|
||||||
|
&blendModeAddKey,
|
||||||
|
&blendModeLigColorKey,
|
||||||
|
&blendModeLinColorKey,
|
||||||
|
&blendModePinLightKey,
|
||||||
|
&blendModeHardMixKey,
|
||||||
|
&blendModeDivideKey,
|
||||||
|
&blendModeOverlayKey,
|
||||||
|
&blendModeSofLightKey,
|
||||||
|
&blendModeDiffKey,
|
||||||
|
&blendModeExclKey,
|
||||||
|
&blendModeSubKey,
|
||||||
|
&blendModeHueKey,
|
||||||
|
&blendModeSatuKey,
|
||||||
|
&blendModeLuminKey,
|
||||||
|
|
||||||
0 };
|
0 };
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -66,67 +66,67 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations {
|
||||||
return kPSDBlendModeScreen
|
return kPSDBlendModeScreen
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 13 {
|
if layer.data.blendMode == 13 {
|
||||||
return kPSDBlendModeHardLight
|
return kPSDBlendModeColor
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 9 {
|
if layer.data.blendMode == 9 {
|
||||||
return kPSDBlendModeColorDodge
|
return kPSDBlendModeColorDodge
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 3 {
|
if layer.data.blendMode == 3 {
|
||||||
//blendMode = kPSDBlendModeSubtract
|
return kPSDBlendModeAdd
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 0 && layer.data.blendMode != layer.data.extendedBlend {
|
if layer.data.blendMode == 0 && layer.data.blendMode != layer.data.extendedBlend {
|
||||||
if layer.data.extendedBlend == 25 {
|
if layer.data.extendedBlend == 25 {
|
||||||
return kPSDBlendModeDarkerColor
|
return kPSDBlendModeDarkerColor
|
||||||
}
|
}
|
||||||
if layer.data.extendedBlend == 24 {
|
if layer.data.extendedBlend == 24 {
|
||||||
//blendMode = kPSDBlendModeLighterColor
|
return kPSDBlendModeLighterColor
|
||||||
}
|
}
|
||||||
if layer.data.extendedBlend == 21 {
|
if layer.data.extendedBlend == 21 {
|
||||||
return kPSDBlendModeVividLight
|
return kPSDBlendModeVividLight
|
||||||
}
|
}
|
||||||
if layer.data.extendedBlend == 22 {
|
if layer.data.extendedBlend == 22 {
|
||||||
//blendMode = kPSdBlendModeLinearLight
|
return kPSdBlendModeLinearLight
|
||||||
}
|
}
|
||||||
if layer.data.extendedBlend == 23 {
|
if layer.data.extendedBlend == 23 {
|
||||||
//blendMode = kPSDBlendModePinLight
|
return kPSDBlendModePinLight
|
||||||
}
|
}
|
||||||
if layer.data.extendedBlend == 20 {
|
if layer.data.extendedBlend == 20 {
|
||||||
//blendMode = kPSDBlendModeHardMix
|
return kPSDBlendModeHardMix
|
||||||
}
|
}
|
||||||
if layer.data.extendedBlend == 26 {
|
if layer.data.extendedBlend == 26 {
|
||||||
//blendMode = kPSDBlendModeDivide
|
return kPSDBlendModeDivide
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if layer.data.blendMode == 11 {
|
if layer.data.blendMode == 11 {
|
||||||
//blendMode = kPSDBlendModeOverlay
|
return kPSDBlendModeOverlay
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 17 {
|
if layer.data.blendMode == 17 {
|
||||||
//return .softLight
|
return kPSDBlendSoftLight
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 12 {
|
if layer.data.blendMode == 12 {
|
||||||
return kPSDBlendModeHardLight
|
return kPSDBlendModeHardLight
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 6 {
|
if layer.data.blendMode == 6 {
|
||||||
//return .difference
|
return kPSDBlendModeDifference
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 5 {
|
if layer.data.blendMode == 5 {
|
||||||
//return .exclusion
|
return kPSDBlendModeExclusion
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 7 {
|
if layer.data.blendMode == 7 {
|
||||||
//return .componentAdd
|
return kPSDBlendModeSubtract
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 15 {
|
if layer.data.blendMode == 15 {
|
||||||
//return .hue
|
return kPSDBlendModeHue
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 16 {
|
if layer.data.blendMode == 16 {
|
||||||
//return .saturation
|
return kPSDBlendModeSaturation
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 13 {
|
if layer.data.blendMode == 13 {
|
||||||
//return .color
|
return kPSDBlendModeColor
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 14 {
|
if layer.data.blendMode == 14 {
|
||||||
//return .luminosity
|
return kPSDBlendModeLuminosity
|
||||||
}
|
}
|
||||||
|
|
||||||
return kPSDBlendModeNormal
|
return kPSDBlendModeNormal
|
||||||
|
|
|
@ -18,7 +18,11 @@ struct SilicaLayerData {
|
||||||
var hidden: Bool = false
|
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 name: String = ""
|
||||||
var data: SilicaLayerData = SilicaLayerData()
|
var data: SilicaLayerData = SilicaLayerData()
|
||||||
var mask: SilicaLayerData?
|
var mask: SilicaLayerData?
|
||||||
|
@ -210,13 +214,13 @@ class Document: NSDocument {
|
||||||
return .screen
|
return .screen
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 13 {
|
if layer.data.blendMode == 13 {
|
||||||
return .hardLight
|
return .color
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 9 {
|
if layer.data.blendMode == 9 {
|
||||||
return .colorDodge
|
return .colorDodge
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 3 {
|
if layer.data.blendMode == 3 {
|
||||||
return .subtract
|
return .componentAdd
|
||||||
}
|
}
|
||||||
|
|
||||||
if layer.data.blendMode == 0 && layer.data.blendMode != layer.data.extendedBlend {
|
if layer.data.blendMode == 0 && layer.data.blendMode != layer.data.extendedBlend {
|
||||||
|
@ -259,7 +263,7 @@ class Document: NSDocument {
|
||||||
return .exclusion
|
return .exclusion
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 7 {
|
if layer.data.blendMode == 7 {
|
||||||
return .componentAdd
|
return .subtract
|
||||||
}
|
}
|
||||||
if layer.data.blendMode == 15 {
|
if layer.data.blendMode == 15 {
|
||||||
return .hue
|
return .hue
|
||||||
|
@ -491,6 +495,7 @@ class Document: NSDocument {
|
||||||
let layerClass = objectsArray[layerClassID] as! NSDictionary
|
let layerClass = objectsArray[layerClassID] as! NSDictionary
|
||||||
|
|
||||||
guard let layer = parseSilicaLayer(archive: archive, dict: layerClass, isMask: false) else { return }
|
guard let layer = parseSilicaLayer(archive: archive, dict: layerClass, isMask: false) else { return }
|
||||||
|
|
||||||
info.layers.append(layer)
|
info.layers.append(layer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -574,6 +579,8 @@ class Document: NSDocument {
|
||||||
|
|
||||||
let kernel = getBlendKernel(layer)
|
let kernel = getBlendKernel(layer)
|
||||||
|
|
||||||
|
Swift.print(layer.name + " - " + kernel!.name)
|
||||||
|
|
||||||
for chunk in layer.data.chunks {
|
for chunk in layer.data.chunks {
|
||||||
layerContext?.setAlpha(CGFloat(layer.data.opacity))
|
layerContext?.setAlpha(CGFloat(layer.data.opacity))
|
||||||
layerContext?.setBlendMode(.normal)
|
layerContext?.setBlendMode(.normal)
|
||||||
|
@ -585,17 +592,31 @@ class Document: NSDocument {
|
||||||
|
|
||||||
let layerImage = layerContext?.makeImage()
|
let layerImage = layerContext?.makeImage()
|
||||||
|
|
||||||
if layer.clipped && previousImage != nil {
|
// apply image
|
||||||
let result = previousImage!.toGrayscale()
|
return kernel!.apply(foreground: CIImage(cgImage: layerImage!), background: previousImage == nil ? CIImage(color: .clear) : CIImage(cgImage: previousImage!), colorSpace: info.colorSpace)!
|
||||||
let newImage = layerImage!.masking(result!)
|
|
||||||
|
|
||||||
previousImage = newImage
|
|
||||||
} else {
|
|
||||||
previousImage = layerImage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply image
|
// this returns all of the layers that are clipping onto this one
|
||||||
return kernel!.apply(foreground: CIImage(cgImage: previousImage!), background: CIImage(color: .clear), colorSpace: info.colorSpace)!
|
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? {
|
func makeComposite() -> NSImage? {
|
||||||
|
@ -618,7 +639,7 @@ class Document: NSDocument {
|
||||||
var previousImage: CGImage? = nil
|
var previousImage: CGImage? = nil
|
||||||
|
|
||||||
for layer in info.layers.reversed() {
|
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
|
// 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)
|
let layerContext = CGContext(data: nil, width: info.width, height: info.height, bitsPerComponent: 8, bytesPerRow: info.width * 4, space: info.colorSpace, bitmapInfo: bitmapInfo.rawValue)
|
||||||
|
|
||||||
|
@ -626,7 +647,10 @@ class Document: NSDocument {
|
||||||
|
|
||||||
var maskContext: CGContext?
|
var maskContext: CGContext?
|
||||||
|
|
||||||
let kernel = getBlendKernel(layer)
|
var kernel = getBlendKernel(layer)
|
||||||
|
//kernel = .sourceOver
|
||||||
|
|
||||||
|
Swift.print(layer.name + " - " + kernel!.name)
|
||||||
|
|
||||||
if layer.mask != nil {
|
if layer.mask != nil {
|
||||||
let grayColorSpace = CGColorSpaceCreateDeviceGray()
|
let grayColorSpace = CGColorSpaceCreateDeviceGray()
|
||||||
|
@ -651,14 +675,36 @@ class Document: NSDocument {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let clippingLayers = getAllClippingLayers(layer: layer)
|
||||||
|
if !clippingLayers.isEmpty {
|
||||||
let layerImage = layerContext?.makeImage()
|
let layerImage = layerContext?.makeImage()
|
||||||
|
|
||||||
if layer.clipped && previousImage != nil {
|
var clippedMaster: CGImage? = layerImage
|
||||||
let result = previousImage!.toGrayscale()
|
for layer in clippingLayers {
|
||||||
let newImage = layerImage!.masking(result!)
|
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.mask != nil && maskContext != nil {
|
||||||
|
Swift.print("masking " + layer.name + "...")
|
||||||
|
|
||||||
previousImage = newImage
|
|
||||||
} else if layer.mask != nil && maskContext != nil {
|
|
||||||
let maskImage = (maskContext?.makeImage())!
|
let maskImage = (maskContext?.makeImage())!
|
||||||
let newImage = layerImage!.masking(maskImage)!
|
let newImage = layerImage!.masking(maskImage)!
|
||||||
|
|
||||||
|
@ -779,34 +825,52 @@ public extension NSImage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension CGImage {
|
class ColorFilter: CIFilter {
|
||||||
|
// 2
|
||||||
|
var inputImage: CIImage?
|
||||||
|
|
||||||
|
// 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? {
|
func toGrayscale() -> CGImage? {
|
||||||
let ciImage = CIImage(cgImage: self)
|
let rect = CGRect(x: 0, y: 0, width: width, height: height)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
let grayColorSpace = CGColorSpaceCreateDeviceGray()
|
let grayColorSpace = CGColorSpaceCreateDeviceGray()
|
||||||
let maskBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue).union(.byteOrder16Big)
|
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)
|
ccgContext?.setFillColor(.black)
|
||||||
maskContext?.fill(CGRect(origin: .zero, size: CGSize(width: self.width, height: self.height)))
|
ccgContext?.fill(rect)
|
||||||
|
|
||||||
maskContext?.draw(image, in: CGRect(origin: .zero, size: CGSize(width: self.width, height: self.height)))
|
ccgContext?.draw(self, in: rect)
|
||||||
|
|
||||||
return maskContext?.makeImage()
|
return ccgContext?.makeImage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue