Привет, меня зовут Сергей, и я мобильный разработчик в компании Brickit. Некоторое время назад мы наконец переписали приложения для iOS и Android на общий код на Flutter. Мы были в восторге от результата, но сам переход был далеко не гладким. Одной из важных частей этой авантюры была настройка и использование плагина камеры, что оказалось нетривиальным как на iOS, так и на Android. В этой статье я расскажу о проблеме с недостаточно высоким разрешением фотографий на iOS, немного объясню, как работает оригинальный плагин, и предоставлю наше решение с примерами кода о том, как сделать это лучше. Ссылка на полный код в конце статьи.
В чем заключалась проблема?
Если кратко, максимальное разрешение фотографий, получаемых с помощью плагина камеры, не соответствовало нашим требованиям, и мы при этом знали, что с нативной камерой можно получить более высокое разрешение.
В нашем приложении камера это часть основного функционала. Пользователи фотографируют свои поделки, устанавливают фото профиля и, что самое важное, снимают изображения своих деталек лего для последующего сканирования в приложении. Поскольку в некоторых случаях на фотографии может быть несколько тысяч деталей, нам необходимо получить максимально возможное разрешение, которое может предоставить камера телефона.
Если вы пользовались плагином камеры для Flutter, то вы могли заметить, что в нем есть несколько пресетов для управления разрешением. Проблема заключалась в том, что установка параметров ResolutionPreset.max
или ResolutionPreset.ultraHigh
приводила к итоговому разрешению изображения 3840x2160, которое не только имеет соотношение сторон 16:9 вместо обычного 4:3 для фотографий, но и не является наивысшим разрешением для большинства современных айфонов.
Таким образом, после долгих поисков на StackOverflow и просмотра обсуждений на GitHub, а также неудачных попыток найти альтернативный плагин, мы решили сделать форк оригинального плагина камеры для Flutter и исправить эту проблему своими силами.
Как это работает?
Установка разрешения для камеры в оригинальном плагине Flutter происходит в три этапа:
Установка пресета разрешения в коде Flutter при инициализации CameraController-а:
final CameraController cameraController = CameraController(
cameraDescription,
ResolutionPreset.max, //or any other preset
enableAudio: enableAudio,
imageFormatGroup: ImageFormatGroup.jpeg,
);
Затем, под капотом, значение енама ResolutionPreset сопоставляется с его аналогом в нативной части IOS:
typedef NS_ENUM(NSInteger, FLTResolutionPreset) {
FLTResolutionPresetVeryLow,
FLTResolutionPresetLow,
FLTResolutionPresetMedium,
FLTResolutionPresetHigh,
FLTResolutionPresetVeryHigh,
FLTResolutionPresetUltraHigh,
FLTResolutionPresetMax,
};
Наконец, само разрешение камеры устанавливается в нативном коде, используя преобразованное значение пресета:
- (void)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset {
switch (resolutionPreset) {
case FLTResolutionPresetMax:
case FLTResolutionPresetUltraHigh:
if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPreset3840x2160]) {
_videoCaptureSession.sessionPreset = AVCaptureSessionPreset3840x2160;
_previewSize = CGSizeMake(3840, 2160);
break;
}
if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPresetHigh]) {
_videoCaptureSession.sessionPreset = AVCaptureSessionPresetHigh;
_previewSize =
CGSizeMake(_captureDevice.activeFormat.highResolutionStillImageDimensions.width,
_captureDevice.activeFormat.highResolutionStillImageDimensions.height);
break;
}
case FLTResolutionPresetVeryHigh:
if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPreset1920x1080]) {
_videoCaptureSession.sessionPreset = AVCaptureSessionPreset1920x1080;
_previewSize = CGSizeMake(1920, 1080);
break;
}
case FLTResolutionPresetHigh:
if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
_videoCaptureSession.sessionPreset = AVCaptureSessionPreset1280x720;
_previewSize = CGSizeMake(1280, 720);
break;
}
//and so on
Как видно из кода, использование максимального пресета приводит к проходу через все варианты в switch до тех пор, пока не будет выбрана наивысшая доступная настройка качества.
Когда устанавливается пресет ultraHigh
, используется AVCaptureSessionPreset3840x2160
, если он доступен. Это корректное поведение при использовании видеовывода, так как 4К сейчас является максимальным разрешением видео для устройств iOS. Однако, если вы используете камеру для фотографий (как в нашем случае), можно использовать другой пресет - AVCaptureSessionPresetPhoto
.
Что мы изменили?
Просмотрев код плагина и изучив документацию Apple, мы пришли к следующему решению:
- (void)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset {
switch (resolutionPreset) {
case FLTResolutionPresetMax:
if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPresetPhoto]) {
_videoCaptureSession.sessionPreset = AVCaptureSessionPresetPhoto;
_previewSize = CGSizeMake(4032, 3024);
break;
}
if ([_videoCaptureSession canSetSessionPreset:AVCaptureSessionPreset3840x2160]) {
_videoCaptureSession.sessionPreset = AVCaptureSessionPreset3840x2160;
_previewSize = CGSizeMake(3840, 2160);
break;
}
case FLTResolutionPresetUltraHigh:
// and so on
В случае с максимальным пресетом мы пытаемся установить разрешение 4032x3024 для камеры. Метод canSetSessionPreset
возвращает булево значение, которое сообщает, можно ли установить запрошенный пресет для устройства пользователя. Если результат положительный, мы передаем пресет в сессию камеры и устанавливаем размер превью в 4032x3024. Все готово!
Поскольку камеру можно использовать как для видео, так и для фотографий, и приложение может работать на широком спектре устройств, нельзя полагаться на то, что 4032x3024 всегда будет доступно. Поэтому, если canSetSessionPreset
возвращает отрицательный результат, мы используем старый добрый 4K, как и в оригинальном плагине.
Так как мы используем соотношение сторон 4:3 для фотографий деталек в нашем приложении, с использованием стандартного плагина нам пришлось бы обрезать фотографию размером 3840x2160 (16:9) до 2880x2160 (4:3). И на выходе получалось бы фото 6 мегапикселей (точнее 6 220 800 пикселей). С нашим исправлением нам не нужно тратить время и ресурсы на обрезку изображения, и в то же время мы получаем фотографию размером 4032x3024, 12 мегапикселей (точнее 12 192 768 пикселей). А это, между прочим, вдвое больше, чем с оригинальным плагином!
Полный код вместе с примером тут.
Мы успешно используем этот код в продакшене примерно 6 месяцев без каких-либо проблем. Хочется предложить эти изменения в виде pull request-а к оригинальному плагину, но это уже совсем другая история.
Спасибо за чтение и не стесняйтесь обращаться с вопросами или отзывами!