Привет, меня зовут Сергей, и я мобильный разработчик в компании 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-а к оригинальному плагину, но это уже совсем другая история.

Спасибо за чтение и не стесняйтесь обращаться с вопросами или отзывами!

Комментарии (8)


  1. ilyamodder
    21.10.2023 17:18
    +2

    Сам создали себе проблему (кроссплатформу) и теперь героически ее решаете (переделывая нативно). Такими темпами, решая остальные проблемы, обратно все на натив перепишете)))


  1. iredun
    21.10.2023 17:18
    +3

    У вас ссылка на код, ведет на OpenAI ChatGPT


    1. easyman
      21.10.2023 17:18
      +2

      Соавторство ????


    1. SergeyDesenko Автор
      21.10.2023 17:18

      Спасибо, ссылку действительно перепутал, поправил


  1. rusik2293
    21.10.2023 17:18

    Обычно наоборот урезают и сжимают, так как за трафик и объем хранилища в облаке берут не дёшево, да даже своего хранилища с таким размером фоток скоро может стать маловато и придется идти диски докупать


  1. stanislav37
    21.10.2023 17:18

    Позвольте поинтересоваться, каким образом Вы используете плагин детально и какую его версию ? А то выглядит как наброс субстанции на вентилятор в погоне за хайпом, баг похожий на описанное был исправлен лет 5 назад, ключевые слова.. setHighResolutionCaptureEnabled. Обвинить официальный плагин камеры flutter в 2023, это мягко говоря интересно, или тут расчет как обычно на то, что читают школьники ?

    п.с. хотя ссылка на код "с примером" ведущая на https://chat.openai.com/c/ссылка - обясняет в общем то все.


    1. SergeyDesenko Автор
      21.10.2023 17:18

      Поправил ссылку


    1. SergeyDesenko Автор
      21.10.2023 17:18

      Подливали к себе недавно 0.10.5. Проблему мы исправляли некоторое время назад, сейчас пристально не следим, починили ли в официальном плагине это. Судя по этим issue, все еще не очень хорошо. А у вас какой-то другой опыт? Расскажите, интересно
      https://github.com/flutter/flutter/issues/78247
      https://github.com/flutter/flutter/issues/78247