Files
Qlyra/lib/core/media/image_optimizer.dart
T

97 lines
2.6 KiB
Dart

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:image/image.dart' as img;
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'gallery_source.dart';
const int kPhotoUploadLimitBytes = 5 * 1024 * 1024;
const int _photoMaxDimension = 2560;
const int _photoJpegQuality = 85;
const int _photoTargetBytes = 4700 * 1024;
const Set<String> _heicExtensions = {'.heic', '.heif'};
bool isHeicPath(String path) {
final dot = path.lastIndexOf('.');
if (dot < 0) return false;
return _heicExtensions.contains(path.substring(dot).toLowerCase());
}
Future<File> optimizePhotoForUpload(File source, {GalleryItem? item}) async {
final heic = isHeicPath(source.path);
var length = 0;
try {
length = await source.length();
} catch (_) {}
if (!heic && length > 0 && length <= kPhotoUploadLimitBytes) {
return source;
}
if (item != null) {
final native = await item.encodeForUpload(
maxDimension: _photoMaxDimension,
quality: _photoJpegQuality,
);
if (native != null) return writePhotoJpeg(native);
}
final fallback = await _encodeWithImagePackage(source);
return fallback ?? source;
}
Future<File?> _encodeWithImagePackage(File source) async {
Uint8List bytes;
try {
bytes = await source.readAsBytes();
} catch (_) {
return null;
}
final jpeg = await compute(_encodePhotoIsolate, (
bytes,
_photoMaxDimension,
_photoJpegQuality,
_photoTargetBytes,
));
if (jpeg == null) return null;
return writePhotoJpeg(jpeg);
}
Uint8List? _encodePhotoIsolate((Uint8List, int, int, int) args) {
final (bytes, maxDim, quality, target) = args;
final decoded = img.decodeImage(bytes);
if (decoded == null) return null;
final oriented = img.bakeOrientation(decoded);
final scaled = oriented.width > maxDim || oriented.height > maxDim
? img.copyResize(
oriented,
width: oriented.width >= oriented.height ? maxDim : null,
height: oriented.height > oriented.width ? maxDim : null,
interpolation: img.Interpolation.average,
)
: oriented;
var quality0 = quality;
var out = img.encodeJpg(scaled, quality: quality0);
while (out.lengthInBytes > target && quality0 > 40) {
quality0 -= 10;
out = img.encodeJpg(scaled, quality: quality0);
}
return out;
}
Future<File> writePhotoJpeg(Uint8List bytes) async {
final dir = await getTemporaryDirectory();
final file = File(
p.join(
dir.path,
'komet_photo_${DateTime.now().microsecondsSinceEpoch}.jpg',
),
);
await file.writeAsBytes(bytes, flush: true);
return file;
}