通过 Swift 替换图像中的一个像素并将其放入另一幅图像中

Replace exactly one pixel in an image and put it in another image via Swift

简单地说,如果我有一个图像I和另一个图像J,我想替换位置I(t,s)处的RGB值并将该像素分配给J(t,s)。我如何在 Core Image 或使用自定义内核中执行此操作?

考虑到 Core Image 的工作方式,这似乎不是一件容易的事。但是,我想知道也许有一种方法可以提取 (t,s) 处的像素值,创建一个仅包含该像素的 KJ 一样大的图像,然后覆盖 JK 仅在那一点。一个想法,希望有更好的方法。

我创建了一个有用的助手来按像素处理图像。它基本上创建了一个 RGBA 图像上下文,将图像复制到其中(以便我们可以处理图像数据,因为它是 jpeg 或其他东西),并获取它的原始数据缓冲区。这是我做的class:

public final class RGBAPixels {
    static let bitmapInfo = CGBitmapInfo.ByteOrder32Little.rawValue | CGImageAlphaInfo.PremultipliedLast.rawValue
    static let colorSpace = CGColorSpaceCreateDeviceRGB()
    public typealias Pixel = (r: UInt8, g: UInt8, b: UInt8, a: UInt8)

    let context : CGContext
    let pointer : UnsafeMutablePointer<Pixel>

    public let width : Int
    public let height : Int

    public let range : (x: Range<Int>, y: Range<Int>)

    /// Generates an image from the current pixels
    public var CGImage : CGImageRef? {
        return CGBitmapContextCreateImage(context)
    }

    public init?(CGImage: CGImageRef) {
        width = CGImageGetWidth(CGImage)
        height = CGImageGetHeight(CGImage)

        range = (0..<width, 0..<height)

        guard let context = CGBitmapContextCreate(
            nil, width, height, 8, sizeof(Pixel) * width,
            RGBAPixels.colorSpace, RGBAPixels.bitmapInfo)
        else { return nil }

        self.context = context

        let rect = CGRect(origin: .zero, size: CGSize(width: width, height: height))
        CGContextDrawImage(context, rect, CGImage)

        pointer = UnsafeMutablePointer(CGBitmapContextGetData(context))
    }


    public subscript (x: Int, y: Int) -> Pixel {
        get {
            assert(range.x ~= x && range.y ~= y, "Pixel position (\(x), \(y)) is out of the bounds \(range))")
            return pointer[y * width + x]
        }
        set {
            assert(range.x ~= x && range.y ~= y, "Pixel position (\(x), \(y)) is out of the bounds \(range))")
            pointer[y * width + x] = newValue
        }
    }
}

可以单独使用,但您可能希望获得更多便利:

public protocol Image {
    var CGImage : CGImageRef? { get }
}

public extension Image {
    public var pixels : RGBAPixels? {
        return CGImage.flatMap(RGBAPixels.init)
    }

    public func copy(@noescape modifying : (pixels: RGBAPixels) -> Void) -> CGImageRef? {
        return pixels.flatMap{ pixels in
            modifying(pixels: pixels)
            return pixels.CGImage
        }
    }
}

extension CGImage : Image {
    public var CGImage: CGImageRef? { return self }
}

#if os(iOS)

import class UIKit.UIImage

extension UIImage : Image {}

extension RGBAPixels {
    public var image : UIImage? {
        return CGImage.map(UIImage.init)
    }
}

#elseif os(OSX)

import class AppKit.NSImage

extension NSImage : Image {
    public var CGImage : CGImageRef? {
        var rect = CGRect(origin: .zero, size: size)
        return CGImageForProposedRect(&rect, context: nil, hints: nil)
    }
}

extension RGBAPixels {
    public var image : NSImage? {
        return cgImage.flatMap{ img in
            NSImage(CGImage: img, size: CGSize(width: width, height: height))
        }
    }
}

#endif

可以这样使用:

let image = UIImage(named: "image")!
let pixels = image.pixels!

// Add a red square
for x in 10..<15 {
    for y in 10..<15 {
        pixels[x, y] = (255, 0, 0, 255)
    }
}

let modifiedImage = pixels.image

// Copy the image, while changing the pixel at (10, 10) to be blue
let otherImage = UIImage(named: "image")!.copy{ [=12=][10, 10] = (0, 255, 0, 255) }


let a = UIImage(named: "image")!.pixels!
let b = UIImage(named: "image")!.pixels!

// Set the pixel at (10, 10) of a to the pixel at (20, 20) of b
a[10, 10] = b[20, 20]

let result = a.image!

我为演示打开包装,实际上不要在您的应用程序中执行此操作。

实施速度与 CPU 一样快。如果您需要以比复制更复杂的方式修改大量图像,您可能需要改用 CoreImage。

我用 OSX 的 NSImages iOS 的 UIImages 完成了这项工作。

如果您只想设置 一个 像素,您可以创建一个小的颜色内核,将传递的目标坐标与当前坐标进行比较,并适当地为输出着色。例如:

let kernel = CIColorKernel(string:
"kernel vec4 setPixelColorAtCoord(__sample pixel, vec2 targetCoord, vec4 targetColor)" +
    "{" +
    "   return int(targetCoord.x) == int(destCoord().x) && int(targetCoord.y) == int(destCoord().y) ? targetColor : pixel; " +
    "}"
)

然后,给定一张图片,假设是纯红色:

let image = CIImage(color: CIColor(red: 1, green: 0, blue: 0))
    .imageByCroppingToRect(CGRect(x: 0, y: 0, width: 640, height: 640))

如果我们想将像素设置为 500,100 为蓝色:

let targetCoord = CIVector(x: 500, y: 100)
let targetColor = CIColor(red: 0, green: 0, blue: 1)

下面将创建一个名为 finalCIImage,在一片红色的海洋中有一个蓝色像素:

let args = [image, targetCoord, targetColor]
let final = kernel?.applyWithExtent(image.extent, arguments: args)

如果您想绘制多个像素,这可能不是最佳解决方案。

西蒙