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.0.0+)
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
- - **HEIF/HEVC Support** - Higher quality at smaller file sizes (iOS 11+)
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
- - **4K Resolution Support** - Highest available resolution (4K → Full HD → Photo)
21
- - **High-Resolution Capture** - Full sensor resolution enabled
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
- No configuration needed - professional quality automatically!
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.2.0",
3
+ "version": "3.4.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -31,22 +31,37 @@ if (!SCANNER_PATH) {
31
31
  console.log('📸 Applying camera quality optimizations...');
32
32
 
33
33
  try {
34
- // Copy optimized iOS file
35
- const iosFile = 'ios/IPDFCameraViewController.m';
36
- const sourcePath = path.join(VENDOR_PATH, iosFile);
37
- const targetPath = path.join(SCANNER_PATH, iosFile);
38
-
39
- if (fs.existsSync(sourcePath)) {
40
- // Backup original if not already backed up
41
- if (!fs.existsSync(targetPath + '.original')) {
42
- fs.copyFileSync(targetPath, targetPath + '.original');
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
- // Copy optimized version
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('⚠️ Optimized iOS file not found in vendor folder');
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 = 1.0f;
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
- session.sessionPreset = AVCaptureSessionPresetPhoto;
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;