I'm working with an API which requires images lower or equals to 100 Kb. Server works only with png and jpeg extensions.
So I made a class that is responsible for compressing images. I use image for compression:
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:image/image.dart' as img;
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
class ImageFileCompressor {
static Future<File> compress(
File image, {
int targetSizeInBytes = 1024 * 1024,
}) async {
var fileToCompress = image;
// The package works only with jpeg on iOS,
// so I have to check it first.
final iosAndPng = _isIosAndPng(image);
if (iosAndPng) {
fileToCompress = await _convertPngToJpeg(image);
}
final compressed = await _compressImageToTargetSize(
fileToCompress,
targetSizeInBytes,
);
return compressed;
}
static int _calcSizeInBytes(File file) => file.lengthSync();
static bool _isIosAndPng(File file) {
final isIos = Platform.isIOS;
final isPng = path.extension(file.path) == '.png';
return isIos && isPng;
}
static Future<File> _convertPngToJpeg(File file) async {
final image = await img.decodePngFile(file.path);
final tempFile = await _tempFile(file, '.jpg');
await img.encodeJpgFile(tempFile.path, image!);
return tempFile;
}
static Future<File> _compressImageToTargetSize(
File imageFile,
int targetFileSize,
) async {
var compressed = imageFile;
var currentFileSize = _calcSizeInBytes(compressed);
while (currentFileSize > targetFileSize) {
compressed = await _compressImage(compressed);
// The problem appears here.
// On particular point image stops compressing
// and returns size that equals or even greater
// than previous file size
final newFileSize = _calcSizeInBytes(compressed);
if (newFileSize >= currentFileSize) {
throw Exception('Unable to compress image');
}
currentFileSize = newFileSize;
}
return compressed;
}
static Future<File> _compressImage(File imageFile) async {
final image = await img.decodeJpgFile(imageFile.path);
final tempFile = await _tempFile(imageFile, '.jpg');
await img.encodeJpgFile(tempFile.path, image!, quality: 1);
return File(tempFile.path);
}
static Future<File> _tempFile(File file, [String? extension]) async {
final dir = await getTemporaryDirectory();
final name = path.basenameWithoutExtension(file.path);
final ext = extension ?? path.extension(file.path);
final newName = '$name-${Random().nextInt(1000)}';
final newPath = '${dir.path}/$newName$ext';
debugPrint(newPath);
return File(newPath);
}
}
On particular point image stops compressing and returns size that equals or even greater than previous file size.
However some images that has lower initial size pass the loop successfully.
I also tried flutter_image_compress but the problem stays the same.
So at this point I am convinced that there is a limit of compression with this method and I should find some other ways to compress images.