Active Filters: Images

When using UIImageViews, sometimes the built-in content modes can cramp our style.

Many times, we're displaying photos of people. In these cases, it'd be great if the image view could somehow be told to intelligently crop the photo around the person's face.

Today we'll check out a library from Beau Nouvelle called AspectFillFaceAware. It's super simple, let's take a look.

AspectFillFaceAware is essentially just an extension on UIImageView. It provides two ways to configure an image view to be "face aware".

The first is in Interface Builder, we can enable the feature by flipping on the feature in the Inspector. (Not seeing the option? Run your project once, then it should appear).

Here's the how it looks:

We can also enable the functionality in code by setting the image view's image using this new function:

imageView.set(image: avatar, focusOnFaces: true)

We can even throw a quick corner radius on the image view's layer to try out the "face aware" functionality on a circular view. (i.e. user avatars):

let radius = imageView.bounds.size.width / 2.0
imageView.layer.cornerRadius = radius

Under the hood, the library is using a low accuracy CIDetector with a type of CIDetectorTypeFace to handle the actual face detection. Want to dive deeper here? We covered CIDetectors way back in Bite #87.

More info about AsyncFillFaceAware can be found at git.io/faceaware

We've looked at allowing users to capture or choose images for use in our apps a couple times here. We learned about UIImagePickerController in Bite #83 and then ImagePicker in Bite #157.

Today we've got a new contender in this space called Fusuma. It's by Yuta Akizuki and aims to provide a very full-featured drop-in solution for this task. Let's try it.

We'll start by presenting the main FusumaViewController:

let fusuma = FusumaViewController()
fusuma.delegate = self

self.presentViewController(fusuma, animated: true, completion: nil)

Then later we conform to the FusumaDelegate protocol:

func fusumaImageSelected(image: UIImage) {
    imageView.image = image
}

There's also fusumaDismissedWithImage and fusumaCameraRollUnauthorized functions for handling those cases.



More info about Fusuma can be found at git.io/fusuma

We've covered using UIImagePickerController to allow users to capture photos or select them from their library in Bite #83.

This is great for some situations, but often we'll want to offer our users a few more features and a little extra usability polish.

Today we'll look at a library called ImagePicker from Hyper. It's absolutely packed with features and has a super slick interface. Let's check it out:

ImagePicker is incredibly simple to use, It's just a view controller:

let imagePickerController = ImagePickerController()
imagePickerController.delegate = self

presentViewController(imagePickerController, animated: true, completion: nil)

We'll be notified via delegate functions as the user selects/takes photos:

func doneButtonDidPress(images: [UIImage]) {
  // TODO: Process images
  dismissViewControllerAnimated(true, completion: nil)
}

We can customize colors, fonts, and even the text shown in labels using a handy configuration struct filled with static properties:

Configuration.doneButtonTitle = "Upload"

More info about ImagePicker can be found at git.io/imagepicker

In Bite #101 we started working on a custom camera view controller.

Today we'll complete it by adding a way for users to capture a photo and do something with it. We'll start by making it easy to use. We'll make the whole screen a capture button by adding a tap gesture recognizer to our view:

let tapGR = UITapGestureRecognizer(target: self, action: "capturePhoto:")
tapGR.numberOfTapsRequired = 1
view.addGestureRecognizer(tapGR)

Next, we want to ask our output to capture a still image. Before we can, we'll need an AVCaptureConnection.

Connections were already implicitly created for us by our session. They represent the conceptual pipes that move data between inputs and outputs.

We grab a connection and use it to ask our output to capture a still image, asynchronously:

func capturePhoto(tapGR: UITapGestureRecognizer) {
  guard let connection = output.connectionWithMediaType(AVMediaTypeVideo) else { return }
  connection.videoOrientation = .Portrait

  output.captureStillImageAsynchronouslyFromConnection(connection) { (sampleBuffer, error) in
    guard sampleBuffer != nil && error == nil else { return }

    let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer)
    guard let image = UIImage(data: imageData) else { return }

    self.presentActivityVCForImage(image)
  }
}

In the closure, we'll do a safety check then convert the CMSampleBuffer we've been given into an NSData then a UIImage.

Lastly, we'll use UIActivityViewController (covered in Bite #71) to allow the user to do something with their new photo.

Download the project we built in Bites #101 & #102 at j.mp/bite102

We looked at allowing our users to capture photos/videos using UIImagePickerController in Bite #83. Now we'll take things to the next level by starting to create our own custom camera view controller. Today we'll get all the plumbing wired up and get the preview on the screen. Let's get started.

func setupSession() {
  session = AVCaptureSession()
  session.sessionPreset = AVCaptureSessionPresetPhoto

  let camera = AVCaptureDevice
    .defaultDeviceWithMediaType(AVMediaTypeVideo)

  do { input = try AVCaptureDeviceInput(device: camera) } catch { return }

  output = AVCaptureStillImageOutput()
  output.outputSettings = [ AVVideoCodecKey: AVVideoCodecJPEG ]

  guard session.canAddInput(input) && session.canAddOutput(output) else { return }

  session.addInput(input)
  session.addOutput(output)

  previewLayer = AVCaptureVideoPreviewLayer(session: session)
  previewLayer!.videoGravity = AVLayerVideoGravityResizeAspect
  previewLayer!.frame = view.bounds
  previewLayer!.connection?.videoOrientation = .Portrait

  view.layer.addSublayer(previewLayer!)

  session.startRunning()
}

We'll start with the "single view" template in Xcode. There are a number of different objects we'll need to setup and glue together, so we'll go into our view controller and add a function called setupSession. We'll call this in viewWillAppear(animated:).

First we'll instantiate an AVCaptureSession. It's sort of the central hub of all this. We can configure it with a number of different presets. We'll use a preset for taking high quality still photos. Now, our session needs some inputs and outputs.

We'll use defaulDeviceWithMediaType and pass in Video to get the default hardware device for capturing and recording images on the user's device (usually the the back camera). Then we'll try to create an AVCaptureDeviceInput from the device. Next up, an output.

Capture sessions can return us data in all sorts of interesting ways: Still images, videos, raw pixel data, and more. Here we'll set up an AVCaptureStillImageOutput and ask it for JPEG photos. We'll do one more safety check then add both our input and output to our session.

Finally, let's display our camera so the user can see what they're photographing.

We'll pass our session into a new AVCapturePreviewLayer and add it to our view. Then we just need to start the session. If we run the app we'll see it's starting to look like a camera, neat!

Tomorrow, we'll finish up by adding the ability to actually capture some photos.

Update: Part 2 is right here

Topics

#83: UIImagePickerController Basics 📷

Topics

UIImagePickerController has been part of iOS since it's first release and it's evolved quite a bit over the years. Let's take a look at what it can do:

  • Capture images and videos
  • Choose images and videos from the Photos library
  • Crop images after choosing/capturing
  • Trim videos after choosing/capturing

Whew! That's quite a bit of functionality packed into this one class.

We can't cover all of that in this Bite, instead let's look at a simple example use case. We'll be letting our users take a photo, crop it, and then show how to access it for use in our app.

The first step is to find out the device we're running on has a camera, can take photos. Then we'll configure the UIImagePickerController and present it.

let types = UIImagePickerController.availableMediaTypesForSourceType(.Camera)!
let canTakePhotos = types.contains(kUTTypeImage as String)

if UIImagePickerController.isSourceTypeAvailable(.Camera) && canTakePhotos {
  let ipc = UIImagePickerController()

  ipc.sourceType = .Camera
  ipc.mediaTypes = [kUTTypeImage as String]
  ipc.allowsEditing = true
  ipc.delegate = self

  presentViewController(ipc, animated: true, completion: nil)
}

Then we'll add a function from UIImagePickerControllerDelegate where we'll get a userInfo dictionary. We'll use the values inside to extract the captured image in either it's original or cropped form. We can also access a few other details, like the cropped rect as a CGRect or the image's metadata as a dictionary.

Note that we'll need to declare conformance to the UINavigationControllerDelegate protocol since UIImagePickerController is actually a subclass of UINavigationController under the hood.

class ViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
  func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: AnyObject]) {
    let uncroppedImage = info[UIImagePickerControllerOriginalImage] as? UIImage
    let croppedImage = info[UIImagePickerControllerEditedImage] as? UIImage
    let cropRect = info[UIImagePickerControllerCropRect]!.CGRectValue

    picker.dismissViewControllerAnimated(true, completion: nil)
  }

  func imagePickerControllerDidCancel(picker: UIImagePickerController) {
    picker.dismissViewControllerAnimated(true, completion: nil)
  }
}

Topics

#51: PINRemoteImage 📌🎮🌄

Topics

PINRemoteImage is a new and promising solution for asynchronously downloading images. It was created by the team at Pinterest and has been battle tested in their extremely popular iOS app. Let's import PINRemoteImage, take a look at how to use it, and see what it can do:

let imageURL = NSURL(string: "http://apple.com/iCar.png")
imageView.setImageFromURL(imageURL)

Images are downloaded and decoded off the main thread. They're then cached using a fast, non-deadlocking parallel object cache. (Courtesy of another Pinterest library, PINCache). Upon subsequent requests the in-memory, then disk caches will be checked before re-downloading.

Processing

You can process the image before caching occurs. Here we'll use Toucan (covered in Bite #40) to convert the image into a circular avatar.

imageView.setImageFromURL(
  imageURL, 
  placeholderImage: placeholder, 
  processorKey: "circleAvatar"
) { (result, cost) -> UIImage! in
  return Toucan.Mask.maskImageWithEllipse(result.image)
}

Progressive JPEGs

Optionally, you can enable a mode supporting progressive JPEG images. They're treated with a nice blur effect, so they look great even at low resolutions.

imageView.updateWithProgress = true

Animated GIF Support

PINRemoteImage also comes with built-in support for Flipboard's blazing fast animated GIF library, FLAnimatedImage. All you have to do to use it is create an FLAnimatedImageView instead of a UIImageView:

let animatedImageView = FLAnimatedImageView(frame: GIFRect)

let GIFURL = NSURL(string: "http://apple.com/eddy-dance.gif")
animatedImageView.setImageFromURL(GIFURL)

// #GIFnotJIF

More info about PINRemoteImage can be found at git.io/pri

Topics

#40: Toucan 🐧

Topics

Whether you're applying a circular crop to user avatars or just resizing a photo downloaded from a web service, processing images can be a bit of a chore. Toucan is a Swift image processing library from Gavin Bunney that makes working with images a breeze.

Let's import Toucan and take a look at what it can do:

Resize Images

// userAvatar is a UIImage downloaded from the network

let resizedAvatar = Toucan.Resize.resizeImage(
    myImage,
    size: CGSize(width: 100, height: 100)
)

Apply a Circular Mask

let circularAvatar = Toucan.maskWithEllipse(resizedAvatar)

Apply a Rounded Rectangle Mask

let roundedAvatar = Toucan.maskWithRoundedRect(height: 5)

Image Borders & Method Chaining

Toucan provides another syntax for chaining different processing steps together. Just call .image at the end to get a final processed UIImage. Here we can also see how to apply a 1 point wide border to the final image.

Toucan(image: userAvatar)
  .resize(CGSize(width: 100, height: 100))
  .maskWithEllipse(
    borderWidth: 1, 
    borderColor: UIColor.lightGrayColor()
  )
  .image

More info about Toucan can be found at git.io/toucan

Topics

#32: Core Image Basics 🌄

Topics

CoreImage has long been a staple on OS X, and was added to iOS a few years ago. It's an incredibly feature-packed image processing API that can apply just about any type of filter or image manipulation you can dream of. Let's take a look at applying a simple color tint to an image:

func applyFilterToImage(image: UIImage) -> UIImage {
  guard let inputImage = image.CIImage else { return image }

  let tintColor = CIColor(red: 0.55, green: 0.33, blue: 0.22)

  let filter = CIFilter(
    name: "CIColorMonochrome",
    withInputParameters: [
      "inputImage" : inputImage,
      "inputColor" : tintColor,
      "inputIntensity" : 1.0
    ]
  )

  return UIImage(CIImage: filter!.outputImage)
}

CoreImage works on CIImages not UIImages, so we convert our image to a CIImage, and use guard since the CIImage property on UIImage is optional. CoreImage also doesn't use UIColor, so we create a CIColor instead.

Then the fun part, we create our filter by name. CoreImage has literally 100's of different filters available, and instead of subclasses, you instantiate them by name.

CIFilters also don't have explicit class properties, you instead supply values for "Input Parameters" on your filter.

Lastly, we apply the filter by simply asking for it's outputImage.