Из этого туториала вы узнаете, как использовать GPUImage для применения фильтров изображения в режиме реального времени при отображении канала камеры устройства. Попутно вы узнаете, как автоматически заполнять изображения в контроллере карусели и как изменять размеры изображений с помощью категорий UIImage +.
Обзор проекта
Учебное пособие
Этот учебник основан на предыдущем посте, озаглавленном «Создание приложения для фотографий с помощью GPUImage» . В предыдущем уроке демонстрировалось, как использовать UIImagePickerController
для выбора фотографий из фотоальбома или камеры устройства, как добавить библиотеку GPUImage
в проект и как использовать класс GPUImageFilter
для улучшения кадров неподвижных камер. Если вы уже знакомы с UIImagePickerController
и можете выяснить, как самостоятельно добавить GPUImage
в ваш проект, вы сможете выбрать, с чего последний учебник закончился.
Шаг 1: Импорт iCarousel
Этот проект будет широко использовать проект с открытым исходным кодом под названием 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»
|
Шаг 2: Импорт UIImage + Категории
Прежде чем мы отобразим наши изображения с помощью iCarousel, нам нужно уменьшить их до приемлемого размера. Вместо того чтобы писать весь код, чтобы сделать это вручную, мы воспользуемся отличным проектом UIImage + Categories , который предоставляет базовую функциональность для изменения размера изображений, а также несколько других приемов манипулирования изображениями.
Загрузите код 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»
|
Шаг 3: добавление iCarousel View в IB
С кодом 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
.
Теперь установите связь между только что объявленным выходом photoCarousel
и UIView
добавленным в качестве подпредставления.
Нам также нужно установить источник данных и делегата для 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 отображалось под UIToolbar
в иерархии представлений. Вы можете сделать это визуально, просто перетащив их в правильном порядке в Интерфейсном Разработчике:
Обратите внимание, как представление iCarousel теперь отображается перед панелью инструментов.
Сохраните свою работу в Интерфейсном Разработчике.
Шаг 4. Реализация протоколов 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;
}
|
Выше мы устанавливаем максимальный размер 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;
}
|
С помощью всего лишь нескольких настроек, проект сможет отображать изображения в карусели!
Шаг 5: Переключитесь на новую модель данных
Ранее в этом руководстве мы заменили свойство 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];
}
|
Последние три строки этого метода фильтруют изображение модели данных, соответствующее текущему индексу карусели, заменяют отображение карусели этим изображением, а затем обновляют карусель.
Если все прошло хорошо, теперь вы сможете скомпилировать и запустить проект! Это позволит вам просматривать изображения в карусели, а не просто в представлении изображений.
Шаг 6: добавьте жест для удаления изображений
Пока что приложение выглядит хорошо, но было бы неплохо, если бы пользователь мог удалить фотографию с карусели после добавления ее на дисплей. Нет проблем! Мы можем выбрать любой подкласс 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 удаляет жест из текущей цели, чтобы предотвратить запуск нескольких жестов во время обработки. Остальные две строки на данный момент ничего нового.
Создайте и снова запустите приложение. Теперь вы сможете динамически удалять предметы из карусели!
Шаг 7: Создайте MTCameraViewController
В оставшейся части этого урока основное внимание будет уделено использованию GPUImageStillCamera
для создания пользовательского GPUImageStillCamera
управления выбора камеры, который может применять фильтры к входящему видеопотоку в режиме реального времени. GPUImageStillCamera
тесно работает с классом GPUImageView
. Кадры камеры, сгенерированные GPUImageStillCamera
, отправляются в назначенный объект GPUImageView
для отображения пользователю. Все это достигается с помощью базовой функциональности, предоставляемой платформой AVFoundation
, которая обеспечивает программный доступ к данным кадра камеры.
Поскольку GPUImageView
является дочерним классом UIView
, мы можем встроить весь экран камеры в наш собственный пользовательский класс UIViewController
.
Добавьте новый подкласс UIViewController
в проект, щелкнув правой кнопкой мыши «PhotoFX» в навигаторе проекта и выбрав « Новый файл»> «Класс Objective-C» . Назовите класс «MTCameraViewController» и введите «UIViewController» в поле «подкласс».
Нажмите «Далее», а затем «Создать», чтобы завершить процесс.
Перейдите в файл 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: Добавьте пользовательскую камеру в IB
Прежде чем код из шага 8 будет работать, нам нужно добавить пользовательский класс MTCameraViewController
только что созданный, в раскадровку проекта.
Откройте файл MainStoryboard.storyboard и перетащите новый View Controller из библиотеки объектов. Выбрав этот объект, перейдите на вкладку «Инспектор удостоверений» и установите для поля «Класс» значение «MTCameraViewController».
Затем перетащите UIToolbar
на экран и установите для его свойства стиля значение «Черный непрозрачный» в инспекторе атрибутов. Затем добавьте два элемента кнопки гибкой панели ширины на панель инструментов с UIBarButtonItem
« UIBarButtonItem
в центре.
Чтобы подключить этот контроллер представления к потоку приложений, щелкните правой кнопкой мыши кнопку «камера» на главном контроллере представления и перетащите сработавшее выходное соединение сегментов на новый контроллер представления:
При появлении запроса выберите «Push» в качестве стиля перехода.
После того, как вновь добавленный объект segue выбран, перейдите в «Инспектор атрибутов» и установите идентификатор «pushMTCamera». Продолжайте и убедитесь, что «Push» выбран из выпадающего «Style».
С созданным UIImagePicker
убедитесь, что UIImagePicker
больше не будет отображаться, когда пользователь нажимает кнопку камеры на первом экране приложения, отсоединив выход IBAction
от метода photoFromCamera
.
Наконец, выберите основной вид вновь созданного MTCameraViewController. Перейдите в инспектор удостоверений и установите значение класса «GPUImageView».
Хотя это еще не идеально, если вы сейчас создаете и запускаете приложение, вы должны иметь возможность MTCameraViewController
в иерархию представлений и наблюдать, как GPUImageView
отображает кадры с камеры в режиме реального времени!
Шаг 9: Добавить выбор фильтра в реальном времени
Теперь мы можем добавить логику, необходимую для управления фильтром, применяемым к дисплею камеры. Сначала перейдите к 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 в режиме реального времени!
Шаг 10. Создание протокола делегирования камеры.
Теперь, когда у нас работают фильтры прямой трансляции, последний важный шаг в руководстве — дать пользователю возможность делать снимки с помощью камеры 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 ).
Спасибо за прочтение!