startupthekid startupthekid - 11 months ago 171
Swift Question

GPUImage crop to CGRect and rotate

Given a

, I want to use GPUImage to crop a video. For example, if the rect is
(0, 0, 50, 50)
, the video would be cropped at
with a length of 50 on each side.

What's throwing me is that
doesn't take a rectangle, rather a normalized crop region with values ranging from 0 to 1. My intuition was to to this:

let assetSize = CGSizeApplyAffineTransform(videoTrack.naturalSize, videoTrack.preferredTransform)
let cropRect = CGRect(x: frame.minX/assetSize.width,
y: frame.minY/assetSize.height,
width: frame.width/assetSize.width,
height: frame.height/assetSize.height)

to calculate the crop region based on the size of the incoming asset. Then:

// Filter
let cropFilter = GPUImageCropFilter(cropRegion: cropRect)
let url = NSURL(fileURLWithPath: "\(NSTemporaryDirectory())\(String.random()).mp4")
let movieWriter = GPUImageMovieWriter(movieURL: url, size: assetSize)
movieWriter.encodingLiveVideo = false
movieWriter.shouldPassthroughAudio = false

// add targets

cropFilter.setInputRotation(kGPUImageRotateRight, atIndex: 0)

What should the movie writer size be? Shouldn't it be the size of the frame I want to crop with? And should I be using
with the size value of my crop frame?

A complete code example would be great; I've been trying for hours and I can't seem to get the section of the video that I want.


if let videoTrack = self.asset.tracks.first {
let movieFile = GPUImageMovie(asset: self.asset)
let transformedRegion = CGRectApplyAffineTransform(region, videoTrack.preferredTransform)

// Filters
let cropFilter = GPUImageCropFilter(cropRegion: transformedRegion)

let url = NSURL(fileURLWithPath: "\(NSTemporaryDirectory())\(String.random()).mp4")
let renderSize = CGSizeApplyAffineTransform(videoTrack.naturalSize, CGAffineTransformMakeScale(transformedRegion.width, transformedRegion.height))

let movieWriter = GPUImageMovieWriter(movieURL: url, size: renderSize)
movieWriter.transform = videoTrack.preferredTransform

movieWriter.encodingLiveVideo = false
movieWriter.shouldPassthroughAudio = false

// add targets

movieWriter.completionBlock = {

movieWriter.failureBlock = { _ in

disposable.addDisposable {



As you note, the GPUImageCropFilter takes in a rectangle in normalized coordinates. You're on the right track, in that you just need to convert your CGRect in pixels to normalized coordinates by dividing the X components (origin.x and size.width) by the width of the image and the Y components by the height.

You don't need to use forceProcessingAtSize(), because the crop will automatically output an image of the appropriate cropped size. The movie writer's size should be matched to this cropped size, which you should know from your original CGRect.

The one complication you introduce is the rotation. If you need to apply a rotation in addition to your crop, you might want to check and make sure that you don't need to swap your X and Y for your crop region. This should be apparent in the output if the two need to be swapped.

There were some bugs with applying rotation at the same time as a crop a while ago, and I can't remember if I fixed all those. If I didn't, you could insert a dummy filter (gamma or brightness set to default values) before or after the crop and apply the rotation at that stage.