change an UI Image into a String array - ios

I have an UIImage made of only 4 types of pixel.
static let black = RGBA32(red: 0, green: 0, blue: 0, alpha: 255)
static let red = RGBA32(red: 255, green: 0, blue: 0, alpha: 255)
static let green = RGBA32(red: 0, green: 255, blue: 0, alpha: 255)
static let blue = RGBA32(red: 0, green: 0, blue: 255, alpha: 255)
( reference to the code used to create the Image https://stackoverflow.com/a/40207142/7010252 )
I saved the image in the phone's photo library then I open the image in my app again. I know that the number of pixel is the same.
So how can I get the RGBA data of all the pixels?

The first thing done here is to loop through your image and create a map of all the points in it.
func getPixelColor(image: UIImage) {
let width = image.size.width
let height = image.size.height
for x in 0..<Int(width) {
for y in 0..<Int(height) {
let color = image.pixelColor(CGPoint(x: CGFloat(x), y: CGFloat(y)))
print(color.RGBA)
}
}
}
Once it has the color it wants, it then calls the RGBA function in the UIColor extension to provide the string you want.
Next, we use an extension of UIImage to then take the points provided by the above function and query the image data for its UIColor data.
extension UIImage {
func pixelColor(_ point: CGPoint) -> UIColor {
let pixelData = cgImage?.dataProvider?.data
let pointerData: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
let pixelInfo = Int(((self.size.width * point.y) + point.x)) * 4
let maxValue: CGFloat = 255.0
let compare: CGFloat = 0.99
let r: CGFloat = (CGFloat(pointerData[pixelInfo]) / maxValue) > compare ? 1.0 : 0.0
let g: CGFloat = (CGFloat(pointerData[pixelInfo + 1]) / maxValue) > compare ? 1.0 : 0.0
let b: CGFloat = (CGFloat(pointerData[pixelInfo + 2]) / maxValue) > compare ? 1.0 : 0.0
let a = CGFloat(pointerData[pixelInfo + 3]) / maxValue
return UIColor(red: r, green: g, blue: b, alpha: a)
}
}
This extension uses the raw data to determine the RGB values of each pixel. It then performs a crude check to play safe with CGFloats to see if the value is 255.0 for that particular color. If it is, it will return a value of 1.0, otherwise it returns 0.0.
Next, there is an extension for UIColor that will provide the formatted string you are looking for.
extension UIColor {
var RGBA: String {
guard let components = cgColor.components, components.count == 4 else {
return ""
}
return "\(components[0])-\(components[1])-\(components[2])-\(components[3])"
}
}
This should provide the 1-0-0-1 type of values you seek. You can also modify this to include any additional information you need.

Related

How convert RGBA to UIColor and back

How can i convert my struct RGBA to UIColor and back
struct RGBA {
var r: UInt8
var g: UInt8
var b: UInt8
var a: UInt8
var toUIColor: UIColor {
let red = CGFloat(r) / CGFloat(255)
let green = CGFloat(g) / CGFloat(255)
let blue = CGFloat(b) / CGFloat(255)
let alpha = CGFloat(a) / CGFloat(255)
return UIColor(displayP3Red: CGFloat(red),
green: CGFloat(green),
blue: CGFloat(blue),
alpha: CGFloat(alpha))
}
}
extension UIColor {
var toRGBA: RGBA {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
getRed(&red, green: &green, blue: &blue, alpha: &alpha)
return RGBA(r: UInt8(max(0, min(255, red * 255))),
g: UInt8(max(0, min(255, green * 255))),
b: UInt8(max(0, min(255, blue * 255))),
a: UInt8(max(0, min(255, alpha * 255))))
}
}
When I use t his code, im loose color accuracy
Draw pipline
select UIColor -> toRGBA ->
0.7490196078431373 0.35294117647058826 0.9490196078431372 1.0
select color on canvas -> toUIColor
draw line -> toRGBA ->
0.803921568627451 0.3215686274509804 0.9803921568627451 1.0
var canvasSize = PixelSize(width: 16, height: 16) // (Int, Int)
func createImage(by pixels: [RGBA]) -> UIImage? {
let colorSpace = CGColorSpaceCreateDeviceRGB()
let data = UnsafeMutableRawPointer(mutating: pixels)
let bitmapContext = CGContext(data: data,
width: canvasSize.width,
height: canvasSize.height,
bitsPerComponent: 8,
bytesPerRow: 4 * canvasSize.width,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
guard let image = bitmapContext?.makeImage() else { return nil }
return UIImage(cgImage: image)
}
Whats wrong?
Welcome!
The issue is that you use two different color spaces:
When converting from RGBA to UIColor, you assume the RGBA values are in Display P3 (UIColor(displayP3Red:...).
But the CGContext you create with the "device RGB" color space, which is an sRGB color space as far as I know.
If you'd use the init(red:, green:, blue:, alpha:) initializer of UIColor it should work.

How to apply CIFilter on Image Pixel

I know it is possible to change color of pixel using pixel buffer, like in the below code, but I just want to blur a pixel using 'CIFilter' rather than changing color. I don't want to apply a 'CIFilter' on whole image.
//data pointer – stores an array of the pixel components. For example (r0, b0, g0, a0, r1, g1, b1, a1 .... rn, gn, bn, an)
let data : UnsafeMutablePointer<UInt8> = calloc(bytesPerRow, height)!.assumingMemoryBound(to: UInt8.self)
//get the index of the pixel (4 components times the x position plus the y position times the row width)
let pixelIndex = 4 * (location.x + (location.y * width))
//set the pixel components to the color components
data[pixelIndex] = red
data[pixelIndex+1] = green
data[pixelIndex+2] = blue
data[pixelIndex+3] = alpha
Also can we use below code for applying CIFilter on Pixel?
if let pixelData = self.cgImage?.dataProvider?.data {
let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
let pixelInfo: Int = ((Int(self.size.width) * Int(pos.y)) + Int(pos.x)) * 4
let red = CGFloat(data[pixelInfo]) / CGFloat(255.0)
let green = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
let blue = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
let alpha = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)
}
Check out the CIBlendWithMask filter. It allows you to create a mask of any shape (even a single pixel) and use that to blend the input with another input. If you make inputBackgroundImage be the original image, and make inputImage be the original image with your desired filter applied, the inputImageMask is an all-black image with only the single pixel you want to change in white.
I typed this up pretty quick without testing code - could be a few errors. I have done something very similar recently, so shouldn't be too off. I'd like to know whatcha get and if this doesn't work, I bet it's close.
/*
Implementations
Notes:
- I don't know what kind of `CIFilter` blur you'd like to apply, so I'm just using one from here
- https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/#//apple_ref/doc/filter/ci/CIBoxBlur
*/
//Normal Image
let inputImage:UIImage = originalImage
//Blurred Image of only the BLURRED PIXELS -- we change the rest of the pixels to clear -- thus we can use this as the backgroundImage and the maskedImage
let unblurredImage = getBackgroundImage()
let filter = CIFilter(name: "CIBoxBlur")
filter?.setValue(unblurredImage, kCIInputImageKey)
let blurredImage = filter?.outputImage
//Now we can blend the 2 images
let blendFilter = CIFilter(name: "CIBlendWithAlphaMask")
blendFilter?.setValue(CIImage(image: inputImage), kCIInputImageKey)
blendFilter?.setValue(blurredImage, "inputBackgroundImage")
blendFilter?.setValue(blurredImage, "inputMaskImage")
let finalCIImage = blendFilter?.outputImage
let finalImage = UIImage(ciImage: finalCIImage)
/*
Functions used in the process
*/
//Create an image of only the pixels we want to blur
func getBackgroundImage(ciimage: CIImage) -> UIImage {
let inputCGImage = ciimage.convertCIImageToCGImage()!
let colorSpace = CGColorSpaceCreateDeviceRGB()
let width = inputCGImage.width
let height = inputCGImage.height
let bytesPerPixel = 4
let bitsPerComponent = 8
let bytesPerRow = bytesPerPixel * width
let bitmapInfo = RGBA32.bitmapInfo
guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else {
print("Couldn't create CGContext")
return nil
}
context.draw(inputCGImage, in: CGRect(x: 0, y: 0, width: width, height: height))
let pixelBuffer = buffer.bindMemory(to: RGBA32.self, capacity: width * height)
for row in 0 ..< Int(height) {
for column in 0 ..< Int(width) {
let offset = row * width + column
/*
You need to define aPixelIWantBlurred however you desire
Also, we don't edit the pixels we want to blur - we edit the other pixels to a transparent value. This allows us to use this as the background image and the masked image
*/
if pixelBuffer[offset] != aPixelIWantBlurred {
pixelBuffer[offset] = RGBA32.init(red: 0, green: 0, blue: 0, alpha: 0)
}
}
}
let outputCGImage = context.makeImage()!
let outputImage = UIImage(cgImage: outputCGImage, scale: image.scale, orientation: image.imageOrientation)
return outputImage
}
extension CIImage {
func convertCIImageToCGImage() -> CGImage! {
let context = CIContext(options: nil)
return context.createCGImage(self, from: self.extent)
}
}
struct RGBA32: Equatable {
private var color: UInt32
var redComponent: UInt8 {
return UInt8((color >> 24) & 255)
}
var greenComponent: UInt8 {
return UInt8((color >> 16) & 255)
}
var blueComponent: UInt8 {
return UInt8((color >> 8) & 255)
}
var alphaComponent: UInt8 {
return UInt8((color >> 0) & 255)
}
init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) {
let red = UInt32(red)
let green = UInt32(green)
let blue = UInt32(blue)
let alpha = UInt32(alpha)
color = (red << 24) | (green << 16) | (blue << 8) | (alpha << 0)
}
static let red = RGBA32(red: 255, green: 0, blue: 0, alpha: 255)
static let green = RGBA32(red: 0, green: 255, blue: 0, alpha: 255)
static let blue = RGBA32(red: 0, green: 0, blue: 255, alpha: 255)
static let white = RGBA32(red: 255, green: 255, blue: 255, alpha: 255)
static let black = RGBA32(red: 0, green: 0, blue: 0, alpha: 255)
static let magenta = RGBA32(red: 255, green: 0, blue: 255, alpha: 255)
static let yellow = RGBA32(red: 255, green: 255, blue: 0, alpha: 255)
static let cyan = RGBA32(red: 0, green: 255, blue: 255, alpha: 255)
static let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
static func ==(lhs: RGBA32, rhs: RGBA32) -> Bool {
return lhs.color == rhs.color
}
}

How to draw a gradient color wheel using CAGradientLayer?

I got some reference from these links-
What is the Algorithm behind a color wheel?
Math behind the Colour Wheel
Basic color schemes
How to fill a bezier path with gradient color
I have gone through the concept of "HSV colour space". But I want to draw a color wheel using RGB with the help of CAGradientLayer.
Here is the code snippet of making a color wheel by using simple RGB color array and UIBezierPath -
func drawColorWheel()
{
context?.saveGState()
range = CGFloat(100.00 / CGFloat(colorArray.count))
for k in 0 ..< colorArray.count
{
drawSlice(startPercent: CGFloat(k) * range, endPercent: CGFloat(CGFloat(k + 1) * range), color: colorArray.object(at: k) as! UIColor)
}
context?.restoreGState()
}
func drawSlice(startPercent: CGFloat, endPercent: CGFloat, color: UIColor)
{
let startAngle = getAngleAt(percentage: startPercent)
let endAngle = getAngleAt(percentage: endPercent)
let path = getArcPath(startAngle: startAngle, endAngle: endAngle)
color.setFill()
path.fill()
}
Where getAngleAt() and getArcPath() are the private functions to draw the path with an angle.
Here is the final output of my code -
Now, my question is the how to give these colors a gradient effect so that each colors mix up with gradient color layer?
One approach is to build an image and manipulate the pixel buffer manually:
Create CGContext of certain size and certain type;
Access its data buffer via data property;
Rebind that to something that makes it easy to manipulate that buffer (I use a Pixel, a struct for the 32-bit representation of a pixel);
Loop through the pixels, one by one, converting that to an angle and radius for the circle within this image; and
Create a pixel of the appropriate color if it's inside the circle; make it a zero-alpha pixel if not.
So in Swift 3:
func buildHueCircle(in rect: CGRect, radius: CGFloat, scale: CGFloat = UIScreen.main.scale) -> UIImage? {
let width = Int(rect.size.width * scale)
let height = Int(rect.size.height * scale)
let center = CGPoint(x: width / 2, y: height / 2)
let space = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width * 4, space: space, bitmapInfo: Pixel.bitmapInfo)!
let buffer = context.data!
let pixels = buffer.bindMemory(to: Pixel.self, capacity: width * height)
var pixel: Pixel
for y in 0 ..< height {
for x in 0 ..< width {
let angle = fmod(atan2(CGFloat(x) - center.x, CGFloat(y) - center.y) + 2 * .pi, 2 * .pi)
let distance = hypot(CGFloat(x) - center.x, CGFloat(y) - center.y)
let value = UIColor(hue: angle / 2 / .pi, saturation: 1, brightness: 1, alpha: 1)
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
value.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
if distance <= (radius * scale) {
pixel = Pixel(red: UInt8(red * 255),
green: UInt8(green * 255),
blue: UInt8(blue * 255),
alpha: UInt8(alpha * 255))
} else {
pixel = Pixel(red: 255, green: 255, blue: 255, alpha: 0)
}
pixels[y * width + x] = pixel
}
}
let cgImage = context.makeImage()!
return UIImage(cgImage: cgImage, scale: scale, orientation: .up)
}
Where
struct Pixel: Equatable {
private var rgba: UInt32
var red: UInt8 {
return UInt8((rgba >> 24) & 255)
}
var green: UInt8 {
return UInt8((rgba >> 16) & 255)
}
var blue: UInt8 {
return UInt8((rgba >> 8) & 255)
}
var alpha: UInt8 {
return UInt8((rgba >> 0) & 255)
}
init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) {
rgba = (UInt32(red) << 24) | (UInt32(green) << 16) | (UInt32(blue) << 8) | (UInt32(alpha) << 0)
}
static let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
static func ==(lhs: Pixel, rhs: Pixel) -> Bool {
return lhs.rgba == rhs.rgba
}
}
That yields:
Or you can tweak the brightness or saturation based upon the radius:

Iterating through all pixels of a UIImage

I need some help with how to iterate through every pixel in a UIImage. I am finding the red, green, blue, and alpha color values in every pixel then comparing them to see if a certain color exists in the photo.
I have an extension that returns these color values of a pixel:
extension UIImage { func getPixelColor(pos: CGPoint) -> UIColor {
let pixelData = self.cgImage!.dataProvider!.data
let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
let pixelInfo: Int = ((Int(self.size.width) * Int(pos.y)) + Int(pos.x)) * 4
let r = CGFloat(data[pixelInfo]) / CGFloat(255.0)
let g = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
let b = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
let a = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)
return UIColor(red: r, green: g, blue: b, alpha: a)
}
}
I am able to find the color value of a specific pixel in the image and check it to see if it matches a specific color value:
let specificPoint = CGPoint(x: 25, y: 25)
let findColorValue = displayImage.image?.getPixelColor(pos: specificPoint)
var redval: CGFloat = 0
var greenval: CGFloat = 0
var blueval: CGFloat = 0
var alphaval: CGFloat = 0
resultLabel.text = "r: \(redval) g: \(greenval) b: \(blueval) a: \(alphaval)"
findColorValue?.getRed(&redval, green: &greenval, blue: &blueval, alpha: &alphaval)
if resultLabel.text == "r: 1.0 g: 0.792156862745098 b: 0.474509803921569 a: 0.0156862745098039" {
resultLabel.text = "YES"
} else {
resultLabel.text = "NO"
}
I need help creating a for loop to loop through all the pixels in the image and being able to test them for the specific color value.
I'm also not sure if how I get the color values of a pixel is 100% right so feel free to tell me if I'm doing something wrong.
Thanks
Use a nested for loop:
for yCo in 0 ..< Int(image.size.height) {
for xCo in 0 ..< Int(image.size.width) {
if image.getPixelColor(pos: CGPoint(x: xCo, y: yCo)) == UIColor(red: 1, green: 1, blue: 1, alpha: 1) {
//Next step
}
}
}

iOS Find Color at Point Between Two Colors

I have a problem: I need to be able to take two colors and make a 'virtual gradient' out of them. I then need to be able to find the color at any point on this line. My current approach is this:
if (fahrenheit < kBottomThreshold)
{
return [UIColor colorWithRed:kBottomR/255.0f green:kBottomG/255.0f blue:kBottomB/255.0f alpha:1];
}
if (fahrenheit > kTopThreshold)
{
return [UIColor colorWithRed:kTopR/255.0f green:kTopG/255.0f blue:kTopB/255.0f alpha:1];
}
double rDiff = kTopR - kBottomR;
double gDiff = kTopG - kBottomG;
double bDiff = kTopB - kBottomB;
double tempDiff = kTopThreshold - kBottomThreshold;
double rValue;
double gValue;
double bValue;
rValue = kBottomR + ((rDiff/tempDiff) * fahrenheit);
gValue = kBottomG + ((gDiff/tempDiff) * fahrenheit);
bValue = kBottomB + ((bDiff/tempDiff) * fahrenheit);
return [UIColor colorWithRed:rValue/255.0f green:gValue/255.0f blue:bValue/255.0f alpha:1];
Variables:
fahrenheit is a variable passed into my function that is the number on this virtual line that I want to find the color for.
kTopR, kTopB, and kTopG are the RGB values for one end of the gradient. Same for their kBottom counterparts.
kBottomThreshold and kTopThreshold are the endpoints of my gradient.
Here's my problem: When fahrenheit goes over either end of the gradient, the gradient seems to 'jump' to a different value.
I've included an example project, hosted on my S3 server, here.
You really need to download the project and try it on the simulator/device to see what I mean (unless you are crazy smart and can tell just by looking at the code)
Swift - 3.0 && 4.0
extension UIColor {
func toColor(_ color: UIColor, percentage: CGFloat) -> UIColor {
let percentage = max(min(percentage, 100), 0) / 100
switch percentage {
case 0: return self
case 1: return color
default:
var (r1, g1, b1, a1): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
var (r2, g2, b2, a2): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
guard self.getRed(&r1, green: &g1, blue: &b1, alpha: &a1) else { return self }
guard color.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) else { return self }
return UIColor(red: CGFloat(r1 + (r2 - r1) * percentage),
green: CGFloat(g1 + (g2 - g1) * percentage),
blue: CGFloat(b1 + (b2 - b1) * percentage),
alpha: CGFloat(a1 + (a2 - a1) * percentage))
}
}
}
Usage:-
let colorRed = UIColor.red
let colorBlue = UIColor.blue
let colorOutput = colorRed.toColor(colorBlue, percentage: 50)
Result
The problem is that you're not subtracting kBottomThreshold from farenheit.
But let's simplify.
First, we want to map the input temperature to a parameter t in the range [0 ... 1]. Then, we want to map t to an output in the range [kBottomR ... kTopR], and also to an output in the range [kBottomG ... kTopG], and also to an output in the range [kBottomB ... kTopB].
UIColor *colorForDegreesFahrenheit(double fahrenheit) {
double t = (fahrenheit - kBottomThreshold) / (kTopThreshold - kBottomThreshold);
// Clamp t to the range [0 ... 1].
t = MAX(0.0, MIN(t, 1.0));
double r = kBottomR + t * (kTopR - kBottomR);
double g = kBottomG + t * (kTopG - kBottomG);
double b = kBottomB + t * (kTopB - kBottomB);
return [UIColor colorWithRed:r/255 green:g/255 blue:b/255 alpha:1];
}
Thanks #ramchandra-n I implemented the extension to get the intermediate color from an array of colors by percentage
extension Array where Element: UIColor {
func intermediate(percentage: CGFloat) -> UIColor {
let percentage = Swift.max(Swift.min(percentage, 100), 0) / 100
switch percentage {
case 0: return first ?? .clear
case 1: return last ?? .clear
default:
let approxIndex = percentage / (1 / CGFloat(count - 1))
let firstIndex = Int(approxIndex.rounded(.down))
let secondIndex = Int(approxIndex.rounded(.up))
let fallbackIndex = Int(approxIndex.rounded())
let firstColor = self[firstIndex]
let secondColor = self[secondIndex]
let fallbackColor = self[fallbackIndex]
var (r1, g1, b1, a1): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
var (r2, g2, b2, a2): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
guard firstColor.getRed(&r1, green: &g1, blue: &b1, alpha: &a1) else { return fallbackColor }
guard secondColor.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) else { return fallbackColor }
let intermediatePercentage = approxIndex - CGFloat(firstIndex)
return UIColor(
red: CGFloat(r1 + (r2 - r1) * intermediatePercentage),
green: CGFloat(g1 + (g2 - g1) * intermediatePercentage),
blue: CGFloat(b1 + (b2 - b1) * intermediatePercentage),
alpha: CGFloat(a1 + (a2 - a1) * intermediatePercentage)
)
}
}
}
You can use it to get an intermediate color between two or more colors:
let color = [.green, .yellow, .red].intermediate(percentage: 70)
In case your gradient is more complex than a 2 color gradient, you may consider drawing a CGGradientRef into a temporary CGImageRef and directly read RGBA values from the image buffer.
Here is something that I had to do with a 5 gradient stops and colors:
CGFloat tmpImagewidth = 1000.0f; // Make this bigger or smaller if you need more or less resolution (number of different colors).
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// create a gradient
CGFloat locations[] = { 0.0,
0.35,
0.55,
0.8,
1.0 };
NSArray *colors = #[(__bridge id) [UIColor redColor].CGColor,
(__bridge id) [UIColor greenColor].CGColor,
(__bridge id) [UIColor blueColor].CGColor,
(__bridge id) [UIColor yellowColor].CGColor,
(__bridge id) [UIColor redColor].CGColor,
];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) colors, locations);
CGPoint startPoint = CGPointMake(0, 0);
CGPoint endPoint = CGPointMake(tmpImagewidth, 0);
// create a bitmap context to draw the gradient to, 1 pixel high.
CGContextRef context = CGBitmapContextCreate(NULL, tmpImagewidth, 1, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
// draw the gradient into it
CGContextAddRect(context, CGRectMake(0, 0, tmpImagewidth, 1));
CGContextClip(context);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
// Get our RGB bytes into a buffer with a couple of intermediate steps...
// CGImageRef -> CFDataRef -> byte array
CGImageRef cgImage = CGBitmapContextCreateImage(context);
CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
CFDataRef pixelData = CGDataProviderCopyData(provider);
// cleanup:
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
CGImageRelease(cgImage);
CGContextRelease(context);
const UInt8* data = CFDataGetBytePtr(pixelData);
// we got all the data we need.
// bytes in the data buffer are a succession of R G B A bytes
// For instance, the color of the point 27% in our gradient is:
CGFloat x = tmpImagewidth * .27;
int pixelIndex = (int)x * 4; // 4 bytes per color
UIColor *color = [UIColor colorWithRed:data[pixelIndex + 0]/255.0f
green:data[pixelIndex + 1]/255.0f
blue:data[pixelIndex + 2]/255.0f
alpha:data[pixelIndex + 3]/255.0f];
// done fetching color data, finally release the buffer
CGDataProviderRelease(provider);
I am not saying this is better than the "math way" in the answer above, certainly there is a memory and cpu tax that goes into producing the temporary image.
The advantage of this however, is that the code complexity stays the same no matter how many gradient stops you need...
I would like to extend/modify #Sebastien Windal´s answer, as his answer works great for my use case, but can be further improved by getting the pixelData directly from the context (see Get pixel data as array from UIImage/CGImage in swift).
I implemented his answer in swift, therefore the changes are also written in swift.
Just pass the Int Array to the context before drawing the gradient. The array will then be filled with the pixelData when drawing the context.
let dataSize = tmpImagewidth * 1 * 4
var pixelData = [UInt8](repeating: 0, count: Int(dataSize))
let context = CGContext(data: &pixelData, width: Int(tmpImagewidth), height: 1, bitsPerComponent: 8, bytesPerRow: 4 * Int(tmpImagewidth), space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
Then we can skip creating an image first to read the pixelData from there.
Swift 5.3
Get n colors between two colors:
import UIKit
extension CGFloat {
var getPercentageValues: [CGFloat] {
let increment: CGFloat = 100/(self-1)
var values = [CGFloat]()
let last: CGFloat = 100
var value: CGFloat = 0
while value <= last {
values.append(value)
value += increment
}
return values
}
}
extension UIColor {
func toColor(_ color: UIColor, percentage: CGFloat) -> UIColor {
let percentage = max(min(percentage, 100), 0) / 100
switch percentage {
case 0: return self
case 1: return color
default:
var (r1, g1, b1, a1): (CGFloat, CGFloat, CGFloat, CGFloat) = (.zero, .zero, .zero, .zero)
var (r2, g2, b2, a2): (CGFloat, CGFloat, CGFloat, CGFloat) = (.zero, .zero, .zero, .zero)
guard self.getRed(&r1, green: &g1, blue: &b1, alpha: &a1) else { return self }
guard color.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) else { return self }
return UIColor(red: CGFloat(r1 + (r2 - r1) * percentage),
green: CGFloat(g1 + (g2 - g1) * percentage),
blue: CGFloat(b1 + (b2 - b1) * percentage),
alpha: CGFloat(a1 + (a2 - a1) * percentage))
}
}
func getColors(to color: UIColor, with quantity: CGFloat) -> [UIColor] {
quantity.getPercentageValues.map { self.toColor(color, percentage: $0 ) }
}
}
let redColor = UIColor.red
let blueColor = UIColor.blue
print(redColor.getColors(to: blueColor, with: 10))
Result
SwiftUI Color operator + overload
func +(lhs: Color, rhs: Color) -> Color {
let lhsUIColor = UIColor(lhs)
let rhsUIColor = UIColor(rhs)
var (r1, g1, b1, a1): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
var (r2, g2, b2, a2): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
guard lhsUIColor.getRed(&r1, green: &g1, blue: &b1, alpha: &a1) else { return Color(lhsUIColor) }
guard rhsUIColor.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) else { return Color(rhsUIColor) }
return Color(Color.RGBColorSpace.sRGB, red: Double(r1 + r2) / 2, green: Double(g1 + g2) / 2, blue: Double(b1 + b2) / 2, opacity: Double(a1 + a2) / 2)
}

Resources