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

扶醉桌前 提交于 2020-01-03 04:53:19

问题


Simply put, if I have an image, I and another image J, I want to replace the RGB value at a position I(t,s) and assign that pixel to J(t,s). How might I do this in Core Image, or using a custom kernel?

This seems like it might not be an easy thing to do, considering the way Core Image works. However, I was wondering maybe there was a way to extract the value of the pixel at (t,s), create an image K as large as J with just that pixel, and then overlay J with K only at that one point. Just an idea, hopefully there's a better way.


回答1:


If you want to set just one pixel, you can create a small color kernel that compares a passed target coordinate with the current coordinate and colors the output appropriately. For example:

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; " +
    "}"
)

Then, given an image, let's say a solid red:

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

If we want to set the pixel at 500, 100 to blue:

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

The following will create a CIImage named final with a single blue pixel in a sea of red:

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

If you want to draw more than one pixel, this may not be the best solution though.

Simon




回答2:


I created a useful helper for handling images by pixel. It basically creates an RGBA image context, copies the image into it (so that we can work with the image data as it's jpeg or something), and gets the raw data buffer of it. This is the class I made:

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

It is useable by itself, but you might want to have some more convenience:

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

It is useable like this:

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{ $0[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!

I unwrapped for the demonstration, don't actually do this an your app.

The implementation is as fast as it can get with the CPU. If you need to modify lots of images in more complicated ways than just copying, you may want to use CoreImage instead.

I made this work with both OSX's NSImages and iOS's UIImages.



来源:https://stackoverflow.com/questions/38547360/replace-exactly-one-pixel-in-an-image-and-put-it-in-another-image-via-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!