@momo-kits/camerakit 0.150.2-beta.3 → 0.150.2-beta.30

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.
@@ -5,6 +5,7 @@ import android.animation.Animator
5
5
  import android.animation.AnimatorListenerAdapter
6
6
  import android.annotation.SuppressLint
7
7
  import android.app.Activity
8
+ import android.content.ContentValues
8
9
  import android.content.Context
9
10
  import android.content.pm.PackageManager
10
11
  import android.graphics.Bitmap
@@ -14,7 +15,6 @@ import android.hardware.SensorManager
14
15
  import android.media.AudioManager
15
16
  import android.media.MediaActionSound
16
17
  import android.net.Uri
17
- import android.util.DisplayMetrics
18
18
  import android.util.Log
19
19
  import android.view.*
20
20
  import android.widget.FrameLayout
@@ -30,9 +30,7 @@ import androidx.core.content.ContextCompat
30
30
  import androidx.lifecycle.LifecycleObserver
31
31
  import com.facebook.react.bridge.Arguments
32
32
  import com.facebook.react.bridge.Promise
33
- import com.facebook.react.bridge.WritableMap
34
33
  import com.facebook.react.uimanager.ThemedReactContext
35
- import com.facebook.react.uimanager.events.RCTEventEmitter
36
34
  import com.rncamerakit.barcode.BarcodeFrame
37
35
  import java.io.File
38
36
  import java.util.*
@@ -42,9 +40,13 @@ import kotlin.math.abs
42
40
  import kotlin.math.max
43
41
  import kotlin.math.min
44
42
  import android.graphics.Canvas
43
+ import android.graphics.Matrix
45
44
  import android.graphics.Paint
46
45
  import android.graphics.Rect
47
46
  import android.graphics.RectF
47
+ import androidx.exifinterface.media.ExifInterface
48
+ import android.os.Build
49
+ import android.provider.MediaStore
48
50
  import android.util.Base64.decode
49
51
  import android.util.Size
50
52
  import com.facebook.react.uimanager.UIManagerHelper
@@ -60,6 +62,13 @@ import kotlinx.coroutines.SupervisorJob
60
62
  import kotlinx.coroutines.launch
61
63
  import jp.co.cyberagent.android.gpuimage.GPUImage
62
64
  import jp.co.cyberagent.android.gpuimage.filter.GPUImageSharpenFilter
65
+ import java.io.FileOutputStream
66
+
67
+ fun Bitmap.rotate(degrees: Int): Bitmap {
68
+ val matrix = Matrix()
69
+ matrix.postRotate(degrees.toFloat())
70
+ return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true)
71
+ }
63
72
 
64
73
  class RectOverlay constructor(context: Context) :
65
74
  View(context) {
@@ -502,6 +511,36 @@ open class CKCamera(context: ThemedReactContext) : FrameLayout(context), Lifecyc
502
511
 
503
512
  override fun onImageSaved(output: ImageCapture.OutputFileResults) {
504
513
  try {
514
+ val capturedBitmap = BitmapFactory.decodeFile(output.savedUri!!.path)
515
+ val targetWidth = options["width"] as? Double
516
+ val targetHeight = options["height"] as? Double
517
+ val targetQuality = options["quality"] as? Int ?: 100
518
+ if (targetWidth != null && targetHeight != null) {
519
+ val exif = output.savedUri!!.path?.let { ExifInterface(it) }
520
+ val rotatedBitmap = when (exif?.getAttributeInt(
521
+ ExifInterface.TAG_ORIENTATION,
522
+ ExifInterface.ORIENTATION_UNDEFINED
523
+ )) {
524
+ ExifInterface.ORIENTATION_ROTATE_90 -> capturedBitmap.rotate(90)
525
+ ExifInterface.ORIENTATION_ROTATE_180 -> capturedBitmap.rotate(180)
526
+ ExifInterface.ORIENTATION_ROTATE_270 -> capturedBitmap.rotate(270)
527
+ else -> capturedBitmap
528
+ }
529
+ val resizedBitmap = Bitmap.createScaledBitmap(
530
+ rotatedBitmap,
531
+ targetWidth.toInt(),
532
+ targetHeight.toInt(),
533
+ true
534
+ )
535
+ val outputStream = FileOutputStream(output.savedUri!!.path)
536
+ resizedBitmap.compress(
537
+ Bitmap.CompressFormat.JPEG,
538
+ targetQuality,
539
+ outputStream
540
+ )
541
+ outputStream.close()
542
+ }
543
+
505
544
  val uri = output.savedUri ?: Uri.fromFile(outputFile)
506
545
  val id = uri?.path
507
546
  val name = uri?.lastPathSegment
@@ -509,14 +548,20 @@ open class CKCamera(context: ThemedReactContext) : FrameLayout(context), Lifecyc
509
548
 
510
549
  val savedUri = (output.savedUri ?: outputPath).toString()
511
550
  onPictureTaken(savedUri)
551
+
552
+ val saveToGallery = options["savePhoto"] as? Boolean ?: false
553
+ if (saveToGallery) {
554
+ saveImageToGallery(outputFile, context)
555
+ }
556
+
512
557
  Log.d(TAG, "CameraView: Photo capture succeeded: $savedUri")
513
558
 
514
559
  val imageInfo = Arguments.createMap()
515
560
  imageInfo.putString("uri", uri.toString())
516
561
  imageInfo.putString("id", id)
517
562
  imageInfo.putString("name", name)
518
- imageInfo.putInt("width", width)
519
- imageInfo.putInt("height", height)
563
+ imageInfo.putDouble("width", targetWidth ?: width.toDouble())
564
+ imageInfo.putDouble("height", targetHeight ?: height.toDouble())
520
565
  imageInfo.putString("path", path)
521
566
 
522
567
  promise.resolve(imageInfo)
@@ -528,6 +573,42 @@ open class CKCamera(context: ThemedReactContext) : FrameLayout(context), Lifecyc
528
573
  })
529
574
  }
530
575
 
576
+ private fun saveImageToGallery(imageFile: File, context: Context) {
577
+ val displayName = "CameraKit_Image_${System.currentTimeMillis()}.jpg"
578
+
579
+ val imagesCollection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
580
+ MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
581
+ } else {
582
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI
583
+ }
584
+
585
+ val contentValues = ContentValues().apply {
586
+ put(MediaStore.Images.Media.DISPLAY_NAME, displayName)
587
+ put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
588
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
589
+ put(MediaStore.Images.Media.IS_PENDING, 1)
590
+ }
591
+ }
592
+
593
+ val resolver = context.contentResolver
594
+
595
+ val imageUri = resolver.insert(imagesCollection, contentValues)
596
+
597
+ imageUri?.let {
598
+ val outputStream = resolver.openOutputStream(it)
599
+ outputStream?.use { stream ->
600
+ imageFile.inputStream().copyTo(stream)
601
+ }
602
+
603
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
604
+ contentValues.clear()
605
+ contentValues.put(MediaStore.Images.Media.IS_PENDING, 0)
606
+ resolver.update(imageUri, contentValues, null, null)
607
+ }
608
+ }
609
+ }
610
+
611
+
531
612
  private fun focusOnPoint(x: Float?, y: Float?) {
532
613
  if (x === null || y === null) {
533
614
  camera?.cameraControl?.cancelFocusAndMetering()
@@ -1,5 +1,4 @@
1
1
  /// <reference types="react-native/types/modules/codegen" />
2
- /// <reference types="react-native/types/modules/codegen" />
3
2
  import type { ViewProps, ColorValue } from 'react-native';
4
3
  import type { DirectEventHandler, Int32, Double, WithDefault, Float } from 'react-native/Libraries/Types/CodegenTypes';
5
4
  type OnReadCodeData = {
@@ -22,7 +22,7 @@ import Foundation
22
22
  options: NSDictionary,
23
23
  resolve: @escaping RCTPromiseResolveBlock,
24
24
  reject: @escaping RCTPromiseRejectBlock) {
25
- camera.capture(onSuccess: { resolve($0) },
25
+ camera.capture(options as! [String: Any], onSuccess: { resolve($0) },
26
26
  onError: { reject("capture_error", $0, nil) })
27
27
  }
28
28
 
@@ -311,7 +311,7 @@ public class CameraView: UIView {
311
311
 
312
312
  // MARK: Public
313
313
 
314
- @objc public func capture(onSuccess: @escaping (_ imageObject: [String: Any]) -> Void,
314
+ @objc public func capture(_ options: [String: Any], onSuccess: @escaping (_ imageObject: [String: Any]) -> Void,
315
315
  onError: @escaping (_ error: String) -> Void) {
316
316
  camera.capturePicture(onWillCapture: { [weak self] in
317
317
  // Flash/dim preview to indicate shutter action
@@ -326,6 +326,7 @@ public class CameraView: UIView {
326
326
  self?.writeCaptured(imageData: imageData,
327
327
  thumbnailData: thumbnailData,
328
328
  dimensions: dimensions,
329
+ options: options,
329
330
  onSuccess: onSuccess,
330
331
  onError: onError)
331
332
 
@@ -428,19 +429,55 @@ public class CameraView: UIView {
428
429
  private func writeCaptured(imageData: Data,
429
430
  thumbnailData: Data?,
430
431
  dimensions: CMVideoDimensions,
432
+ options: [String: Any],
431
433
  onSuccess: @escaping (_ imageObject: [String: Any]) -> Void,
432
434
  onError: @escaping (_ error: String) -> Void) {
433
435
  do {
434
- let temporaryImageFileURL = try saveToTmpFolder(imageData)
435
-
436
- onSuccess([
437
- "size": imageData.count,
438
- "uri": temporaryImageFileURL.description,
439
- "name": temporaryImageFileURL.lastPathComponent,
440
- "thumb": "",
441
- "height": dimensions.height,
442
- "width": dimensions.width
443
- ])
436
+ // let temporaryImageFileURL = try saveToTmpFolder(imageData)
437
+ //
438
+ // onSuccess([
439
+ // "size": imageData.count,
440
+ // "uri": temporaryImageFileURL.description,
441
+ // "name": temporaryImageFileURL.lastPathComponent,
442
+ // "thumb": "",
443
+ // "height": dimensions.height,
444
+ // "width": dimensions.width
445
+ // ])
446
+ guard var image = UIImage(data: imageData) else {
447
+ onError("Failed to create UIImage from captured data")
448
+ return
449
+ }
450
+
451
+ // Get width, height, quality from options
452
+ let targetWidth = options["width"] as? Int
453
+ let targetHeight = options["height"] as? Int
454
+ let targetQuality = CGFloat((options["quality"] as? Int ?? 100)) / 100.0
455
+
456
+ // Resize image if width and height are specified
457
+ if let width = targetWidth, let height = targetHeight {
458
+ image = resizeImage(image: image, targetSize: CGSize(width: width, height: height))
459
+ }
460
+
461
+ // Convert to JPEG data with specified quality
462
+ guard let processedImageData = image.jpegData(compressionQuality: targetQuality) else {
463
+ onError("Failed to convert processed image to JPEG data")
464
+ return
465
+ }
466
+
467
+ let temporaryImageFileURL = try saveToTmpFolder(processedImageData)
468
+
469
+ onSuccess([
470
+ "size": processedImageData.count,
471
+ "uri": temporaryImageFileURL.description,
472
+ "name": temporaryImageFileURL.lastPathComponent,
473
+ "thumb": "",
474
+ "width": targetWidth ?? dimensions.width,
475
+ "height": targetHeight ?? dimensions.height,
476
+ ])
477
+
478
+ if let savePhoto = options["savePhoto"] as? Bool, savePhoto {
479
+ UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
480
+ }
444
481
  } catch {
445
482
  let errorMessage = "Error occurred while writing image data to a temporary file: \(error)"
446
483
  print(errorMessage)
@@ -464,6 +501,36 @@ public class CameraView: UIView {
464
501
 
465
502
  return temporaryFileURL
466
503
  }
504
+
505
+ private func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage {
506
+ let widthRatio = targetSize.width / image.size.width
507
+ let heightRatio = targetSize.height / image.size.height
508
+
509
+ // Chọn scale lớn hơn để đảm bảo ảnh phủ kín targetSize
510
+ let scaleFactor = max(widthRatio, heightRatio)
511
+
512
+ let scaledWidth = image.size.width * scaleFactor
513
+ let scaledHeight = image.size.height * scaleFactor
514
+
515
+ // Resize trước
516
+ UIGraphicsBeginImageContextWithOptions(CGSize(width: scaledWidth, height: scaledHeight), false, 1.0)
517
+ image.draw(in: CGRect(origin: .zero, size: CGSize(width: scaledWidth, height: scaledHeight)))
518
+ let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
519
+ UIGraphicsEndImageContext()
520
+
521
+ guard let finalScaledImage = scaledImage else { return image }
522
+
523
+ // Crop giữa ảnh để đúng targetSize
524
+ let cropX = (scaledWidth - targetSize.width) / 2
525
+ let cropY = (scaledHeight - targetSize.height) / 2
526
+ let cropRect = CGRect(x: cropX, y: cropY, width: targetSize.width, height: targetSize.height)
527
+
528
+ if let cgImage = finalScaledImage.cgImage?.cropping(to: cropRect) {
529
+ return UIImage(cgImage: cgImage)
530
+ }
531
+
532
+ return finalScaledImage
533
+ }
467
534
 
468
535
  private func onBarcodeRead(barcode: String, codeFormat:CodeFormat) {
469
536
  // Throttle barcode detection
@@ -46,7 +46,7 @@ RCT_EXPORT_MODULE();
46
46
 
47
47
  RCT_EXPORT_METHOD(capture:(NSDictionary *)options tag:(nonnull NSNumber *)tag resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
48
48
  [self withCamera:tag block:^(CKCameraView *view) {
49
- [CKCameraManager captureWithCamera:view options:[NSDictionary new] resolve:resolve reject:reject];
49
+ [CKCameraManager captureWithCamera:view options:options resolve:resolve reject:reject];
50
50
  } reject:reject methodName:@"capture"];
51
51
  }
52
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/camerakit",
3
- "version": "0.150.2-beta.3",
3
+ "version": "0.150.2-beta.30",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/teslamotors/react-native-camera-kit.git"
@@ -11,44 +11,16 @@
11
11
  "description": "A high performance, fully featured, rock solid camera library for React Native applications",
12
12
  "nativePackage": true,
13
13
  "scripts": {
14
- "build": "tsc --project tsconfig.json",
15
- "clean": "rm -rf dist/",
14
+ "build": "echo",
16
15
  "test": "jest",
17
- "lint": "yarn eslint -c .eslintrc.js",
18
- "check-ios": "scripts/check-ios.sh",
19
- "release": "yarn clean && yarn build && yarn publish --verbose",
20
- "release:beta": "yarn clean && yarn build && yarn publish --tag beta --verbose",
21
- "release:local": "yarn clean && yarn build && tmp=$(mktemp) && yarn pack --filename $tmp.tar.gz && open -R $tmp.tar.gz",
22
- "start": "watchman watch-del-all && node node_modules/react-native/local-cli/cli.js start",
23
- "bootstrap": "cd example/ && bundle install && yarn && cd ios/ && bundle exec pod install",
24
- "bootstrap-linux": "cd example/ && yarn"
16
+ "lint": "yarn eslint -c .eslintrc.js"
25
17
  },
26
- "main": "dist/index.js",
27
- "module": "dist/index.js",
28
- "source": "./src/",
29
- "types": "dist/index.d.ts",
30
- "react-native": "src/index",
18
+ "main": "./src/index.ts",
31
19
  "dependencies": {},
32
20
  "license": "MIT",
33
21
  "devDependencies": {
34
- "@babel/core": "^7.20.0",
35
- "@babel/preset-env": "^7.20.0",
36
- "@babel/runtime": "^7.20.0",
37
- "@react-native/babel-preset": "0.74.83",
38
- "@react-native/eslint-config": "0.74.83",
39
- "@react-native/metro-config": "0.74.83",
40
- "@react-native/typescript-config": "0.74.83",
41
- "@types/react": "^18.2.6",
42
- "@types/react-test-renderer": "^18.0.0",
43
- "babel-jest": "^29.6.3",
44
- "eslint": "^8.19.0",
45
- "eslint-plugin-import": "^2.22.1",
46
- "jest": "^29.6.3",
47
- "prettier": "2.8.8",
48
- "react": "18.2.0",
49
- "react-native": "0.74.1",
50
- "react-test-renderer": "18.2.0",
51
- "typescript": "5.0.4"
22
+ "react": "19.0.0",
23
+ "react-native": "0.80.1"
52
24
  },
53
25
  "peerDependencies": {
54
26
  "react": "*",