【问题标题】:Get average color of UIImage in Swift在 Swift 中获取 UIImage 的平均颜色
【发布时间】:2014-10-12 23:05:28
【问题描述】:

我最近尝试将代码从 here 转换为 Swift。但是,无论图像如何,我都会不断获得白色。这是我的代码:

// 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()

提前致谢。

【问题讨论】:

标签: ios swift uiimage


【解决方案1】:

iOS 9 中的 CoreImage:使用 CIAreaAverage 过滤器并传递要平均的整个图像的范围。

此外,它的速度要快得多,因为它既可以在 GPU 上运行,也可以作为高度优化的 CPU CIKernel 运行。

【讨论】:

  • 你真的分析过它@god-of-biscuits吗?我认为您会惊讶地发现 CoreImage 在这种情况下速度较慢,尽管 CIAreaAverage 过滤器提供了更好的结果。
  • 您确定您只设置一次 CIContext,而不是拆除一个并为每个图像运行重新启动另一个吗?
  • 我同意这里,如果您只设置 CIContext 一次,CoreImage 的性能令人印象深刻。当您想尝试有效地提取图像的颜色时,就会出现问题。我还没有找到一种有效的方法。
  • 查看 ImageIO API。它们让您可以在内存占用非常小的情况下操作非常大的图像。做所有的平铺、切片、切块和切丝:)。非常简单的 API。
  • 你有一些代码作为示例@GodofBiscuits吗?
【解决方案2】:
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
    }
}

斯威夫特 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
    }

【讨论】:

  • 出于某种原因,这段代码的 iOS 9 版本让我为不同的图像返回了一个恒定的灰色?
  • iOS 9 swift 3 代码在我的应用程序中仍然消耗最多的 CPU 使用率。这么贵,有没有其他办法?
【解决方案3】:

这里有一个解决方案:

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

【讨论】:

  • 上下文应该创建一次,而不是每次调用函数时。
  • @DannyBravo 我想看看你的解决方案。
  • 您应该在核心映像中完成所有操作。如果您正在制作多个图像,请创建一个 CIContexr,然后使用适当的 CIFilter 循环遍历图像以获取它。
  • 这是一个扩展,所以对我来说拥有全局上下文没有任何意义。
  • 永远,永远,永远,永远手动计算这个:) 使用 GPU。很简单:hackingwithswift.com/example-code/media/…
【解决方案4】:

斯威夫特 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
}

【讨论】:

    【解决方案5】:

    您是否正确设置了上下文?如果我查看 CGBitmapContext Reference 的文档:

    https://developer.apple.com/library/ios/documentation/graphicsimaging/Reference/CGBitmapContext/index.html#//apple_ref/c/func/CGBitmapContextCreate

    看起来您只为将包含在 CGFloat 数组中的图像分配了足够的内存。看起来您还告诉编译器您的图像只会是一个像素一个像素的。

    当您在 CGContextDrawImage 中设置 CGRect 时,看起来该大小也被确认为一个像素一个像素。

    如果 Playground 只逐个像素地创建图像,那就可以解释为什么您只看到白屏。

    【讨论】:

    • 我基本上是从我包含的链接 (bobbygeorgescu.com/2011/08/finding-average-color-of-uiimage) 中复制代码并将其转换为 Swift。它试图做的是让 Quartz 将整个图像绘制到一个像素中,从而使图像平滑并为一个像素提供平均颜色。在我看来,这很聪明,如果它在 Swift 中工作。我相信我的问题要么与我的数组的启动/引用方式有关,要么与您提到的分配的内存量有关。你介意给我一个更准确的答案吗?谢谢!
    • 如果您查看操场上的控制台,您会发现代表颜色的浮点数组永远不会从零开始变化。我相信你没有看到任何颜色的原因是因为数组永远不会改变。您永远不会调用语句的第一部分,因为 alpha 永远不会大于零。此外,一切都将乘以零,并且什么都不会改变。我注意到在您设置了只有“rgba”的上下文之后,您在代码中有一行。我想知道为什么它在那里。你打算做些什么来改变价值观吗?
    • 我最初很困惑,因为在过去我使用 Quartz 并且您有白色时,白色代表一个或更大的值。我本来希望所有的零都是黑色而不是白色。
    • 啊。对不起,我弄错了。 alpha 等于 0,因此它显示为白色。我的错。
    • 当我将“rgba”单独放置时,我可以在操场上看到那里的价值。