flutter_bicubic_resize
Fast, consistent bicubic image resizing for Flutter.
Features
- 100% Native C performance (stb_image + stb_image_resize + stb_image_write)
- Identical results on iOS and Android
- Bicubic interpolation (Catmull-Rom, same as OpenCV)
- Full native pipeline: decode -> resize -> encode (no Dart image libraries)
- RGB and RGBA support
- JPEG and PNG support with alpha channel preservation
- EXIF orientation support - automatically rotates JPEG images correctly
- Flexible crop system - anchor position, aspect ratio modes, custom ratios
- Edge handling modes - clamp, wrap, reflect, zero
- PNG compression control - adjustable compression level
- Zero external Dart dependencies (only
ffi)
Installation
Add to your pubspec.yaml:
dependencies:
flutter_bicubic_resize: ^1.2.2
Or run:
flutter pub add flutter_bicubic_resize
Usage
Resize JPEG
import 'package:flutter_bicubic_resize/flutter_bicubic_resize.dart';
final resized = BicubicResizer.resizeJpeg(
jpegBytes: originalBytes,
outputWidth: 224,
outputHeight: 224,
quality: 95, // optional, default 95
);
Resize PNG
final resized = BicubicResizer.resizePng(
pngBytes: originalBytes,
outputWidth: 224,
outputHeight: 224,
compressionLevel: 6, // optional, 0-9 (default: 6)
);
Resize raw RGB/RGBA bytes
// RGB (3 bytes per pixel)
final resizedRgb = BicubicResizer.resizeRgb(
input: rgbBytes,
inputWidth: 1920,
inputHeight: 1080,
outputWidth: 224,
outputHeight: 224,
);
// RGBA (4 bytes per pixel)
final resizedRgba = BicubicResizer.resizeRgba(
input: rgbaBytes,
inputWidth: 1920,
inputHeight: 1080,
outputWidth: 224,
outputHeight: 224,
);
Custom filter selection
// Use different bicubic filter
final resized = BicubicResizer.resizeJpeg(
jpegBytes: originalBytes,
outputWidth: 224,
outputHeight: 224,
filter: BicubicFilter.mitchell, // or .cubicBSpline
);
Available filters:
BicubicFilter.catmullRom- Default. Same as OpenCV/PIL. Best for ML.BicubicFilter.cubicBSpline- Smoother, more blurry.BicubicFilter.mitchell- Balanced between sharp and smooth.
Crop with anchor position
Control where the crop is taken from:
// Crop from top of image (good for portraits)
final portrait = BicubicResizer.resizeJpeg(
jpegBytes: photoBytes,
outputWidth: 224,
outputHeight: 224,
crop: 0.8,
cropAnchor: CropAnchor.topCenter,
);
Available anchors:
┌─────────────────┐
│ TL TC TR │ topLeft, topCenter, topRight
│ │
│ CL CENTER CR │ centerLeft, center (default), centerRight
│ │
│ BL BC BR │ bottomLeft, bottomCenter, bottomRight
└─────────────────┘
Crop aspect ratio modes
Control the shape of the crop:
// Square crop (default) - 1:1 aspect ratio
final square = BicubicResizer.resizeJpeg(
jpegBytes: originalBytes,
outputWidth: 224,
outputHeight: 224,
cropAspectRatio: CropAspectRatio.square,
);
// Keep original proportions
final proportional = BicubicResizer.resizeJpeg(
jpegBytes: originalBytes,
outputWidth: 800,
outputHeight: 600,
cropAspectRatio: CropAspectRatio.original,
);
// Custom aspect ratio (16:9)
final widescreen = BicubicResizer.resizeJpeg(
jpegBytes: originalBytes,
outputWidth: 1920,
outputHeight: 1080,
cropAspectRatio: CropAspectRatio.custom,
aspectRatioWidth: 16.0,
aspectRatioHeight: 9.0,
);
Edge handling modes
Control how pixels outside the image bounds are handled:
// Wrap mode - creates tiled pattern
final tiled = BicubicResizer.resizeJpeg(
jpegBytes: textureBytes,
outputWidth: 512,
outputHeight: 512,
edgeMode: EdgeMode.wrap,
);
Available modes:
EdgeMode.clamp- Default. Repeat edge pixels.EdgeMode.wrap- Tile/repeat image (wrap around).EdgeMode.reflect- Mirror reflection at edges.EdgeMode.zero- Black/transparent pixels outside bounds.
EXIF orientation control
For JPEG images, EXIF orientation is applied by default. You can disable it:
// Get raw pixel orientation (ignore EXIF)
final raw = BicubicResizer.resizeJpeg(
jpegBytes: photoBytes,
outputWidth: 224,
outputHeight: 224,
applyExifOrientation: false,
);
Complete example with all options
final result = BicubicResizer.resizeJpeg(
jpegBytes: originalBytes,
outputWidth: 1920,
outputHeight: 1080,
quality: 90,
filter: BicubicFilter.catmullRom,
edgeMode: EdgeMode.clamp,
crop: 0.9,
cropAnchor: CropAnchor.center,
cropAspectRatio: CropAspectRatio.custom,
aspectRatioWidth: 16.0,
aspectRatioHeight: 9.0,
applyExifOrientation: true,
);
Why?
Default platform APIs use different algorithms:
- Android: Typically Bilinear
- iOS: Depends on context (Lanczos, Bilinear, etc.)
This package uses the same C code on both platforms, ensuring identical output for the same input.
Architecture
The entire image processing pipeline runs in native C code:
- Decode - stb_image decodes JPEG/PNG to raw pixels
- EXIF orientation - For JPEG: parses EXIF metadata and applies correct rotation/flip (optional)
- Crop - Extracts region based on anchor position and aspect ratio mode
- Resize - stb_image_resize2 applies bicubic interpolation with selected edge mode
- Encode - stb_image_write encodes back to JPEG/PNG
This means:
- No Dart image libraries needed
- Minimal memory overhead
- Maximum performance
- Consistent results across platforms
- Photos from mobile cameras display correctly (no rotation issues)
Algorithm
Uses stb_image_resize2 with STBIR_FILTER_CATMULLROM (Catmull-Rom spline).
This is the same algorithm used by:
- OpenCV
cv2.INTER_CUBIC - PIL/Pillow
Image.BICUBIC
Perfect for ML preprocessing (OpenCLIP, ResNet, etc.) where consistent results with training pipeline matter.
Performance
The entire pipeline is native C, making it significantly faster than pure Dart solutions. Operations are synchronous but very fast due to native performance.
Requirements
- Flutter 3.0+
- Android SDK 21+
- iOS 11.0+
Sponsor
Documentation
- API Reference - Complete API documentation
- Example App - Working demo application
License
MIT License - see LICENSE file.
Libraries
- flutter_bicubic_resize
- Fast, consistent bicubic image resizing for Flutter.
