lightdrift-libraw 1.0.0-alpha.1 → 1.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +730 -374
- package/README.md +1032 -757
- package/lib/index.d.ts +178 -0
- package/lib/index.js +1358 -761
- package/package.json +50 -38
- package/src/libraw_wrapper_new.cpp +0 -853
package/lib/index.js
CHANGED
|
@@ -1,761 +1,1358 @@
|
|
|
1
|
-
const path = require("path");
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
*
|
|
278
|
-
* @returns {Promise<
|
|
279
|
-
*/
|
|
280
|
-
async
|
|
281
|
-
return new Promise((resolve, reject) => {
|
|
282
|
-
try {
|
|
283
|
-
const
|
|
284
|
-
resolve(
|
|
285
|
-
} catch (error) {
|
|
286
|
-
reject(error);
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
*
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
*
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
*
|
|
327
|
-
* @param {
|
|
328
|
-
* @returns {Promise<boolean>} - Success status
|
|
329
|
-
*/
|
|
330
|
-
async
|
|
331
|
-
return new Promise((resolve, reject) => {
|
|
332
|
-
try {
|
|
333
|
-
const result = this._wrapper.
|
|
334
|
-
resolve(result);
|
|
335
|
-
} catch (error) {
|
|
336
|
-
reject(error);
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
*
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
*
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
*
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
*
|
|
609
|
-
* @returns {Promise<boolean>} - Success status
|
|
610
|
-
*/
|
|
611
|
-
async
|
|
612
|
-
return new Promise((resolve, reject) => {
|
|
613
|
-
try {
|
|
614
|
-
const result = this._wrapper.
|
|
615
|
-
resolve(result);
|
|
616
|
-
} catch (error) {
|
|
617
|
-
reject(error);
|
|
618
|
-
}
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
*
|
|
626
|
-
* @
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
*
|
|
644
|
-
* @returns {Promise<
|
|
645
|
-
*/
|
|
646
|
-
async
|
|
647
|
-
return new Promise((resolve, reject) => {
|
|
648
|
-
try {
|
|
649
|
-
const result = this._wrapper.
|
|
650
|
-
resolve(result);
|
|
651
|
-
} catch (error) {
|
|
652
|
-
reject(error);
|
|
653
|
-
}
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
*
|
|
661
|
-
* @param {
|
|
662
|
-
* @
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
*
|
|
680
|
-
* @
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
*
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const sharp = require("sharp");
|
|
3
|
+
|
|
4
|
+
let librawAddon;
|
|
5
|
+
try {
|
|
6
|
+
librawAddon = require("../build/Release/libraw_addon");
|
|
7
|
+
} catch (err) {
|
|
8
|
+
try {
|
|
9
|
+
librawAddon = require("../build/Debug/libraw_addon");
|
|
10
|
+
} catch (err2) {
|
|
11
|
+
throw new Error('LibRaw addon not built. Run "npm run build" first.');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class LibRaw {
|
|
16
|
+
constructor() {
|
|
17
|
+
this._wrapper = new librawAddon.LibRawWrapper();
|
|
18
|
+
this._isProcessed = false; // Track if processImage() has been called
|
|
19
|
+
this._processedImageData = null; // Cache processed image data
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ============== FILE OPERATIONS ==============
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Load a RAW file from filesystem
|
|
26
|
+
* @param {string} filename - Path to the RAW file
|
|
27
|
+
* @returns {Promise<boolean>} - Success status
|
|
28
|
+
*/
|
|
29
|
+
async loadFile(filename) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
try {
|
|
32
|
+
const result = this._wrapper.loadFile(filename);
|
|
33
|
+
this._isProcessed = false; // Reset processing state for new file
|
|
34
|
+
this._processedImageData = null; // Clear cached data
|
|
35
|
+
resolve(result);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
reject(error);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Load a RAW file from memory buffer
|
|
44
|
+
* @param {Buffer} buffer - Buffer containing RAW data
|
|
45
|
+
* @returns {Promise<boolean>} - Success status
|
|
46
|
+
*/
|
|
47
|
+
async loadBuffer(buffer) {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
try {
|
|
50
|
+
const result = this._wrapper.loadBuffer(buffer);
|
|
51
|
+
resolve(result);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
reject(error);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Close and cleanup resources
|
|
60
|
+
* @returns {Promise<boolean>} - Success status
|
|
61
|
+
*/
|
|
62
|
+
async close() {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
try {
|
|
65
|
+
const result = this._wrapper.close();
|
|
66
|
+
resolve(result);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
reject(error);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ============== ERROR HANDLING ==============
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get the last error message
|
|
77
|
+
* @returns {string} - Last error message
|
|
78
|
+
*/
|
|
79
|
+
getLastError() {
|
|
80
|
+
return this._wrapper.getLastError();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Convert error code to string
|
|
85
|
+
* @param {number} errorCode - Error code
|
|
86
|
+
* @returns {string} - Error message
|
|
87
|
+
*/
|
|
88
|
+
strerror(errorCode) {
|
|
89
|
+
return this._wrapper.strerror(errorCode);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ============== METADATA & INFORMATION ==============
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get basic metadata from the loaded RAW file
|
|
96
|
+
* @returns {Promise<Object>} - Metadata object
|
|
97
|
+
*/
|
|
98
|
+
async getMetadata() {
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
try {
|
|
101
|
+
const metadata = this._wrapper.getMetadata();
|
|
102
|
+
resolve(metadata);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
reject(error);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get image dimensions and size information
|
|
111
|
+
* @returns {Promise<Object>} - Size object with width/height details
|
|
112
|
+
*/
|
|
113
|
+
async getImageSize() {
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
try {
|
|
116
|
+
const size = this._wrapper.getImageSize();
|
|
117
|
+
resolve(size);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
reject(error);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get advanced metadata including color matrices and calibration data
|
|
126
|
+
* @returns {Promise<Object>} - Advanced metadata object
|
|
127
|
+
*/
|
|
128
|
+
async getAdvancedMetadata() {
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
try {
|
|
131
|
+
const metadata = this._wrapper.getAdvancedMetadata();
|
|
132
|
+
resolve(metadata);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
reject(error);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get lens information
|
|
141
|
+
* @returns {Promise<Object>} - Lens metadata object
|
|
142
|
+
*/
|
|
143
|
+
async getLensInfo() {
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
try {
|
|
146
|
+
const lensInfo = this._wrapper.getLensInfo();
|
|
147
|
+
resolve(lensInfo);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
reject(error);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get color information including white balance and color matrices
|
|
156
|
+
* @returns {Promise<Object>} - Color information object
|
|
157
|
+
*/
|
|
158
|
+
async getColorInfo() {
|
|
159
|
+
return new Promise((resolve, reject) => {
|
|
160
|
+
try {
|
|
161
|
+
const colorInfo = this._wrapper.getColorInfo();
|
|
162
|
+
resolve(colorInfo);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
reject(error);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ============== IMAGE PROCESSING ==============
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Unpack thumbnail data
|
|
173
|
+
* @returns {Promise<boolean>} - Success status
|
|
174
|
+
*/
|
|
175
|
+
async unpackThumbnail() {
|
|
176
|
+
return new Promise((resolve, reject) => {
|
|
177
|
+
try {
|
|
178
|
+
const result = this._wrapper.unpackThumbnail();
|
|
179
|
+
resolve(result);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
reject(error);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Process the RAW image with current settings
|
|
188
|
+
* @returns {Promise<boolean>} - Success status
|
|
189
|
+
*/
|
|
190
|
+
async processImage() {
|
|
191
|
+
return new Promise((resolve, reject) => {
|
|
192
|
+
try {
|
|
193
|
+
const result = this._wrapper.processImage();
|
|
194
|
+
this._isProcessed = true; // Mark as processed
|
|
195
|
+
resolve(result);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
reject(error);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Subtract black level from RAW data
|
|
204
|
+
* @returns {Promise<boolean>} - Success status
|
|
205
|
+
*/
|
|
206
|
+
async subtractBlack() {
|
|
207
|
+
return new Promise((resolve, reject) => {
|
|
208
|
+
try {
|
|
209
|
+
const result = this._wrapper.subtractBlack();
|
|
210
|
+
resolve(result);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
reject(error);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Convert RAW data to image format
|
|
219
|
+
* @returns {Promise<boolean>} - Success status
|
|
220
|
+
*/
|
|
221
|
+
async raw2Image() {
|
|
222
|
+
return new Promise((resolve, reject) => {
|
|
223
|
+
try {
|
|
224
|
+
const result = this._wrapper.raw2Image();
|
|
225
|
+
resolve(result);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
reject(error);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Adjust maximum values in the image
|
|
234
|
+
* @returns {Promise<boolean>} - Success status
|
|
235
|
+
*/
|
|
236
|
+
async adjustMaximum() {
|
|
237
|
+
return new Promise((resolve, reject) => {
|
|
238
|
+
try {
|
|
239
|
+
const result = this._wrapper.adjustMaximum();
|
|
240
|
+
resolve(result);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
reject(error);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ============== MEMORY IMAGE CREATION ==============
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Create processed image in memory
|
|
251
|
+
* @returns {Promise<Object>} - Image data object with Buffer
|
|
252
|
+
*/
|
|
253
|
+
async createMemoryImage() {
|
|
254
|
+
return new Promise((resolve, reject) => {
|
|
255
|
+
try {
|
|
256
|
+
// Return cached data if available
|
|
257
|
+
if (this._processedImageData) {
|
|
258
|
+
resolve(this._processedImageData);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const imageData = this._wrapper.createMemoryImage();
|
|
263
|
+
|
|
264
|
+
// Cache the result if image was processed
|
|
265
|
+
if (this._isProcessed) {
|
|
266
|
+
this._processedImageData = imageData;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
resolve(imageData);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
reject(error);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Create thumbnail image in memory
|
|
278
|
+
* @returns {Promise<Object>} - Thumbnail data object with Buffer
|
|
279
|
+
*/
|
|
280
|
+
async createMemoryThumbnail() {
|
|
281
|
+
return new Promise((resolve, reject) => {
|
|
282
|
+
try {
|
|
283
|
+
const thumbData = this._wrapper.createMemoryThumbnail();
|
|
284
|
+
resolve(thumbData);
|
|
285
|
+
} catch (error) {
|
|
286
|
+
reject(error);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ============== FILE WRITERS ==============
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Write processed image as PPM file
|
|
295
|
+
* @param {string} filename - Output filename
|
|
296
|
+
* @returns {Promise<boolean>} - Success status
|
|
297
|
+
*/
|
|
298
|
+
async writePPM(filename) {
|
|
299
|
+
return new Promise((resolve, reject) => {
|
|
300
|
+
try {
|
|
301
|
+
const result = this._wrapper.writePPM(filename);
|
|
302
|
+
resolve(result);
|
|
303
|
+
} catch (error) {
|
|
304
|
+
reject(error);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Write processed image as TIFF file
|
|
311
|
+
* @param {string} filename - Output filename
|
|
312
|
+
* @returns {Promise<boolean>} - Success status
|
|
313
|
+
*/
|
|
314
|
+
async writeTIFF(filename) {
|
|
315
|
+
return new Promise((resolve, reject) => {
|
|
316
|
+
try {
|
|
317
|
+
const result = this._wrapper.writeTIFF(filename);
|
|
318
|
+
resolve(result);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
reject(error);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Write thumbnail to file
|
|
327
|
+
* @param {string} filename - Output filename
|
|
328
|
+
* @returns {Promise<boolean>} - Success status
|
|
329
|
+
*/
|
|
330
|
+
async writeThumbnail(filename) {
|
|
331
|
+
return new Promise((resolve, reject) => {
|
|
332
|
+
try {
|
|
333
|
+
const result = this._wrapper.writeThumbnail(filename);
|
|
334
|
+
resolve(result);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
reject(error);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ============== CONFIGURATION & SETTINGS ==============
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Set output parameters for processing
|
|
345
|
+
* @param {Object} params - Parameter object
|
|
346
|
+
* @returns {Promise<boolean>} - Success status
|
|
347
|
+
*/
|
|
348
|
+
async setOutputParams(params) {
|
|
349
|
+
return new Promise((resolve, reject) => {
|
|
350
|
+
try {
|
|
351
|
+
const result = this._wrapper.setOutputParams(params);
|
|
352
|
+
resolve(result);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
reject(error);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Get current output parameters
|
|
361
|
+
* @returns {Promise<Object>} - Current parameters
|
|
362
|
+
*/
|
|
363
|
+
async getOutputParams() {
|
|
364
|
+
return new Promise((resolve, reject) => {
|
|
365
|
+
try {
|
|
366
|
+
const params = this._wrapper.getOutputParams();
|
|
367
|
+
resolve(params);
|
|
368
|
+
} catch (error) {
|
|
369
|
+
reject(error);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ============== UTILITY FUNCTIONS ==============
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Check if image uses floating point data
|
|
378
|
+
* @returns {Promise<boolean>} - Floating point status
|
|
379
|
+
*/
|
|
380
|
+
async isFloatingPoint() {
|
|
381
|
+
return new Promise((resolve, reject) => {
|
|
382
|
+
try {
|
|
383
|
+
const result = this._wrapper.isFloatingPoint();
|
|
384
|
+
resolve(result);
|
|
385
|
+
} catch (error) {
|
|
386
|
+
reject(error);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Check if image is Fuji rotated
|
|
393
|
+
* @returns {Promise<boolean>} - Fuji rotation status
|
|
394
|
+
*/
|
|
395
|
+
async isFujiRotated() {
|
|
396
|
+
return new Promise((resolve, reject) => {
|
|
397
|
+
try {
|
|
398
|
+
const result = this._wrapper.isFujiRotated();
|
|
399
|
+
resolve(result);
|
|
400
|
+
} catch (error) {
|
|
401
|
+
reject(error);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Check if image is sRAW format
|
|
408
|
+
* @returns {Promise<boolean>} - sRAW status
|
|
409
|
+
*/
|
|
410
|
+
async isSRAW() {
|
|
411
|
+
return new Promise((resolve, reject) => {
|
|
412
|
+
try {
|
|
413
|
+
const result = this._wrapper.isSRAW();
|
|
414
|
+
resolve(result);
|
|
415
|
+
} catch (error) {
|
|
416
|
+
reject(error);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Check if thumbnail is JPEG format
|
|
423
|
+
* @returns {Promise<boolean>} - JPEG thumbnail status
|
|
424
|
+
*/
|
|
425
|
+
async isJPEGThumb() {
|
|
426
|
+
return new Promise((resolve, reject) => {
|
|
427
|
+
try {
|
|
428
|
+
const result = this._wrapper.isJPEGThumb();
|
|
429
|
+
resolve(result);
|
|
430
|
+
} catch (error) {
|
|
431
|
+
reject(error);
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Get error count during processing
|
|
438
|
+
* @returns {Promise<number>} - Number of errors
|
|
439
|
+
*/
|
|
440
|
+
async errorCount() {
|
|
441
|
+
return new Promise((resolve, reject) => {
|
|
442
|
+
try {
|
|
443
|
+
const count = this._wrapper.errorCount();
|
|
444
|
+
resolve(count);
|
|
445
|
+
} catch (error) {
|
|
446
|
+
reject(error);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ============== EXTENDED UTILITY FUNCTIONS ==============
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Check if image is Nikon sRAW format
|
|
455
|
+
* @returns {Promise<boolean>} - True if Nikon sRAW
|
|
456
|
+
*/
|
|
457
|
+
async isNikonSRAW() {
|
|
458
|
+
return new Promise((resolve, reject) => {
|
|
459
|
+
try {
|
|
460
|
+
const result = this._wrapper.isNikonSRAW();
|
|
461
|
+
resolve(result);
|
|
462
|
+
} catch (error) {
|
|
463
|
+
reject(error);
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Check if image is Coolscan NEF format
|
|
470
|
+
* @returns {Promise<boolean>} - True if Coolscan NEF
|
|
471
|
+
*/
|
|
472
|
+
async isCoolscanNEF() {
|
|
473
|
+
return new Promise((resolve, reject) => {
|
|
474
|
+
try {
|
|
475
|
+
const result = this._wrapper.isCoolscanNEF();
|
|
476
|
+
resolve(result);
|
|
477
|
+
} catch (error) {
|
|
478
|
+
reject(error);
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Check if image has floating point data
|
|
485
|
+
* @returns {Promise<boolean>} - True if floating point data available
|
|
486
|
+
*/
|
|
487
|
+
async haveFPData() {
|
|
488
|
+
return new Promise((resolve, reject) => {
|
|
489
|
+
try {
|
|
490
|
+
const result = this._wrapper.haveFPData();
|
|
491
|
+
resolve(result);
|
|
492
|
+
} catch (error) {
|
|
493
|
+
reject(error);
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Get sRAW midpoint value
|
|
500
|
+
* @returns {Promise<number>} - sRAW midpoint
|
|
501
|
+
*/
|
|
502
|
+
async srawMidpoint() {
|
|
503
|
+
return new Promise((resolve, reject) => {
|
|
504
|
+
try {
|
|
505
|
+
const result = this._wrapper.srawMidpoint();
|
|
506
|
+
resolve(result);
|
|
507
|
+
} catch (error) {
|
|
508
|
+
reject(error);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Check if thumbnail is OK
|
|
515
|
+
* @param {number} [maxSize=-1] - Maximum size limit
|
|
516
|
+
* @returns {Promise<number>} - Thumbnail status
|
|
517
|
+
*/
|
|
518
|
+
async thumbOK(maxSize = -1) {
|
|
519
|
+
return new Promise((resolve, reject) => {
|
|
520
|
+
try {
|
|
521
|
+
const result = this._wrapper.thumbOK(maxSize);
|
|
522
|
+
resolve(result);
|
|
523
|
+
} catch (error) {
|
|
524
|
+
reject(error);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Get unpacker function name
|
|
531
|
+
* @returns {Promise<string>} - Name of the unpacker function
|
|
532
|
+
*/
|
|
533
|
+
async unpackFunctionName() {
|
|
534
|
+
return new Promise((resolve, reject) => {
|
|
535
|
+
try {
|
|
536
|
+
const result = this._wrapper.unpackFunctionName();
|
|
537
|
+
resolve(result);
|
|
538
|
+
} catch (error) {
|
|
539
|
+
reject(error);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Get decoder information
|
|
546
|
+
* @returns {Promise<Object>} - Decoder info with name and flags
|
|
547
|
+
*/
|
|
548
|
+
async getDecoderInfo() {
|
|
549
|
+
return new Promise((resolve, reject) => {
|
|
550
|
+
try {
|
|
551
|
+
const result = this._wrapper.getDecoderInfo();
|
|
552
|
+
resolve(result);
|
|
553
|
+
} catch (error) {
|
|
554
|
+
reject(error);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// ============== ADVANCED PROCESSING ==============
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Unpack RAW data (low-level operation)
|
|
563
|
+
* @returns {Promise<boolean>} - Success status
|
|
564
|
+
*/
|
|
565
|
+
async unpack() {
|
|
566
|
+
return new Promise((resolve, reject) => {
|
|
567
|
+
try {
|
|
568
|
+
const result = this._wrapper.unpack();
|
|
569
|
+
resolve(result);
|
|
570
|
+
} catch (error) {
|
|
571
|
+
reject(error);
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Convert RAW to image with extended options
|
|
578
|
+
* @param {boolean} [subtractBlack=true] - Whether to subtract black level
|
|
579
|
+
* @returns {Promise<boolean>} - Success status
|
|
580
|
+
*/
|
|
581
|
+
async raw2ImageEx(subtractBlack = true) {
|
|
582
|
+
return new Promise((resolve, reject) => {
|
|
583
|
+
try {
|
|
584
|
+
const result = this._wrapper.raw2ImageEx(subtractBlack);
|
|
585
|
+
resolve(result);
|
|
586
|
+
} catch (error) {
|
|
587
|
+
reject(error);
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Adjust sizes for information only (no processing)
|
|
594
|
+
* @returns {Promise<boolean>} - Success status
|
|
595
|
+
*/
|
|
596
|
+
async adjustSizesInfoOnly() {
|
|
597
|
+
return new Promise((resolve, reject) => {
|
|
598
|
+
try {
|
|
599
|
+
const result = this._wrapper.adjustSizesInfoOnly();
|
|
600
|
+
resolve(result);
|
|
601
|
+
} catch (error) {
|
|
602
|
+
reject(error);
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Free processed image data
|
|
609
|
+
* @returns {Promise<boolean>} - Success status
|
|
610
|
+
*/
|
|
611
|
+
async freeImage() {
|
|
612
|
+
return new Promise((resolve, reject) => {
|
|
613
|
+
try {
|
|
614
|
+
const result = this._wrapper.freeImage();
|
|
615
|
+
resolve(result);
|
|
616
|
+
} catch (error) {
|
|
617
|
+
reject(error);
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Convert floating point to integer data
|
|
624
|
+
* @param {number} [dmin=4096] - Minimum data value
|
|
625
|
+
* @param {number} [dmax=32767] - Maximum data value
|
|
626
|
+
* @param {number} [dtarget=16383] - Target value
|
|
627
|
+
* @returns {Promise<boolean>} - Success status
|
|
628
|
+
*/
|
|
629
|
+
async convertFloatToInt(dmin = 4096, dmax = 32767, dtarget = 16383) {
|
|
630
|
+
return new Promise((resolve, reject) => {
|
|
631
|
+
try {
|
|
632
|
+
const result = this._wrapper.convertFloatToInt(dmin, dmax, dtarget);
|
|
633
|
+
resolve(result);
|
|
634
|
+
} catch (error) {
|
|
635
|
+
reject(error);
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// ============== MEMORY OPERATIONS EXTENDED ==============
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Get memory image format information
|
|
644
|
+
* @returns {Promise<Object>} - Format info with width, height, colors, bps
|
|
645
|
+
*/
|
|
646
|
+
async getMemImageFormat() {
|
|
647
|
+
return new Promise((resolve, reject) => {
|
|
648
|
+
try {
|
|
649
|
+
const result = this._wrapper.getMemImageFormat();
|
|
650
|
+
resolve(result);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
reject(error);
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Copy memory image to buffer
|
|
659
|
+
* @param {Buffer} buffer - Destination buffer
|
|
660
|
+
* @param {number} stride - Row stride in bytes
|
|
661
|
+
* @param {boolean} bgr - Whether to use BGR order
|
|
662
|
+
* @returns {Promise<boolean>} - Success status
|
|
663
|
+
*/
|
|
664
|
+
async copyMemImage(buffer, stride, bgr = false) {
|
|
665
|
+
return new Promise((resolve, reject) => {
|
|
666
|
+
try {
|
|
667
|
+
const result = this._wrapper.copyMemImage(buffer, stride, bgr);
|
|
668
|
+
resolve(result);
|
|
669
|
+
} catch (error) {
|
|
670
|
+
reject(error);
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// ============== COLOR OPERATIONS ==============
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Get color filter at specific position
|
|
679
|
+
* @param {number} row - Row position
|
|
680
|
+
* @param {number} col - Column position
|
|
681
|
+
* @returns {Promise<number>} - Color value
|
|
682
|
+
*/
|
|
683
|
+
async getColorAt(row, col) {
|
|
684
|
+
return new Promise((resolve, reject) => {
|
|
685
|
+
try {
|
|
686
|
+
const result = this._wrapper.getColorAt(row, col);
|
|
687
|
+
resolve(result);
|
|
688
|
+
} catch (error) {
|
|
689
|
+
reject(error);
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// ============== JPEG CONVERSION (NEW FEATURE) ==============
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Convert RAW to JPEG with advanced options
|
|
698
|
+
* @param {string} outputPath - Output JPEG file path
|
|
699
|
+
* @param {Object} options - JPEG conversion options
|
|
700
|
+
* @param {number} [options.quality=85] - JPEG quality (1-100)
|
|
701
|
+
* @param {number} [options.width] - Target width (maintains aspect ratio if height not specified)
|
|
702
|
+
* @param {number} [options.height] - Target height (maintains aspect ratio if width not specified)
|
|
703
|
+
* @param {boolean} [options.progressive=false] - Use progressive JPEG
|
|
704
|
+
* @param {boolean} [options.mozjpeg=true] - Use mozjpeg encoder for better compression
|
|
705
|
+
* @param {number} [options.chromaSubsampling='4:2:0'] - Chroma subsampling ('4:4:4', '4:2:2', '4:2:0')
|
|
706
|
+
* @param {boolean} [options.trellisQuantisation=false] - Enable trellis quantisation
|
|
707
|
+
* @param {boolean} [options.optimizeScans=false] - Optimize scan order
|
|
708
|
+
* @param {number} [options.overshootDeringing=false] - Overshoot deringing
|
|
709
|
+
* @param {boolean} [options.optimizeCoding=true] - Optimize Huffman coding
|
|
710
|
+
* @param {string} [options.colorSpace='srgb'] - Output color space ('srgb', 'rec2020', 'p3', 'cmyk')
|
|
711
|
+
* @returns {Promise<Object>} - Conversion result with metadata
|
|
712
|
+
*/
|
|
713
|
+
async convertToJPEG(outputPath, options = {}) {
|
|
714
|
+
return new Promise(async (resolve, reject) => {
|
|
715
|
+
try {
|
|
716
|
+
// Set default options with performance-optimized values
|
|
717
|
+
const opts = {
|
|
718
|
+
quality: options.quality || 85,
|
|
719
|
+
progressive: options.progressive || false,
|
|
720
|
+
mozjpeg: options.mozjpeg !== false, // Default to true for better compression
|
|
721
|
+
chromaSubsampling: options.chromaSubsampling || "4:2:0",
|
|
722
|
+
trellisQuantisation: options.trellisQuantisation || false,
|
|
723
|
+
optimizeScans: options.optimizeScans || false,
|
|
724
|
+
overshootDeringing: options.overshootDeringing || false,
|
|
725
|
+
optimizeCoding: options.optimizeCoding !== false, // Default to true
|
|
726
|
+
colorSpace: options.colorSpace || "srgb",
|
|
727
|
+
...options,
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
const startTime = process.hrtime.bigint();
|
|
731
|
+
|
|
732
|
+
// Smart processing: only process if not already processed
|
|
733
|
+
if (!this._isProcessed) {
|
|
734
|
+
await this.processImage();
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Create processed image in memory (uses cache if available)
|
|
738
|
+
const imageData = await this.createMemoryImage();
|
|
739
|
+
|
|
740
|
+
if (!imageData || !imageData.data) {
|
|
741
|
+
throw new Error("Failed to create memory image from RAW data");
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Convert the LibRaw RGB data to Sharp-compatible buffer
|
|
745
|
+
let sharpInstance;
|
|
746
|
+
|
|
747
|
+
// Determine if this is a large image for performance optimizations
|
|
748
|
+
const isLargeImage = imageData.width * imageData.height > 20_000_000; // > 20MP
|
|
749
|
+
const fastMode = opts.fastMode !== false; // Default to fast mode
|
|
750
|
+
|
|
751
|
+
// Optimized Sharp configuration
|
|
752
|
+
const sharpConfig = {
|
|
753
|
+
raw: {
|
|
754
|
+
width: imageData.width,
|
|
755
|
+
height: imageData.height,
|
|
756
|
+
channels: imageData.colors,
|
|
757
|
+
premultiplied: false,
|
|
758
|
+
},
|
|
759
|
+
// Performance optimizations
|
|
760
|
+
sequentialRead: true,
|
|
761
|
+
limitInputPixels: false,
|
|
762
|
+
density: fastMode ? 72 : 300, // Lower DPI for speed
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
if (imageData.bits === 16) {
|
|
766
|
+
sharpConfig.raw.depth = "ushort";
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
sharpInstance = sharp(imageData.data, sharpConfig);
|
|
770
|
+
|
|
771
|
+
// Apply resizing if specified with performance optimizations
|
|
772
|
+
if (opts.width || opts.height) {
|
|
773
|
+
const resizeOptions = {
|
|
774
|
+
withoutEnlargement: true,
|
|
775
|
+
// Use faster kernel for large images or when fast mode is enabled
|
|
776
|
+
kernel:
|
|
777
|
+
isLargeImage || fastMode
|
|
778
|
+
? sharp.kernel.cubic
|
|
779
|
+
: sharp.kernel.lanczos3,
|
|
780
|
+
fit: "inside",
|
|
781
|
+
fastShrinkOnLoad: true, // Enable fast shrink-on-load optimization
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
if (opts.width && opts.height) {
|
|
785
|
+
sharpInstance = sharpInstance.resize(
|
|
786
|
+
opts.width,
|
|
787
|
+
opts.height,
|
|
788
|
+
resizeOptions
|
|
789
|
+
);
|
|
790
|
+
} else if (opts.width) {
|
|
791
|
+
sharpInstance = sharpInstance.resize(
|
|
792
|
+
opts.width,
|
|
793
|
+
null,
|
|
794
|
+
resizeOptions
|
|
795
|
+
);
|
|
796
|
+
} else {
|
|
797
|
+
sharpInstance = sharpInstance.resize(
|
|
798
|
+
null,
|
|
799
|
+
opts.height,
|
|
800
|
+
resizeOptions
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Configure color space
|
|
806
|
+
switch (opts.colorSpace.toLowerCase()) {
|
|
807
|
+
case "rec2020":
|
|
808
|
+
sharpInstance = sharpInstance.toColorspace("rec2020");
|
|
809
|
+
break;
|
|
810
|
+
case "p3":
|
|
811
|
+
sharpInstance = sharpInstance.toColorspace("p3");
|
|
812
|
+
break;
|
|
813
|
+
case "cmyk":
|
|
814
|
+
sharpInstance = sharpInstance.toColorspace("cmyk");
|
|
815
|
+
break;
|
|
816
|
+
case "srgb":
|
|
817
|
+
default:
|
|
818
|
+
sharpInstance = sharpInstance.toColorspace("srgb");
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Configure JPEG options with performance optimizations
|
|
823
|
+
const jpegOptions = {
|
|
824
|
+
quality: Math.max(1, Math.min(100, opts.quality)),
|
|
825
|
+
progressive: fastMode ? false : opts.progressive, // Disable progressive for speed
|
|
826
|
+
mozjpeg: fastMode ? false : opts.mozjpeg, // Disable mozjpeg for speed
|
|
827
|
+
trellisQuantisation: fastMode ? false : opts.trellisQuantisation,
|
|
828
|
+
optimizeScans: fastMode ? false : opts.optimizeScans,
|
|
829
|
+
overshootDeringing: false, // Always disable for speed
|
|
830
|
+
optimizeCoding: fastMode ? false : opts.optimizeCoding,
|
|
831
|
+
// Add effort control for JPEG encoding
|
|
832
|
+
effort: fastMode ? 1 : Math.min(opts.effort || 4, 6),
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
// Set chroma subsampling
|
|
836
|
+
switch (opts.chromaSubsampling) {
|
|
837
|
+
case "4:4:4":
|
|
838
|
+
jpegOptions.chromaSubsampling = "4:4:4";
|
|
839
|
+
break;
|
|
840
|
+
case "4:2:2":
|
|
841
|
+
jpegOptions.chromaSubsampling = "4:4:4"; // Sharp doesn't support 4:2:2, use 4:4:4 instead
|
|
842
|
+
break;
|
|
843
|
+
case "4:2:0":
|
|
844
|
+
default:
|
|
845
|
+
jpegOptions.chromaSubsampling = "4:2:0";
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// Convert to JPEG and save
|
|
850
|
+
const jpegBuffer = await sharpInstance
|
|
851
|
+
.jpeg(jpegOptions)
|
|
852
|
+
.toBuffer({ resolveWithObject: true });
|
|
853
|
+
|
|
854
|
+
// Write to file
|
|
855
|
+
await sharp(jpegBuffer.data).toFile(outputPath);
|
|
856
|
+
|
|
857
|
+
const endTime = process.hrtime.bigint();
|
|
858
|
+
const processingTime = Number(endTime - startTime) / 1000000; // Convert to milliseconds
|
|
859
|
+
|
|
860
|
+
// Get output file stats
|
|
861
|
+
const fs = require("fs");
|
|
862
|
+
const stats = fs.statSync(outputPath);
|
|
863
|
+
|
|
864
|
+
// Calculate compression ratio
|
|
865
|
+
const originalSize = imageData.dataSize;
|
|
866
|
+
const compressedSize = stats.size;
|
|
867
|
+
const compressionRatio = originalSize / compressedSize;
|
|
868
|
+
|
|
869
|
+
const result = {
|
|
870
|
+
success: true,
|
|
871
|
+
outputPath: outputPath,
|
|
872
|
+
metadata: {
|
|
873
|
+
originalDimensions: {
|
|
874
|
+
width: imageData.width,
|
|
875
|
+
height: imageData.height,
|
|
876
|
+
},
|
|
877
|
+
outputDimensions: {
|
|
878
|
+
width: jpegBuffer.info.width,
|
|
879
|
+
height: jpegBuffer.info.height,
|
|
880
|
+
},
|
|
881
|
+
fileSize: {
|
|
882
|
+
original: originalSize,
|
|
883
|
+
compressed: compressedSize,
|
|
884
|
+
compressionRatio: compressionRatio.toFixed(2),
|
|
885
|
+
},
|
|
886
|
+
processing: {
|
|
887
|
+
timeMs: processingTime.toFixed(2),
|
|
888
|
+
throughputMBps: (
|
|
889
|
+
originalSize /
|
|
890
|
+
1024 /
|
|
891
|
+
1024 /
|
|
892
|
+
(processingTime / 1000)
|
|
893
|
+
).toFixed(2),
|
|
894
|
+
},
|
|
895
|
+
jpegOptions: jpegOptions,
|
|
896
|
+
},
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
resolve(result);
|
|
900
|
+
} catch (error) {
|
|
901
|
+
reject(new Error(`JPEG conversion failed: ${error.message}`));
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Batch convert multiple RAW files to JPEG
|
|
908
|
+
* @param {string[]} inputPaths - Array of input RAW file paths
|
|
909
|
+
* @param {string} outputDir - Output directory for JPEG files
|
|
910
|
+
* @param {Object} options - JPEG conversion options (same as convertToJPEG)
|
|
911
|
+
* @returns {Promise<Object>} - Batch conversion results
|
|
912
|
+
*/
|
|
913
|
+
async batchConvertToJPEG(inputPaths, outputDir, options = {}) {
|
|
914
|
+
return new Promise(async (resolve, reject) => {
|
|
915
|
+
try {
|
|
916
|
+
const fs = require("fs");
|
|
917
|
+
const path = require("path");
|
|
918
|
+
|
|
919
|
+
// Ensure output directory exists
|
|
920
|
+
if (!fs.existsSync(outputDir)) {
|
|
921
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const results = {
|
|
925
|
+
successful: [],
|
|
926
|
+
failed: [],
|
|
927
|
+
summary: {
|
|
928
|
+
total: inputPaths.length,
|
|
929
|
+
processed: 0,
|
|
930
|
+
errors: 0,
|
|
931
|
+
totalProcessingTime: 0,
|
|
932
|
+
averageCompressionRatio: 0,
|
|
933
|
+
totalOriginalSize: 0,
|
|
934
|
+
totalCompressedSize: 0,
|
|
935
|
+
},
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
const startTime = process.hrtime.bigint();
|
|
939
|
+
|
|
940
|
+
for (const inputPath of inputPaths) {
|
|
941
|
+
try {
|
|
942
|
+
// Generate output filename
|
|
943
|
+
const baseName = path.basename(inputPath, path.extname(inputPath));
|
|
944
|
+
const outputPath = path.join(outputDir, `${baseName}.jpg`);
|
|
945
|
+
|
|
946
|
+
// Load the RAW file
|
|
947
|
+
await this.close(); // Close any previous file
|
|
948
|
+
await this.loadFile(inputPath);
|
|
949
|
+
|
|
950
|
+
// Convert to JPEG
|
|
951
|
+
const result = await this.convertToJPEG(outputPath, options);
|
|
952
|
+
|
|
953
|
+
results.successful.push({
|
|
954
|
+
input: inputPath,
|
|
955
|
+
output: outputPath,
|
|
956
|
+
result: result,
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
results.summary.processed++;
|
|
960
|
+
results.summary.totalOriginalSize +=
|
|
961
|
+
result.metadata.fileSize.original;
|
|
962
|
+
results.summary.totalCompressedSize +=
|
|
963
|
+
result.metadata.fileSize.compressed;
|
|
964
|
+
} catch (error) {
|
|
965
|
+
results.failed.push({
|
|
966
|
+
input: inputPath,
|
|
967
|
+
error: error.message,
|
|
968
|
+
});
|
|
969
|
+
results.summary.errors++;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
const endTime = process.hrtime.bigint();
|
|
974
|
+
results.summary.totalProcessingTime =
|
|
975
|
+
Number(endTime - startTime) / 1000000; // ms
|
|
976
|
+
|
|
977
|
+
if (results.summary.totalOriginalSize > 0) {
|
|
978
|
+
results.summary.averageCompressionRatio = (
|
|
979
|
+
results.summary.totalOriginalSize /
|
|
980
|
+
results.summary.totalCompressedSize
|
|
981
|
+
).toFixed(2);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
results.summary.averageProcessingTimePerFile = (
|
|
985
|
+
results.summary.totalProcessingTime / inputPaths.length
|
|
986
|
+
).toFixed(2);
|
|
987
|
+
|
|
988
|
+
resolve(results);
|
|
989
|
+
} catch (error) {
|
|
990
|
+
reject(new Error(`Batch JPEG conversion failed: ${error.message}`));
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Get optimal JPEG conversion settings based on image analysis
|
|
997
|
+
* @param {Object} analysisOptions - Options for image analysis
|
|
998
|
+
* @returns {Promise<Object>} - Recommended JPEG settings
|
|
999
|
+
*/
|
|
1000
|
+
async getOptimalJPEGSettings(analysisOptions = {}) {
|
|
1001
|
+
return new Promise(async (resolve, reject) => {
|
|
1002
|
+
try {
|
|
1003
|
+
// Get image metadata and process for analysis
|
|
1004
|
+
const metadata = await this.getMetadata();
|
|
1005
|
+
const imageSize = await this.getImageSize();
|
|
1006
|
+
|
|
1007
|
+
// Analyze image characteristics
|
|
1008
|
+
const imageArea = metadata.width * metadata.height;
|
|
1009
|
+
const isHighRes = imageArea > 6000 * 4000; // > 24MP
|
|
1010
|
+
const isMediumRes = imageArea > 3000 * 2000; // > 6MP
|
|
1011
|
+
|
|
1012
|
+
// Default settings based on image characteristics
|
|
1013
|
+
let recommendedSettings = {
|
|
1014
|
+
quality: 85,
|
|
1015
|
+
progressive: false,
|
|
1016
|
+
mozjpeg: true,
|
|
1017
|
+
chromaSubsampling: "4:2:0",
|
|
1018
|
+
optimizeCoding: true,
|
|
1019
|
+
trellisQuantisation: false,
|
|
1020
|
+
optimizeScans: false,
|
|
1021
|
+
reasoning: [],
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
// Adjust settings based on image size
|
|
1025
|
+
if (isHighRes) {
|
|
1026
|
+
recommendedSettings.quality = 80; // Slightly lower quality for large images
|
|
1027
|
+
recommendedSettings.progressive = true; // Progressive loading for large images
|
|
1028
|
+
recommendedSettings.trellisQuantisation = true; // Better compression for large images
|
|
1029
|
+
recommendedSettings.reasoning.push(
|
|
1030
|
+
"High resolution image detected - optimizing for file size"
|
|
1031
|
+
);
|
|
1032
|
+
} else if (isMediumRes) {
|
|
1033
|
+
recommendedSettings.quality = 85;
|
|
1034
|
+
recommendedSettings.reasoning.push(
|
|
1035
|
+
"Medium resolution image - balanced quality/size"
|
|
1036
|
+
);
|
|
1037
|
+
} else {
|
|
1038
|
+
recommendedSettings.quality = 90; // Higher quality for smaller images
|
|
1039
|
+
recommendedSettings.chromaSubsampling = "4:4:4"; // Better chroma for small images (Sharp compatible)
|
|
1040
|
+
recommendedSettings.reasoning.push(
|
|
1041
|
+
"Lower resolution image - prioritizing quality"
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// Adjust for different use cases
|
|
1046
|
+
if (analysisOptions.usage === "web") {
|
|
1047
|
+
recommendedSettings.quality = Math.min(
|
|
1048
|
+
recommendedSettings.quality,
|
|
1049
|
+
80
|
|
1050
|
+
);
|
|
1051
|
+
recommendedSettings.progressive = true;
|
|
1052
|
+
recommendedSettings.optimizeScans = true;
|
|
1053
|
+
recommendedSettings.reasoning.push(
|
|
1054
|
+
"Web usage - optimized for loading speed"
|
|
1055
|
+
);
|
|
1056
|
+
} else if (analysisOptions.usage === "print") {
|
|
1057
|
+
recommendedSettings.quality = Math.max(
|
|
1058
|
+
recommendedSettings.quality,
|
|
1059
|
+
90
|
|
1060
|
+
);
|
|
1061
|
+
recommendedSettings.chromaSubsampling = "4:4:4"; // Use 4:4:4 instead of 4:2:2 for Sharp compatibility
|
|
1062
|
+
recommendedSettings.reasoning.push(
|
|
1063
|
+
"Print usage - optimized for quality"
|
|
1064
|
+
);
|
|
1065
|
+
} else if (analysisOptions.usage === "archive") {
|
|
1066
|
+
recommendedSettings.quality = 95;
|
|
1067
|
+
recommendedSettings.chromaSubsampling = "4:4:4";
|
|
1068
|
+
recommendedSettings.trellisQuantisation = true;
|
|
1069
|
+
recommendedSettings.reasoning.push(
|
|
1070
|
+
"Archive usage - maximum quality preservation"
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// Camera-specific optimizations
|
|
1075
|
+
if (metadata.make) {
|
|
1076
|
+
const make = metadata.make.toLowerCase();
|
|
1077
|
+
if (make.includes("canon") || make.includes("nikon")) {
|
|
1078
|
+
// Professional cameras often benefit from slightly different settings
|
|
1079
|
+
recommendedSettings.reasoning.push(
|
|
1080
|
+
`${metadata.make} camera detected - professional settings`
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
resolve({
|
|
1086
|
+
recommended: recommendedSettings,
|
|
1087
|
+
imageAnalysis: {
|
|
1088
|
+
dimensions: {
|
|
1089
|
+
width: metadata.width,
|
|
1090
|
+
height: metadata.height,
|
|
1091
|
+
area: imageArea,
|
|
1092
|
+
},
|
|
1093
|
+
category: isHighRes
|
|
1094
|
+
? "high-resolution"
|
|
1095
|
+
: isMediumRes
|
|
1096
|
+
? "medium-resolution"
|
|
1097
|
+
: "low-resolution",
|
|
1098
|
+
camera: {
|
|
1099
|
+
make: metadata.make,
|
|
1100
|
+
model: metadata.model,
|
|
1101
|
+
},
|
|
1102
|
+
},
|
|
1103
|
+
});
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
reject(
|
|
1106
|
+
new Error(
|
|
1107
|
+
`Failed to analyze image for optimal settings: ${error.message}`
|
|
1108
|
+
)
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// ============== CANCELLATION SUPPORT ==============
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Set cancellation flag to stop processing
|
|
1118
|
+
* @returns {Promise<boolean>} - Success status
|
|
1119
|
+
*/
|
|
1120
|
+
async setCancelFlag() {
|
|
1121
|
+
return new Promise((resolve, reject) => {
|
|
1122
|
+
try {
|
|
1123
|
+
const result = this._wrapper.setCancelFlag();
|
|
1124
|
+
resolve(result);
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
reject(error);
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* Clear cancellation flag
|
|
1133
|
+
* @returns {Promise<boolean>} - Success status
|
|
1134
|
+
*/
|
|
1135
|
+
async clearCancelFlag() {
|
|
1136
|
+
return new Promise((resolve, reject) => {
|
|
1137
|
+
try {
|
|
1138
|
+
const result = this._wrapper.clearCancelFlag();
|
|
1139
|
+
resolve(result);
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
reject(error);
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// ============== VERSION INFORMATION (INSTANCE METHODS) ==============
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Get LibRaw version string
|
|
1150
|
+
* @returns {string} - Version string
|
|
1151
|
+
*/
|
|
1152
|
+
version() {
|
|
1153
|
+
return this._wrapper.version();
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* Get LibRaw version as array [major, minor, patch]
|
|
1158
|
+
* @returns {number[]} - Version number array
|
|
1159
|
+
*/
|
|
1160
|
+
versionNumber() {
|
|
1161
|
+
return this._wrapper.versionNumber();
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// ============== STATIC METHODS ==============
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Get LibRaw version
|
|
1168
|
+
* @returns {string} - Version string
|
|
1169
|
+
*/
|
|
1170
|
+
static getVersion() {
|
|
1171
|
+
return librawAddon.LibRawWrapper.getVersion();
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/**
|
|
1175
|
+
* Get LibRaw capabilities
|
|
1176
|
+
* @returns {number} - Capabilities flags
|
|
1177
|
+
*/
|
|
1178
|
+
static getCapabilities() {
|
|
1179
|
+
return librawAddon.LibRawWrapper.getCapabilities();
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
/**
|
|
1183
|
+
* Get list of supported cameras
|
|
1184
|
+
* @returns {string[]} - Array of camera names
|
|
1185
|
+
*/
|
|
1186
|
+
static getCameraList() {
|
|
1187
|
+
return librawAddon.LibRawWrapper.getCameraList();
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* Get count of supported cameras
|
|
1192
|
+
* @returns {number} - Number of supported cameras
|
|
1193
|
+
*/
|
|
1194
|
+
static getCameraCount() {
|
|
1195
|
+
return librawAddon.LibRawWrapper.getCameraCount();
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
/**
|
|
1199
|
+
* High-performance fast JPEG conversion with minimal processing
|
|
1200
|
+
* @param {string} outputPath - Output JPEG file path
|
|
1201
|
+
* @param {Object} options - Speed-optimized JPEG options
|
|
1202
|
+
* @returns {Promise<Object>} - Conversion result
|
|
1203
|
+
*/
|
|
1204
|
+
async convertToJPEGFast(outputPath, options = {}) {
|
|
1205
|
+
return this.convertToJPEG(outputPath, {
|
|
1206
|
+
fastMode: true,
|
|
1207
|
+
effort: 1, // Fastest encoding
|
|
1208
|
+
progressive: false,
|
|
1209
|
+
trellisQuantisation: false,
|
|
1210
|
+
optimizeScans: false,
|
|
1211
|
+
mozjpeg: false,
|
|
1212
|
+
quality: options.quality || 80,
|
|
1213
|
+
...options,
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* Create multiple JPEG sizes from single RAW (thumbnail, web, full)
|
|
1219
|
+
* @param {string} baseOutputPath - Base output path (without extension)
|
|
1220
|
+
* @param {Object} options - Multi-size options
|
|
1221
|
+
* @returns {Promise<Object>} - Multi-size conversion results
|
|
1222
|
+
*/
|
|
1223
|
+
async convertToJPEGMultiSize(baseOutputPath, options = {}) {
|
|
1224
|
+
const sizes = options.sizes || [
|
|
1225
|
+
{ name: "thumb", width: 400, quality: 85 },
|
|
1226
|
+
{ name: "web", width: 1920, quality: 80 },
|
|
1227
|
+
{ name: "full", quality: 85 },
|
|
1228
|
+
];
|
|
1229
|
+
|
|
1230
|
+
// Process the RAW once (uses smart caching)
|
|
1231
|
+
if (!this._isProcessed) {
|
|
1232
|
+
await this.processImage();
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
const results = {};
|
|
1236
|
+
const startTime = Date.now();
|
|
1237
|
+
|
|
1238
|
+
// Create all sizes sequentially to reuse cached data
|
|
1239
|
+
for (const sizeConfig of sizes) {
|
|
1240
|
+
const outputPath = `${baseOutputPath}_${sizeConfig.name}.jpg`;
|
|
1241
|
+
const sizeStart = Date.now();
|
|
1242
|
+
|
|
1243
|
+
const result = await this.convertToJPEG(outputPath, {
|
|
1244
|
+
fastMode: true,
|
|
1245
|
+
width: sizeConfig.width,
|
|
1246
|
+
height: sizeConfig.height,
|
|
1247
|
+
quality: sizeConfig.quality || 85,
|
|
1248
|
+
effort: sizeConfig.effort || 2,
|
|
1249
|
+
...sizeConfig,
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
const sizeEnd = Date.now();
|
|
1253
|
+
|
|
1254
|
+
results[sizeConfig.name] = {
|
|
1255
|
+
name: sizeConfig.name,
|
|
1256
|
+
outputPath,
|
|
1257
|
+
dimensions: result.metadata.outputDimensions,
|
|
1258
|
+
fileSize: result.metadata.fileSize.compressed,
|
|
1259
|
+
processingTime: sizeEnd - sizeStart,
|
|
1260
|
+
config: sizeConfig,
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
const endTime = Date.now();
|
|
1265
|
+
const totalTime = endTime - startTime;
|
|
1266
|
+
|
|
1267
|
+
return {
|
|
1268
|
+
success: true,
|
|
1269
|
+
sizes: results,
|
|
1270
|
+
originalDimensions: Object.values(results)[0]
|
|
1271
|
+
? Object.values(results)[0].dimensions
|
|
1272
|
+
: { width: 0, height: 0 },
|
|
1273
|
+
totalProcessingTime: totalTime,
|
|
1274
|
+
averageTimePerSize: `${(totalTime / sizes.length).toFixed(2)}ms`,
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/**
|
|
1279
|
+
* High-performance parallel batch conversion using worker threads
|
|
1280
|
+
* @param {string[]} inputPaths - Array of RAW file paths
|
|
1281
|
+
* @param {string} outputDir - Output directory
|
|
1282
|
+
* @param {Object} options - Conversion options
|
|
1283
|
+
* @returns {Promise<Object>} - Batch conversion results
|
|
1284
|
+
*/
|
|
1285
|
+
static async batchConvertToJPEGParallel(inputPaths, outputDir, options = {}) {
|
|
1286
|
+
const fs = require("fs");
|
|
1287
|
+
const path = require("path");
|
|
1288
|
+
const os = require("os");
|
|
1289
|
+
|
|
1290
|
+
if (!fs.existsSync(outputDir)) {
|
|
1291
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
const maxConcurrency =
|
|
1295
|
+
options.maxConcurrency || Math.min(os.cpus().length, 4);
|
|
1296
|
+
const results = [];
|
|
1297
|
+
const errors = [];
|
|
1298
|
+
const startTime = Date.now();
|
|
1299
|
+
|
|
1300
|
+
// Process files in parallel batches
|
|
1301
|
+
for (let i = 0; i < inputPaths.length; i += maxConcurrency) {
|
|
1302
|
+
const batch = inputPaths.slice(i, i + maxConcurrency);
|
|
1303
|
+
|
|
1304
|
+
const batchPromises = batch.map(async (inputPath) => {
|
|
1305
|
+
try {
|
|
1306
|
+
const fileName = path.parse(inputPath).name;
|
|
1307
|
+
const outputPath = path.join(outputDir, `${fileName}.jpg`);
|
|
1308
|
+
|
|
1309
|
+
const libraw = new LibRaw();
|
|
1310
|
+
await libraw.loadFile(inputPath);
|
|
1311
|
+
|
|
1312
|
+
const result = await libraw.convertToJPEG(outputPath, {
|
|
1313
|
+
fastMode: true,
|
|
1314
|
+
effort: 1,
|
|
1315
|
+
quality: options.quality || 85,
|
|
1316
|
+
...options,
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
await libraw.close();
|
|
1320
|
+
|
|
1321
|
+
return {
|
|
1322
|
+
inputPath,
|
|
1323
|
+
outputPath,
|
|
1324
|
+
success: true,
|
|
1325
|
+
fileSize: result.metadata.fileSize.compressed,
|
|
1326
|
+
processingTime: result.metadata.processing.timeMs,
|
|
1327
|
+
};
|
|
1328
|
+
} catch (error) {
|
|
1329
|
+
errors.push({ inputPath, error: error.message });
|
|
1330
|
+
return {
|
|
1331
|
+
inputPath,
|
|
1332
|
+
success: false,
|
|
1333
|
+
error: error.message,
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
const batchResults = await Promise.all(batchPromises);
|
|
1339
|
+
results.push(...batchResults);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
const endTime = Date.now();
|
|
1343
|
+
const successCount = results.filter((r) => r.success).length;
|
|
1344
|
+
|
|
1345
|
+
return {
|
|
1346
|
+
totalFiles: inputPaths.length,
|
|
1347
|
+
successCount,
|
|
1348
|
+
errorCount: errors.length,
|
|
1349
|
+
results,
|
|
1350
|
+
errors,
|
|
1351
|
+
totalProcessingTime: endTime - startTime,
|
|
1352
|
+
averageTimePerFile:
|
|
1353
|
+
successCount > 0 ? (endTime - startTime) / successCount : 0,
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
module.exports = LibRaw;
|