绘制 CIImage 太慢
Drawing a CIImage is too slow
我正在创建一个需要对图像实时应用滤镜的应用程序。将 UIImage
转换为 CIImage
和应用过滤器都是非常快速的操作,但是将创建的 CIImage
转换回 CGImageRef
并显示图像(1/5 秒,如果需要实时编辑,这实际上很多)。
图像大约为 2500 x 2500 像素大,这很可能是部分问题
目前,我正在使用
let image: CIImage //CIImage with applied filters
let eagl = EAGLContext(API: EAGLRenderingAPI.OpenGLES2)
let context = CIContext(EAGLContext: eagl, options: [kCIContextWorkingColorSpace : NSNull()])
//this line takes too long for real-time processing
let cg: CGImage = context.createCGImage(image, fromRect: image.extent)
我研究过使用 EAGLContext.drawImage()
context.drawImage(image, inRect: destinationRect, fromRect: image.extent)
但是我找不到任何可靠的文档来说明这是如何完成的,或者它是否会更快
有没有更快的方式在屏幕上显示 CIImage
(在 UIImageView
中或直接在 CALayer
中)?我想避免过度降低图像质量,因为这可能会引起用户的注意。
这是一张相当大的图片,所以这绝对是其中的一部分。我建议查看 GPUImage 来进行单幅图像过滤。您可以完全跳过使用 CoreImage。
let inputImage:UIImage = //... some image
let stillImageSource = GPUImagePicture(image: inputImage)
let filter = GPUImageSepiaFilter()
stillImageSource.addTarget(filter)
filter.useNextFrameForImageCapture()
stillImageSource.processImage()
您可以使用 GlkView 并按照您所说的 context.drawImage() 进行渲染:
let glView = GLKView(frame: superview.bounds, context: EAGLContext(API: .OpenGLES2))
let context = CIContext(EAGLContext: glView.context)
处理后渲染图像:
glView.bindDrawable()
context.drawImage(image, inRect: destinationRect, fromRect: image.extent)
glView.display()
我最终使用了 context.drawImage(image, inRect: destinationRect, fromRect: image.extent)
方法。这是我创建的图像视图 class
import Foundation
//GLKit must be linked and imported
import GLKit
class CIImageView: GLKView{
var image: CIImage?
var ciContext: CIContext?
//initialize with the frame, and CIImage to be displayed
//(or nil, if the image will be set using .setRenderImage)
init(frame: CGRect, image: CIImage?){
super.init(frame: frame, context: EAGLContext(API: EAGLRenderingAPI.OpenGLES2))
self.image = image
//Set the current context to the EAGLContext created in the super.init call
EAGLContext.setCurrentContext(self.context)
//create a CIContext from the EAGLContext
self.ciContext = CIContext(EAGLContext: self.context)
}
//for usage in Storyboards
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
self.context = EAGLContext(API: EAGLRenderingAPI.OpenGLES2)
EAGLContext.setCurrentContext(self.context)
self.ciContext = CIContext(EAGLContext: self.context)
}
//set the current image to image
func setRenderImage(image: CIImage){
self.image = image
//tell the processor that the view needs to be redrawn using drawRect()
self.setNeedsDisplay()
}
//called automatically when the view is drawn
override func drawRect(rect: CGRect){
//unwrap the current CIImage
if let image = self.image{
//multiply the frame by the screen's scale (ratio of points : pixels),
//because the following .drawImage() call uses pixels, not points
let scale = UIScreen.mainScreen().scale
let newFrame = CGRectMake(rect.minX, rect.minY, rect.width * scale, rect.height * scale)
//draw the image
self.ciContext?.drawImage(
image,
inRect: newFrame,
fromRect: image.extent
)
}
}
}
然后,要使用它,只需
let myFrame: CGRect //frame in self.view where the image should be displayed
let myImage: CIImage //CIImage with applied filters
let imageView: CIImageView = CIImageView(frame: myFrame, image: myImage)
self.view.addSubview(imageView)
在将 UIImage 转换为 CIImage 之前将其调整为屏幕大小也有帮助。在高质量图像的情况下,它可以大大加快速度。实际保存时请确保使用全尺寸图像。
就是这样!然后,更新视图中的图像
imageView.setRenderImage(newCIImage)
//note that imageView.image = newCIImage won't work because
//the view won't be redrawn
可能值得考虑 Metal 并用 MTKView
显示。
您需要一个可以使用 MTLCreateSystemDefaultDevice()
创建的 Metal 设备。这用于创建命令队列和 Core Image 上下文。这两个对象都是持久的并且实例化起来非常昂贵,因此理想情况下应该创建一次:
lazy var commandQueue: MTLCommandQueue =
{
return self.device!.newCommandQueue()
}()
lazy var ciContext: CIContext =
{
return CIContext(MTLDevice: self.device!)
}()
您还需要一种颜色 space:
let colorSpace = CGColorSpaceCreateDeviceRGB()!
在渲染 CIImage
时,您需要创建一个短暂的命令缓冲区:
let commandBuffer = commandQueue.commandBuffer()
您需要将 CIImage
(我们称之为 image
)渲染到 MTKView
的 currentDrawable?.texture
。如果绑定到 targetTexture
,则呈现语法为:
ciContext.render(image,
toMTLTexture: targetTexture,
commandBuffer: commandBuffer,
bounds: image.extent,
colorSpace: colorSpace)
commandBuffer.presentDrawable(currentDrawable!)
commandBuffer.commit()
我有一个工作版本 here。
希望对您有所帮助!
西蒙
我正在创建一个需要对图像实时应用滤镜的应用程序。将 UIImage
转换为 CIImage
和应用过滤器都是非常快速的操作,但是将创建的 CIImage
转换回 CGImageRef
并显示图像(1/5 秒,如果需要实时编辑,这实际上很多)。
图像大约为 2500 x 2500 像素大,这很可能是部分问题
目前,我正在使用
let image: CIImage //CIImage with applied filters
let eagl = EAGLContext(API: EAGLRenderingAPI.OpenGLES2)
let context = CIContext(EAGLContext: eagl, options: [kCIContextWorkingColorSpace : NSNull()])
//this line takes too long for real-time processing
let cg: CGImage = context.createCGImage(image, fromRect: image.extent)
我研究过使用 EAGLContext.drawImage()
context.drawImage(image, inRect: destinationRect, fromRect: image.extent)
但是我找不到任何可靠的文档来说明这是如何完成的,或者它是否会更快
有没有更快的方式在屏幕上显示 CIImage
(在 UIImageView
中或直接在 CALayer
中)?我想避免过度降低图像质量,因为这可能会引起用户的注意。
这是一张相当大的图片,所以这绝对是其中的一部分。我建议查看 GPUImage 来进行单幅图像过滤。您可以完全跳过使用 CoreImage。
let inputImage:UIImage = //... some image
let stillImageSource = GPUImagePicture(image: inputImage)
let filter = GPUImageSepiaFilter()
stillImageSource.addTarget(filter)
filter.useNextFrameForImageCapture()
stillImageSource.processImage()
您可以使用 GlkView 并按照您所说的 context.drawImage() 进行渲染:
let glView = GLKView(frame: superview.bounds, context: EAGLContext(API: .OpenGLES2))
let context = CIContext(EAGLContext: glView.context)
处理后渲染图像:
glView.bindDrawable()
context.drawImage(image, inRect: destinationRect, fromRect: image.extent)
glView.display()
我最终使用了 context.drawImage(image, inRect: destinationRect, fromRect: image.extent)
方法。这是我创建的图像视图 class
import Foundation
//GLKit must be linked and imported
import GLKit
class CIImageView: GLKView{
var image: CIImage?
var ciContext: CIContext?
//initialize with the frame, and CIImage to be displayed
//(or nil, if the image will be set using .setRenderImage)
init(frame: CGRect, image: CIImage?){
super.init(frame: frame, context: EAGLContext(API: EAGLRenderingAPI.OpenGLES2))
self.image = image
//Set the current context to the EAGLContext created in the super.init call
EAGLContext.setCurrentContext(self.context)
//create a CIContext from the EAGLContext
self.ciContext = CIContext(EAGLContext: self.context)
}
//for usage in Storyboards
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
self.context = EAGLContext(API: EAGLRenderingAPI.OpenGLES2)
EAGLContext.setCurrentContext(self.context)
self.ciContext = CIContext(EAGLContext: self.context)
}
//set the current image to image
func setRenderImage(image: CIImage){
self.image = image
//tell the processor that the view needs to be redrawn using drawRect()
self.setNeedsDisplay()
}
//called automatically when the view is drawn
override func drawRect(rect: CGRect){
//unwrap the current CIImage
if let image = self.image{
//multiply the frame by the screen's scale (ratio of points : pixels),
//because the following .drawImage() call uses pixels, not points
let scale = UIScreen.mainScreen().scale
let newFrame = CGRectMake(rect.minX, rect.minY, rect.width * scale, rect.height * scale)
//draw the image
self.ciContext?.drawImage(
image,
inRect: newFrame,
fromRect: image.extent
)
}
}
}
然后,要使用它,只需
let myFrame: CGRect //frame in self.view where the image should be displayed
let myImage: CIImage //CIImage with applied filters
let imageView: CIImageView = CIImageView(frame: myFrame, image: myImage)
self.view.addSubview(imageView)
在将 UIImage 转换为 CIImage 之前将其调整为屏幕大小也有帮助。在高质量图像的情况下,它可以大大加快速度。实际保存时请确保使用全尺寸图像。
就是这样!然后,更新视图中的图像
imageView.setRenderImage(newCIImage)
//note that imageView.image = newCIImage won't work because
//the view won't be redrawn
可能值得考虑 Metal 并用 MTKView
显示。
您需要一个可以使用 MTLCreateSystemDefaultDevice()
创建的 Metal 设备。这用于创建命令队列和 Core Image 上下文。这两个对象都是持久的并且实例化起来非常昂贵,因此理想情况下应该创建一次:
lazy var commandQueue: MTLCommandQueue =
{
return self.device!.newCommandQueue()
}()
lazy var ciContext: CIContext =
{
return CIContext(MTLDevice: self.device!)
}()
您还需要一种颜色 space:
let colorSpace = CGColorSpaceCreateDeviceRGB()!
在渲染 CIImage
时,您需要创建一个短暂的命令缓冲区:
let commandBuffer = commandQueue.commandBuffer()
您需要将 CIImage
(我们称之为 image
)渲染到 MTKView
的 currentDrawable?.texture
。如果绑定到 targetTexture
,则呈现语法为:
ciContext.render(image,
toMTLTexture: targetTexture,
commandBuffer: commandBuffer,
bounds: image.extent,
colorSpace: colorSpace)
commandBuffer.presentDrawable(currentDrawable!)
commandBuffer.commit()
我有一个工作版本 here。
希望对您有所帮助!
西蒙