1+ import 'dart:io' ;
2+ import 'dart:isolate' ;
3+ import 'package:image/image.dart' as img;
4+ import 'package:path/path.dart' as p;
5+
6+ /// Scales images in [sourceDir] to [maxSize] using a fixed pool of [maxConcurrency] threads.
7+ Future <void > downscaleImages (
8+ String sourceDir,
9+ String destDir,
10+ int maxSize, {
11+ int maxConcurrency = 4 ,
12+ }) async {
13+ final dir = Directory (sourceDir);
14+ final dest = Directory (destDir);
15+ if (! dest.existsSync ()) {
16+ dest.createSync (recursive: true );
17+ }
18+
19+ final files = dir.listSync ().whereType <File >().where ((f) {
20+ final ext = p.extension (f.path).toLowerCase ();
21+ return ext == '.jpg' || ext == '.jpeg' || ext == '.png' ;
22+ }).toList ();
23+
24+ if (files.isEmpty) return ;
25+
26+ // Work-stealing queue to keep exactly [maxConcurrency] isolates busy
27+ int fileIndex = 0 ;
28+ final workers = List .generate (maxConcurrency, (_) async {
29+ while (fileIndex < files.length) {
30+ final currentFile = files[fileIndex++ ];
31+ await Isolate .run (
32+ () => _resizeWorker (currentFile.path, destDir, maxSize),
33+ );
34+ }
35+ });
36+
37+ await Future .wait (workers);
38+ }
39+
40+ /// Standalone top-level worker executed inside an Isolate
41+ void _resizeWorker (String filePath, String destDir, int maxSize) {
42+ final bytes = File (filePath).readAsBytesSync ();
43+ final image = img.decodeImage (bytes);
44+
45+ if (image == null ) return ;
46+
47+ final fileName = p.basename (filePath);
48+ final outPath = p.join (destDir, fileName);
49+
50+ // Skip downscaling if already within target dimensions
51+ if (image.width <= maxSize && image.height <= maxSize) {
52+ File (filePath).copySync (outPath);
53+ return ;
54+ }
55+
56+ int targetWidth = image.width;
57+ int targetHeight = image.height;
58+
59+ if (image.width > image.height) {
60+ targetWidth = maxSize;
61+ targetHeight = (image.height * (maxSize / image.width)).round ();
62+ } else {
63+ targetHeight = maxSize;
64+ targetWidth = (image.width * (maxSize / image.height)).round ();
65+ }
66+
67+ // Fast bilinear resizing suitable for photogrammetry inputs
68+ final resized = img.copyResize (
69+ image,
70+ width: targetWidth,
71+ height: targetHeight,
72+ interpolation: img.Interpolation .linear,
73+ );
74+
75+ File (outPath).writeAsBytesSync (img.encodeJpg (resized, quality: 95 ));
76+ }
0 commit comments