RawExposeEmbedded/ImageViewController.swift

/*
    Copyright (C) 2016 Apple Inc. All Rights Reserved.
    See LICENSE.txt for this sample’s licensing information
    
    Abstract:
    Application Image view controller.
*/
 
import UIKit
import GLKit
import ImageIO
import Photos
import CoreImage
 
/**
        This UIViewController displays an image processed using
        the CoreImage CIRawFilter in a GLKView.
        It also allows the user to perform simple edit, like
        adjusting exposure, temperature and tint.
 */
class ImageViewController: UIViewController, GLKViewDelegate {
    // MARK: Properties
 
    /// Outlet to sliders used to edit the image.
    @IBOutlet weak var exposureSlider : UISlider!
    @IBOutlet weak var tempSlider : UISlider!
    @IBOutlet weak var tintSlider : UISlider!
 
    /**
        View used to display the CoreImage output produced by the
        CIRawFilter.
     */
    @IBOutlet weak var imageView : GLKView!
 
    // Asset containing the image to render.
    var asset : PHAsset?
 
    // Original values of temperature and tint from the image.
    var originalTemp : Float = 0.0
    var originalTint : Float = 0.0
 
    // CIRawFilter used to process the Raw image
    var ciRawFilter : CIFilter?
 
    // Size of the processed image.
    var imageNativeSize : CGSize?
 
    // Context used to render the CIImage to display.
    var ciContext : CIContext?
 
    // MARK: Image load
    
    /// On load, construct the CIRawFilter
    override func viewDidLoad() {
        super.viewDidLoad()
 
        guard let asset = asset else { return }
 
        // Setup options to request original image.
        let options = PHImageRequestOptions()
        options.version = .original
        options.isSynchronous = true
 
        // Request the image data and UTI type for the image.
        PHImageManager.default().requestImageData(for: asset, options: options) { imageData, dataUTI, _, _ in
            guard let imageData = imageData, let dataUTI = dataUTI else { return }
 
            /**
                    Create a CIRawFilter from original image data.
                    UTI type is passed in to provide the CIRawFilter with
                    a hint about the UTI type of the Raw file.
             */
            let rawOptions = [ String(kCGImageSourceTypeIdentifierHint) : dataUTI ]
            self.ciRawFilter = CIFilter(imageData: imageData as Data, options: rawOptions)
            
            guard let ciRawFilter = self.ciRawFilter else { return }
 
            // Get the native size of the image produced by the CIRawFilter.
            if let value = ciRawFilter.value(forKey: kCIOutputNativeSizeKey) as? CIVector {
                self.imageNativeSize = CGSize(width: value.x, height: value.y)
            }
 
            // Record the original value of the temperature, and setup the editing slider.
            if let value = ciRawFilter.value(forKey: kCIInputNeutralTemperatureKey) {
                self.originalTemp = (value as AnyObject).floatValue
                self.tempSlider.setValue((value as AnyObject).floatValue, animated: false)
            }
            
            // Record the original value of the tint, and setup the editing slider.
            if let value = ciRawFilter.value(forKey: kCIInputNeutralTintKey) {
                self.originalTint = (value as AnyObject).floatValue
                self.tintSlider.setValue((value as AnyObject).floatValue, animated: false)
            }
        }
 
        // Create EAGL context used to render the CIImage produced by the CIRawFilter to display.
        imageView.context = EAGLContext(api : .openGLES3)
        ciContext = CIContext(eaglContext: imageView.context, options:[ kCIContextWorkingFormat : Int(kCIFormatRGBAh) ])
    }
    
    // MARK: Image edit actions
 
    /// Resets the CIRawFilter to the original values.
    @IBAction func resetSettings(_ sender: UIBarButtonItem) {
        guard let ciRawFilter = ciRawFilter else { return }
 
        ciRawFilter.setValue(0.0, forKey: kCIInputEVKey)
        exposureSlider.setValue(0.0, animated: false)
        
        ciRawFilter.setValue(originalTemp, forKey: kCIInputNeutralTemperatureKey)
        tempSlider.setValue(originalTemp, animated: false)
        
        ciRawFilter.setValue(originalTint, forKey: kCIInputNeutralTintKey)
        tintSlider.setValue(originalTint, animated: false)
        
        imageView.setNeedsDisplay()
    }
    
    /// Adjust the exposure of the image
    @IBAction func exposureAdjusted(sender: UISlider) {
        guard let ciRawFilter = ciRawFilter else { return }
        
        ciRawFilter.setValue(sender.value, forKey: kCIInputEVKey)
        imageView.setNeedsDisplay()
    }
    
    /// Adjust the temperature of the image
    @IBAction func temperatureAdjusted(sender: UISlider) {
        guard let ciRawFilter = ciRawFilter else { return }
        
        ciRawFilter.setValue(sender.value, forKey: kCIInputNeutralTemperatureKey)
        imageView.setNeedsDisplay()
    }
 
    /// Adjust the tint of the image
    @IBAction func tintAdjusted(sender: UISlider) {
        guard let ciRawFilter = ciRawFilter else { return }
        
        ciRawFilter.setValue(sender.value, forKey: kCIInputNeutralTintKey)
        imageView.setNeedsDisplay()
    }
 
    /// Update the image when the device is rotated.
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        self.imageView.setNeedsDisplay()
    }
    
    /// Render image to display.
    func glkView(_ view: GLKView, drawIn rect: CGRect) {
        guard let context = ciContext, let ciRawFilter = ciRawFilter, let imageNativeSize = imageNativeSize else { return }
 
        // OpenGLES drawing setup.
        glClearColor(0.0, 0.0, 0.0, 1.0)
        glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
        
        // Set the blend mode to "source over" so that CI will use that.
        glEnable(GLenum(GL_BLEND))
        glBlendFunc(GLenum(GL_ONE), GLenum(GL_ONE_MINUS_SRC_ALPHA))
 
        // Calculate scale to show the image at.
        let contentScaledRect = rect.applying(CGAffineTransform(scaleX: view.contentScaleFactor, y: view.contentScaleFactor))
        let scale = min(contentScaledRect.width / imageNativeSize.width, contentScaledRect.height / imageNativeSize.height)
 
        // Set scale factor of the CIRawFilter to size it correctly for display.
        ciRawFilter.setValue(scale, forKey: kCIInputScaleFactorKey)
 
        // Calculate rectangle to display image in.
        var displayRect = CGRect(x:0, y:0, width:imageNativeSize.width, height:imageNativeSize.height).applying(CGAffineTransform(scaleX: scale, y: scale))
    
        // Ensure the image is centered.
        displayRect.origin.x = (contentScaledRect.width - displayRect.width) / 2.0
        displayRect.origin.y = (contentScaledRect.height - displayRect.height) / 2.0
 
        guard let image = ciRawFilter.outputImage else { return }
 
        // Display the image scaled to fit.
        context.draw(image, in:displayRect, from:image.extent)
    }
}