Flutter: Image compression to specific size

63 views Asked by At

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.

0

There are 0 answers