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,
|
||||
|
||||
kPSDBlendModeVividLight,
|
||||
kPSDBlendModeHardLight
|
||||
kPSDBlendModeHardLight,
|
||||
|
||||
kPSDBlendModeColor,
|
||||
kPSDBlendModeAdd,
|
||||
kPSDBlendModeLighterColor,
|
||||
kPSdBlendModeLinearLight,
|
||||
kPSDBlendModePinLight,
|
||||
kPSDBlendModeHardMix,
|
||||
kPSDBlendModeDivide,
|
||||
kPSDBlendModeOverlay,
|
||||
kPSDBlendSoftLight,
|
||||
kPSDBlendModeDifference,
|
||||
kPSDBlendModeExclusion,
|
||||
kPSDBlendModeSubtract,
|
||||
kPSDBlendModeHue,
|
||||
kPSDBlendModeSaturation,
|
||||
kPSDBlendModeLuminosity,
|
||||
};
|
||||
|
||||
@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 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 };
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
@ -585,17 +592,31 @@ class Document: NSDocument {
|
|||
|
||||
let layerImage = layerContext?.makeImage()
|
||||
|
||||
if layer.clipped && previousImage != nil {
|
||||
let result = previousImage!.toGrayscale()
|
||||
let newImage = layerImage!.masking(result!)
|
||||
|
||||
previousImage = newImage
|
||||
} else {
|
||||
previousImage = layerImage
|
||||
// apply image
|
||||
return kernel!.apply(foreground: CIImage(cgImage: layerImage!), background: previousImage == nil ? CIImage(color: .clear) : CIImage(cgImage: previousImage!), colorSpace: info.colorSpace)!
|
||||
}
|
||||
|
||||
// apply image
|
||||
return kernel!.apply(foreground: CIImage(cgImage: previousImage!), background: CIImage(color: .clear), 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,7 +647,10 @@ 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()
|
||||
|
@ -651,14 +675,36 @@ class Document: NSDocument {
|
|||
}
|
||||
}
|
||||
|
||||
let clippingLayers = getAllClippingLayers(layer: layer)
|
||||
if !clippingLayers.isEmpty {
|
||||
let layerImage = layerContext?.makeImage()
|
||||
|
||||
if layer.clipped && previousImage != nil {
|
||||
let result = previousImage!.toGrayscale()
|
||||
let newImage = layerImage!.masking(result!)
|
||||
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.mask != nil && maskContext != nil {
|
||||
Swift.print("masking " + layer.name + "...")
|
||||
|
||||
previousImage = newImage
|
||||
} else if layer.mask != nil && maskContext != nil {
|
||||
let maskImage = (maskContext?.makeImage())!
|
||||
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? {
|
||||
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)
|
||||
|
||||
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 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)))
|
||||
ccgContext?.draw(self, in: rect)
|
||||
|
||||
return maskContext?.makeImage()
|
||||
return ccgContext?.makeImage()
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue