capacitor-camera-multicapture 0.12.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.
Files changed (76) hide show
  1. package/CameraMultiCapture.podspec +17 -0
  2. package/LICENSE +21 -0
  3. package/Package.swift +28 -0
  4. package/README.md +551 -0
  5. package/android/build.gradle +69 -0
  6. package/android/src/main/AndroidManifest.xml +6 -0
  7. package/android/src/main/java/dev/hemang/cameramulticapture/CameraConfig.java +45 -0
  8. package/android/src/main/java/dev/hemang/cameramulticapture/CameraConfigMapper.java +98 -0
  9. package/android/src/main/java/dev/hemang/cameramulticapture/CameraMultiCapture.java +10 -0
  10. package/android/src/main/java/dev/hemang/cameramulticapture/CameraMultiCapturePlugin.java +1340 -0
  11. package/android/src/main/java/dev/hemang/cameramulticapture/ExifWrapper.java +108 -0
  12. package/android/src/main/java/dev/hemang/cameramulticapture/GenericUploadWorker.java +173 -0
  13. package/android/src/main/java/dev/hemang/cameramulticapture/ImageUtils.java +215 -0
  14. package/android/src/main/java/dev/hemang/cameramulticapture/PluginVersion.java +6 -0
  15. package/android/src/main/java/dev/hemang/cameramulticapture/ThumbnailGenerator.java +48 -0
  16. package/android/src/main/res/.gitkeep +0 -0
  17. package/dist/esm/controllers/camera-controller.d.ts +127 -0
  18. package/dist/esm/controllers/camera-controller.js +388 -0
  19. package/dist/esm/controllers/camera-controller.js.map +1 -0
  20. package/dist/esm/controllers/gallery-controller.d.ts +66 -0
  21. package/dist/esm/controllers/gallery-controller.js +333 -0
  22. package/dist/esm/controllers/gallery-controller.js.map +1 -0
  23. package/dist/esm/definitions.d.ts +372 -0
  24. package/dist/esm/definitions.js +6 -0
  25. package/dist/esm/definitions.js.map +1 -0
  26. package/dist/esm/index.d.ts +5 -0
  27. package/dist/esm/index.js +6 -0
  28. package/dist/esm/index.js.map +1 -0
  29. package/dist/esm/overlay-manager.d.ts +124 -0
  30. package/dist/esm/overlay-manager.js +732 -0
  31. package/dist/esm/overlay-manager.js.map +1 -0
  32. package/dist/esm/overlay.d.ts +2 -0
  33. package/dist/esm/overlay.js +39 -0
  34. package/dist/esm/overlay.js.map +1 -0
  35. package/dist/esm/registration.d.ts +5 -0
  36. package/dist/esm/registration.js +8 -0
  37. package/dist/esm/registration.js.map +1 -0
  38. package/dist/esm/types/ui-types.d.ts +78 -0
  39. package/dist/esm/types/ui-types.js +2 -0
  40. package/dist/esm/types/ui-types.js.map +1 -0
  41. package/dist/esm/ui/capture-gesture-handler.d.ts +15 -0
  42. package/dist/esm/ui/capture-gesture-handler.js +87 -0
  43. package/dist/esm/ui/capture-gesture-handler.js.map +1 -0
  44. package/dist/esm/ui/default-styles.d.ts +27 -0
  45. package/dist/esm/ui/default-styles.js +134 -0
  46. package/dist/esm/ui/default-styles.js.map +1 -0
  47. package/dist/esm/ui/image-editor.d.ts +10 -0
  48. package/dist/esm/ui/image-editor.js +103 -0
  49. package/dist/esm/ui/image-editor.js.map +1 -0
  50. package/dist/esm/ui/layout-manager.d.ts +42 -0
  51. package/dist/esm/ui/layout-manager.js +199 -0
  52. package/dist/esm/ui/layout-manager.js.map +1 -0
  53. package/dist/esm/ui/media-viewer.d.ts +2 -0
  54. package/dist/esm/ui/media-viewer.js +267 -0
  55. package/dist/esm/ui/media-viewer.js.map +1 -0
  56. package/dist/esm/ui/pinch-zoom-handler.d.ts +37 -0
  57. package/dist/esm/ui/pinch-zoom-handler.js +118 -0
  58. package/dist/esm/ui/pinch-zoom-handler.js.map +1 -0
  59. package/dist/esm/ui/ui-factory.d.ts +39 -0
  60. package/dist/esm/ui/ui-factory.js +272 -0
  61. package/dist/esm/ui/ui-factory.js.map +1 -0
  62. package/dist/esm/version.d.ts +1 -0
  63. package/dist/esm/version.js +3 -0
  64. package/dist/esm/version.js.map +1 -0
  65. package/dist/esm/web.d.ts +69 -0
  66. package/dist/esm/web.js +122 -0
  67. package/dist/esm/web.js.map +1 -0
  68. package/dist/plugin.cjs.js +6274 -0
  69. package/dist/plugin.cjs.js.map +1 -0
  70. package/dist/plugin.js +6276 -0
  71. package/dist/plugin.js.map +1 -0
  72. package/ios/Sources/CameraMultiCapturePlugin/CameraMultiCapture.swift +8 -0
  73. package/ios/Sources/CameraMultiCapturePlugin/CameraMultiCapturePlugin.swift +1551 -0
  74. package/ios/Sources/CameraMultiCapturePlugin/PluginVersion.swift +2 -0
  75. package/ios/Tests/CameraMultiCapturePluginTests/CameraMultiCapturePluginTests.swift +15 -0
  76. package/package.json +93 -0
@@ -0,0 +1,108 @@
1
+ package dev.hemang.cameramulticapture;
2
+
3
+ import androidx.exifinterface.media.ExifInterface;
4
+ import android.util.Log;
5
+
6
+ import java.io.IOException;
7
+
8
+ /**
9
+ * Adapted from Capacitor Camera Plugin
10
+ * Source: https://github.com/ionic-team/capacitor-plugins/blob/main/camera/android/src/main/java/com/capacitorjs/plugins/camera/ExifWrapper.java
11
+ * Copyright 2020-present Ionic (https://ionic.io)
12
+ * Licensed under MIT License
13
+ */
14
+ public class ExifWrapper {
15
+ private static final String TAG = "ExifWrapper";
16
+ private final ExifInterface exif;
17
+
18
+ private final String[] attributes = new String[] {
19
+ ExifInterface.TAG_APERTURE_VALUE,
20
+ ExifInterface.TAG_ARTIST,
21
+ ExifInterface.TAG_DATETIME,
22
+ ExifInterface.TAG_DATETIME_DIGITIZED,
23
+ ExifInterface.TAG_DATETIME_ORIGINAL,
24
+ ExifInterface.TAG_EXPOSURE_TIME,
25
+ ExifInterface.TAG_FLASH,
26
+ ExifInterface.TAG_FOCAL_LENGTH,
27
+ ExifInterface.TAG_GPS_ALTITUDE,
28
+ ExifInterface.TAG_GPS_ALTITUDE_REF,
29
+ ExifInterface.TAG_GPS_DATESTAMP,
30
+ ExifInterface.TAG_GPS_LATITUDE,
31
+ ExifInterface.TAG_GPS_LATITUDE_REF,
32
+ ExifInterface.TAG_GPS_LONGITUDE,
33
+ ExifInterface.TAG_GPS_LONGITUDE_REF,
34
+ ExifInterface.TAG_GPS_PROCESSING_METHOD,
35
+ ExifInterface.TAG_GPS_TIMESTAMP,
36
+ ExifInterface.TAG_IMAGE_LENGTH,
37
+ ExifInterface.TAG_IMAGE_WIDTH,
38
+ ExifInterface.TAG_ISO_SPEED,
39
+ ExifInterface.TAG_MAKE,
40
+ ExifInterface.TAG_MODEL,
41
+ ExifInterface.TAG_ORIENTATION,
42
+ ExifInterface.TAG_SUBSEC_TIME,
43
+ ExifInterface.TAG_SUBSEC_TIME_DIGITIZED,
44
+ ExifInterface.TAG_SUBSEC_TIME_ORIGINAL,
45
+ ExifInterface.TAG_WHITE_BALANCE
46
+ };
47
+
48
+ public ExifWrapper(ExifInterface exif) {
49
+ this.exif = exif;
50
+ }
51
+
52
+ public ExifWrapper(String filePath) throws IOException {
53
+ this.exif = new ExifInterface(filePath);
54
+ }
55
+
56
+ /**
57
+ * Copy EXIF data from this wrapper to a destination file
58
+ * @param destFile Path to destination file
59
+ */
60
+ public void copyExif(String destFile) {
61
+ if (exif == null) {
62
+ Log.w(TAG, "Source EXIF is null, cannot copy");
63
+ return;
64
+ }
65
+
66
+ try {
67
+ ExifInterface destExif = new ExifInterface(destFile);
68
+ for (String attribute : attributes) {
69
+ String value = exif.getAttribute(attribute);
70
+ if (value != null) {
71
+ destExif.setAttribute(attribute, value);
72
+ }
73
+ }
74
+ destExif.saveAttributes();
75
+ Log.d(TAG, "EXIF data copied successfully to " + destFile);
76
+ } catch (IOException e) {
77
+ Log.e(TAG, "Failed to copy EXIF data: " + e.getMessage(), e);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Reset orientation to normal (1) after physical rotation
83
+ */
84
+ public void resetOrientation() {
85
+ if (exif != null) {
86
+ try {
87
+ exif.setAttribute(ExifInterface.TAG_ORIENTATION,
88
+ String.valueOf(ExifInterface.ORIENTATION_NORMAL));
89
+ exif.saveAttributes();
90
+ Log.d(TAG, "EXIF orientation reset to NORMAL");
91
+ } catch (IOException e) {
92
+ Log.e(TAG, "Failed to reset orientation: " + e.getMessage(), e);
93
+ }
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Get the current orientation value
99
+ * @return EXIF orientation value
100
+ */
101
+ public int getOrientation() {
102
+ if (exif == null) {
103
+ return ExifInterface.ORIENTATION_NORMAL;
104
+ }
105
+ return exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
106
+ }
107
+ }
108
+
@@ -0,0 +1,173 @@
1
+ package dev.hemang.cameramulticapture;
2
+
3
+ import android.content.Context;
4
+ import android.net.Uri;
5
+ import android.util.Log;
6
+ import androidx.annotation.NonNull;
7
+ import androidx.work.Data;
8
+ import androidx.work.Worker;
9
+ import androidx.work.WorkerParameters;
10
+ import com.getcapacitor.JSObject;
11
+ import org.json.JSONObject;
12
+ import java.io.File;
13
+ import java.io.IOException;
14
+ import java.util.Iterator;
15
+ import java.util.concurrent.TimeUnit;
16
+ import okhttp3.*;
17
+
18
+ public class GenericUploadWorker extends Worker {
19
+ private static final String TAG = "GenericUploadWorker";
20
+ private static final MediaType MEDIA_TYPE_JPEG = MediaType.parse("image/jpeg");
21
+
22
+ private static class UploadResult {
23
+ boolean success;
24
+ String errorMessage;
25
+
26
+ UploadResult(boolean success, String errorMessage) {
27
+ this.success = success;
28
+ this.errorMessage = errorMessage;
29
+ }
30
+ }
31
+
32
+ public GenericUploadWorker(@NonNull Context context, @NonNull WorkerParameters params) {
33
+ super(context, params);
34
+ }
35
+
36
+ @NonNull
37
+ @Override
38
+ public Result doWork() {
39
+ try {
40
+ String jobId = getInputData().getString("jobId");
41
+ String imageUri = getInputData().getString("imageUri");
42
+ String uploadEndpoint = getInputData().getString("uploadEndpoint");
43
+ String headersJson = getInputData().getString("headers");
44
+ String formDataJson = getInputData().getString("formData");
45
+ String method = getInputData().getString("method");
46
+ String fileName = getInputData().getString("fileName");
47
+ boolean deleteAfterUpload = getInputData().getBoolean("deleteAfterUpload", true);
48
+
49
+ Log.d(TAG, "Starting upload job: " + jobId);
50
+
51
+ UploadResult result = performHttpUpload(imageUri, uploadEndpoint, headersJson, formDataJson, method, fileName);
52
+
53
+ if (result.success) {
54
+ Log.d(TAG, "Upload completed successfully: " + jobId);
55
+
56
+ if (deleteAfterUpload) {
57
+ try {
58
+ File imageFile = new File(Uri.parse(imageUri).getPath());
59
+ if (imageFile.exists() && imageFile.delete()) {
60
+ Log.d(TAG, "✅ Cleaned up file after successful upload: " + imageUri);
61
+ }
62
+ } catch (Exception e) {
63
+ Log.w(TAG, "Failed to clean up file: " + e.getMessage());
64
+ }
65
+ }
66
+
67
+ return Result.success();
68
+ } else {
69
+ Log.e(TAG, "Upload failed: " + jobId + " - " + result.errorMessage);
70
+ Data errorData = new Data.Builder()
71
+ .putString("error", result.errorMessage != null ? result.errorMessage : "Upload failed")
72
+ .build();
73
+ return Result.failure(errorData);
74
+ }
75
+
76
+ } catch (Exception e) {
77
+ Log.e(TAG, "Upload error: " + e.getMessage(), e);
78
+ Data errorData = new Data.Builder()
79
+ .putString("error", e.getMessage())
80
+ .build();
81
+ return Result.retry();
82
+ }
83
+ }
84
+
85
+ private UploadResult performHttpUpload(String imageUri, String endpoint, String headersJson,
86
+ String formDataJson, String method, String fileName) {
87
+ try {
88
+ OkHttpClient client = new OkHttpClient.Builder()
89
+ .connectTimeout(45, TimeUnit.SECONDS)
90
+ .writeTimeout(120, TimeUnit.SECONDS)
91
+ .readTimeout(90, TimeUnit.SECONDS)
92
+ .retryOnConnectionFailure(true)
93
+ .connectionPool(new okhttp3.ConnectionPool(10, 5, TimeUnit.MINUTES))
94
+ .build();
95
+
96
+ File imageFile = new File(Uri.parse(imageUri).getPath());
97
+ if (!imageFile.exists()) {
98
+ Log.e(TAG, "Image file not found: " + imageUri);
99
+ return new UploadResult(false, "Image file not found: " + imageUri);
100
+ }
101
+
102
+ JSONObject headers = new JSONObject(headersJson);
103
+ JSONObject formData = new JSONObject(formDataJson);
104
+
105
+ Request.Builder requestBuilder = new Request.Builder().url(endpoint);
106
+
107
+ Iterator<String> headerKeys = headers.keys();
108
+ while (headerKeys.hasNext()) {
109
+ String key = headerKeys.next();
110
+ String value = headers.getString(key);
111
+ requestBuilder.addHeader(key, value);
112
+ }
113
+
114
+ RequestBody requestBody;
115
+ if ("PUT".equalsIgnoreCase(method)) {
116
+ requestBody = RequestBody.create(MEDIA_TYPE_JPEG, imageFile);
117
+ } else {
118
+ // For POST requests, use multipart form
119
+ MultipartBody.Builder multipartBuilder = new MultipartBody.Builder()
120
+ .setType(MultipartBody.FORM);
121
+
122
+ Iterator<String> formKeys = formData.keys();
123
+ while (formKeys.hasNext()) {
124
+ String key = formKeys.next();
125
+ String value = formData.getString(key);
126
+ multipartBuilder.addFormDataPart(key, value);
127
+ }
128
+
129
+ String finalFileName = (fileName != null && !fileName.isEmpty()) ?
130
+ fileName : "photo_" + System.currentTimeMillis() + ".jpg";
131
+ multipartBuilder.addFormDataPart("file", finalFileName,
132
+ RequestBody.create(MEDIA_TYPE_JPEG, imageFile));
133
+
134
+ requestBody = multipartBuilder.build();
135
+ }
136
+
137
+ if ("PUT".equalsIgnoreCase(method)) {
138
+ requestBuilder.put(requestBody);
139
+ } else {
140
+ requestBuilder.post(requestBody);
141
+ }
142
+
143
+ Request request = requestBuilder.build();
144
+ try (Response response = client.newCall(request).execute()) {
145
+ boolean success = response.isSuccessful();
146
+ Log.d(TAG, "Upload response: " + response.code() + " - " + response.message());
147
+
148
+ if (!success) {
149
+ String errorBody = response.body() != null ? response.body().string() : "No error details";
150
+ Log.e(TAG, "Upload failed with response: " + response.code() + " - " + errorBody);
151
+ return new UploadResult(false, "HTTP " + response.code() + ": " + response.message() + " - " + errorBody);
152
+ }
153
+
154
+ return new UploadResult(true, null);
155
+ }
156
+
157
+ } catch (java.net.ConnectException e) {
158
+ String errorMsg = "Connection failed to Azure Blob Storage. This usually happens when uploading multiple files simultaneously. Try uploading fewer files at once.";
159
+ Log.e(TAG, errorMsg + " Details: " + e.getMessage(), e);
160
+ return new UploadResult(false, errorMsg);
161
+
162
+ } catch (java.net.SocketTimeoutException e) {
163
+ String errorMsg = "Upload timeout. The file may be too large or network is slow.";
164
+ Log.e(TAG, errorMsg + " Details: " + e.getMessage(), e);
165
+ return new UploadResult(false, errorMsg);
166
+
167
+ } catch (Exception e) {
168
+ String errorMsg = "Upload failed: " + e.getClass().getSimpleName() + " - " + e.getMessage();
169
+ Log.e(TAG, "HTTP upload error: " + errorMsg, e);
170
+ return new UploadResult(false, errorMsg);
171
+ }
172
+ }
173
+ }
@@ -0,0 +1,215 @@
1
+ package dev.hemang.cameramulticapture;
2
+
3
+ import android.content.Context;
4
+ import android.graphics.Bitmap;
5
+ import android.graphics.BitmapFactory;
6
+ import android.graphics.Matrix;
7
+ import android.net.Uri;
8
+ import android.util.Log;
9
+ import androidx.exifinterface.media.ExifInterface;
10
+
11
+ import java.io.File;
12
+ import java.io.FileOutputStream;
13
+ import java.io.IOException;
14
+ import java.io.InputStream;
15
+
16
+ /**
17
+ * Image orientation correction utilities
18
+ * Adapted from Capacitor Camera Plugin
19
+ * Source: https://github.com/ionic-team/capacitor-plugins/blob/main/camera/android/src/main/java/com/capacitorjs/plugins/camera/ImageUtils.java
20
+ * Copyright 2020-present Ionic (https://ionic.io)
21
+ * Licensed under MIT License
22
+ */
23
+ public class ImageUtils {
24
+ private static final String TAG = "ImageUtils";
25
+
26
+ /**
27
+ * Correct the orientation of an image file by reading EXIF, physically rotating, and resetting EXIF
28
+ * @param imageFile The image file to correct
29
+ * @return true if correction was successful or not needed, false if failed
30
+ */
31
+ public static boolean correctImageOrientation(File imageFile) {
32
+ if (imageFile == null || !imageFile.exists()) {
33
+ Log.e(TAG, "Image file does not exist");
34
+ return false;
35
+ }
36
+
37
+ try {
38
+ ExifWrapper exifWrapper = new ExifWrapper(imageFile.getAbsolutePath());
39
+ int orientation = exifWrapper.getOrientation();
40
+
41
+ if (orientation == ExifInterface.ORIENTATION_NORMAL ||
42
+ orientation == ExifInterface.ORIENTATION_UNDEFINED) {
43
+ return true;
44
+ }
45
+
46
+ Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath());
47
+ if (bitmap == null) {
48
+ Log.e(TAG, "Failed to decode image file");
49
+ return false;
50
+ }
51
+
52
+ Bitmap rotatedBitmap = rotateBitmapByExif(bitmap, orientation);
53
+
54
+ if (rotatedBitmap != bitmap) {
55
+ FileOutputStream out = new FileOutputStream(imageFile);
56
+ rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 95, out);
57
+ out.flush();
58
+ out.close();
59
+
60
+ bitmap.recycle();
61
+ rotatedBitmap.recycle();
62
+
63
+ ExifWrapper newExifWrapper = new ExifWrapper(imageFile.getAbsolutePath());
64
+ newExifWrapper.resetOrientation();
65
+
66
+ return true;
67
+ } else {
68
+ bitmap.recycle();
69
+ exifWrapper.resetOrientation();
70
+ return true;
71
+ }
72
+
73
+ } catch (Exception e) {
74
+ Log.e(TAG, "Error correcting image orientation: " + e.getMessage(), e);
75
+ return false;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Rotate bitmap according to EXIF orientation
81
+ * @param bitmap Source bitmap
82
+ * @param exifOrientation EXIF orientation value
83
+ * @return Rotated bitmap, or same bitmap if no rotation needed
84
+ */
85
+ private static Bitmap rotateBitmapByExif(Bitmap bitmap, int exifOrientation) {
86
+ Matrix matrix = new Matrix();
87
+
88
+ switch (exifOrientation) {
89
+ case ExifInterface.ORIENTATION_ROTATE_90:
90
+ matrix.postRotate(90);
91
+ break;
92
+ case ExifInterface.ORIENTATION_ROTATE_180:
93
+ matrix.postRotate(180);
94
+ break;
95
+ case ExifInterface.ORIENTATION_ROTATE_270:
96
+ matrix.postRotate(270);
97
+ break;
98
+ case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
99
+ matrix.setScale(-1, 1);
100
+ break;
101
+ case ExifInterface.ORIENTATION_FLIP_VERTICAL:
102
+ matrix.setScale(1, -1);
103
+ break;
104
+ case ExifInterface.ORIENTATION_TRANSPOSE:
105
+ matrix.postRotate(90);
106
+ matrix.postScale(-1, 1);
107
+ break;
108
+ case ExifInterface.ORIENTATION_TRANSVERSE:
109
+ matrix.postRotate(270);
110
+ matrix.postScale(-1, 1);
111
+ break;
112
+ case ExifInterface.ORIENTATION_NORMAL:
113
+ case ExifInterface.ORIENTATION_UNDEFINED:
114
+ default:
115
+ return bitmap;
116
+ }
117
+
118
+ try {
119
+ Bitmap rotatedBitmap = Bitmap.createBitmap(
120
+ bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true
121
+ );
122
+ return rotatedBitmap;
123
+ } catch (OutOfMemoryError e) {
124
+ Log.e(TAG, "Out of memory rotating bitmap", e);
125
+ return bitmap;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Generate a thumbnail from an already orientation-corrected image file
131
+ * @param imageFile Source image file (must already have corrected orientation)
132
+ * @param thumbnailSize Target thumbnail size (will be square)
133
+ * @return Base64 data URI of thumbnail, or null if failed
134
+ */
135
+ public static String generateThumbnail(File imageFile, int thumbnailSize) {
136
+ if (imageFile == null || !imageFile.exists()) {
137
+ Log.e(TAG, "Image file does not exist for thumbnail generation");
138
+ return null;
139
+ }
140
+
141
+ try {
142
+ BitmapFactory.Options options = new BitmapFactory.Options();
143
+ options.inSampleSize = calculateSampleSize(imageFile, thumbnailSize * 2);
144
+
145
+ Bitmap originalBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
146
+ if (originalBitmap == null) {
147
+ Log.e(TAG, "Failed to decode image file for thumbnail");
148
+ return null;
149
+ }
150
+
151
+ Bitmap thumbnail = createSquareThumbnail(originalBitmap, thumbnailSize);
152
+ originalBitmap.recycle();
153
+
154
+ if (thumbnail == null) {
155
+ Log.e(TAG, "Failed to generate thumbnail");
156
+ return null;
157
+ }
158
+
159
+ String base64Thumbnail = ThumbnailGenerator.bitmapToBase64(thumbnail, 85);
160
+ thumbnail.recycle();
161
+
162
+ return base64Thumbnail;
163
+
164
+ } catch (Exception e) {
165
+ Log.e(TAG, "Error generating thumbnail: " + e.getMessage(), e);
166
+ return null;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Create a square thumbnail by center-cropping
172
+ * @param source Source bitmap
173
+ * @param size Target size
174
+ * @return Square thumbnail bitmap
175
+ */
176
+ private static Bitmap createSquareThumbnail(Bitmap source, int size) {
177
+ int sourceWidth = source.getWidth();
178
+ int sourceHeight = source.getHeight();
179
+
180
+ int cropSize = Math.min(sourceWidth, sourceHeight);
181
+ int x = (sourceWidth - cropSize) / 2;
182
+ int y = (sourceHeight - cropSize) / 2;
183
+
184
+ Bitmap croppedBitmap = Bitmap.createBitmap(source, x, y, cropSize, cropSize);
185
+ Bitmap scaledBitmap = Bitmap.createScaledBitmap(croppedBitmap, size, size, true);
186
+
187
+ if (croppedBitmap != scaledBitmap) {
188
+ croppedBitmap.recycle();
189
+ }
190
+
191
+ return scaledBitmap;
192
+ }
193
+
194
+ /**
195
+ * Calculate sample size for efficient loading
196
+ * @param imageFile Image file
197
+ * @param targetSize Target size
198
+ * @return Sample size for BitmapFactory
199
+ */
200
+ private static int calculateSampleSize(File imageFile, int targetSize) {
201
+ BitmapFactory.Options options = new BitmapFactory.Options();
202
+ options.inJustDecodeBounds = true;
203
+ BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
204
+
205
+ int sampleSize = 1;
206
+ int maxDimension = Math.max(options.outWidth, options.outHeight);
207
+
208
+ while (maxDimension / sampleSize > targetSize) {
209
+ sampleSize *= 2;
210
+ }
211
+
212
+ return sampleSize;
213
+ }
214
+ }
215
+
@@ -0,0 +1,6 @@
1
+ // Auto-generated from package.json — do not edit. Run npm run build to update.
2
+ package dev.hemang.cameramulticapture;
3
+
4
+ public final class PluginVersion {
5
+ public static final String VERSION = "0.12.2";
6
+ }
@@ -0,0 +1,48 @@
1
+ package dev.hemang.cameramulticapture;
2
+
3
+ import android.graphics.Bitmap;
4
+ import android.util.Base64;
5
+ import android.util.Log;
6
+
7
+ import java.io.ByteArrayOutputStream;
8
+ import java.io.File;
9
+
10
+ public class ThumbnailGenerator {
11
+
12
+ private static final String TAG = "ThumbnailGenerator";
13
+ private static final int DEFAULT_THUMBNAIL_SIZE = 200;
14
+
15
+ /**
16
+ * Generate a thumbnail from an already orientation-corrected image file
17
+ * @param imageFile Image file (must already have corrected orientation)
18
+ * @param thumbnailSize Target thumbnail size
19
+ * @return Base64 data URI of thumbnail
20
+ */
21
+ public static String generateThumbnail(File imageFile, int thumbnailSize) {
22
+ return ImageUtils.generateThumbnail(imageFile, thumbnailSize);
23
+ }
24
+
25
+ /**
26
+ * Generate a thumbnail with default size
27
+ * @param imageFile Image file
28
+ * @return Base64 data URI of thumbnail
29
+ */
30
+ public static String generateThumbnail(File imageFile) {
31
+ return generateThumbnail(imageFile, DEFAULT_THUMBNAIL_SIZE);
32
+ }
33
+
34
+ /**
35
+ * Convert bitmap to Base64 data URI
36
+ * @param bitmap Source bitmap
37
+ * @param quality JPEG quality (0-100)
38
+ * @return Base64 data URI string
39
+ */
40
+ public static String bitmapToBase64(Bitmap bitmap, int quality) {
41
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
42
+ bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream);
43
+ byte[] bytes = outputStream.toByteArray();
44
+
45
+ String base64 = Base64.encodeToString(bytes, Base64.NO_WRAP);
46
+ return "data:image/jpeg;base64," + base64;
47
+ }
48
+ }
File without changes
@@ -0,0 +1,127 @@
1
+ import { TorchState } from '../definitions';
2
+ import type { CameraImageData, CameraMultiCapturePlugin, CameraVideoData } from '../definitions';
3
+ import type { CameraOverlayUIOptions } from '../types/ui-types';
4
+ /**
5
+ * Interface for camera rectangle dimensions
6
+ */
7
+ export interface CameraRect {
8
+ width: number;
9
+ height: number;
10
+ x: number;
11
+ y: number;
12
+ }
13
+ /**
14
+ * Manages camera operations
15
+ */
16
+ export declare class CameraController {
17
+ private plugin;
18
+ private options;
19
+ private flashMode;
20
+ private flashAutoModeEnabled;
21
+ private torchState;
22
+ private isRecording;
23
+ private currentZoom;
24
+ private preRecordingState;
25
+ private availableCameras;
26
+ constructor(plugin: CameraMultiCapturePlugin, options: CameraOverlayUIOptions);
27
+ /**
28
+ * Initializes the camera
29
+ */
30
+ initialize(containerElement: HTMLElement, quality: number): Promise<void>;
31
+ /**
32
+ * Captures an image
33
+ */
34
+ captureImage(): Promise<CameraImageData | undefined>;
35
+ startVideoRecording(): Promise<void>;
36
+ stopVideoRecording(): Promise<CameraVideoData | undefined>;
37
+ private restorePreRecordingState;
38
+ /**
39
+ * Returns whether video recording is currently active.
40
+ */
41
+ getRecordingState(): boolean;
42
+ /**
43
+ * Sets the zoom level
44
+ */
45
+ setZoom(level: number): Promise<void>;
46
+ /**
47
+ * Gets the current zoom level (tracked after setZoom calls)
48
+ */
49
+ getCurrentZoom(): number;
50
+ /**
51
+ * Gets available cameras information
52
+ */
53
+ getAvailableCameras(): Promise<{
54
+ hasUltrawide: boolean;
55
+ hasWide: boolean;
56
+ hasTelephoto: boolean;
57
+ ultrawideZoomFactor?: number;
58
+ wideZoomFactor: number;
59
+ telephotoZoomFactor?: number;
60
+ }>;
61
+ /**
62
+ * Switches to a physical camera based on zoom factor
63
+ * This will switch to the appropriate lens (ultrawide, wide, telephoto)
64
+ */
65
+ switchToPhysicalCamera(zoomFactor: number): Promise<void>;
66
+ /**
67
+ * Gets available zoom levels for the current camera
68
+ */
69
+ getAvailableZoomLevels(): Promise<{
70
+ minZoom: number;
71
+ maxZoom: number;
72
+ presetLevels: number[];
73
+ }>;
74
+ /**
75
+ * Switches between front and back camera
76
+ */
77
+ switchCamera(): Promise<void>;
78
+ /**
79
+ * Stops the camera
80
+ */
81
+ stop(): Promise<void>;
82
+ /**
83
+ * Sets the flash mode
84
+ */
85
+ setFlash(mode: 'on' | 'off' | 'auto'): Promise<void>;
86
+ /**
87
+ * Gets the current flash mode
88
+ */
89
+ getFlashMode(): 'on' | 'off' | 'auto';
90
+ /**
91
+ * Toggles flash mode between off, on, and optionally auto
92
+ */
93
+ toggleFlash(): Promise<'on' | 'off' | 'auto'>;
94
+ /**
95
+ * Gets whether auto mode is enabled
96
+ */
97
+ isFlashAutoModeEnabled(): boolean;
98
+ /**
99
+ * Sets the torch (flashlight) state.
100
+ */
101
+ setTorch(state: TorchState): Promise<void>;
102
+ /**
103
+ * Gets the current torch state.
104
+ */
105
+ getTorch(): Promise<TorchState>;
106
+ /**
107
+ * Toggles the torch state and returns the new value.
108
+ */
109
+ toggleTorch(): Promise<TorchState>;
110
+ /**
111
+ * Updates the camera preview rectangle dimensions
112
+ * Call this method when the container size changes (e.g., orientation change)
113
+ */
114
+ refresh(): Promise<void>;
115
+ /**
116
+ * Gets smart zoom levels that include physical camera switches
117
+ * Returns an array of zoom levels and whether each is a physical camera
118
+ */
119
+ getSmartZoomLevels(): Promise<{
120
+ level: number;
121
+ isPhysicalCamera: boolean;
122
+ }[]>;
123
+ /**
124
+ * Performs smart zoom - switches physical cameras when appropriate
125
+ */
126
+ performSmartZoom(targetZoom: number): Promise<void>;
127
+ }