-->

Creating transparent gradient and use it as an alp

2020-07-18 09:56发布

问题:

I am trying to make a gradient and use it as an alpha mask. Right now, I am able to make an image similar to this (from black to transparent):

This is the code which I use to make all this:

private func createImage(width: CGFloat, height: CGFloat) -> CGImageRef?{

        if let ciFilter = CIFilter(name: "CILinearGradient"){

            let ciContext = CIContext()

            ciFilter.setDefaults()

            let startColor  = CIColor(red: 0, green: 0, blue: 0, alpha: 0)
            let endColor    = CIColor(red: 0, green: 0, blue: 0, alpha: 1)

            let startVector = CIVector(x: 0, y: height-10)
            let endVector   = CIVector(x: 0, y: height-22)

            ciFilter.setValue(startColor,   forKey: "inputColor0")
            ciFilter.setValue(endColor,     forKey: "inputColor1")
            ciFilter.setValue(startVector,  forKey: "inputPoint0")
            ciFilter.setValue(endVector,    forKey: "inputPoint1")



            if let outputImage = ciFilter.outputImage {

                let  cgImage:CGImageRef = ciContext.createCGImage(outputImage, fromRect: CGRect(x: 0, y: 0, width: width, height: height))

                return  cgImage

            }

        }

        return nil
    }

For me, this way works pretty much perfect because I create a mask only once in my code (when GUI element is created) so performance is not affected at all.

The thing is that I actually need a resulting image (gradient texture) to look like this (gradients should have fixed height, but size of black area may vary):

Because CILinearGradient filter doesn't have an inputImage parameter, I can't chain two filters one after another. But rather I have to create an image, and apply a filter, then to create another image, and apply a new filter.

I solved this just like described above. Also I had to extend a process in three steps (create upper gradient, create middle, black area and create lower gradient) and then to combine all that into one image.

I wonder is there anything about CILinearGradient filter I am not aware of ? Maybe there is an easier way to attack this problem and make a complex gradient?

Note that I am using SpriteKit and solving this with CAGradientLayer is not a way I want to go.

回答1:

I'm not sure Core Image is the right tool for the job here — you'd probably be better off using CGGradient functions to draw your image.


Aside: What you're asking would be possible but cumbersome in CoreImage. Think of it by analogy to what you'd do to create these images as a user of Photoshop or other common graphics software... the CoreImage way would be something like this:

  1. Create a layer for the upper gradient, fill the entire canvas with your gradient, and transform the layer to the proper position and size
  2. Create a layer for the middle black portion, fill the entire canvas with black, and transform the layer to the proper position and size
  3. Ditto #1, but for the bottom gradient.

You could do this with a combination of generator filters, transform filters, and compositing filters...

gradient    -> transform -\
                           }-> composite -\
solid color -> transform -/                \
                                            }-> composite
gradient    -> transform ————————————————--/

But setting that up would be ugly.

Likewise, in Photoshop you could use selection tools and fill/gradient tools to create your gradient-fill-gradient design completely within a single layer... that's what drawing a single image using CoreGraphics is analogous to.


So, how to do that single-pass CG drawing? Something like this...

func createImage(width: CGFloat, _ height: CGFloat) -> CGImage {

    let gradientOffset: CGFloat = 10 // white at bottom and top before/after gradient
    let gradientLength: CGFloat = 12 // height of gradient region

    // create colors and gradient for drawing with
    let space = CGColorSpaceCreateDeviceRGB()
    let startColor = UIColor.whiteColor().CGColor
    let endColor = UIColor.blackColor().CGColor
    let colors: CFArray = [startColor, endColor]
    let gradient = CGGradientCreateWithColors(space, colors, [0,1])

    // start an image context
    UIGraphicsBeginImageContext(CGSize(width: width, height: height))
    defer { UIGraphicsEndImageContext() }
    let context = UIGraphicsGetCurrentContext()

    // fill the whole thing with white (that'll show through at the ends when done)
    CGContextSetFillColorWithColor(context, startColor)
    CGContextFillRect(context, CGRect(x: 0, y: 0, width: width, height: height))

    // draw top gradient
    CGContextDrawLinearGradient(context, gradient, CGPoint(x: 0, y: gradientOffset), CGPoint(x: 0, y: gradientOffset + gradientLength), [])

    // fill solid black middle
    CGContextSetFillColorWithColor(context, endColor)
    CGContextFillRect(context, CGRect(x: 0, y: gradientOffset + gradientLength, width: width, height: height - 2 * gradientOffset - 2 * gradientLength))

    // draw bottom gradient
    CGContextDrawLinearGradient(context, gradient, CGPoint(x: 0, y: height - gradientOffset), CGPoint(x: 0, y: height - gradientOffset - gradientLength), [])

    return CGBitmapContextCreateImage(context)!
}