'Core Graphics' CGBlendModes seem (partially) not consistent with PDF specs

I am running into some weird behavior with CGBlendMode in Core Graphics. My understanding is that the first 16 blend modes (normal (0) to luminosity (15)) are based straight on the PDF specification https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf.

(I am working with RGB blending space only and opaqueness is always 1.) My observed trouble is that when I implement the blendmodes manually based on the PDF specification and then compare to the colors to what core graphics paints, I get in some cases discrepancies. For example, according to the PDF specs of ColorBurn (p.325), when using sourceColor black (0,0,0) on backdropColor green (0,1,0) in colorBurn blend mode, the blended color should be black.

So if I run this code below, there should be a small black square showing on top of a larger green square (as shown on the right). But the actual blending result is the one on the left.

I get similar weirdness with ColorDodge and Hue and some others.

I have tried on several iOS devices and simulators and the errors are consistent across. Does anyone have some idea what I am doing wrong or misunderstanding?

Or am I wrong to assume that CoreGraphics and the PDF specification should match with regards to blend modes?

 public override func draw(_ rect: CGRect) {
    
        let blendColorSpace = CGColorSpaceCreateDeviceRGB()
        let bigSize: CGSize = CGSize(width: 100, height: 100) // Size of backdrop square
        let smallSize: CGSize = CGSize(width: 60, height: 60) // Size of source square
        
        guard let context = UIGraphicsGetCurrentContext() else { return }
        
        context.saveGState()
        
        // Draw opaque green backdrop
        let bdColor = CGColor(colorSpace: blendColorSpace, components: [0,1,0,1])! // Green (alpha 1)
        context.setFillColor(bdColor)
        context.setAlpha(1)
        context.setBlendMode(.normal)
        let origin = CGPoint(x: 100, y: 100)
        context.addRect(CGRect(origin: origin, size: bigSize))
        context.drawPath(using: .fill)
        
        // Draw opaque black smaller square on top with colorburn
        let scColor = CGColor(colorSpace: blendColorSpace, components: [0,0,0,1])! // Black (alpha 1)
        context.setFillColor(scColor)
        context.setBlendMode(.colorBurn)
        context.addRect(CGRect(origin: origin, size: smallSize))
        context.drawPath(using: .fill)
        
        context.restoreGState()
    }

Left is the CG reality, right what I would expect from the PDF specs.



Solution 1:[1]

It's just that your background color is too bright, so it refuses any darkening. Use a less intense background color and you'll see the darkening you expect.

Here's a simpler example (I use HSB notation because these blend modes depend upon luminance):

override func draw(_ rect: CGRect) {
    let bigSize = CGSize(width: 100, height: 100)
    let smallSize = CGSize(width: 60, height: 60)

    let brightness: CGFloat = 0.998 // **
    UIColor.init(hue: 0.3, saturation: 1, brightness: brightness, alpha: 1)
        .setFill()
    UIBezierPath(rect: CGRect(origin: .zero, size: bigSize))
        .fill(with: .normal, alpha: 1)

    UIColor.init(hue: 0.0, saturation: 1, brightness: 0.0, alpha: 1)
        .setFill()
    UIBezierPath(rect: CGRect(origin: .zero, size: smallSize))
        .fill(with: .colorBurn, alpha: 1)
}

The brightness of the background color is 0.998. If it is any higher, there is no darkening of the background color.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1