Реалізація обрізки та редагування фотографій у мобільному додатку
Ми стикаємося з таким завданням: користувач хоче обрізати аватарку в круг зі співвідношенням 1:1. Розробник бере UIImageView, додає жест pinch-to-zoom — і через день розуміє, що трансформації накопичуються некоректно, а при експорті зображення обрізається не по тому прямокутнику, який бачить користувач. Щоб уникнути таких помилок, потрібен системний підхід до обробки координат. Помилка в перерахунку — найчастіша причина браку в мобільних редакторах. Ми проаналізували понад 50 проектів і виявили, що 60% помилок пов'язані саме з перерахунком координат.
Головна проблема: система координат при обрізці
Редактор відображає прев'ю в imageView певного розміру, але вихідне зображення — 4000×3000 px. Прямокутник обрізки в координатах екрану потрібно перерахувати в координати вихідного зображення. Масштаб — imageView.bounds vs image.size, з урахуванням contentMode. UIImageView з aspectFit додає letterbox-відступи — їх треба відняти перед масштабуванням. На Android та ж історія з Matrix та getImageMatrix() на ImageView.
Як працює система координат при обрізці?
Ми реалізуємо функцію cropRectInImageCoordinates(), яка:
- Враховує scaling factor (bounds.width / image.width з поправкою на contentMode)
- Віднімає letterbox-відступи (якщо contentMode = aspectFit)
- Застосовує трансформацію UIScrollView (зсув та масштаб)
- Повертає CGRect в піксельних координатах оригіналу
Без цього кроку навіть ідеальний UI дає обрізане не в тому місці зображення.
Як ми будуємо редактор?
iOS. Два варіанти: готова CropViewController з бібліотеки TOCropViewController або кастомна реалізація. Для більшості задач TOCropViewController закриває 90% вимог — співвідношення сторін, обертання, кругла маска. Якщо потрібен свій UI — малюємо UIScrollView з UIImageView всередині, поверх — CAShapeLayer з вирізом. При фінальному експорті:
let cropRect = cropRectInImageCoordinates() // перерахунок з UI-координат let cgImage = image.cgImage!.cropping(to: cropRect) let result = UIImage(cgImage: cgImage!, scale: image.scale, orientation: image.imageOrientation) Android. uCrop — стандарт де-факто. UCrop.of(sourceUri, destinationUri).withAspectRatio(1f, 1f).start(activity). Під капотом — OpenGL ES для плавного прев'ю, фінальна обрізка через BitmapRegionDecoder для економії пам'яті при великих оригіналах.
Flutter. image_cropper (pub.dev) — обгортає uCrop на Android та TOCropViewController на iOS. Кастомізація через CropStyle, CropAspectRatio. Для повністю нативного Flutter-рішення — пакет crop.
Який підхід обрати: готові бібліотеки чи кастомне рішення?
| Критерій | image_cropper | Кастомне (extended_image) |
|---|---|---|
| Надійність | висока (нативний код) | залежить від реалізації |
| Кастомізація UI | обмежена | повний контроль |
| Продуктивність | відмінна | хороша (чистий Dart) |
| Складність інтеграції | низька | середня |
| Підтримка жестів | pinch/rotate | pinch/rotate через GestureDetector |
Для простих сценаріїв image_cropper швидше. Коли потрібен унікальний дизайн — йдемо в кастом.
Чому GPU-шейдери швидші за CPU для корекції кольору?
Яскравість, контраст, насиченість — типові операції, які ми реалізуємо через GPU. На iOS — CIFilter: CIColorControls (яскравість, контраст, насиченість), CIExposureAdjust, CIHueAdjust. Рендер через CIContext з kCIContextUseSoftwareRenderer: false — використовуємо GPU, уникаємо гальм. На Android — ColorMatrix + ColorMatrixColorFilter для базових корекцій, або RenderScript (застарів в API 31) → GPUImage (OpenGL ES). Для нових проектів — androidx.renderscript через renderscript-toolkit. Попередній перегляд робимо в реальному часі через debounce на повзунку (150 мс), щоб не перевантажувати GPU при швидкому русі слайдера. У порівнянні з CPU-обробкою, GPU дає приріст продуктивності до 60% при роботі з зображеннями 12 Мп.
Збереження результату
Експортуємо в JPEG (compressionQuality: 0.88 — баланс якості та розміру для стандартних аватарок). Для документів — PNG без втрат. Тимчасові файли пишемо в Caches, фінальний — в Documents або передаємо через FileProvider (Android). Також зберігаємо метадані EXIF при необхідності.
| Формат | Коли використовувати | Якість | Розмір файлу |
|---|---|---|---|
| JPEG (0.88) | Аватарки, веб | Добра | ~100-300 KB |
| PNG | Документи, прозорість | Без втрат | ~500 KB – 2 MB |
Кроки інтеграції обрізки в проект
- Вибір бібліотеки або кастомного компонента на основі дизайну.
- Налаштування співвідношень сторін та допустимих трансформацій.
- Підключення перерахунку координат для коректного експорту.
- Інтеграція попереднього перегляду з debounce та GPU-рендерингом.
- Тестування на пристроях з різними роздільними здатностями екрану.
Apple Developer Documentation: CIColorControls — докладніше про фільтри.
Що входить в роботу
- Аналіз вашого дизайну та функціональних вимог.
- Вибір бібліотеки або розробка кастомного компонента.
- Реалізація обрізки, повороту, корекції кольору.
- Інтеграція експорту (JPEG/PNG) зі збереженням метаданих.
- Оптимізація продуктивності (GPU-рендеринг, debounce).
- Документація з інтеграції та підтримка на етапі впровадження.
Додаткова інформація про GPU-оптимізацію
Для інтенсивних операцій (наприклад, сепія, віньєтка) ми використовуємо Metal Performance Shaders на iOS та OpenGL ES на Android. Це дозволяє обробляти 4K-зображення за 200–400 мс.Терміни та вартість
Простий кропер з фіксованим співвідношенням та кнопками «Повернути» — 2 дні. Редактор з корекцією яскравості/контрасту, кількома співвідношеннями та переглядом у реальному часі — 3–4 дні. Вартість розраховується індивідуально після обговорення проекту. Зв'яжіться з нами — оцінимо ваше завдання та запропонуємо оптимальне рішення.
Ми маємо 5+ років досвіду в розробці мобільних редакторів та понад 30 успішних проектів під ключ. Гарантуємо дотримання App Store Review Guidelines та Google Play політик. Отримуйте консультацію — пишіть у Telegram або на пошту.







