问题
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 NSImage
s and iOS's UIImage
s.
来源:https://stackoverflow.com/questions/38547360/replace-exactly-one-pixel-in-an-image-and-put-it-in-another-image-via-swift