Applying Gradient to UIImage Smoothly - ios

I am trying to apply gradient to UIImage using CoreGraphic; however, the result I got is not very nice. I want to create a black to transparent gradient at the bottom of the image to create a contrast for me to place some text. However, the gradient I was able doesn't blend well with the image; you can clearly see the separation in the center. The result I am looking for is like this application: http://capptivate.co/2014/02/17/yummly-3/
How should I apply the gradient to achieve this? ( I have to apply this to large quantity of images ).
My Result:
Here is my code:
func imageWithGradient(img:UIImage!) -> UIImage{
UIGraphicsBeginImageContext(img.size)
var context = UIGraphicsGetCurrentContext()
img.drawAtPoint(CGPointMake(0, 0))
let colorSpace = CGColorSpaceCreateDeviceRGB()
let locations:[CGFloat] = [0.50, 1.0]
//1 = opaque
//0 = transparent
let bottom = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5).CGColor
let top = UIColor(red: 0, green: 0, blue: 0, alpha: 0).CGColor
let gradient = CGGradientCreateWithColors(colorSpace,
[top, bottom], locations)
let startPoint = CGPointMake(img.size.width/2, 0)
let endPoint = CGPointMake(img.size.width/2, img.size.height)
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}

SWIFT 3
func imageWithGradient(img:UIImage!) -> UIImage {
UIGraphicsBeginImageContext(img.size)
let context = UIGraphicsGetCurrentContext()
img.draw(at: CGPoint(x: 0, y: 0))
let colorSpace = CGColorSpaceCreateDeviceRGB()
let locations:[CGFloat] = [0.0, 1.0]
let bottom = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5).cgColor
let top = UIColor(red: 0, green: 0, blue: 0, alpha: 0).cgColor
let colors = [top, bottom] as CFArray
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: locations)
let startPoint = CGPoint(x: img.size.width/2, y: 0)
let endPoint = CGPoint(x: img.size.width/2, y: img.size.height)
context!.drawLinearGradient(gradient!, start: startPoint, end: endPoint, options: CGGradientDrawingOptions(rawValue: UInt32(0)))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}

I tried this out in a Swift playground, I couldn't replicate the exact issue you are describing, however here are some suggestions that you could try.
You could change the way you set up the stops in the gradient, I think it would make more sense to instead of shifting the gradient using the stops, shift the start point, i.e instead of starting from the top edge, start from the vertical center, something like this
UIGraphicsBeginImageContext(image.size)
var context = UIGraphicsGetCurrentContext()
image.drawAtPoint(CGPointMake(0, 0))
let colorSpace = CGColorSpaceCreateDeviceRGB()
let locations:[CGFloat] = [0.0, 1.0]
let bottom = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5).CGColor
let top = UIColor(red: 0, green: 0, blue: 0, alpha: 0).CGColor
let gradient = CGGradientCreateWithColors(colorSpace,
[top, bottom], locations)
let startPoint = CGPointMake(image.size.width / 2, image.size.height / 2)
let endPoint = CGPointMake(image.size.width / 2, image.size.height)
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0)
let finalImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
Notice locations is now 0 and 1, while the startPoint y coordinate has shifted downwards.
Alternatively, look at passing kCGGradientDrawsBeforeStartLocation as an option to CGContextDrawLinearGradient, as that could also make a difference.

Related

Why is CILinearGradient resulting in a very NON-linear gradient?

I'm a relatively new Swift developer and I am using the CILinearGradient CIFilter to generate gradients that I can then use as backgrounds and textures. I was pretty happy with the way it was working, until I realized that the gradients coming out of it seem to be heavily skewed towards away from the black end of the spectrum.
At first I thought I was nuts, but then I created pure black-to-white and white-to-black gradients and put them on screen next to each other. I took a screenshot and brought it into Photoshop. then I looked at the color values. You can see that the ends of each gradient line up (pure black over pure white on one end, and the opposite on the other), but the halfway point of each gradient is significantly skewed towards the black end.
Is this an issue with the CIFilter or am I doing something wrong? Thanks to anyone with any insight on this!
Here's my code:
func gradient2colorIMG(UIcolor1: UIColor, UIcolor2: UIColor, width: CGFloat, height: CGFloat) -> CGImage? {
if let gradientFilter = CIFilter(name: "CILinearGradient") {
let startVector:CIVector = CIVector(x: 0 + 10, y: 0)
let endVector:CIVector = CIVector(x: width - 10, y: 0)
let color1 = CIColor(color: UIcolor1)
let color2 = CIColor(color: UIcolor2)
let context = CIContext(options: nil)
if let currentFilter = CIFilter(name: "CILinearGradient") {
currentFilter.setValue(startVector, forKey: "inputPoint0")
currentFilter.setValue(endVector, forKey: "inputPoint1")
currentFilter.setValue(color1, forKey: "inputColor0")
currentFilter.setValue(color2, forKey: "inputColor1")
if let output = currentFilter.outputImage {
if let cgimg = context.createCGImage(output, from: CGRect(x: 0, y: 0, width: width, height: height)) {
let gradImage = cgimg
return gradImage
}
}
}
}
return nil
}
and then I call it in SpriteKit using this code (but this is just so I can see them on the screen to compare the CGImages that are output by the function) ...
if let gradImage = gradient2colorIMG(UIcolor1: UIColor(red: 255.0 / 255.0, green: 255.0 / 255.0, blue: 255.0 / 255.0, alpha: 1.0), UIcolor2: UIColor(red: 0.0 / 255.0, green: 0.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0), width: 250, height: 80) {
let sampleback = SKShapeNode(path: CGPath(roundedRect: CGRect(x: 0, y: 0, width: 250, height: 80), cornerWidth: 5, cornerHeight: 5, transform: nil))
sampleback.fillColor = .white
sampleback.fillTexture = SKTexture(cgImage: gradImage)
sampleback.zPosition = 200
sampleback.position = CGPoint(x: 150, y: 50)
self.addChild(sampleback)
}
if let gradImage2 = gradient2colorIMG(UIcolor1: UIColor(red: 0.0 / 255.0, green: 0.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0), UIcolor2: UIColor(red: 255.0 / 255.0, green: 255.0 / 255.0, blue: 255.0 / 255.0, alpha: 1.0), width: 250, height: 80) {
let sampleback2 = SKShapeNode(path: CGPath(roundedRect: CGRect(x: 0, y: 0, width: 250, height: 80), cornerWidth: 5, cornerHeight: 5, transform: nil))
sampleback2.fillColor = .white
sampleback2.fillTexture = SKTexture(cgImage: gradImage2)
sampleback2.zPosition = 200
sampleback2.position = CGPoint(x: 150, y: 150)
self.addChild(sampleback2)
}
As another follow-up, I tried doing a red-blue gradient (so purely a change in hue) and it is perfectly linear (see below). The issue seems to be around the brightness.
A red-blue gradient DOES ramp its hue in a perfectly linear fashion
Imagine that black is 0 and white is 1. Then the problem here is that we intuitively think that 50% of black "is" a grayscale value of 0.5 — and that is not true.
To see this, consider the following core image experiment:
let con = CIContext(options: nil)
let white = CIFilter(name:"CIConstantColorGenerator")!
white.setValue(CIColor(color:.white), forKey:"inputColor")
let black = CIFilter(name:"CIConstantColorGenerator")!
black.setValue(CIColor(color:UIColor.black.withAlphaComponent(0.5)),
forKey:"inputColor")
let atop = CIFilter(name:"CISourceAtopCompositing")!
atop.setValue(white.outputImage!, forKey:"inputBackgroundImage")
atop.setValue(black.outputImage!, forKey:"inputImage")
let cgim = con.createCGImage(atop.outputImage!,
from: CGRect(x: 0, y: 0, width: 201, height: 50))!
let image = UIImage(cgImage: cgim)
let iv = UIImageView(image:image)
self.view.addSubview(iv)
iv.frame.origin = CGPoint(x: 100, y: 150)
What I've done here is to lay a 50% transparency black swatch on top of a white swatch. We intuitively imagine that the result will be a swatch that will read as 0.5. But it isn't; it's 0.737, the very same shade that is appearing at the midpoint of your gradients:
The reason is that everything here is happening, not in some mathematical vacuum, but in a color space adjusted for a specific gamma.
Now, you may justly ask: "But where did I specify this color space? This is not what I want!" Aha. You specified it in the first line, when you created a CIContext without overriding the default working color space.
Let's fix that. Change the first line to this:
let con = CIContext(options: [.workingColorSpace : NSNull()])
Now the output is this:
Presto, that's your 0.5 gray!
So what I'm saying is, if you create your CIContext like that, you will get the gradient you are after, with 0.5 gray at the midpoint. I'm not saying that that is any more "right" than the result you are getting, but at least it shows how to get that particular result with the code you already have.
(In fact, I think what you were getting originally is more "right", as it is adjusted for human perception.)
The midpoint of the CILinearGradient appears to correspond to 188, 188, 188, which looks like the “absolute whiteness” rendition of middle gray, which is not entirely unreasonable. (The CISmoothLinearGradient offers a smoother transition, but it doesn’t have the midpoint at 0.5, 0.5, 0.5, either.) As an aside, the “linear” in CILinearGradient and CISmoothLinearGradient refer to the shape of the gradient (to differentiate it from a “radial” gradient), not the nature of the color transitions within the gradient.
However if you want a gradient whose midpoint is 0.5, 0.5, 0.5, you can use CGGradient:
func simpleGradient(in rect: CGRect) -> UIImage {
return UIGraphicsImageRenderer(bounds: rect).image { context in
let colors = [UIColor.white.cgColor, UIColor.black.cgColor]
let colorSpace = CGColorSpaceCreateDeviceGray() // or RGB works, too
guard let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: nil) else { return }
context.cgContext.drawLinearGradient(gradient, start: .zero, end: CGPoint(x: rect.maxX, y: 0), options: [])
}
}
Alternatively, if you want a gradient background, you might define a UIView subclass that uses a CAGradientLayer as its backing layer:
class GradientView: UIView {
override class var layerClass: AnyClass { return CAGradientLayer.self }
var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer }
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
func configure() {
gradientLayer.colors = [UIColor.white.cgColor, UIColor.black.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
}
}

Decrease text alpha effect swift [duplicate]

I'm trying to draw a gradient to UILabel, but it draws only the colors I can't see the text.
I saw the code from here StackOverflow
my modification:
extension String{
func gradient(x:CGFloat,y:CGFloat, fontSize:CGFloat)->UIImage{
let font:UIFont = UIFont.systemFontOfSize(fontSize)
let name:String = NSFontAttributeName
let textSize: CGSize = self.sizeWithAttributes([name:font])
let width:CGFloat = textSize.width // max 1024 due to Core Graphics limitations
let height:CGFloat = textSize.height // max 1024 due to Core Graphics limitations
UIGraphicsBeginImageContext(CGSizeMake(width, height))
let context = UIGraphicsGetCurrentContext()
UIGraphicsPushContext(context!)
//draw gradient
let glossGradient:CGGradientRef?
let rgbColorspace:CGColorSpaceRef?
let num_locations:size_t = 2
let locations:[CGFloat] = [ 0.0, 1.0 ]
let components:[CGFloat] = [(202 / 255.0), (197 / 255.0), (52 / 255.0), 1.0, // Start color
(253 / 255.0), (248 / 255.0), (101 / 255.0), 1.0] // End color
rgbColorspace = CGColorSpaceCreateDeviceRGB()
glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations)
let topCenter = CGPointMake(0, 0);
let bottomCenter = CGPointMake(0, textSize.height);
CGContextDrawLinearGradient(context, glossGradient, topCenter, bottomCenter, CGGradientDrawingOptions.DrawsBeforeStartLocation)
UIGraphicsPopContext()
let gradientImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return gradientImage
}
}
and the setup of the imageView
self.timeLeftIV = UIImageView(frame: CGRectMake(self.pyramidFakeView.frame.origin.x/2-25,0, 100,20))
self.timeLeftIV.image = "59:48".gradient(timeLeftIV.frame.origin.x, y: timeLeftIV.frame.origin.y, fontSize: 6.0)
the result of the code:
There is an easier way of getting a gradient as a UIImage. You can use a CAGradientLayer. For example:
func yellowGradientImage(bounds:CGRect) -> UIImage
{
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor(red: (202 / 255.0), green: (197 / 255.0), blue: (52 / 255.0), alpha: 1.0).CGColor, UIColor(red: (253 / 255.0), green: (248 / 255.0), blue: (101 / 255.0), alpha: 1.0).CGColor]
gradientLayer.bounds = bounds
UIGraphicsBeginImageContextWithOptions(gradientLayer.bounds.size, true, 0.0)
let context = UIGraphicsGetCurrentContext()
gradientLayer.renderInContext(context!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
To apply the gradient to text you next need to use the image returned as the textColor using UIColor(patternImage: ...). For example:
let label = UILabel()
label.font = UIFont.systemFontOfSize(150.0)
label.text = "HELLO WORLD"
label.sizeToFit()
let image = yellowGradientImage(label.bounds)
label.textColor = UIColor(patternImage: image)
Which results in:
swift 3.0:
func yellowGradientImage(bounds:CGRect) -> UIImage
{
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor(red: (202 / 255.0), green: (197 / 255.0), blue: (52 / 255.0), alpha: 1.0).cgColor, UIColor(red: (253 / 255.0), green: (248 / 255.0), blue: (101 / 255.0), alpha: 1.0).cgColor]
gradientLayer.bounds = bounds
UIGraphicsBeginImageContextWithOptions(gradientLayer.bounds.size, true, 0.0)
let context = UIGraphicsGetCurrentContext()
gradientLayer.render(in: context!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 150.0)
label.text = "HELLO WORLD"
label.sizeToFit()
let image = yellowGradientImage(bounds: label.bounds)
label.textColor = UIColor(patternImage: image)
add a view, modify draw rect with below code.
override func drawRect(rect: CGRect) {
// Drawing code
let currentContext = UIGraphicsGetCurrentContext()
CGContextSaveGState(currentContext);
let colorSpace = CGColorSpaceCreateDeviceRGB()
let startColor = UIColor(red: 0.1, green: 0.0, blue: 0.0, alpha: 0.0)
let startColorComponents = CGColorGetComponents(startColor.CGColor)
let middleColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.2)
let middleColorComponents = CGColorGetComponents(middleColor.CGColor)
let lowerMiddleColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.3)
let lowerMiddleColorComponents = CGColorGetComponents(lowerMiddleColor.CGColor)
let endColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.5)
let endColorComponents = CGColorGetComponents(endColor.CGColor)
var colorComponents = [startColorComponents[0], startColorComponents[1], startColorComponents[2], startColorComponents[3], middleColorComponents[0], middleColorComponents[1], middleColorComponents[2], middleColorComponents[3], lowerMiddleColorComponents[0], lowerMiddleColorComponents[1], lowerMiddleColorComponents[2], lowerMiddleColorComponents[3], endColorComponents[0], endColorComponents[1], endColorComponents[2], endColorComponents[3]]
var locations:[CGFloat] = [0.0, 0.6, 0.8, 1.0]
let gradient = CGGradientCreateWithColorComponents(colorSpace,&colorComponents,&locations,4)
let startPoint = CGPointMake(0, 0)
let endPoint = CGPointMake(0, self.bounds.height)
CGContextAddRect(currentContext, CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height));
CGContextClip(currentContext);
CGContextDrawLinearGradient(currentContext,gradient,startPoint,endPoint, CGGradientDrawingOptions.DrawsBeforeStartLocation)
CGContextRestoreGState(currentContext)
}
Now in storyboard add a view change class to your gradient class and add a label to this view.
See if it works for you.

I want to color from touch point of gradient layer, how?

I want to color from touchpoint of gradient layer.
I tried this :
func viewtap(sender: UITapGestureRecognizer) {
let touchPoint = sender.locationInView(self.gradientview) // Change to whatever view you want the point for
print("\(touchPoint))")
colorOfPoint(touchPoint)
}
This method I used but it gives original color only.
func colorOfPoint(point:CGPoint) -> UIColor
{
let colorSpace:CGColorSpace = CGColorSpaceCreateDeviceRGB()!
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue)
var pixelData:[UInt8] = [0, 0, 0, 0]
let context = CGBitmapContextCreate(&pixelData, 1, 1, 8, 4, colorSpace, bitmapInfo.rawValue)
CGContextTranslateCTM(context, -point.x, -point.y);
self.view.layer.renderInContext(context!)
let red:CGFloat = CGFloat(pixelData[0])/CGFloat(255.0)
let green:CGFloat = CGFloat(pixelData[1])/CGFloat(255.0)
let blue:CGFloat = CGFloat(pixelData[2])/CGFloat(255.0)
let alpha:CGFloat = CGFloat(pixelData[3])/CGFloat(255.0)
let color:UIColor = UIColor(red: red, green: green, blue: blue, alpha: alpha)
colorChange.tintColor = UIColor(red: red, green: green, blue: blue, alpha: alpha)
return color
}
Gradient Layer:
Always give .CGColor after uicolor. now i will pick color from any in UIView
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.shadowview.bounds
let colorTop = UIColor(red:CGFloat(RedColor)/255.0 , green: CGFloat (GreenColor)/255.0 , blue: CGFloat(BlueColor)/255.0,alpha: CGFloat(alphalab)/255.0)**.CGColor**
let colorBottom = UIColor(red: 35.0/255.0, green: 2.0/255.0, blue: 2.0/255.0, alpha: 1.0)**.CGColor**
colorChange.tintColor = UIColor(red:CGFloat(RedColor)/255.0 , green: CGFloat (GreenColor)/255.0 , blue: CGFloat(BlueColor)/255.0,alpha: CGFloat(alphalab)/255.0)
gradientLayer.colors = [colorTop, colorBottom]
gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.locations = [ 0.0, 1.0]
self.shadowview.layer.addSublayer(gradientLayer)

Core Graphics- Using CGGradient with strokes and text

So here is my code for creating a particular gradient:
let newBlueOne = UIColor(red: 0.141, green: 0.776, blue: 0.863, alpha: 1.000)
let newBlueTwo = UIColor(red: 0.318, green: 0.290, blue: 0.616, alpha: 1.000)
And here is the code for the gradient itself:
let newBlue = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), [newBlue1.CGColor, newBlue2.CGColor], [0, 1])!
Here is the code for the rectangle I'm trying to make:
let rectanglePath = UIBezierPath(roundedRect: CGRect(x: 22, y: 35, width: 194, height: 38), cornerRadius: 19)
CGContextSaveGState(context)
rectanglePath.addClip()
CGContextDrawLinearGradient(context, translucent1, CGPoint(x: 119, y: 35), CGPoint(x: 119, y: 73), CGGradientDrawingOptions())
CGContextRestoreGState(context)
rectanglePath.lineWidth = 1
rectanglePath.stroke()
I want to use that newBlue gradient above as the stroke colour for this rectangle created. I also want to change the colour of some text to that newBlue gradient. Unfortunately, I can't quite figure it out. Can anyone help?
I appreciate your responses and look forward to reading them. Thank you so much :)
You can not directly stroke a path with a gradient.
To do what you want, you will need to go to the Core Graphics Layer, and use the CGContext Functions.
First, you create your path, and set its various properties, such as line weight.
Then, you use the CGContextReplacePathWithStrokedPath(context) function. Look it up in the CGContext documentation.
Then, you use the resultant path as a clip path.
And then, you paint that area with your gradient.
Like This:
override func drawRect(rect: CGRect) {
let newBlueOne = UIColor(red: 0.141, green: 0.776, blue: 0.863, alpha: 1.000)
let newBlueTwo = UIColor(red: 0.318, green: 0.290, blue: 0.616, alpha: 1.000)
let newBlue = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), [newBlueOne.CGColor, newBlueTwo.CGColor], [0, 1])!
let lineWeight: CGFloat = 20.0
let context: CGContext = UIGraphicsGetCurrentContext()!
let rect: CGRect = CGRectMake(40.0, 40.0, 200.0, 200.0)
CGContextAddRect(context, rect)
CGContextSetLineWidth(context, lineWeight)
CGContextReplacePathWithStrokedPath(context)
CGContextClip(context)
let startPoint: CGPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect))
let endPoint: CGPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect))
CGContextDrawLinearGradient(context, newBlue, startPoint, endPoint, CGGradientDrawingOptions(rawValue: 0))
}
I only addressed painting the border. I don't know about the text.

Get average color of UIImage in Swift

I was recently attempting to convert the code from here into Swift. However, I keep getting a white color, no matter the image. Here's my code:
// Playground - noun: a place where people can play
import UIKit
extension UIImage {
func averageColor() -> UIColor {
var colorSpace = CGColorSpaceCreateDeviceRGB()
var rgba: [CGFloat] = [0,0,0,0]
var context = CGBitmapContextCreate(&rgba, 1, 1, 8, 4, colorSpace, CGBitmapInfo.fromRaw(CGImageAlphaInfo.PremultipliedLast.toRaw())!)
rgba
CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), self.CGImage)
if rgba[3] > 0 {
var alpha = rgba[3] / 255
var multiplier = alpha / 255
return UIColor(red: rgba[0] * multiplier, green: rgba[1] * multiplier, blue: rgba[2] * multiplier, alpha: alpha)
} else {
return UIColor(red: rgba[0] / 255, green: rgba[1] / 255, blue: rgba[2] / 255, alpha: rgba[3] / 255)
}
}
}
var img = UIImage(data: NSData(contentsOfURL: NSURL(string: "http://upload.wikimedia.org/wikipedia/commons/c/c3/Aurora_as_seen_by_IMAGE.PNG")))
img.averageColor()
Thanks in advance.
CoreImage in iOS 9: use the CIAreaAverage filter and pass the extent of your entire image to be averaged.
Plus, it's much faster since it'll either be running on the GPU or as a highly-optimized CPU CIKernel.
import UIKit
extension UIImage {
func areaAverage() -> UIColor {
var bitmap = [UInt8](count: 4, repeatedValue: 0)
if #available(iOS 9.0, *) {
// Get average color.
let context = CIContext()
let inputImage = CIImage ?? CoreImage.CIImage(CGImage: CGImage!)
let extent = inputImage.extent
let inputExtent = CIVector(x: extent.origin.x, y: extent.origin.y, z: extent.size.width, w: extent.size.height)
let filter = CIFilter(name: "CIAreaAverage", withInputParameters: [kCIInputImageKey: inputImage, kCIInputExtentKey: inputExtent])!
let outputImage = filter.outputImage!
let outputExtent = outputImage.extent
assert(outputExtent.size.width == 1 && outputExtent.size.height == 1)
// Render to bitmap.
context.render(outputImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: kCIFormatRGBA8, colorSpace: CGColorSpaceCreateDeviceRGB())
} else {
// Create 1x1 context that interpolates pixels when drawing to it.
let context = CGBitmapContextCreate(&bitmap, 1, 1, 8, 4, CGColorSpaceCreateDeviceRGB(), CGBitmapInfo.ByteOrderDefault.rawValue | CGImageAlphaInfo.PremultipliedLast.rawValue)!
let inputImage = CGImage ?? CIContext().createCGImage(CIImage!, fromRect: CIImage!.extent)
// Render to bitmap.
CGContextDrawImage(context, CGRect(x: 0, y: 0, width: 1, height: 1), inputImage)
}
// Compute result.
let result = UIColor(red: CGFloat(bitmap[0]) / 255.0, green: CGFloat(bitmap[1]) / 255.0, blue: CGFloat(bitmap[2]) / 255.0, alpha: CGFloat(bitmap[3]) / 255.0)
return result
}
}
Swift 3
func areaAverage() -> UIColor {
var bitmap = [UInt8](repeating: 0, count: 4)
if #available(iOS 9.0, *) {
// Get average color.
let context = CIContext()
let inputImage: CIImage = ciImage ?? CoreImage.CIImage(cgImage: cgImage!)
let extent = inputImage.extent
let inputExtent = CIVector(x: extent.origin.x, y: extent.origin.y, z: extent.size.width, w: extent.size.height)
let filter = CIFilter(name: "CIAreaAverage", withInputParameters: [kCIInputImageKey: inputImage, kCIInputExtentKey: inputExtent])!
let outputImage = filter.outputImage!
let outputExtent = outputImage.extent
assert(outputExtent.size.width == 1 && outputExtent.size.height == 1)
// Render to bitmap.
context.render(outputImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: kCIFormatRGBA8, colorSpace: CGColorSpaceCreateDeviceRGB())
} else {
// Create 1x1 context that interpolates pixels when drawing to it.
let context = CGContext(data: &bitmap, width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 4, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
let inputImage = cgImage ?? CIContext().createCGImage(ciImage!, from: ciImage!.extent)
// Render to bitmap.
context.draw(inputImage!, in: CGRect(x: 0, y: 0, width: 1, height: 1))
}
// Compute result.
let result = UIColor(red: CGFloat(bitmap[0]) / 255.0, green: CGFloat(bitmap[1]) / 255.0, blue: CGFloat(bitmap[2]) / 255.0, alpha: CGFloat(bitmap[3]) / 255.0)
return result
}
Here's a solution:
func averageColor() -> UIColor {
let rgba = UnsafeMutablePointer<CUnsignedChar>.alloc(4)
let colorSpace: CGColorSpaceRef = CGColorSpaceCreateDeviceRGB()
let info = CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue)
let context: CGContextRef = CGBitmapContextCreate(rgba, 1, 1, 8, 4, colorSpace, info)
CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), self.CGImage)
if rgba[3] > 0 {
let alpha: CGFloat = CGFloat(rgba[3]) / 255.0
let multiplier: CGFloat = alpha / 255.0
return UIColor(red: CGFloat(rgba[0]) * multiplier, green: CGFloat(rgba[1]) * multiplier, blue: CGFloat(rgba[2]) * multiplier, alpha: alpha)
} else {
return UIColor(red: CGFloat(rgba[0]) / 255.0, green: CGFloat(rgba[1]) / 255.0, blue: CGFloat(rgba[2]) / 255.0, alpha: CGFloat(rgba[3]) / 255.0)
}
}
Swift 3:
func areaAverage() -> UIColor {
var bitmap = [UInt8](repeating: 0, count: 4)
let context = CIContext(options: nil)
let cgImg = context.createCGImage(CoreImage.CIImage(cgImage: self.cgImage!), from: CoreImage.CIImage(cgImage: self.cgImage!).extent)
let inputImage = CIImage(cgImage: cgImg!)
let extent = inputImage.extent
let inputExtent = CIVector(x: extent.origin.x, y: extent.origin.y, z: extent.size.width, w: extent.size.height)
let filter = CIFilter(name: "CIAreaAverage", withInputParameters: [kCIInputImageKey: inputImage, kCIInputExtentKey: inputExtent])!
let outputImage = filter.outputImage!
let outputExtent = outputImage.extent
assert(outputExtent.size.width == 1 && outputExtent.size.height == 1)
// Render to bitmap.
context.render(outputImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: kCIFormatRGBA8, colorSpace: CGColorSpaceCreateDeviceRGB())
// Compute result.
let result = UIColor(red: CGFloat(bitmap[0]) / 255.0, green: CGFloat(bitmap[1]) / 255.0, blue: CGFloat(bitmap[2]) / 255.0, alpha: CGFloat(bitmap[3]) / 255.0)
return result
}
Are you setting up your context correctly? If I look at the documentation for the CGBitmapContext Reference:
https://developer.apple.com/library/ios/documentation/graphicsimaging/Reference/CGBitmapContext/index.html#//apple_ref/c/func/CGBitmapContextCreate
it looks like you are only allocating enough memory for the image that would be contained in the CGFloat array. It also looks like you are telling the compiler that your image is only going to be one pixel by one pixel.
It looks like that size is also being confirmed as one pixel by one pixel when you are setting your CGRect in CGContextDrawImage.
If the Playground is only creating an image one pixel by one pixel, that would explain why you are only seeing a white screen.

Resources