file_compression_plus 1.0.0
file_compression_plus: ^1.0.0 copied to clipboard
Compress images and PDFs in Flutter. Hit an exact target file size, batch compress with progress, convert JPG PNG WebP HEIC, and shrink PDF documents.
File Compression Plus
Compress images and PDF files in Flutter.
Pick a file, get a smaller one back. No plugin registration, no platform setup, no native code.
final smaller = await FileCompressor.compressImage(file: photo);
Built and maintained by Tisankan Jeyakumar.
Why this package #
Most compression packages give you a quality slider and stop there. Real apps need more than that, so this one covers the jobs you actually hit:
| You need to | Use |
|---|---|
| Make a photo smaller | compressImage |
| Get a file under an upload limit | compressImageToTargetSize |
| Compress a whole gallery selection | compressBatch with progress |
| Compress bytes you never wrote to disk | compressImageBytes |
| Shrink a PDF | compressPdf |
| Handle a picked file without checking its type | compress |
| Show the user what was saved | CompressionResult, formatBytes |
It also refuses to hand you a file that got bigger, which is what happens when you compress an already optimised JPEG. That is on by default.
Features #
- Images: JPG, PNG, WebP and HEIC, with quality, resizing and rotation
- Target file size: say "under 1 MB" and it finds the best quality that fits
- Batch compression with per-file progress and per-file error collection
- Bytes in, bytes out for images and PDFs, no disk needed
- PDF compression with three levels, plus optional metadata stripping
- Never returns a larger file than it was given
- Format conversion, for example PNG to JPG or JPG to HEIC
- Size reporting: before, after, bytes saved, percentage saved
- Choose the output path, or let the package use the temporary directory
- Optionally delete the original after a successful compression
Platform support #
| Platform | Image | |
|---|---|---|
| Android | Yes | Yes |
| iOS | Yes | Yes |
| macOS | Yes, no WebP | Yes |
| Linux | Yes, needs flutter_image_compress_linux |
Yes |
| Windows | No | Yes |
| Web | No | No |
Image compression runs through flutter_image_compress, which uses the
platform image encoders. PDF compression is pure Dart, so it works anywhere
dart:io is available. This package uses dart:io, so it does not support
web.
Two platform notes worth knowing before you ship:
- macOS needs a deployment target of 10.15 or higher for image compression,
and it cannot encode WebP. Use
ImageFormat.jpgorImageFormat.pngthere. - Linux image compression needs the community package
flutter_image_compress_linuxin your app. Without it the call throwsUnimplementedError.
Install #
flutter pub add file_compression_plus
Or add it to pubspec.yaml:
dependencies:
file_compression_plus: ^1.0.0
There is nothing else to configure. This is a plain Dart package, not a plugin.
Usage #
Compress an image #
import 'dart:io';
import 'package:file_compression_plus/file_compression_plus.dart';
final compressed = await FileCompressor.compressImage(
file: File('/path/to/photo.jpg'),
);
print(compressed.path);
Compress an image with your own settings #
final compressed = await FileCompressor.compressImage(
file: File('/path/to/photo.png'),
quality: 70, // 0 to 100, lower means smaller
maxWidth: 1280, // the image fits inside this box
maxHeight: 720,
format: ImageFormat.jpg, // convert PNG to JPG
rotate: 90, // turn it clockwise while compressing
outputPath: '/path/to/output/photo.jpg',
keepExif: true, // keep camera metadata
skipIfLarger: true, // never hand back a bigger file
deleteOriginal: false,
);
Hit an exact file size #
The most common real requirement: the server rejects anything over 1 MB.
final result = await FileCompressor.compressImageToTargetSize(
file: photo,
targetBytes: 1024 * 1024,
);
if (result.compressedSize <= 1024 * 1024) {
await upload(result.file);
} else {
// The picture could not be squeezed that far. Tell the user.
}
It binary searches the quality range, so you keep the highest quality that still fits rather than a guessed number. If even the lowest quality is too big it halves the dimensions and searches again, up to three times by default.
This runs the encoder several times, so it is slower than a plain
compressImage. Worth it when a hard limit is involved.
Compress many files with progress #
final batch = await FileCompressor.compressBatch(
files: pickedFiles,
quality: 75,
outputDirectory: cacheDir.path,
onProgress: (progress) {
setState(() => _progress = progress.fraction);
},
);
print('${batch.successes.length} compressed, ${batch.failures.length} failed');
print('Saved ${formatBytes(batch.totalSavedBytes)}');
for (final failure in batch.failures) {
print('${failure.file.path} failed: ${failure.error}');
}
Each file is routed to the image or the PDF compressor by its extension. One
bad file does not stop the run, it lands in failures instead. Files are
processed one at a time on purpose, because running many native encoders at
once is a good way to run a phone out of memory.
Work with bytes, not files #
For an image you downloaded, received from a camera stream, or are about to upload straight to a server.
final smaller = await FileCompressor.compressImageBytes(
bytes: downloadedBytes,
quality: 70,
format: ImageFormat.jpg,
);
await http.post(uri, body: smaller);
The same works for PDFs with compressPdfBytes. Nothing touches the disk.
Compress a PDF #
final compressed = await FileCompressor.compressPdf(
file: File('/path/to/document.pdf'),
compressionLevel: PdfCompressionLevel.best,
removeMetadata: true, // clears title, author, creator and the rest
);
Handle any picked file #
When the file comes from a picker and you do not know the type up front:
if (FileCompressor.canCompress(file)) {
final result = await FileCompressor.compress(file: file);
print(result); // 4.2 MB -> 380.1 KB, saved 91.2%
}
Show the user what happened #
final result = await FileCompressor.compressImageWithResult(file: photo);
Text('${result.readableOriginalSize} to ${result.readableCompressedSize}');
Text('Saved ${result.savedPercentage.toStringAsFixed(1)} percent');
if (result.wasSkipped) {
Text('This file was already as small as it gets.');
}
API #
Image methods #
| Method | Returns | Use it for |
|---|---|---|
compressImage |
File |
The everyday case |
compressImageWithResult |
CompressionResult |
When you want the sizes too |
compressImageBytes |
Uint8List |
In-memory images, no disk |
compressImageToTargetSize |
CompressionResult |
Upload limits |
compressImage parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
file |
File |
required | The image to compress |
quality |
int |
80 |
0 to 100. Ignored for PNG output, PNG is lossless |
maxWidth |
int |
1920 |
Maximum output width, aspect ratio kept |
maxHeight |
int |
1080 |
Maximum output height, aspect ratio kept |
format |
ImageFormat? |
source format | jpg, png, webp or heic |
rotate |
int |
0 |
Degrees clockwise |
outputPath |
String? |
temporary directory | Exact output path, folders created |
keepExif |
bool |
false |
Keep camera metadata |
skipIfLarger |
bool |
true |
Keep the original if compression grows it |
deleteOriginal |
bool |
false |
Delete the source after success |
compressImageToTargetSize adds:
| Parameter | Type | Default | Description |
|---|---|---|---|
targetBytes |
int |
required | The size budget to fit inside |
minQuality |
int |
10 |
Lowest quality it will accept |
maxQuality |
int |
95 |
Highest quality it will try |
allowDownscale |
bool |
true |
Halve the size when quality alone is not enough |
maxDownscaleSteps |
int |
3 |
How many times it may halve |
PDF methods #
| Method | Returns | Use it for |
|---|---|---|
compressPdf |
File |
The everyday case |
compressPdfWithResult |
CompressionResult |
When you want the sizes too |
compressPdfBytes |
Uint8List |
In-memory documents, no disk |
| Parameter | Type | Default | Description |
|---|---|---|---|
file |
File |
required | The PDF to compress |
compressionLevel |
PdfCompressionLevel |
best |
none, normal or best |
removeMetadata |
bool |
false |
Clear title, author, subject, keywords, creator, producer |
outputPath |
String? |
temporary directory | Exact output path, folders created |
skipIfLarger |
bool |
true |
Keep the original if compression grows it |
deleteOriginal |
bool |
false |
Delete the source after success |
Any file #
| Member | Description |
|---|---|
compress |
Routes to the image or PDF compressor by extension |
compressBatch |
Compresses a list of files with progress |
canCompress(file) |
True when the extension is supported |
supportedExtensions |
Every extension this package handles |
supportedImageExtensions |
Just the image extensions |
CompressionResult #
| Member | Type | Description |
|---|---|---|
file |
File |
The compressed file |
path |
String |
Path of the compressed file |
originalSize |
int |
Source size in bytes |
compressedSize |
int |
Output size in bytes |
savedBytes |
int |
Bytes saved |
savedPercentage |
double |
Percentage saved, 0 to 100 |
wasSkipped |
bool |
True when the original was kept because compression grew it |
readableOriginalSize |
String |
For example 2.4 MB |
readableCompressedSize |
String |
For example 310.5 KB |
BatchCompressionResult #
| Member | Type | Description |
|---|---|---|
successes |
List<CompressionResult> |
Files that compressed |
failures |
List<CompressionFailure> |
Files that failed, with the reason |
totalFiles |
int |
Successes plus failures |
totalOriginalSize |
int |
Combined source size |
totalCompressedSize |
int |
Combined output size |
totalSavedBytes |
int |
Combined bytes saved |
savedPercentage |
double |
Percentage saved across the batch |
isCompleteSuccess |
bool |
True when nothing failed |
BatchProgress #
| Member | Type | Description |
|---|---|---|
completed |
int |
Files finished so far |
total |
int |
Files in the batch |
file |
File |
The file that just finished |
result |
CompressionResult? |
Its result, or null when it failed |
fraction |
double |
0.0 to 1.0, ready for a progress bar |
percentage |
int |
0 to 100 |
Helpers #
formatBytes(int bytes, {int decimals = 1}) turns a byte count into
something a user can read: formatBytes(2517000) gives 2.4 MB.
Errors #
| Error | When it is thrown |
|---|---|
ArgumentError |
Quality outside 0 to 100, a size limit of zero or less, an unsupported file extension, empty bytes, or an invalid quality range |
FileSystemException |
The file is missing, empty, not a readable PDF, or the encoder returned no data |
UnsupportedError |
The platform cannot encode the requested format. WebP on macOS, HEIC on most devices |
try {
final compressed = await FileCompressor.compressImage(
file: source,
format: ImageFormat.heic,
);
} on UnsupportedError {
// This device cannot write HEIC. Fall back to JPEG.
final compressed = await FileCompressor.compressImage(
file: source,
format: ImageFormat.jpg,
);
} on ArgumentError catch (error) {
// Bad settings or an unsupported file type.
} on FileSystemException catch (error) {
// Missing file, empty file, or the encoder failed.
}
In a batch, none of these stop the run. They collect in failures.
Notes and limits #
- Without
outputPaththe result goes to the temporary directory, which the operating system may clear at any time. Copy the file somewhere permanent if you need to keep it. - Compression cannot shrink every file. An already optimised image or PDF may
come out larger.
skipIfLargeris on by default, so you get the original back withwasSkippedset instead of a worse file. - PDF compression packs the document streams. It does not re-encode images that are already embedded in the PDF, so a PDF full of large photos will not shrink much. That needs image extraction, which the underlying PDF library does not expose.
qualitydoes nothing for PNG output. PNG is lossless. To shrink a PNG, lowermaxWidthandmaxHeight, or convert it withformat: ImageFormat.jpg.- HEIC output throws
UnsupportedErroron most devices. Always catch it and fall back to JPEG. compressImageToTargetSizeruns the encoder about seven times, plus once per downscale step. Do not call it in a tight loop over hundreds of files.
Example #
A runnable demo is in example/. It covers single file
compression, batch compression with a progress bar, and target size
compression.
Testing #
The package ships 54 tests covering argument validation, file handling, batch behaviour, PDF compression and the result models.
Image compression calls the native platform encoders, so it cannot run in a
plain flutter test process. Those paths are covered by the example app on a
real device rather than by a unit test that would only ever assert against a
mock.
flutter test
Dependencies #
flutter_image_compressfor image compressionsyncfusion_flutter_pdffor PDF compression. Syncfusion is free under their community licence for small teams and paid otherwise. Check their licence before shipping a commercial app.
Documentation #
| Document | What is in it |
|---|---|
| Recipes | Copy and paste solutions for the common jobs |
| Migration guide | Moving from 0.0.x to 1.0.0 |
| Changelog | What changed in every release |
| Contributing | How to report a bug or send a pull request |
| API reference | Generated Dart docs |
Contributors wanted #
This package is actively maintained and open to contributors. If you would like to help, get in touch at hello@tisankan.dev and tell me what you want to work on. New contributors are welcome, including first time open source contributors.
Areas where help is genuinely useful right now:
| Area | What is needed |
|---|---|
| Windows image support | Wire up a Windows encoder so images work there, not only PDFs |
| Web support | A dart:io free path so the package runs on Flutter web |
| PDF image downsampling | Re-encode images embedded inside a PDF for much bigger savings |
| Integration tests | Device tests for the image paths that unit tests cannot reach |
| Documentation | More recipes, translations, and worked examples |
| Benchmarks | Real numbers for compression time and output size per platform |
Pick anything from the list, or open an issue with your own idea. Small pull requests are just as welcome as large ones.
Before opening a pull request:
flutter analyze
flutter test
dart format .
Full guide in CONTRIBUTING.md.
Support #
- Found a bug or want a feature? Open an issue
- Something private or commercial? Email hello@tisankan.dev
If this package saved you time, a like on pub.dev helps other developers find it.
Author #
|
Tisankan Jeyakumar Chief Technical Officer, Yarl Ventures (PVT) Ltd tisankan.dev | hello@tisankan.dev | GitHub |
Building production Flutter and Node.js systems for education, healthcare and commerce. Available for consulting at hello@tisankan.dev.
License #
MIT. Copyright (c) Tisankan Jeyakumar. See LICENSE.