Статьи

Улучшение приложения для фотографий с помощью GPUImage & iCarousel

Из этого туториала вы узнаете, как использовать GPUImage для применения фильтров изображения в режиме реального времени при отображении канала камеры устройства. Попутно вы узнаете, как автоматически заполнять изображения в контроллере карусели и как изменять размеры изображений с помощью категорий UIImage +.



Этот учебник основан на предыдущем посте, озаглавленном «Создание приложения для фотографий с помощью GPUImage» . В предыдущем уроке демонстрировалось, как использовать UIImagePickerController для выбора фотографий из фотоальбома или камеры устройства, как добавить библиотеку GPUImage в проект и как использовать класс GPUImageFilter для улучшения кадров неподвижных камер. Если вы уже знакомы с UIImagePickerController и можете выяснить, как самостоятельно добавить GPUImage в ваш проект, вы сможете выбрать, с чего последний учебник закончился.


Этот проект будет широко использовать проект с открытым исходным кодом под названием iCarousel, чтобы добавить стильное отображение выбранных фотографий.

Чтобы включить iCarousel в свой проект, перейдите на официальную страницу GitHub и загрузите исходный код в виде zip-файла. Извлеките код из файла ZIP, а затем перетащите папку с названием «iCarousel» в Навигатор проекта XCode. Эта папка должна содержать как iCarousel.h, так и iCarousel.m . Обязательно выберите «Создать группы для любых добавленных папок» и установите флажок «Копировать элементы в папку целевой группы (если необходимо)», а также установите флажок рядом с именем цели вашего проекта в области «Добавить к целям».

Затем перейдите к ViewController.m и добавьте оператор импорта для iCarousel:

1
2
3
#import «ViewController.h»
#import «GPUImage.h»
#import «iCarousel/iCarousel.h»

Прежде чем мы отобразим наши изображения с помощью iCarousel, нам нужно уменьшить их до приемлемого размера. Вместо того чтобы писать весь код, чтобы сделать это вручную, мы воспользуемся отличным проектом UIImage + Categories , который предоставляет базовую функциональность для изменения размера изображений, а также несколько других приемов манипулирования изображениями.

Совет: Вы можете альтернативно использовать проект MGImageUtilities для этой задачи. Хотя детали реализации будут незначительно отличаться, он также обеспечивает отличную поддержку масштабирования UIImage.

Загрузите код UIImage+Categories из GitHub и затем создайте новую группу с тем же именем в XCode. Перетащите файлы реализации и заголовочные файлы для UIImage+Alpha , UIImage+Resize и UIImage+RoundedCorner в ваш проект. Обязательно выберите «Создать группы для любых добавленных папок» и установите флажок «Копировать элементы в папку целевой группы (если необходимо)», а также установите флажок рядом с именем цели вашего проекта в области «Добавить к целям».

В файле ViewController.m импортируйте категории изображений со следующей строкой кода:

1
2
3
4
#import «ViewController.h»
#import «GPUImage.h»
#import «iCarousel.h»
#import «UIImage+Resize.h»

С кодом iCarousel, импортированным в наш проект, переключитесь на файл MainStoryboard.storyboard, чтобы переделать наш интерфейс.

Сначала выберите текущий UIImageView подключенный к IBOutlet selectedImageView и удалите его. Вернитесь к ViewController.m и измените код проекта следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@property(nonatomic, weak) IBOutlet iCarousel *photoCarousel;
@property(nonatomic, weak) IBOutlet UIBarButtonItem *filterButton;
@property(nonatomic, weak) IBOutlet UIBarButtonItem *saveButton;
 
— (IBAction)photoFromAlbum;
— (IBAction)photoFromCamera;
— (IBAction)saveImageToAlbum;
— (IBAction)applyImageFilter:(id)sender;
 
@end
 
@implementation ViewController
 
@synthesize photoCarousel, filterButton, saveButton;

В приведенной выше строке 1 замените selectedImageView iCarousel выход photoCarousel именем photoCarousel . Поменяйте местами переменные в выражении синтеза в строке 14 выше.

Вернитесь в Interface Builder и перетащите новый UIView на контроллер представления. UIView новый UIView , перейдите на вкладку «Инспектор удостоверений» на панели «Утилиты» и установите для поля «Класс» значение «iCarousel». Это говорит Интерфейсному UIView что UIView мы добавили в проект, должен быть создан как экземпляр класса iCarousel .

Добавление представления iCarousel

Теперь установите связь между только что объявленным выходом photoCarousel и UIView добавленным в качестве подпредставления.

Добавление представления iCarousel

Нам также нужно установить источник данных и делегата для photoCarousel , и мы можем добиться этого в Интерфейсном Разработчике. Сначала перейдите к ViewController.h и объявите, что этот контроллер представления будет соответствовать соответствующим протоколам:

1
2
3
4
#import <UIKit/UIKit.h>
#import «iCarousel/iCarousel.h»
 
@interface ViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate, UIActionSheetDelegate, iCarouselDataSource, iCarouselDelegate>

В строке 2 мы импортируем iCarousel, а в строке 4 мы объявляем соответствие как делегату, так и источнику данных.

Вернувшись в файл раскадровки, теперь вы можете сопоставить источник данных и делегата с контроллером представления.

Добавление представления iCarousel

Прежде чем двигаться дальше, измените цвет фона вида iCarousel на черный.

Хорошо, только еще одна вещь. Мы хотим, чтобы представление iCarousel отображалось под UIToolbar в иерархии представлений. Вы можете сделать это визуально, просто перетащив их в правильном порядке в Интерфейсном Разработчике:

Добавление представления iCarousel

Обратите внимание, как представление iCarousel теперь отображается перед панелью инструментов.

Сохраните свою работу в Интерфейсном Разработчике.


В iCarousel используется шаблон проектирования, аналогичный UITableView в котором источник данных используется для подачи ввода в элемент управления, а делегат — для взаимодействия с элементом управления.

Для нашего проекта источником данных будет простой NSMutableArray называется «displayImages». Добавьте это к расширению класса в ViewController.m сейчас:

1
2
3
4
5
6
7
8
#import «UIImage+Resize.h»
 
@interface ViewController ()
{
    NSMutableArray *displayImages;
}
 
@property(nonatomic, weak) IBOutlet iCarousel *photoCarousel;

Затем мы хотим выделить память для массива в инициализаторе класса. В нашем случае экземпляр представления будет создан из Storyboard, поэтому правильный инициализатор — initWithCoder: Однако, если класс должен быть создан программно из XIB, правильным инициализатором будет initWithNibName:bundle: Чтобы приспособить любой стиль инициализации, мы напишем наш собственный инициализатор и вызовем его из обоих, например так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
— (void)customSetup
{
    displayImages = [[NSMutableArray alloc] init];
}
 
— (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]))
    {
        [self customSetup];
    }
    return self;
}
 
— (id)initWithCoder:(NSCoder *)aDecoder
{
    if ((self = [super initWithCoder:aDecoder]))
    {
        [self customSetup];
    }
    return self;
}

Теперь мы можем реализовать источник данных и делегировать. Начнем с метода источника данных numberOfItemsInCarousel: примерно так:

1
2
3
4
5
6
7
#pragma mark
#pragma mark iCarousel DataSource/Delegate/Custom
 
— (NSUInteger)numberOfItemsInCarousel:(iCarousel *)carousel
{
    return [displayImages count];
}

Это скажет iCarousel, сколько изображений отображать, посмотрев на количество изображений, хранящихся в массиве источника данных.

Затем напишите метод, который будет фактически генерировать представление для каждого изображения, отображаемого в карусели:

01
02
03
04
05
06
07
08
09
10
11
12
13
— (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view
{
    // Create new view if no view is available for recycling
    if (view == nil)
    {
        view = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 300.0f, 300.0f)];
        view.contentMode = UIViewContentModeCenter;
    }
 
   ((UIImageView *)view).image = [displayImages objectAtIndex:index];
     
    return view;
}

Это хорошее начало, но есть одна очень важная проблема с вышесказанным: изображения должны быть уменьшены перед передачей в iCarousel. Добавьте следующие строки кода для обновления метода:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
— (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view
{
    // Create new view if no view is available for recycling
    if (view == nil)
    {
        view = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 300.0f, 300.0f)];
        view.contentMode = UIViewContentModeCenter;
    }
     
    // Intelligently scale down to a max of 250px in width or height
    UIImage *originalImage = [displayImages objectAtIndex:index];
     
    CGSize maxSize = CGSizeMake(250.0f, 250.0f);
    CGSize targetSize;
     
    // If image is landscape, set width to 250px and dynamically figure out height
    if(originalImage.size.width >= originalImage.size.height)
    {
        float newHeightMultiplier = maxSize.width / originalImage.size.width;
        targetSize = CGSizeMake(maxSize.width, round(originalImage.size.height * newHeightMultiplier));
    } // If image is portrait, set height to 250px and dynamically figure out width
    else
    {
        float newWidthMultiplier = maxSize.height / originalImage.size.height;
        targetSize = CGSizeMake( round(newWidthMultiplier * originalImage.size.width), maxSize.height );
    }
     
    // Resize the source image down to fit nicely in iCarousel
    ((UIImageView *)view).image = [[displayImages objectAtIndex:index] resizedImage:targetSize interpolationQuality:kCGInterpolationHigh];
     
    return view;
}
Pro Совет: использовать этот метод в производственном приложении? Рассмотрите возможность улучшения кода для повышения производительности путем изменения размера изображения в фоновом потоке и сохранения отдельного массива NSMutableArray, который кэширует уменьшенные версии изображений.
ОБНОВЛЕНИЕ 27.09.2012: Ник Локвуд (автор iCarousel) выпустил проект под названием FXImageView, который будет автоматически обрабатывать загрузку изображений в фоновом потоке. Это также идет с другими полезными прибамбасами как тени падения и скругленные углы, так что проверьте это!

Выше мы устанавливаем максимальный размер 250 пикселей для ширины или высоты изображения, а затем уменьшаем противоположный атрибут до соответствия. Это ограничивает пропорции изображения и выглядит намного лучше, чем простое уменьшение до размера 250 на 250 пикселей.

Два вышеупомянутых метода — это все, что нужно iCarousel для отображения изображений.

С настроенными методами делегата и источника данных самое время настроить объект viewDidLoad методе viewDidLoad :

1
2
3
4
5
6
7
8
— (void)viewDidLoad
{
    [super viewDidLoad];
     
    // iCarousel Configuration
    self.photoCarousel.type = iCarouselTypeCoverFlow2;
    self.photoCarousel.bounces = NO;
}

С помощью всего лишь нескольких настроек, проект сможет отображать изображения в карусели!


Ранее в этом руководстве мы заменили свойство selectedImageView свойство photoCarousel , обновили соответствующий интерфейс Storyboard и создали NSMutableArray будет действовать как модель данных iCarousel. Однако в ViewController.m есть несколько методов, которые до сих пор используют старую модель данных, которая будет препятствовать компиляции проекта, поэтому давайте исправим их сейчас. Обновите метод saveImageToAlbum следующим образом:

1
2
3
4
5
— (IBAction)saveImageToAlbum
{
    UIImage *selectedImage = [displayImages objectAtIndex:self.photoCarousel.currentItemIndex];
    UIImageWriteToSavedPhotosAlbum(selectedImage, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}

Строка 3 выбирает UIImage из модели данных, которая соответствует текущему индексу iCarousel. Строка 4 выполняет фактическую запись на диск с этим образом.

Затем перейдите к UIImagePickerController делегата UIImagePickerController и измените код:

01
02
03
04
05
06
07
08
09
10
11
12
— (void)imagePickerController:(UIImagePickerController *)photoPicker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    self.saveButton.enabled = YES;
    self.filterButton.enabled = YES;
     
    [displayImages addObject:[info valueForKey:UIImagePickerControllerOriginalImage]];
     
    [self.photoCarousel reloadData];
         
    [photoPicker dismissViewControllerAnimated:YES completion:NULL];
     
}

В строке 6 выше мы добавляем выбранную фотографию в новую модель, а в строке 8 принудительно обновляем карусель.

Еще одно изменение. Перейдите к методу clickedButtonAtIndex: метод и измените код следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#pragma mark —
#pragma mark UIActionSheetDelegate
 
— (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
     
    if(buttonIndex == actionSheet.cancelButtonIndex)
    {
        return;
    }
     
    GPUImageFilter *selectedFilter;
     
    switch (buttonIndex) {
        case 0:
            selectedFilter = [[GPUImageGrayscaleFilter alloc] init];
            break;
        case 1:
            selectedFilter = [[GPUImageSepiaFilter alloc] init];
            break;
        case 2:
            selectedFilter = [[GPUImageSketchFilter alloc] init];
            break;
        case 3:
            selectedFilter = [[GPUImagePixellateFilter alloc] init];
            break;
        case 4:
            selectedFilter = [[GPUImageColorInvertFilter alloc] init];
            break;
        case 5:
            selectedFilter = [[GPUImageToonFilter alloc] init];
            break;
        case 6:
            selectedFilter = [[GPUImagePinchDistortionFilter alloc] init];
            break;
        case 7:
            selectedFilter = [[GPUImageFilter alloc] init];
            break;
        default:
            break;
    }
     
    UIImage *filteredImage = [selectedFilter imageByFilteringImage:[displayImages objectAtIndex:self.photoCarousel.currentItemIndex]];
    [displayImages replaceObjectAtIndex:self.photoCarousel.currentItemIndex withObject:filteredImage];
    [self.photoCarousel reloadData];
}

Последние три строки этого метода фильтруют изображение модели данных, соответствующее текущему индексу карусели, заменяют отображение карусели этим изображением, а затем обновляют карусель.

Если все прошло хорошо, теперь вы сможете скомпилировать и запустить проект! Это позволит вам просматривать изображения в карусели, а не просто в представлении изображений.


Пока что приложение выглядит хорошо, но было бы неплохо, если бы пользователь мог удалить фотографию с карусели после добавления ее на дисплей. Нет проблем! Мы можем выбрать любой подкласс UIGestureRecognizer чтобы это произошло. Для этого урока я выбрал двойное касание двумя пальцами. Этот жест может быть не сразу интуитивно понятным, но его легко выполнить, а дополнительная сложность поможет предотвратить случайное удаление изображений.

В файле ViewController.m перейдите к carousel:viewForItemAtIndex:reusingView: метод и добавьте следующие строки непосредственно перед концом метода:

01
02
03
04
05
06
07
08
09
10
// Resize the source image down to fit nicely in iCarousel
((UIImageView *)view).image = [[displayImages objectAtIndex:index] resizedImage:targetSize interpolationQuality:kCGInterpolationHigh];
 
// Two finger double-tap will delete an image
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(removeImageFromCarousel:)];
gesture.numberOfTouchesRequired = 2;
gesture.numberOfTapsRequired = 2;
view.gestureRecognizers = [NSArray arrayWithObject:gesture];
 
return view;

Строки 4 — 8 объявляют новый объект UITapGestureRecognizer , устанавливают количество касаний (т. UITapGestureRecognizer ), необходимое для запуска жеста, UITapGestureRecognizer 2, и также устанавливают количество требуемых касаний 2. Наконец, непосредственно перед передачей представления обратно объекту iCarousel, мы устанавливаем свойство gestureRecognizers с помощью вновь созданного распознавателя.

Обратите внимание, что при срабатывании этот распознаватель жестов запустит селектор removeImageFromCarousel: Давайте реализуем это следующим образом:

1
2
3
4
5
6
— (void)removeImageFromCarousel:(UIGestureRecognizer *)gesture
{
    [gesture removeTarget:self action:@selector(removeImageFromCarousel:)];
    [displayImages removeObjectAtIndex:self.photoCarousel.currentItemIndex];
    [self.photoCarousel reloadData];
}

Строка 3 удаляет жест из текущей цели, чтобы предотвратить запуск нескольких жестов во время обработки. Остальные две строки на данный момент ничего нового.

Создайте и снова запустите приложение. Теперь вы сможете динамически удалять предметы из карусели!


В оставшейся части этого урока основное внимание будет уделено использованию GPUImageStillCamera для создания пользовательского GPUImageStillCamera управления выбора камеры, который может применять фильтры к входящему видеопотоку в режиме реального времени. GPUImageStillCamera тесно работает с классом GPUImageView . Кадры камеры, сгенерированные GPUImageStillCamera , отправляются в назначенный объект GPUImageView для отображения пользователю. Все это достигается с помощью базовой функциональности, предоставляемой платформой AVFoundation , которая обеспечивает программный доступ к данным кадра камеры.

Поскольку GPUImageView является дочерним классом UIView , мы можем встроить весь экран камеры в наш собственный пользовательский класс UIViewController .

Добавьте новый подкласс UIViewController в проект, щелкнув правой кнопкой мыши «PhotoFX» в навигаторе проекта и выбрав « Новый файл»> «Класс Objective-C» . Назовите класс «MTCameraViewController» и введите «UIViewController» в поле «подкласс».

Создание MTCameraViewController

Нажмите «Далее», а затем «Создать», чтобы завершить процесс.

Перейдите в файл MTCameraViewController.m и импортируйте GPUImage:

1
2
#import «MTCameraViewController.h»
#import «GPUImage.h»

Затем создайте расширение класса с необходимыми членами данных GPUImage:

1
2
3
4
5
6
@interface MTCameraViewController () <UIActionSheetDelegate>
{
    GPUImageStillCamera *stillCamera;
    GPUImageFilter *filter;
}
@end

Наконец, перейдите к viewDidLoad: метод и добавьте код для запуска захвата камеры:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
— (void)viewDidLoad
{
    [super viewDidLoad];
 
    // Setup initial camera filter
    filter = [[GPUImageFilter alloc] init];
    [filter prepareForImageCapture];
    GPUImageView *filterView = (GPUImageView *)self.view;
    [filter addTarget:filterView];
 
    // Create custom GPUImage camera
    stillCamera = [[GPUImageStillCamera alloc] init];
    stillCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
    [stillCamera addTarget:filter];
     
    // Begin showing video camera stream
    [stillCamera startCameraCapture];
     
}

Строки 5-9 создают новый GPUImageView для отображения канала камеры и экземпляр GPUImageFilter умолчанию для применения специального эффекта к представлению. Мы могли бы так же легко использовать один из подклассов GPUImageFilter , такой как GPUImageSketchFilter , но вместо этого мы начнем с фильтра по умолчанию (т.е. без манипуляций) и позволим пользователю динамически выбирать фильтр позже.

Строки 11 — 17 создают экземпляр экземпляра камеры графического процессора и применяют фильтр, созданный ранее к камере, перед началом захвата.


Прежде чем код из шага 8 будет работать, нам нужно добавить пользовательский класс MTCameraViewController только что созданный, в раскадровку проекта.

Откройте файл MainStoryboard.storyboard и перетащите новый View Controller из библиотеки объектов. Выбрав этот объект, перейдите на вкладку «Инспектор удостоверений» и установите для поля «Класс» значение «MTCameraViewController».

Затем перетащите UIToolbar на экран и установите для его свойства стиля значение «Черный непрозрачный» в инспекторе атрибутов. Затем добавьте два элемента кнопки гибкой панели ширины на панель инструментов с UIBarButtonItem « UIBarButtonItem в центре.

Добавление MTCameraViewController Подвидов

Чтобы подключить этот контроллер представления к потоку приложений, щелкните правой кнопкой мыши кнопку «камера» на главном контроллере представления и перетащите сработавшее выходное соединение сегментов на новый контроллер представления:

Добавление нового Segue

При появлении запроса выберите «Push» в качестве стиля перехода.

После того, как вновь добавленный объект segue выбран, перейдите в «Инспектор атрибутов» и установите идентификатор «pushMTCamera». Продолжайте и убедитесь, что «Push» выбран из выпадающего «Style».

Конфигурирование Segue

С созданным UIImagePicker убедитесь, что UIImagePicker больше не будет отображаться, когда пользователь нажимает кнопку камеры на первом экране приложения, отсоединив выход IBAction от метода photoFromCamera .

Наконец, выберите основной вид вновь созданного MTCameraViewController. Перейдите в инспектор удостоверений и установите значение класса «GPUImageView».

Хотя это еще не идеально, если вы сейчас создаете и запускаете приложение, вы должны иметь возможность MTCameraViewController в иерархию представлений и наблюдать, как GPUImageView отображает кадры с камеры в режиме реального времени!


Теперь мы можем добавить логику, необходимую для управления фильтром, применяемым к дисплею камеры. Сначала перейдите к viewDidLoad: метод в файле MTCameraViewController.m и добавьте код, который создаст кнопку «Фильтр» в правом верхнем углу контроллера представления:

1
2
3
4
5
6
7
— (void)viewDidLoad
{
    [super viewDidLoad];
 
    // Add Filter Button to Interface
    UIBarButtonItem *filterButton = [[UIBarButtonItem alloc] initWithTitle:@»Filter» style:UIBarButtonItemStylePlain target:self action:@selector(applyImageFilter:)];
    self.navigationItem.rightBarButtonItem = filterButton;

В строке 6 выше мы создаем пользовательский UIBarButtonItem который будет вызывать applyImageFilter: при applyImageFilter: .

Теперь создайте метод селектора:

01
02
03
04
05
06
07
08
09
10
— (IBAction)applyImageFilter:(id)sender
{
    UIActionSheet *filterActionSheet = [[UIActionSheet alloc] initWithTitle:@»Select Filter»
                                                                   delegate:self
                                                          cancelButtonTitle:@»Cancel»
                                                     destructiveButtonTitle:nil
                                                          otherButtonTitles:@»Grayscale», @»Sepia», @»Sketch», @»Pixellate», @»Color Invert», @»Toon», @»Pinch Distort», @»None», nil];
     
    [filterActionSheet showFromBarButtonItem:sender animated:YES];
}

После добавления вышеупомянутого вы увидите предупреждение компилятора о том, что текущий контроллер представления не соответствует протоколу UIActionSheetDelegate . Исправьте эту проблему сейчас, перейдя в MTCameraViewController.h и изменив объявление класса следующим образом:

1
2
3
4
5
#import <UIKit/UIKit.h>
 
@interface MTCameraViewController : UIViewController <UIActionSheetDelegate>
 
@end

Завершите круг, вернувшись к файлу MTCameraViewController.m и добавив логику, которая будет реагировать на представленную таблицу UIActionSheet :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
— (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
    // Bail if the cancel button was tapped
    if(actionSheet.cancelButtonIndex == buttonIndex)
    {
        return;
    }
     
    GPUImageFilter *selectedFilter;
     
    [stillCamera removeAllTargets];
    [filter removeAllTargets];
 
     
    switch (buttonIndex) {
        case 0:
            selectedFilter = [[GPUImageGrayscaleFilter alloc] init];
            break;
        case 1:
            selectedFilter = [[GPUImageSepiaFilter alloc] init];
            break;
        case 2:
            selectedFilter = [[GPUImageSketchFilter alloc] init];
            break;
        case 3:
            selectedFilter = [[GPUImagePixellateFilter alloc] init];
            break;
        case 4:
            selectedFilter = [[GPUImageColorInvertFilter alloc] init];
            break;
        case 5:
            selectedFilter = [[GPUImageToonFilter alloc] init];
            break;
        case 6:
            selectedFilter = [[GPUImagePinchDistortionFilter alloc] init];
            break;
        case 7:
            selectedFilter = [[GPUImageFilter alloc] init];
            break;
        default:
            break;
    }
         
    filter = selectedFilter;
    GPUImageView *filterView = (GPUImageView *)self.view;
    [filter addTarget:filterView];
    [stillCamera addTarget:filter];
     
}

Строки 11-12 используются для сброса текущего выбранного фильтра.

Строки 15 — 42 выше должны выглядеть знакомо логике в ViewController.m ; мы просто включаем выбранную кнопку, чтобы создать экземпляр корреляционного фильтра.

Строки 44 — 47 берут вновь созданный фильтр и применяют его к камере GPUImage.

Если вы соберете и запустите проект сейчас, вы должны увидеть, что вновь созданная кнопка фильтра позволяет пользователю опробовать фильтры GPUImage в режиме реального времени!


Теперь, когда у нас работают фильтры прямой трансляции, последний важный шаг в руководстве — дать пользователю возможность делать снимки с помощью камеры GPUImage, а затем отображать их обратно в карусели фотографий контроллера основного вида.

Чтобы достичь этого, мы будем передавать сообщения между контроллерами представления, используя шаблон проектирования делегирования. В частности, мы создадим наш собственный протокол формального делегата в MTCameraViewController а затем настроим основной класс ViewController для соответствия этому протоколу для получения сообщений делегирования.

Для начала перейдите в MTViewController.h и измените код следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
#import <UIKit/UIKit.h>
 
@protocol MTCameraViewControllerDelegate
 
— (void)didSelectStillImage:(NSData *)image withError:(NSError *)error;
 
@end
 
@interface MTCameraViewController : UIViewController
 
@property(weak, nonatomic) id delegate;
 
@end

Приведенный выше код объявляет формальный шаблон делегата MTCameraViewControllerDelegate в строках 3-7, а затем создает объект делегата в строке 11.

Затем переключитесь на MTCameraViewController.m и синтезируйте свойство делегата:

1
2
3
@implementation MTCameraViewController
 
@synthesize delegate;

С объявленным протоколом нам теперь нужно реализовать его в основном классе ViewController . Перейдите в ViewController.h и добавьте следующие строки:

1
2
3
4
5
6
7
#import <UIKit/UIKit.h>
#import «iCarousel.h»
#import «MTCameraViewController.h»
 
@interface ViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate, UIActionSheetDelegate, MTCameraViewControllerDelegate, iCarouselDataSource, iCarouselDelegate>
 
@end

Теперь откройте файл ViewController.m . Мы хотим назначить свойство делегата, когда создается экземпляр контроллера представления. Поскольку мы используем раскадровки, правильное место для этого — метод prepareForSegue:sender: который будет вызван непосредственно перед тем, как новый контроллер представления будет выведен на экран:

1
2
3
4
5
6
7
8
9
— (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([segue.identifier isEqualToString:@»pushMTCamera»])
    {
        // Set the delegate so this controller can received snapped photos
        MTCameraViewController *cameraViewController = (MTCameraViewController *) segue.destinationViewController;
        cameraViewController.delegate = self;
    }
}

Далее нам нужно реализовать didSelectStillImage:withError: метод, требуемый протоколом MTCameraViewControllerDelegate :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#pragma mark —
#pragma mark MTCameraViewController
 
// This delegate method is called after our custom camera class takes a photo
— (void)didSelectStillImage:(NSData *)imageData withError:(NSError *)error
{
    if(!error)
    {
        UIImage *image = [[UIImage alloc] initWithData:imageData];
        [displayImages addObject:image];
        [self.photoCarousel reloadData];
         
        self.filterButton.enabled = YES;
        self.saveButton.enabled = YES;
 
    }
    else
    {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@»Capture Error» message:@»Unable to capture photo.»
         
        [alert show];
    }
}

Приведенный выше код преобразует объект NSData переданный методу, в UIImage а затем перезагружает карусель фотографий.

Наконец, нам нужно закончить, вернувшись к MTCameraViewController.m и добавив соответствующий вызов метода делегата. Сначала настройте метод IBAction который будет запускать привязку камеры:

1
2
3
4
5
6
    GPUImageFilter *filter;
}
 
— (IBAction)captureImage:(id)sender;
 
@end

Прежде чем продолжить, подключите этот метод к кнопке «Сделать фото» в файле MainStoryboard.storyboard .

Наконец, добавьте реализацию метода:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-(IBAction)captureImage:(id)sender
{
    // Disable to prevent multiple taps while processing
    UIButton *captureButton = (UIButton *)sender;
    captureButton.enabled = NO;
     
    // Snap Image from GPU camera, send back to main view controller
    [stillCamera capturePhotoAsJPEGProcessedUpToFilter:filter withCompletionHandler:^(NSData *processedJPEG, NSError *error)
     {
         if([delegate respondsToSelector:@selector(didSelectStillImage:withError:)])
         {
             [self.delegate didSelectStillImage:processedJPEG withError:error];
         }
         else
         {
             NSLog(@»Delegate did not respond to message»);
         }
 
         runOnMainQueueWithoutDeadlocking(^{
             [self.navigationController popToRootViewControllerAnimated:YES];
         });
     }];
}

Строки 3-5 выше отключают кнопку «Сделать фото», чтобы предотвратить многократные нажатия во время обработки.

В строках 7 — 22 используется метод capturePhotoAsJPEGProcessedUpToFilter:withCompletionHandler: для фактического сохранения изображения JPEG, проверьте, установлен ли делегат, а затем отправьте данные изображения делегату, если он установлен.

Строка 19 выводит текущий контроллер представления, но делает это в главном потоке приложения.


Поздравляем! Если вы уже прошли этот урок, то у вас должно быть полнофункциональное приложение для фотосъемки! Если у вас есть вопросы или отзывы, не стесняйтесь оставлять их в разделе комментариев ниже или отправлять их мне напрямую через Twitter ( @markhammonds ).

Спасибо за прочтение!