如何在 UIImageView 中找到当前颜色并重复更改?

How to find current color in UIImageView and change repeatedly?

我有这个UIImageView,我只改变图像的白色。当白色改变时,它不会再改变,因为白色不再是白色了。我想在每次按下按钮时访问新颜色并将其更改为不同的颜色。我正在使用我在 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;
   }

}

以下是我所做的一些观察,可能会影响您看到的结果:

  1. 正如我们在评论中讨论的那样,如果你想从之前更改的颜色开始,你需要在图像更新超出你的功能范围后保持颜色(你做到了)

  2. 下一个最后一个颜色的问题估计和容错性有很大关系

  • 当您尝试以给定颜色的 0.5 (50%) 容错更改图像中的颜色时,您在第一遍中更改了图像中的大量颜色
  • 如果颜色系统中有 100 种颜色,您将在图像中寻找其中的 50 种颜色并将它们更改为 1 种特定颜色
  • 在第一关中,您从白色开始。假设图像的 75% 具有与白色相似的颜色且容错率为 50% - 图像的 75% 将变为该颜色
  • 具有如此高的容错率,很快就会出现一种颜色,该颜色与图像中的大多数颜色接近,容错率为 50%,您最终会得到 1 张图像的颜色

改进结果的一些想法

  1. 设置较低的容错率 - 您会看到较小的变化,同样的结果可能会出现在 1 种颜色上,但它会在更长的时间内发生
  2. 如果你真的想随机化并且没有得到这 1 种颜色的结果,我建议改变你使用 currentColor 的方式并对原始图像进行更改,而不是更新后的图像(我在下面有这个例子)
  3. 这不会影响解决方案,但可以更好地更安全地处理选项,因为我看到很多 ! 所以我建议更改
  4. 在后台线程中执行图像处理(也在下面的示例中)

这是一个带有示例的更新

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
        }
    }
}

从这里开始后:

大约 50 次尝试后,它似乎仍然有效:

您可以观看 a longer video here 以了解发生的更多颜色变化而不会导致单一颜色

希望这足以让您为您的解决方案创建所需的解决方法