Update: 2026-05-07 00:14:28
This commit is contained in:
81
musadaq-app/lib/core/services/image_processing_service.dart
Normal file
81
musadaq-app/lib/core/services/image_processing_service.dart
Normal file
@@ -0,0 +1,81 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import '../utils/logger.dart';
|
||||
|
||||
class ImageProcessingService {
|
||||
/// Processes an image for OCR/AI extraction:
|
||||
/// 1. Compresses the original image to reduce memory usage
|
||||
/// 2. Converts to grayscale
|
||||
/// 3. Increases contrast
|
||||
/// 4. Saves as high-quality JPEG
|
||||
static Future<File?> processInvoiceImage(File originalImage) async {
|
||||
try {
|
||||
AppLogger.print('Started processing image: ${originalImage.path}');
|
||||
|
||||
// Step 1: Initial compression using flutter_image_compress (Native, fast)
|
||||
final dir = await getTemporaryDirectory();
|
||||
final compressedPath = path.join(
|
||||
dir.path, 'compressed_${DateTime.now().millisecondsSinceEpoch}.jpg');
|
||||
|
||||
final XFile? compressedXFile =
|
||||
await FlutterImageCompress.compressAndGetFile(
|
||||
originalImage.path,
|
||||
compressedPath,
|
||||
quality: 85,
|
||||
minWidth: 1920,
|
||||
minHeight: 1080,
|
||||
);
|
||||
|
||||
if (compressedXFile == null) {
|
||||
AppLogger.error('Failed to compress image initially', null);
|
||||
return originalImage; // Fallback to original
|
||||
}
|
||||
|
||||
File compressedFile = File(compressedXFile.path);
|
||||
|
||||
// Step 2: Grayscale and Contrast using `image` package (in Isolate to avoid UI freeze)
|
||||
final processedBytes =
|
||||
await compute(_applyFilters, await compressedFile.readAsBytes());
|
||||
|
||||
if (processedBytes == null) {
|
||||
AppLogger.error('Failed to apply filters', null);
|
||||
return compressedFile; // Fallback to just compressed
|
||||
}
|
||||
|
||||
// Step 3: Save final processed image
|
||||
final finalPath = path.join(
|
||||
dir.path, 'processed_${DateTime.now().millisecondsSinceEpoch}.jpg');
|
||||
final finalFile = File(finalPath);
|
||||
await finalFile.writeAsBytes(processedBytes);
|
||||
|
||||
AppLogger.print('Finished processing image: ${finalFile.path}');
|
||||
return finalFile;
|
||||
} catch (e, stack) {
|
||||
AppLogger.error('Error processing image', e, stack);
|
||||
return originalImage; // Fallback
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs in an isolate to prevent UI freezing
|
||||
static List<int>? _applyFilters(List<int> bytes) {
|
||||
try {
|
||||
img.Image? decodedImage = img.decodeImage(Uint8List.fromList(bytes));
|
||||
if (decodedImage == null) return null;
|
||||
|
||||
// Convert to Grayscale
|
||||
img.grayscale(decodedImage);
|
||||
|
||||
// Increase contrast (adjust as needed, e.g., 1.5)
|
||||
img.contrast(decodedImage, contrast: 150);
|
||||
|
||||
// Encode back to JPG
|
||||
return img.encodeJpg(decodedImage, quality: 90);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
76
musadaq-app/lib/core/services/invoice_upload_service.dart
Normal file
76
musadaq-app/lib/core/services/invoice_upload_service.dart
Normal file
@@ -0,0 +1,76 @@
|
||||
import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import '../network/dio_client.dart';
|
||||
import '../utils/logger.dart';
|
||||
|
||||
class InvoiceUploadService {
|
||||
final Dio _dio = DioClient().client;
|
||||
|
||||
/// Uploads a batch of images to the server
|
||||
/// Returns the batchId if successful, null otherwise.
|
||||
Future<String?> uploadBatch({
|
||||
required String companyId,
|
||||
required List<File> images,
|
||||
required Function(int current, int total) onProgress,
|
||||
}) async {
|
||||
try {
|
||||
// 1. Create Batch
|
||||
AppLogger.print('Creating new batch for company: $companyId');
|
||||
final createResponse = await _dio.post('batches/create', data: {
|
||||
'company_id': companyId,
|
||||
'total_images': images.length,
|
||||
});
|
||||
|
||||
if (createResponse.statusCode != 200 && createResponse.statusCode != 201) {
|
||||
throw Exception('Failed to create batch');
|
||||
}
|
||||
|
||||
final String batchId = createResponse.data['data']['batch_id'];
|
||||
AppLogger.print('Batch created successfully: $batchId');
|
||||
|
||||
// 2. Upload Images sequentially
|
||||
for (int i = 0; i < images.length; i++) {
|
||||
final file = images[i];
|
||||
final fileName = file.path.split('/').last;
|
||||
|
||||
FormData formData = FormData.fromMap({
|
||||
'batch_id': batchId,
|
||||
'image': await MultipartFile.fromFile(file.path, filename: fileName),
|
||||
'order_index': i + 1,
|
||||
});
|
||||
|
||||
AppLogger.print('Uploading image ${i + 1}/${images.length}: $fileName');
|
||||
|
||||
await _dio.post(
|
||||
'batches/upload-image',
|
||||
data: formData,
|
||||
onSendProgress: (int sent, int total) {
|
||||
// Can be used for detailed progress bar per image if needed
|
||||
},
|
||||
);
|
||||
|
||||
onProgress(i + 1, images.length);
|
||||
}
|
||||
|
||||
// 3. Finalize Batch
|
||||
AppLogger.print('Finalizing batch: $batchId');
|
||||
final finalizeResponse = await _dio.post('batches/finalize', data: {
|
||||
'batch_id': batchId,
|
||||
});
|
||||
|
||||
if (finalizeResponse.statusCode != 200) {
|
||||
throw Exception('Failed to finalize batch');
|
||||
}
|
||||
|
||||
AppLogger.print('Batch finalized successfully!');
|
||||
return batchId;
|
||||
|
||||
} on DioException catch (e) {
|
||||
AppLogger.error('Upload batch failed (DioException)', e.response?.data);
|
||||
return null;
|
||||
} catch (e) {
|
||||
AppLogger.error('Upload batch failed', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user