2016-05-26 4 views
4

Ich erstelle eine App, die Echtzeit-Anwendung von Filtern für Bilder erfordert. Konvertieren der in eine CIImage, und die Anwendung der Filter sind beide extrem schnelle Operationen, aber es dauert zu lange, um die erstellten CIImage zurück zu einem CGImageRef zu konvertieren und das Bild anzuzeigen (1/5 einer Sekunde, die tatsächlich eine Menge ist, wenn Sie bearbeiten muss in Echtzeit sein).Zeichnen eines CImage ist zu langsam

Das Bild ist etwa 2500 von 2500 Pixel groß, was das Problem

Derzeit höchstwahrscheinlich Teil ist, bin ich

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) 

I EAGLContext.drawImage()

context.drawImage(image, inRect: destinationRect, fromRect: image.extent) 
in Verwendung haben gesucht verwenden

Dennoch kann ich keine solide Dokumentation darüber finden, wie genau das gemacht wird, oder ob es schneller wäre

Gibt es eine schnellere Möglichkeit, eine CIImage auf dem Bildschirm anzuzeigen (entweder in einer UIImageView oder direkt auf einer CALayer)? Ich möchte vermeiden, die Bildqualität zu sehr zu verringern, weil dies für den Benutzer wahrnehmbar ist.

Antwort

3

Ich landete mit dem context.drawImage(image, inRect: destinationRect, fromRect: image.extent) Methode. Hier ist die Bildansichtsklasse, die ich

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

Dann, es zu benutzen, einfach

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) 

Ändern der Größe des UIImage an die Bildschirmgröße, bevor es zu einem CIImage Umwandlung auch hilft. Es beschleunigt die Dinge sehr bei Bildern mit hoher Qualität. Stellen Sie sicher, dass Sie das Bild in voller Größe verwenden, wenn Sie es tatsächlich speichern.

Das ist es! Dann zu aktualisieren, um das Bild in der Ansicht

imageView.setRenderImage(newCIImage) 
//note that imageView.image = newCIImage won't work because 
//the view won't be redrawn 
+0

Sie didSet auf Bildeigenschaft verwenden: 'var Bild: CIImage? { didset { self.setNeedsDisplay() } } ' Dann wird die ganze setRenderImage Methode obsolet. –

+0

Sie müssen den Kontext in init? (Coder :) (wie in init (frame)) setzen, damit dies vom Storyboard funktioniert. –

0

Das ist ein ziemlich großes Bild, das ist definitiv ein Teil davon. Ich würde empfehlen, GPUImage zu betrachten, um einzelne Bildfilter zu machen. Sie können mit CoreImage ganz überspringen.

let inputImage:UIImage = //... some image 
let stillImageSource = GPUImagePicture(image: inputImage) 
let filter = GPUImageSepiaFilter() 
stillImageSource.addTarget(filter) 
filter.useNextFrameForImageCapture() 
stillImageSource.processImage() 
2

können Sie GlkView verwenden und machen, wie Sie mit context.drawImage sagte():

let glView = GLKView(frame: superview.bounds, context: EAGLContext(API: .OpenGLES2)) 
let context = CIContext(EAGLContext: glView.context) 

Nach der Verarbeitung des Bildes machen:

glView.bindDrawable() 
context.drawImage(image, inRect: destinationRect, fromRect: image.extent) 
glView.display() 
4

Es kann sein, eine Überlegung wert, Metall und Anzeigen mit einem MTKView.

Sie benötigen ein Metallgerät, das mit MTLCreateSystemDefaultDevice() erstellt werden kann. Dies wird verwendet, um eine Befehlswarteschlange und einen Core Image-Kontext zu erstellen.Beide Objekte sind persistent und sehr teuer zu instanziiert, so sollte idealerweise einmal erstellt werden:

lazy var commandQueue: MTLCommandQueue = 
{ 
    return self.device!.newCommandQueue() 
}() 

lazy var ciContext: CIContext = 
{ 
    return CIContext(MTLDevice: self.device!) 
}() 

Sie werden auch einen Farbraum benötigen:

let colorSpace = CGColorSpaceCreateDeviceRGB()! 

Wenn es um eine CIImage Rendering, Sie ‚ll braucht einen kurzlebigen Befehlspuffer zu erstellen:

let commandBuffer = commandQueue.commandBuffer() 

Sie wollen Ihre CIImage machen (lassen Sie sich es image nennen) zu die currentDrawable?.texture einer MTKView. Wenn das zu targetTexture gebunden ist, ist die Rendering-Syntax:

ciContext.render(image, 
     toMTLTexture: targetTexture, 
     commandBuffer: commandBuffer, 
     bounds: image.extent, 
     colorSpace: colorSpace) 

    commandBuffer.presentDrawable(currentDrawable!) 

    commandBuffer.commit() 

Ich habe eine funktionierende Version here.

Hoffe, dass hilft!

Simon

+0

Ich fand dies die performanteste Lösung. Beachten Sie, dass ich in meinem Code, da ich MTKView nicht untergliederte, unmittelbar nach dem Aufruf von 'commit()' 'draw()' auf der 'MTKView' aufrufen musste und auch das' device' der Ansicht auf den erzeugten setzen musste und sein 'framebufferOnly' zu' false'. – Ash

+0

Es lohnt sich auch, 'CIRenderDestination' und die' startTask' Methode auf 'CIContext' zu betrachten. –

+0

Ich habe einen Ärger mit dieser Funktion bemerkt, die ich gerade an der Adressierung arbeite, und wenn die Größe der Ansicht geändert wird, scheint die Texturgröße des Hintergrunds immer einen Schritt hinter der neuen Größe zu liegen und ich finde keinen Weg um eine korrekte Aktualisierung zu erzwingen. So wie es aussieht, negiert Ihre eigene Ansicht dieses Problem mit Stretching, aber ich erstelle eine grafische Anwendung, die eine Ausgabe in hoher Qualität erfordert, und ich möchte, dass das Bild genau in der Größe der enthaltenen Ansicht gerendert wird. – Ash