Implementation of Photo Cropping and Editing in a Mobile Application
User wants to crop avatar into a circle with 1:1 ratio. Developer takes UIImageView, adds pinch-to-zoom gesture — and a day later realizes transformations accumulate incorrectly, and on export the image crops to wrong rectangle from what user sees.
Main Problem: Coordinate System During Cropping
Editor displays preview in imageView of specific size, but source image is 4000×3000 px. Crop rectangle in screen coordinates must be recalculated to source image coordinates. Scale is imageView.bounds vs image.size, accounting for contentMode. UIImageView with aspectFit adds letterbox padding — must subtract before scaling.
On Android same story with Matrix and getImageMatrix() on ImageView.
How We Build Editor
iOS. Two options: ready CropViewController from TOCropViewController library or custom implementation. For most tasks TOCropViewController covers 90% requirements — aspect ratios, rotation, circular mask. If custom UI needed — draw UIScrollView with UIImageView inside, on top — CAShapeLayer with cutout. On final export:
let cropRect = cropRectInImageCoordinates() // recalculate from UI coords
let cgImage = image.cgImage!.cropping(to: cropRect)
let result = UIImage(cgImage: cgImage!, scale: image.scale, orientation: image.imageOrientation)
Android. uCrop — de facto standard. UCrop.of(sourceUri, destinationUri).withAspectRatio(1f, 1f).start(activity). Under the hood — OpenGL ES for smooth preview, final crop via BitmapRegionDecoder for memory savings with large source.
Flutter. image_cropper (pub.dev) — wraps uCrop on Android and TOCropViewController on iOS. Customization via CropStyle, CropAspectRatio. For fully native Flutter solution — crop package.
Brightness, Contrast, Saturation
Implement via GPU shaders. On iOS — CIFilter: CIColorControls (brightness, contrast, saturation), CIExposureAdjust, CIHueAdjust. Render via CIContext with kCIContextUseSoftwareRenderer: false — use GPU, avoid lags.
On Android — ColorMatrix + ColorMatrixColorFilter for basic corrections, or RenderScript (deprecated in API 31) → GPUImage (OpenGL ES). For new projects — androidx.renderscript via renderscript-toolkit.
Preview in real time via debounce on slider (150 ms) to not overload GPU on fast slider movement.
Saving Result
Export to JPEG (compressionQuality: 0.88 — quality/size balance for standard avatars). For documents — PNG lossless. Write temp files to Caches, final to Documents or pass via FileProvider (Android).
Flutter: image_cropper vs Custom Solution
image_cropper (pub.dev) calls native uCrop/TOCropViewController. Plus — native component reliability. Minus — UI can't customize deeply, hard to fit into app design system. For full UI control use extended_image (pub.dev) — Flutter-native cropper with pinch and rotate gesture support, draws via CustomPaint.
Timeline
Simple cropper with fixed ratio and "Rotate" buttons — 2 days. Editor with brightness/contrast correction, multiple ratios, real-time preview — 3–4 days.







