react-native-rectangle-doc-scanner 3.2.0 → 3.4.0
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
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [3.2.1] - 2025-10-17
|
|
6
|
+
|
|
7
|
+
### 🔧 Auto-Install Improvements
|
|
8
|
+
|
|
9
|
+
- Updated postinstall script to copy both camera files automatically
|
|
10
|
+
- Now copies `IPDFCameraViewController.m` and `DocumentScannerView.m`
|
|
11
|
+
- Complete automation - no manual steps required
|
|
12
|
+
|
|
13
|
+
## [3.2.0] - 2025-10-17
|
|
14
|
+
|
|
15
|
+
### 🔧 Camera Quality Fixes
|
|
16
|
+
|
|
17
|
+
Fixed critical quality issues and app crashes:
|
|
18
|
+
|
|
19
|
+
#### Fixed Issues
|
|
20
|
+
- **Fixed app crash** from HEVC format compatibility issues
|
|
21
|
+
- **Fixed JPEG compression quality** - now enforces minimum 95% quality
|
|
22
|
+
- Previously used user's quality setting which could be very low
|
|
23
|
+
- Now uses `MAX(self.quality, 0.95)` to prevent quality loss
|
|
24
|
+
- **Improved session preset** - uses `AVCaptureSessionPresetHigh` for better quality
|
|
25
|
+
- **Added nil checks** to prevent crashes when photoOutput is unavailable
|
|
26
|
+
|
|
27
|
+
#### Quality Improvements
|
|
28
|
+
- Minimum 95% JPEG quality ensures no visible compression artifacts
|
|
29
|
+
- Simplified capture to use reliable JPEG format (removed HEVC compatibility issues)
|
|
30
|
+
- Better error handling for edge cases
|
|
31
|
+
|
|
5
32
|
## [3.0.0] - 2025-10-17
|
|
6
33
|
|
|
7
34
|
### 🚀 BREAKING CHANGE - Modern Camera API
|
|
@@ -17,9 +44,6 @@ Complete camera system overhaul for professional-grade image quality!
|
|
|
17
44
|
- **Computational Photography Support**
|
|
18
45
|
- Automatic HDR, Deep Fusion, Smart HDR
|
|
19
46
|
- These features were impossible with the old API
|
|
20
|
-
- **HEIF/HEVC Format Support** (iOS 11+)
|
|
21
|
-
- Better quality at smaller file sizes
|
|
22
|
-
- Fallback to JPEG for compatibility
|
|
23
47
|
- **Full Resolution Capture**
|
|
24
48
|
- 12MP+ on modern iPhones (up to 48MP on iPhone 14 Pro+)
|
|
25
49
|
- Old API was limited to lower resolutions
|
|
@@ -30,7 +54,6 @@ Complete camera system overhaul for professional-grade image quality!
|
|
|
30
54
|
#### Technical Improvements
|
|
31
55
|
- Delegate-based capture instead of callback-based
|
|
32
56
|
- Better error handling and edge cases
|
|
33
|
-
- Native file format support (HEIF)
|
|
34
57
|
- Reduced memory usage
|
|
35
58
|
- Faster capture times
|
|
36
59
|
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ React Native-friendly wrapper around [`react-native-document-scanner`](https://g
|
|
|
4
4
|
|
|
5
5
|
> The native implementation lives inside the upstream library (Objective‑C/OpenCV on iOS, Kotlin/OpenCV on Android). This package simply re-exports a type-safe wrapper, optional crop editor helpers, and a full-screen scanner flow.
|
|
6
6
|
|
|
7
|
-
## ✨ Professional Camera Quality (v3.
|
|
7
|
+
## ✨ Professional Camera Quality (v3.2+)
|
|
8
8
|
|
|
9
9
|
**Major Update:** Upgraded to modern `AVCapturePhotoOutput` API for dramatically improved image quality!
|
|
10
10
|
|
|
@@ -12,14 +12,13 @@ React Native-friendly wrapper around [`react-native-document-scanner`](https://g
|
|
|
12
12
|
- **Modern Camera API** - Uses `AVCapturePhotoOutput` (iOS 10+) instead of deprecated `AVCaptureStillImageOutput`
|
|
13
13
|
- **iPhone Native Quality** - Same quality as the built-in Camera app
|
|
14
14
|
- **Computational Photography** - Automatic HDR, Deep Fusion, and Smart HDR support
|
|
15
|
-
- **
|
|
16
|
-
- **12MP+ Resolution** - Full resolution capture on modern iPhones
|
|
15
|
+
- **12MP+ Resolution** - Full resolution capture on modern iPhones (up to 48MP on iPhone 14 Pro+)
|
|
17
16
|
- **Maximum Quality Priority** - iOS 13+ quality prioritization enabled
|
|
17
|
+
- **95%+ JPEG Quality** - Enforced minimum compression quality to prevent quality loss
|
|
18
18
|
|
|
19
19
|
### 🎯 Automatic Optimizations:
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
- **Retina Display** - Proper scale factor for crisp preview
|
|
20
|
+
- **High-Resolution Capture** - Full sensor resolution enabled (`AVCaptureSessionPresetHigh`)
|
|
21
|
+
- **Minimum 95% JPEG** - Prevents quality degradation from compression
|
|
23
22
|
- **Advanced Features**:
|
|
24
23
|
- Video stabilization for sharper images
|
|
25
24
|
- Continuous autofocus for always-sharp captures
|
|
@@ -27,7 +26,11 @@ React Native-friendly wrapper around [`react-native-document-scanner`](https://g
|
|
|
27
26
|
- Low-light boost in dark environments
|
|
28
27
|
- **Hardware-Accelerated** - CIContext for efficient processing
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
### ⚡ Fully Automatic Installation:
|
|
30
|
+
Just install with yarn/npm - **no manual configuration needed!**
|
|
31
|
+
- Postinstall script automatically patches camera quality
|
|
32
|
+
- Optimized iOS files copied during installation
|
|
33
|
+
- Works immediately after `pod install`
|
|
31
34
|
|
|
32
35
|
## Installation
|
|
33
36
|
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -31,22 +31,37 @@ if (!SCANNER_PATH) {
|
|
|
31
31
|
console.log('📸 Applying camera quality optimizations...');
|
|
32
32
|
|
|
33
33
|
try {
|
|
34
|
-
//
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
// Files to copy
|
|
35
|
+
const filesToCopy = [
|
|
36
|
+
'ios/IPDFCameraViewController.m',
|
|
37
|
+
'ios/DocumentScannerView.m'
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
let copiedCount = 0;
|
|
41
|
+
|
|
42
|
+
for (const iosFile of filesToCopy) {
|
|
43
|
+
const sourcePath = path.join(VENDOR_PATH, iosFile);
|
|
44
|
+
const targetPath = path.join(SCANNER_PATH, iosFile);
|
|
45
|
+
|
|
46
|
+
if (fs.existsSync(sourcePath)) {
|
|
47
|
+
// Backup original if not already backed up
|
|
48
|
+
if (!fs.existsSync(targetPath + '.original')) {
|
|
49
|
+
fs.copyFileSync(targetPath, targetPath + '.original');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Copy optimized version
|
|
53
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
54
|
+
copiedCount++;
|
|
43
55
|
}
|
|
56
|
+
}
|
|
44
57
|
|
|
45
|
-
|
|
46
|
-
fs.copyFileSync(sourcePath, targetPath);
|
|
58
|
+
if (copiedCount === filesToCopy.length) {
|
|
47
59
|
console.log('✅ iOS camera quality optimizations applied!');
|
|
60
|
+
console.log(' - Modern AVCapturePhotoOutput API');
|
|
61
|
+
console.log(' - High quality JPEG compression (95%+)');
|
|
62
|
+
console.log(' - Quality prioritization enabled');
|
|
48
63
|
} else {
|
|
49
|
-
console.log('⚠️
|
|
64
|
+
console.log('⚠️ Some optimized files not found in vendor folder');
|
|
50
65
|
}
|
|
51
66
|
|
|
52
67
|
console.log('✨ Setup complete!');
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#import "DocumentScannerView.h"
|
|
2
|
+
#import "IPDFCameraViewController.h"
|
|
3
|
+
|
|
4
|
+
@implementation DocumentScannerView
|
|
5
|
+
|
|
6
|
+
- (instancetype)init {
|
|
7
|
+
self = [super init];
|
|
8
|
+
if (self) {
|
|
9
|
+
[self setEnableBorderDetection:YES];
|
|
10
|
+
[self setDelegate: self];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return self;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
- (void) didDetectRectangle:(CIRectangleFeature *)rectangle withType:(IPDFRectangeType)type {
|
|
18
|
+
switch (type) {
|
|
19
|
+
case IPDFRectangeTypeGood:
|
|
20
|
+
self.stableCounter ++;
|
|
21
|
+
break;
|
|
22
|
+
default:
|
|
23
|
+
self.stableCounter = 0;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
if (self.onRectangleDetect) {
|
|
27
|
+
self.onRectangleDetect(@{@"stableCounter": @(self.stableCounter), @"lastDetectionType": @(type)});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (self.stableCounter > self.detectionCountBeforeCapture){
|
|
31
|
+
[self capture];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
- (void) capture {
|
|
36
|
+
[self captureImageWithCompletionHander:^(UIImage *croppedImage, UIImage *initialImage, CIRectangleFeature *rectangleFeature) {
|
|
37
|
+
if (self.onPictureTaken) {
|
|
38
|
+
// Use maximum JPEG quality (1.0) or user's quality setting, whichever is higher
|
|
39
|
+
// This ensures no quality loss during compression
|
|
40
|
+
CGFloat imageQuality = MAX(self.quality, 0.95);
|
|
41
|
+
NSData *croppedImageData = UIImageJPEGRepresentation(croppedImage, imageQuality);
|
|
42
|
+
|
|
43
|
+
if (initialImage.imageOrientation != UIImageOrientationUp) {
|
|
44
|
+
UIGraphicsBeginImageContextWithOptions(initialImage.size, false, initialImage.scale);
|
|
45
|
+
[initialImage drawInRect:CGRectMake(0, 0, initialImage.size.width
|
|
46
|
+
, initialImage.size.height)];
|
|
47
|
+
initialImage = UIGraphicsGetImageFromCurrentImageContext();
|
|
48
|
+
UIGraphicsEndImageContext();
|
|
49
|
+
}
|
|
50
|
+
NSData *initialImageData = UIImageJPEGRepresentation(initialImage, imageQuality);
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
RectangleCoordinates expects a rectanle viewed from portrait,
|
|
54
|
+
while rectangleFeature returns a rectangle viewed from landscape, which explains the nonsense of the mapping below.
|
|
55
|
+
Sorry about that.
|
|
56
|
+
*/
|
|
57
|
+
NSDictionary *rectangleCoordinates = rectangleFeature ? @{
|
|
58
|
+
@"topLeft": @{ @"y": @(rectangleFeature.bottomLeft.x + 30), @"x": @(rectangleFeature.bottomLeft.y)},
|
|
59
|
+
@"topRight": @{ @"y": @(rectangleFeature.topLeft.x + 30), @"x": @(rectangleFeature.topLeft.y)},
|
|
60
|
+
@"bottomLeft": @{ @"y": @(rectangleFeature.bottomRight.x), @"x": @(rectangleFeature.bottomRight.y)},
|
|
61
|
+
@"bottomRight": @{ @"y": @(rectangleFeature.topRight.x), @"x": @(rectangleFeature.topRight.y)},
|
|
62
|
+
} : [NSNull null];
|
|
63
|
+
if (self.useBase64) {
|
|
64
|
+
self.onPictureTaken(@{
|
|
65
|
+
@"croppedImage": [croppedImageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength],
|
|
66
|
+
@"initialImage": [initialImageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength],
|
|
67
|
+
@"rectangleCoordinates": rectangleCoordinates });
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
NSString *dir = NSTemporaryDirectory();
|
|
71
|
+
if (self.saveInAppDocument) {
|
|
72
|
+
dir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
|
73
|
+
}
|
|
74
|
+
NSString *croppedFilePath = [dir stringByAppendingPathComponent:[NSString stringWithFormat:@"cropped_img_%i.jpeg",(int)[NSDate date].timeIntervalSince1970]];
|
|
75
|
+
NSString *initialFilePath = [dir stringByAppendingPathComponent:[NSString stringWithFormat:@"initial_img_%i.jpeg",(int)[NSDate date].timeIntervalSince1970]];
|
|
76
|
+
|
|
77
|
+
[croppedImageData writeToFile:croppedFilePath atomically:YES];
|
|
78
|
+
[initialImageData writeToFile:initialFilePath atomically:YES];
|
|
79
|
+
|
|
80
|
+
self.onPictureTaken(@{
|
|
81
|
+
@"croppedImage": croppedFilePath,
|
|
82
|
+
@"initialImage": initialFilePath,
|
|
83
|
+
@"rectangleCoordinates": rectangleCoordinates });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!self.captureMultiple) {
|
|
88
|
+
[self stop];
|
|
89
|
+
}
|
|
90
|
+
}];
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@end
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
80
80
|
view.translatesAutoresizingMaskIntoConstraints = YES;
|
|
81
81
|
view.context = self.context;
|
|
82
|
-
view.contentScaleFactor =
|
|
82
|
+
view.contentScaleFactor = [UIScreen mainScreen].scale;
|
|
83
83
|
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
|
|
84
84
|
[self insertSubview:view atIndex:0];
|
|
85
85
|
_glkView = view;
|
|
@@ -117,7 +117,14 @@
|
|
|
117
117
|
|
|
118
118
|
NSError *error = nil;
|
|
119
119
|
AVCaptureDeviceInput* input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
|
|
120
|
-
|
|
120
|
+
|
|
121
|
+
// Set session preset to highest quality
|
|
122
|
+
if ([session canSetSessionPreset:AVCaptureSessionPresetHigh]) {
|
|
123
|
+
session.sessionPreset = AVCaptureSessionPresetHigh;
|
|
124
|
+
} else {
|
|
125
|
+
session.sessionPreset = AVCaptureSessionPresetPhoto;
|
|
126
|
+
}
|
|
127
|
+
|
|
121
128
|
[session addInput:input];
|
|
122
129
|
|
|
123
130
|
AVCaptureVideoDataOutput *dataOutput = [[AVCaptureVideoDataOutput alloc] init];
|
|
@@ -400,6 +407,15 @@
|
|
|
400
407
|
{
|
|
401
408
|
if (_isCapturing) return;
|
|
402
409
|
|
|
410
|
+
// Check if photoOutput is available
|
|
411
|
+
if (!self.photoOutput) {
|
|
412
|
+
NSLog(@"Error: photoOutput is nil");
|
|
413
|
+
if (completionHandler) {
|
|
414
|
+
completionHandler(nil, nil, nil);
|
|
415
|
+
}
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
403
419
|
__weak typeof(self) weakSelf = self;
|
|
404
420
|
|
|
405
421
|
[weakSelf hideGLKView:YES completion:^
|
|
@@ -415,7 +431,7 @@
|
|
|
415
431
|
// Store completion handler for delegate callback
|
|
416
432
|
self.photoCaptureCompletionHandler = completionHandler;
|
|
417
433
|
|
|
418
|
-
// Create photo settings with maximum quality
|
|
434
|
+
// Create photo settings with maximum quality - use JPEG for compatibility
|
|
419
435
|
AVCapturePhotoSettings *photoSettings = [AVCapturePhotoSettings photoSettings];
|
|
420
436
|
|
|
421
437
|
// Enable high resolution photo capture
|
|
@@ -426,17 +442,6 @@
|
|
|
426
442
|
photoSettings.photoQualityPrioritization = AVCapturePhotoQualityPrioritizationQuality;
|
|
427
443
|
}
|
|
428
444
|
|
|
429
|
-
// Use HEIF format if available for better quality (iOS 11+)
|
|
430
|
-
if (@available(iOS 11.0, *)) {
|
|
431
|
-
if ([self.photoOutput.availablePhotoCodecTypes containsObject:AVVideoCodecTypeHEVC]) {
|
|
432
|
-
photoSettings = [AVCapturePhotoSettings photoSettingsWithFormat:@{AVVideoCodecKey: AVVideoCodecTypeHEVC}];
|
|
433
|
-
photoSettings.highResolutionPhotoEnabled = YES;
|
|
434
|
-
if (@available(iOS 13.0, *)) {
|
|
435
|
-
photoSettings.photoQualityPrioritization = AVCapturePhotoQualityPrioritizationQuality;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
445
|
// Enable auto flash
|
|
441
446
|
if (self.photoOutput.supportedFlashModes && [self.photoOutput.supportedFlashModes containsObject:@(AVCaptureFlashModeAuto)]) {
|
|
442
447
|
photoSettings.flashMode = AVCaptureFlashModeAuto;
|