Home > Net >  How to find current color in UIImageView and change repeatedly?
How to find current color in UIImageView and change repeatedly?

Time:02-23

I have this UIImageView where I am only changing the white color of the image. When the white color changes it doesn't change again because the white color is no longer white anymore. I want to access the new color and change it to a different color every time I press a button. Im using this func I found on github.

var currentColor = UIColor.init(red: 1, green: 1, blue: 1, alpha: 1)

@IBAction func changeColors(_ sender: Any) {

        
let randomRGB =  CGFloat.random(in: 0.0...1.0)
let randomRGB2 = CGFloat.random(in: 0.0...1.0)
let randomRGB3 = CGFloat.random(in: 0.0...1.0)


//randomly change color
var newColor = UIColor.init(red: randomRGB3, green: randomRGB2, blue: randomRGB, alpha: 1)

let changeColor = replaceColor(color: currentColor, withColor: newColor, image: mainImage.image!, tolerance: 0.5)

mainImage.image = changeColor

//change current color to new color
currentColor = newColor

}

extension ViewController {

func replaceColor(color: UIColor, withColor: UIColor, image: UIImage, tolerance: CGFloat) -> UIImage {

// This function expects to get source color(color which is supposed to be replaced)
// and target color in RGBA color space, hence we expect to get 4 color components: r, g, b, a

assert(color.cgColor.numberOfComponents == 4 && withColor.cgColor.numberOfComponents == 4,
       "Must be RGBA colorspace")

// Allocate bitmap in memory with the same width and size as source image
let imageRef = image.cgImage!
let width = imageRef.width
let height = imageRef.height

let bytesPerPixel = 4
let bytesPerRow = bytesPerPixel * width;
let bitsPerComponent = 8
let bitmapByteCount = bytesPerRow * height

let rawData = UnsafeMutablePointer<UInt8>.allocate(capacity: bitmapByteCount)

let context = CGContext(data: rawData, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: CGColorSpace(name: CGColorSpace.genericRGBLinear)!,
                        bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue)


let rc = CGRect(x: 0, y: 0, width: width, height: height)

// Draw source image on created context
context!.draw(imageRef, in: rc)


// Get color components from replacement color
let withColorComponents = withColor.cgColor.components
let r2 = UInt8(withColorComponents![0] * 255)
let g2 = UInt8(withColorComponents![1] * 255)
let b2 = UInt8(withColorComponents![2] * 255)
let a2 = UInt8(withColorComponents![3] * 255)

// Prepare to iterate over image pixels
var byteIndex = 0

while byteIndex < bitmapByteCount {

    // Get color of current pixel
    let red = CGFloat(rawData[byteIndex   0]) / 255
    let green = CGFloat(rawData[byteIndex   1]) / 255
    let blue = CGFloat(rawData[byteIndex   2]) / 255
    let alpha = CGFloat(rawData[byteIndex   3]) / 255

    let currentColor = UIColor(red: red, green: green, blue: blue, alpha: alpha)

    // Compare two colors using given tolerance value
    if compareColor(color: color, withColor: currentColor , withTolerance: tolerance) {

        // If the're 'similar', then replace pixel color with given target color
        rawData[byteIndex   0] = r2
        rawData[byteIndex   1] = g2
        rawData[byteIndex   2] = b2
        rawData[byteIndex   3] = a2
    }

    byteIndex = byteIndex   4;
}
    
// Retrieve image from memory context
let imgref = context!.makeImage()
let result = UIImage(cgImage: imgref!)

// Clean up a bit
rawData.deallocate()

return result
}

func compareColor(color: UIColor, withColor: UIColor, withTolerance: CGFloat) -> Bool 
{

var r1: CGFloat = 0.0, g1: CGFloat = 0.0, b1: CGFloat = 0.0, a1: CGFloat = 0.0;
var r2: CGFloat = 0.0, g2: CGFloat = 0.0, b2: CGFloat = 0.0, a2: CGFloat = 0.0;

color.getRed(&r1, green: &g1, blue: &b1, alpha: &a1);
withColor.getRed(&r2, green: &g2, blue: &b2, alpha: &a2);

return abs(r1 - r2) <= withTolerance &&
    abs(g1 - g2) <= withTolerance &&
    abs(b1 - b2) <= withTolerance &&
    abs(a1 - a2) <= withTolerance;
   }

}

CodePudding user response:

Here are a few observations that I made which might be impacting the results you see:

  1. As we discussed in the comments, if you want to start from the color which was changed previously, you need to hold on the color after the image has been updated beyond the scope of your function (you did this)

  2. The next issue about ending up with one color probably has a lot to do with the fault tolerance

  • When you try to change a color in an image with 0.5 (50%) fault tolerance of a given color, you are changing a huge number of colors in an image in the first pass
  • If there were 100 colors in a color system, you are going to look for 50 of those colors in the image and change them to 1 specific color
  • In the first pass, you start with white. Lets say that 75% of the image has colors that are similar to white with a 50% fault tolerance - 75% of the image is going to change to that color
  • With such a high fault tolerance, soon enough one color will appear that will be close to most of the colors in the image with a 50% fault tolerance and you will end up with colors with 1 image

Some ideas to improve the results

  1. Set a lower fault tolerance - you will see smaller changes and the same result could occur with 1 color but it will happen over a longer period of time
  2. If you really want to randomize and no get this 1 color results, I suggest to change how you use the currentColor and make changes to the original image, not the updated image (I have this example below)
  3. This will not impact the solution but better to handle optionals more safely as I see a lot of ! so I would recommend changing that
  4. Perform the image processing in a background thread (also in the example below)

Here is an update with an example

class ImageColorVC: UIViewController
{
    // UI Related
    private var loaderController: UIAlertController!
    let mainImage = UIImageView(image: UIImage(named: "art"))
    
    // Save the current color and the original image
    var currentColor = UIColor.init(red: 1, green: 1, blue: 1, alpha: 1)
    var originalImage: UIImage!
    
    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        // UI configuration, you can ignore
        view.backgroundColor = .white
        title = "Image Color"
        configureBarButton()
        configureImageView()
        
        // Store the original image
        originalImage = mainImage.image!
    }
    
    // MARK: AUTO LAYOUT
    private func configureBarButton()
    {
        let barButton = UIBarButtonItem(barButtonSystemItem: .refresh,
                                        target: self,
                                        action: #selector(changeColors))
        navigationItem.rightBarButtonItem = barButton
    }
    
    private func configureImageView()
    {
        mainImage.translatesAutoresizingMaskIntoConstraints = false
        mainImage.contentMode = .scaleAspectFit
        mainImage.clipsToBounds = true
        view.addSubview(mainImage)
        
        view.addConstraints([
            
            mainImage.leadingAnchor
                .constraint(equalTo: view.leadingAnchor),
            mainImage.topAnchor
                .constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            mainImage.trailingAnchor
                .constraint(equalTo: view.trailingAnchor),
            mainImage.bottomAnchor
                .constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            
        ])
    }
    
    // Configures a loader to show while image is processing
    private func configureLoaderController()
    {
        loaderController = UIAlertController(title: nil,
                                             message: "Processing",
                                             preferredStyle: .alert)
        
        let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10,
                                                                     y: 5,
                                                                     width: 50,
                                                                     height: 50))
        loadingIndicator.hidesWhenStopped = true
        loadingIndicator.style = UIActivityIndicatorView.Style.medium
        loadingIndicator.startAnimating();
        loaderController.view.addSubview(loadingIndicator)
    }
    
    //MARK: FACTORY
    
    // Similar to your function, only difference is that it uses
    // the original image
    private func performChangeColors()
    {
        let randomRGB =  CGFloat.random(in: 0.0...1.0)
        let randomRGB2 = CGFloat.random(in: 0.0...1.0)
        let randomRGB3 = CGFloat.random(in: 0.0...1.0)
        
        //randomly change color
        let newColor = UIColor.init(red: randomRGB3,
                                    green: randomRGB2,
                                    blue: randomRGB,
                                    alpha: 1)
        
        // Do work in the back ground
        DispatchQueue.global(qos: .background).async
        {
            let imageWithNewColor = self.replaceColor(color: self.currentColor,
                                                      withColor: newColor,
                                                      image: self.originalImage!,
                                                      tolerance: 0.5)
            
            // Update the UI on the main thread
            DispatchQueue.main.async
            {
                self.updateImageView(with: imageWithNewColor)
                
                //change current color to new color
                self.currentColor = newColor
            }
        }
    }
    
    @objc
    private func changeColors()
    {
        // Configure a loader to show while image is processing
        configureLoaderController()
        
        present(loaderController, animated: true) { [weak self] in
            self?.performChangeColors()
        }
    }
    
    // Update the UI
    private func updateImageView(with image: UIImage)
    {
        dismiss(animated: true) { [weak self] in
            self?.mainImage.image = image
        }
    }
}

After starting with this:

Change color of pixels in an image swift iOS

About 50 tries later, it still seems to work well:

Change color of UIImage pixels swift iOS

You can watch a longer video here to see a few more color changes that happen without leading to one single color

Hope this gives you enough to create the required workaround for your solution

  • Related