react-native-rectangle-doc-scanner 3.129.0 → 3.131.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.
@@ -0,0 +1,220 @@
1
+ package com.reactnativerectangledocscanner
2
+
3
+ import android.graphics.Bitmap
4
+ import android.graphics.BitmapFactory
5
+ import android.graphics.Matrix
6
+ import android.util.Base64
7
+ import android.util.Log
8
+ import org.opencv.android.Utils
9
+ import org.opencv.core.*
10
+ import org.opencv.imgproc.Imgproc
11
+ import java.io.ByteArrayOutputStream
12
+ import java.io.File
13
+ import java.io.FileOutputStream
14
+ import kotlin.math.max
15
+ import kotlin.math.min
16
+
17
+ object ImageProcessor {
18
+ private const val TAG = "ImageProcessor"
19
+
20
+ init {
21
+ try {
22
+ System.loadLibrary("opencv_java4")
23
+ Log.d(TAG, "OpenCV library loaded successfully")
24
+ } catch (e: Exception) {
25
+ Log.e(TAG, "Failed to load OpenCV library", e)
26
+ }
27
+ }
28
+
29
+ data class ProcessedImage(
30
+ val croppedImage: Bitmap,
31
+ val initialImage: Bitmap,
32
+ val rectangle: Rectangle?
33
+ )
34
+
35
+ /**
36
+ * Apply perspective correction to crop document from detected rectangle
37
+ */
38
+ fun cropAndCorrectPerspective(
39
+ bitmap: Bitmap,
40
+ rectangle: Rectangle
41
+ ): Bitmap {
42
+ val srcMat = Mat()
43
+ Utils.bitmapToMat(bitmap, srcMat)
44
+
45
+ // Convert to proper format if needed
46
+ if (srcMat.channels() == 4) {
47
+ Imgproc.cvtColor(srcMat, srcMat, Imgproc.COLOR_RGBA2RGB)
48
+ }
49
+
50
+ // Create source points from rectangle (match iOS order)
51
+ val srcPoints = MatOfPoint2f(
52
+ Point(rectangle.topLeft.x, rectangle.topLeft.y),
53
+ Point(rectangle.topRight.x, rectangle.topRight.y),
54
+ Point(rectangle.bottomLeft.x, rectangle.bottomLeft.y),
55
+ Point(rectangle.bottomRight.x, rectangle.bottomRight.y)
56
+ )
57
+
58
+ // Calculate destination rectangle dimensions
59
+ val widthTop = distance(rectangle.topLeft, rectangle.topRight)
60
+ val widthBottom = distance(rectangle.bottomLeft, rectangle.bottomRight)
61
+ val heightLeft = distance(rectangle.topLeft, rectangle.bottomLeft)
62
+ val heightRight = distance(rectangle.topRight, rectangle.bottomRight)
63
+
64
+ val maxWidth = max(widthTop, widthBottom).toInt()
65
+ val maxHeight = max(heightLeft, heightRight).toInt()
66
+
67
+ // Create destination points for a rectangle
68
+ val dstPoints = MatOfPoint2f(
69
+ Point(0.0, 0.0),
70
+ Point(maxWidth.toDouble(), 0.0),
71
+ Point(0.0, maxHeight.toDouble()),
72
+ Point(maxWidth.toDouble(), maxHeight.toDouble())
73
+ )
74
+
75
+ // Get perspective transform matrix and apply it
76
+ val transformMatrix = Imgproc.getPerspectiveTransform(srcPoints, dstPoints)
77
+ val dstMat = Mat()
78
+ Imgproc.warpPerspective(srcMat, dstMat, transformMatrix, Size(maxWidth.toDouble(), maxHeight.toDouble()))
79
+
80
+ // Convert back to bitmap
81
+ val croppedBitmap = Bitmap.createBitmap(maxWidth, maxHeight, Bitmap.Config.ARGB_8888)
82
+ Utils.matToBitmap(dstMat, croppedBitmap)
83
+
84
+ // Cleanup
85
+ srcMat.release()
86
+ dstMat.release()
87
+ transformMatrix.release()
88
+ srcPoints.release()
89
+ dstPoints.release()
90
+
91
+ return croppedBitmap
92
+ }
93
+
94
+ /**
95
+ * Apply color adjustments (brightness, contrast, saturation)
96
+ */
97
+ fun applyColorControls(
98
+ bitmap: Bitmap,
99
+ brightness: Float = 0f,
100
+ contrast: Float = 1f,
101
+ saturation: Float = 1f
102
+ ): Bitmap {
103
+ val srcMat = Mat()
104
+ Utils.bitmapToMat(bitmap, srcMat)
105
+
106
+ // Convert to HSV for saturation adjustment
107
+ if (saturation != 1f) {
108
+ val hsvMat = Mat()
109
+ Imgproc.cvtColor(srcMat, hsvMat, Imgproc.COLOR_RGB2HSV)
110
+
111
+ val channels = mutableListOf<Mat>()
112
+ Core.split(hsvMat, channels)
113
+
114
+ // Adjust saturation channel
115
+ channels[1].convertTo(channels[1], -1, saturation.toDouble(), 0.0)
116
+
117
+ Core.merge(channels, hsvMat)
118
+ Imgproc.cvtColor(hsvMat, srcMat, Imgproc.COLOR_HSV2RGB)
119
+
120
+ hsvMat.release()
121
+ channels.forEach { it.release() }
122
+ }
123
+
124
+ // Apply brightness and contrast
125
+ val dstMat = Mat()
126
+ srcMat.convertTo(dstMat, -1, contrast.toDouble(), brightness * 255.0)
127
+
128
+ val resultBitmap = Bitmap.createBitmap(bitmap.width, bitmap.height, bitmap.config)
129
+ Utils.matToBitmap(dstMat, resultBitmap)
130
+
131
+ srcMat.release()
132
+ dstMat.release()
133
+
134
+ return resultBitmap
135
+ }
136
+
137
+ /**
138
+ * Save bitmap to file with specified quality
139
+ */
140
+ fun saveBitmapToFile(
141
+ bitmap: Bitmap,
142
+ directory: File,
143
+ filename: String,
144
+ quality: Float
145
+ ): String {
146
+ val qualityPercent = max(95f, min(100f, quality * 100f)).toInt()
147
+ val file = File(directory, filename)
148
+
149
+ FileOutputStream(file).use { out ->
150
+ bitmap.compress(Bitmap.CompressFormat.JPEG, qualityPercent, out)
151
+ }
152
+
153
+ return file.absolutePath
154
+ }
155
+
156
+ /**
157
+ * Convert bitmap to Base64 string
158
+ */
159
+ fun bitmapToBase64(bitmap: Bitmap, quality: Float): String {
160
+ val qualityPercent = max(95f, min(100f, quality * 100f)).toInt()
161
+ val byteArrayOutputStream = ByteArrayOutputStream()
162
+ bitmap.compress(Bitmap.CompressFormat.JPEG, qualityPercent, byteArrayOutputStream)
163
+ val byteArray = byteArrayOutputStream.toByteArray()
164
+ return Base64.encodeToString(byteArray, Base64.NO_WRAP)
165
+ }
166
+
167
+ /**
168
+ * Calculate Euclidean distance between two points
169
+ */
170
+ private fun distance(p1: Point, p2: Point): Double {
171
+ val dx = p1.x - p2.x
172
+ val dy = p1.y - p2.y
173
+ return kotlin.math.sqrt(dx * dx + dy * dy)
174
+ }
175
+
176
+ /**
177
+ * Rotate bitmap by specified degrees
178
+ */
179
+ fun rotateBitmap(bitmap: Bitmap, degrees: Float): Bitmap {
180
+ val matrix = Matrix()
181
+ matrix.postRotate(degrees)
182
+ return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
183
+ }
184
+
185
+ /**
186
+ * Process captured image with optional cropping and color adjustments
187
+ */
188
+ fun processImage(
189
+ imagePath: String,
190
+ rectangle: Rectangle?,
191
+ brightness: Float = 0f,
192
+ contrast: Float = 1f,
193
+ saturation: Float = 1f,
194
+ shouldCrop: Boolean = true
195
+ ): ProcessedImage {
196
+ var initialBitmap = BitmapFactory.decodeFile(imagePath)
197
+ var croppedBitmap = initialBitmap
198
+
199
+ // Apply perspective correction if rectangle detected and cropping enabled
200
+ if (shouldCrop && rectangle != null) {
201
+ try {
202
+ croppedBitmap = cropAndCorrectPerspective(initialBitmap, rectangle)
203
+ } catch (e: Exception) {
204
+ Log.e(TAG, "Failed to crop image, using original", e)
205
+ croppedBitmap = initialBitmap
206
+ }
207
+ }
208
+
209
+ // Apply color adjustments to cropped image
210
+ if (brightness != 0f || contrast != 1f || saturation != 1f) {
211
+ try {
212
+ croppedBitmap = applyColorControls(croppedBitmap, brightness, contrast, saturation)
213
+ } catch (e: Exception) {
214
+ Log.e(TAG, "Failed to apply color controls", e)
215
+ }
216
+ }
217
+
218
+ return ProcessedImage(croppedBitmap, initialBitmap, rectangle)
219
+ }
220
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.129.0",
3
+ "version": "3.131.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -49,5 +49,9 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "expo-image-manipulator": "^12.0.5"
52
+ },
53
+ "react-native": {
54
+ "android": "./android",
55
+ "ios": "./ios"
52
56
  }
53
57
  }