Реализация обрезки и редактирования фотографий в мобильном приложении
Пользователь хочет обрезать аватарку в круг с соотношением 1:1. Разработчик берёт UIImageView, добавляет жест pinch-to-zoom — и через день понимает, что трансформации накапливаются некорректно, а при экспорте изображение обрезается не по тому прямоугольнику, что видит пользователь.
Главная проблема: система координат при обрезке
Редактор отображает превью в imageView определённого размера, но исходное изображение — 4000×3000 px. Прямоугольник обрезки в координатах экрана нужно пересчитать в координаты исходного изображения. Масштаб — imageView.bounds vs image.size, с учётом contentMode. UIImageView с aspectFit добавляет letterbox-отступы — их надо вычесть перед масштабированием.
На Android та же история с Matrix и getImageMatrix() на ImageView.
Как строим редактор
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.
Яркость, контраст, насыщенность
Реализуем через 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 при быстром движении слайдера.
Сохранение результата
Экспортируем в JPEG (compressionQuality: 0.88 — баланс качества и размера для стандартных аватарок). Для документов — PNG без потерь. Временные файлы пишем в Caches, финальный — в Documents или передаём через FileProvider (Android).
Flutter: image_cropper vs кастомное решение
image_cropper (pub.dev) вызывает нативные uCrop/TOCropViewController. Плюс — надёжность нативных компонентов. Минус — UI не кастомизируется глубоко, сложно вписать в дизайн-систему приложения. Для полного контроля над UI используем extended_image (pub.dev) — Flutter-нативный кроппер с поддержкой жестов pinch и rotate, рисует через CustomPaint.
Сроки
Простой кроппер с фиксированным соотношением и кнопками «Повернуть» — 2 дня. Редактор с коррекцией яркости/контраста, несколькими соотношениями и превью в реальном времени — 3–4 дня.







