Мы сталкиваемся с такой задачей: пользователь хочет обрезать аватарку в круг с соотношением 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 или на почту.







