通过 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)
处的像素值,创建一个仅包含该像素的 K
与 J
一样大的图像,然后覆盖 J
与 K
仅在那一点。一个想法,希望有更好的方法。
我创建了一个有用的助手来按像素处理图像。它基本上创建了一个 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 的 NSImage
s 和 iOS 的 UIImage
s 完成了这项工作。
如果您只想设置 一个 像素,您可以创建一个小的颜色内核,将传递的目标坐标与当前坐标进行比较,并适当地为输出着色。例如:
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)
下面将创建一个名为 final
的 CIImage
,在一片红色的海洋中有一个蓝色像素:
let args = [image, targetCoord, targetColor]
let final = kernel?.applyWithExtent(image.extent, arguments: args)
如果您想绘制多个像素,这可能不是最佳解决方案。
西蒙
简单地说,如果我有一个图像I
和另一个图像J
,我想替换位置I(t,s)
处的RGB值并将该像素分配给J(t,s)
。我如何在 Core Image 或使用自定义内核中执行此操作?
考虑到 Core Image 的工作方式,这似乎不是一件容易的事。但是,我想知道也许有一种方法可以提取 (t,s)
处的像素值,创建一个仅包含该像素的 K
与 J
一样大的图像,然后覆盖 J
与 K
仅在那一点。一个想法,希望有更好的方法。
我创建了一个有用的助手来按像素处理图像。它基本上创建了一个 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 的 NSImage
s 和 iOS 的 UIImage
s 完成了这项工作。
如果您只想设置 一个 像素,您可以创建一个小的颜色内核,将传递的目标坐标与当前坐标进行比较,并适当地为输出着色。例如:
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)
下面将创建一个名为 final
的 CIImage
,在一片红色的海洋中有一个蓝色像素:
let args = [image, targetCoord, targetColor]
let final = kernel?.applyWithExtent(image.extent, arguments: args)
如果您想绘制多个像素,这可能不是最佳解决方案。
西蒙