Feature spec
IntermediateCategory
ai-features
Build with AI
3–6 hours with FlutterFlow + custom backend
Custom build
1–2 weeks custom dev
Running cost
$0–25/mo up to 100 users; $89–115/mo at 1K users
Works on
Everything it takes to ship AI-Powered Image Resizing — parts, prompts, and real costs.
AI-powered image resizing uses smart crop to keep faces and focal objects centered across multiple output sizes — not just a center crop. You need three pieces: an image picker, a Cloudinary or AWS Rekognition smart crop API called from a Supabase Edge Function, and a before/after comparison UI. With FlutterFlow you can ship a working build in 3–5 hours. Costs run $0–25/month at 100 users on Cloudinary's free tier; $89–115/month at 1,000 users on the Plus plan.
What AI-Powered Image Resizing Actually Is
Standard image resizing crops from the center — which works for landscapes but cuts off faces in portrait photos and misses the subject in product shots. AI-powered smart crop uses computer vision to detect the most important region (faces, primary objects) and crops around it, so the output looks intentional rather than accidental. Cloudinary's g_auto transformation handles face detection and focal point selection server-side with a single URL parameter. AWS Rekognition returns face bounding boxes you use to calculate the crop coordinates in sharp.js. The real engineering work is the pipeline: image picker → user-scoped Supabase Storage upload → smart crop API call → status tracking in an image_jobs table → before/after comparison UI → signed URL download.
What users consider table stakes in 2026
- Smart crop that keeps faces and focal subjects centered, not a simple center-crop at a different aspect ratio
- Before/after comparison slider so users can see exactly what the AI changed before saving
- Multiple preset output sizes: square 1:1, story 9:16, banner 16:9, and thumbnail 4:3
- Progress indicator during AI processing — the Cloudinary transformation takes 1–3 seconds and users need feedback that something is happening
- Download or share the resized result directly from the app without re-uploading
- Batch mode for processing multiple images from a gallery selection in one operation
Anatomy of the Feature
Seven components across UI, backend, and service layers. AI tools correctly generate the file picker and storage upload. The smart crop API routing and the before/after comparison widget are where first builds fail.
Image Picker
UIFlutter image_picker package on mobile (FlutterFlow native action) or HTML input[type=file] with accept='image/*' on web (Lovable). Accepts JPEG, PNG, and WEBP. Should apply imageQuality: 80 and maxWidth: 2048 constraints before upload to prevent out-of-memory crashes on high-resolution devices.
Note: FlutterFlow's in-browser preview does not expose the native image_picker. Test on a real iOS or Android device build, not the FlutterFlow web preview.
Upload Layer
BackendSupabase Storage bucket receives the original image at path originals/{user_id}/{uuid}.jpg using a signed upload URL pattern. The client uploads directly to Storage; the Edge Function receives only the storage path, not the image bytes — this avoids the 6MB Edge Function request body limit.
Note: Use the signed upload URL pattern: call supabase.storage.from('originals').createSignedUploadUrl(path) on the server, return the URL to the client, then the client uploads directly to Supabase Storage without going through the Edge Function.
Smart Crop API
ServiceCloudinary AI crop via the g_auto:face or g_auto:subject transformation URL parameter. The transformation URL is constructed server-side: https://res.cloudinary.com/{cloud_name}/image/upload/c_fill,g_auto:face,w_{width},h_{height}/{public_id}. Alternatively, AWS Rekognition DetectFaces returns bounding box coordinates that you pass to sharp.js for precise crop math.
Note: g_auto:faces (plural) detects multiple faces in group photos. Always set a fallback gravity (g_center) explicitly so images without detected faces fall back to center crop rather than an error.
Transformation Queue
BackendA Supabase Edge Function orchestrates the pipeline: receives the storage path and output dimensions, constructs the Cloudinary transformation URL or calls AWS Rekognition, stores the result URL in image_jobs, and returns the processed image URL. Job status (pending/processing/done/failed) is tracked per row.
Note: For batch processing, the Edge Function should accept an array of paths and process them sequentially; parallel processing risks hitting Cloudinary rate limits on the free tier.
Before/After Slider
UIreact-compare-slider on web displays the original and processed images side by side with a draggable divider. On Flutter, a custom Stack widget with a GestureDetector handles horizontal drag to reveal the processed image using a ClipRect with varying width.
Note: Both images must be fully loaded before the slider renders, or users will see a blank before-state on initial display. Use loading state to defer rendering the comparison until both image URLs are resolved.
Output Size Presets
UIA toggle button group with four presets — Square 1:1 (1080×1080), Story 9:16 (1080×1920), Banner 16:9 (1920×1080), Thumbnail 4:3 (800×600). The selected preset passes target width and height to the Transformation Queue Edge Function as query parameters.
Note: Presets should be displayed with visual ratio previews (small rectangles in the correct aspect ratio) so users understand the output shape before processing.
Download/Share Button
UIOn web, triggers a Supabase Storage createSignedUrl() (3600-second expiry) for the processed image path, then uses an anchor[download] tag to initiate the browser download. On Flutter, uses the flutter_share package to share the signed URL or saves to photo gallery via gallery_saver.
Note: The processed images bucket must be private. Never use a public bucket for user-uploaded processed content — use signed URLs with short expiry times.
The data model
One table tracks each image processing job with status and output paths. Run this in the Supabase SQL editor:
1create table public.image_jobs (2 id uuid primary key default gen_random_uuid(),3 user_id uuid references auth.users(id) on delete cascade not null,4 original_path text not null,5 output_path text,6 status text not null default 'pending'7 check (status in ('pending', 'processing', 'done', 'failed')),8 width int,9 height int,10 crop_mode text not null default 'g_auto:face',11 error_message text,12 created_at timestamptz not null default now()13);1415alter table public.image_jobs enable row level security;1617create policy "Users see own jobs"18 on public.image_jobs for select19 using (auth.uid() = user_id);2021create policy "Users insert own jobs"22 on public.image_jobs for insert23 with check (auth.uid() = user_id);2425create policy "Users update own jobs"26 on public.image_jobs for update27 using (auth.uid() = user_id);2829create index image_jobs_user_status_idx30 on public.image_jobs (user_id, status, created_at desc);Heads up: The storage bucket policies should also be configured: originals bucket — authenticated users read and write their own path prefix ({user_id}/); processed bucket — authenticated users read their own path prefix only. Set up these bucket policies in the Supabase Dashboard under Storage → Policies.
Build it — pick your path
Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.
The right choice for batch processing, on-device inference, or integrating into an existing CDN or Digital Asset Management pipeline. Eliminates per-image API cost with a self-hosted sharp.js + face-api.js pipeline.
Step by step
- 1Build the image picker → Supabase Storage upload pipeline with client-side image compression (canvas drawImage + toBlob at 0.85 quality) before upload
- 2Deploy a Node.js server (or Supabase Edge Function with WASM sharp.js build) that runs: face-api.js DetectAllFaces for bounding boxes → calculate optimal crop rectangle → sharp.js resize + crop to target dimensions
- 3Implement batch processing: accept an array of storage paths, process sequentially, update image_jobs status per item, and emit real-time progress via Supabase Realtime channel
- 4Integrate with CDN: after processing, push the result to Cloudflare R2 or Cloudinary via their upload API; return the CDN URL for fast global delivery
Where this path bites
- Running sharp.js in Deno Edge Functions requires a WASM build of libvips; for high-volume processing, a dedicated Node.js server is more reliable
- face-api.js models are 5–30MB each — loading them on each Edge Function cold start adds significant latency; a persistent Node.js server with models pre-loaded is required for sub-second processing
Third-party services you'll need
Smart crop is an API-driven feature. Three viable services at different cost and accuracy levels:
| Service | What it does | Free tier | Paid from |
|---|---|---|---|
| Cloudinary | Smart crop via g_auto:face/subject transformation URL; handles resize and format conversion in one step | 25 credits/month (~25 unique transformations); unlimited delivery of cached transforms | Plus $89/mo (1,000 credits); Advanced $224/mo (3,000 credits) (approx) |
| AWS Rekognition | Face detection returning bounding box coordinates; combined with sharp.js for actual crop math | 5,000 images/month for 12 months (trial); pay-as-you-go after | $0.001 per image + S3 storage costs (approx) |
| Imagga | Smart Cropping API with subject-aware crop for non-face images (products, landscapes) | 1,000 API calls/month | Basic $29/mo (10K calls); Standard $99/mo (50K calls) (approx) |
| Supabase Storage | Stores original uploads and processed output; provides signed URLs for private access | 1GB included | Pro plan $25/mo includes 100GB; $0.021/GB beyond |
| sharp.js (WASM) | Open-source image processing; crop, resize, format conversion; zero API cost | Open-source, free | Compute cost only (server/Edge Function runtime) |
Swipe the table sideways to see pricing.
What it costs to run
Drag through the tiers to see how your monthly bill scales with users — no surprises later.
Estimated monthly running cost
Cloudinary free tier (25 credits/mo) covers low-volume testing. Supabase free tier covers Storage and Edge Functions. Most transformations are cached by Cloudinary after first request.
Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.
What breaks when AI tools build this
The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.
Image upload works in browser but fails silently in FlutterFlow preview
Symptom: FlutterFlow's browser-based preview runs as a web app, which means the image_picker package falls back to HTML file input. The native iOS/Android image picker (with camera access, gallery browsing, and quality controls) only activates in a real device build. Users testing in the browser preview see a broken or unresponsive picker that appears to work but doesn't produce a usable image.
Fix: Test image picker functionality on a real device via TestFlight (iOS) or an APK install (Android), never in the FlutterFlow browser preview. Use the FlutterFlow mobile test app on your phone as a quick first check.
Cloudinary returns cropped image with faces cut off
Symptom: The g_auto:face parameter (singular) detects a single primary face. In group photos or images where a face is partially obscured, Cloudinary may fall back to center gravity silently without indicating that face detection failed. The result is a crop that looks like a default center crop — which is exactly the behavior the user wanted to avoid.
Fix: Use g_auto:faces (plural) to detect multiple faces. Add an explicit fallback gravity: gravity_center so the fallback behavior is predictable. Show the before/after comparison slider before confirming the save, so users can see if the smart crop failed and choose a different preset or manual crop.
Large image uploads fail with 413 error from Edge Function
Symptom: Supabase Edge Functions have a 6MB request body limit. A 10MB RAW photo uploaded directly as base64 or multipart to the Edge Function exceeds this limit. The 413 error often appears as a generic network error in the client rather than a clear message, making it hard to diagnose.
Fix: Use the signed upload URL pattern: the Edge Function generates a Supabase Storage signed upload URL and returns it to the client. The client uploads directly to Supabase Storage, bypassing the Edge Function entirely. The Edge Function only receives the storage path once the upload completes.
Processed image URL is publicly accessible to anyone with the link
Symptom: If the Supabase Storage bucket containing processed images is set to public, anyone who obtains the URL (from browser history, network logs, or a shared screenshot) can access any user's processed images permanently. This is a privacy issue for apps where users process personal photos.
Fix: Set the processed images bucket to private. Generate short-lived signed URLs using supabase.storage.from('processed').createSignedUrl(path, 3600) — the URL expires after 1 hour. For downloads, generate a fresh signed URL at the moment the user clicks Download.
FlutterFlow app crashes when processing a 10MB photo
Symptom: The image_picker package returns the full-resolution image from the device gallery. A 10MB+ photo loaded entirely into memory on the device causes an OutOfMemoryError on phones with limited RAM, particularly older Android devices. The app crashes without a useful error message.
Fix: Configure image_picker with imageQuality: 80 and maxWidth: 2048 (or maxHeight for portrait). These constraints compress and resize the image before it enters app memory. For most smart crop use cases, a 2048px wide image is more than sufficient quality and weighs 200–500KB rather than 10MB.
Best practices
Apply imageQuality: 80 and maxWidth: 2048 in image_picker before upload — prevents OOM crashes and reduces storage costs without visible quality loss
Use the signed upload URL pattern to bypass the 6MB Edge Function body limit for all file uploads
Set processed images bucket to private with short-lived signed URLs; never expose user photo processing results publicly
Use g_auto:faces (plural) with explicit g_center fallback so group photos and non-face images degrade gracefully
Show a before/after comparison slider before the user confirms the save — let them see the smart crop result and choose a different preset if the AI missed the subject
Track every processing job in the image_jobs table with status so users can resume failed batch jobs without re-uploading
Cache Cloudinary transformation URLs by storing the output_url in image_jobs — the same transformation on the same image is free on delivery from Cloudinary's CDN
When You Need Custom Development
AI tools with Cloudinary handle smart crop for standard portrait and product photography. These workloads need a custom pipeline:
- Batch processing of 100+ images per session for content creators or e-commerce catalog teams — requires a queued job system and real-time progress tracking
- On-device inference required for privacy-first apps where images must never leave the device — needs TensorFlow Lite face detection model bundled in the app
- Custom-trained crop model for a specific subject type: product photography where labels must stay visible, real estate where the exterior facade must be centered, food photography where the hero dish is rarely the image center
- Integration with an existing CDN pipeline or Digital Asset Management system (Bynder, Canto, Widen) that has its own upload and transformation API
RapidDev builds these features for production
Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.
Frequently asked questions
What is smart cropping versus regular cropping?
Regular cropping cuts a rectangular region from a fixed position — typically the center of the image. Smart cropping uses computer vision to detect the most important region (faces, primary objects, focal points) and crops around that instead. A portrait photo center-cropped to 1:1 might cut off the top of the head; smart crop detects the face and adjusts the crop window to keep it centered and fully visible.
Does Cloudinary work with FlutterFlow?
Not directly. FlutterFlow cannot call Cloudinary's transformation API with authentication credentials from the client side without exposing your API secret. The correct pattern is a Supabase Edge Function that constructs the Cloudinary transformation URL using the API secret from Secrets, then returns the result URL to the FlutterFlow app. The Edge Function acts as a secure proxy.
How do I keep user images private?
Set your Supabase Storage buckets (both originals and processed) to private. Use Supabase RLS storage policies that restrict access to the uploading user's own path prefix (user_id/). Generate signed URLs with short expiry (1 hour) for downloads. Never put processed image URLs in a public-readable database column without access control.
What file sizes are supported?
Supabase Storage accepts files up to 50MB on the free tier. However, the Edge Function body limit is 6MB, so always use the signed upload URL pattern to upload directly from the client to Storage. For practical purposes, apply imageQuality: 80 and maxWidth: 2048 before upload — this keeps files under 2MB without noticeable quality loss for smart crop use cases.
Can I process images without uploading them to a server?
For basic resize without smart crop: yes. The canvas API on web can resize images in the browser using drawImage() with target dimensions. On Flutter, the image package's copyResize() function works on-device. Smart crop with AI face detection requires a server-side call to Cloudinary or AWS Rekognition — these models don't run in the browser without a WASM build, which is a significant engineering effort.
How do I add batch image resizing?
The image_jobs table already supports batch: submit multiple jobs with individual rows, each tracking their own status. The Edge Function can accept an array of storage paths and process them sequentially, updating status per row. On the client, poll the image_jobs table (or use a Supabase Realtime subscription) to show progress per image. At scale, consider a Supabase Database Function triggered by new image_jobs rows to avoid the Edge Function timeout limit.
What happens if no face is detected?
Cloudinary's g_auto:face falls back to center gravity silently when no face is detected. To make the fallback explicit, always include a gravity parameter: set gravity_center as the fallback. The before/after comparison slider lets users see the result before confirming — if the AI chose a poor crop, they can switch to a manual preset. Alternatively, g_auto:subject (not g_auto:face) detects non-face focal points like products and landmarks.
How much does Cloudinary cost at scale?
Cloudinary charges per transformation credit: one credit = one unique transformation (unique image + unique output dimensions). The free tier is 25 credits per month. Critically, Cloudinary caches every transformation — so if 1,000 users view the same processed image, that's 1 credit, not 1,000. At 1,000 active users generating unique crops, the Plus plan at $89/month (1,000 credits) is the right tier. At 10,000 users the Advanced tier (~$224/mo) or a self-hosted sharp.js pipeline becomes more economical.
Need this feature production-ready?
RapidDev builds ai-powered image resizing into real apps — auth, database, payments — at $13K–$25K.
Book a free consultation30-min call. No commitment.