react-native-photo-video-editor 0.3.2 → 0.3.5

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,10 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.3.3
6
+
7
+ - **Direct photo PNG export on iOS**: Removed the "Export & Share" action sheet confirmation dialog on iOS. Tapping the header completion button (Done / Export) now directly exports the photo as a PNG file without requiring format confirmation.
8
+
5
9
  ## 0.3.2
6
10
 
7
11
  - **Fix iOS video export crash**: Resolved `EXC_BREAKPOINT` / `_xpc_api_misuse` crash on iOS 26+ in `basicvideocompositor.output` by replacing `AVVideoCompositionCoreAnimationTool` with `AVVideoComposition(asset:applyingCIFiltersWithHandler:)` and Core Image compositing in `VideoExporter`.
@@ -126,6 +126,19 @@ object PhotoExporter {
126
126
  ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
127
127
  ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.postScale(-1f, 1f)
128
128
  ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.postScale(1f, -1f)
129
+ // Mirrored-and-rotated: what a front-facing camera writes for a portrait
130
+ // shot (CameraX marks the capture as reversed-horizontal, which combines
131
+ // with the 90/270 sensor rotation into TRANSPOSE/TRANSVERSE rather than
132
+ // a plain ROTATE_90/270). Falling through to `else` here left those
133
+ // photos unrotated in the editor.
134
+ ExifInterface.ORIENTATION_TRANSPOSE -> {
135
+ matrix.postRotate(90f)
136
+ matrix.postScale(-1f, 1f)
137
+ }
138
+ ExifInterface.ORIENTATION_TRANSVERSE -> {
139
+ matrix.postRotate(270f)
140
+ matrix.postScale(-1f, 1f)
141
+ }
129
142
  else -> return bitmap
130
143
  }
131
144
  return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
@@ -112,6 +112,19 @@ class PhotoEditSession(private val context: Context, sourceUri: String, maxPrevi
112
112
  ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
113
113
  ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.postScale(-1f, 1f)
114
114
  ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.postScale(1f, -1f)
115
+ // Mirrored-and-rotated: what a front-facing camera writes for a portrait
116
+ // shot (CameraX marks the capture as reversed-horizontal, which combines
117
+ // with the 90/270 sensor rotation into TRANSPOSE/TRANSVERSE rather than
118
+ // a plain ROTATE_90/270). Falling through to `else` here left those
119
+ // photos unrotated in the editor.
120
+ ExifInterface.ORIENTATION_TRANSPOSE -> {
121
+ matrix.postRotate(90f)
122
+ matrix.postScale(-1f, 1f)
123
+ }
124
+ ExifInterface.ORIENTATION_TRANSVERSE -> {
125
+ matrix.postRotate(270f)
126
+ matrix.postScale(-1f, 1f)
127
+ }
115
128
  else -> return decoded
116
129
  }
117
130
  return Bitmap.createBitmap(decoded, 0, 0, decoded.width, decoded.height, matrix, true)
@@ -1556,7 +1556,7 @@ final class PhotoVideoEditorViewController: UIViewController {
1556
1556
  let adjustments = session.adjustments
1557
1557
  let layers = session.layerStack.layers
1558
1558
  var options = exportOptions
1559
- if let selectedPhotoExportFormat { options["imageFormat"] = selectedPhotoExportFormat }
1559
+ options["imageFormat"] = selectedPhotoExportFormat ?? "png"
1560
1560
  let sourceUri = uri
1561
1561
  DispatchQueue.global(qos: .userInitiated).async { [weak self] in
1562
1562
  do {
@@ -2628,8 +2628,7 @@ final class PhotoVideoEditorViewController: UIViewController {
2628
2628
  fillColor: primaryColor,
2629
2629
  textColor: onPrimaryColor
2630
2630
  ) { [weak self] in
2631
- guard let self else { return }
2632
- if self.mediaType == "photo" { self.showPhotoExportConfiguration() } else { self.doneEditor() }
2631
+ self?.doneEditor()
2633
2632
  }
2634
2633
  header.addArrangedSubview(done)
2635
2634
 
@@ -2716,26 +2715,6 @@ final class PhotoVideoEditorViewController: UIViewController {
2716
2715
  schedulePhotoPreviewRender()
2717
2716
  }
2718
2717
 
2719
- private func showPhotoExportConfiguration() {
2720
- let sheet = UIAlertController(
2721
- title: "Export & Share",
2722
- message: "Choose an output format. Quality and size limits continue to use your export settings.",
2723
- preferredStyle: .actionSheet
2724
- )
2725
- [("JPEG", "jpeg"), ("PNG", "png"), ("WebP", "webp")].forEach { label, format in
2726
- sheet.addAction(UIAlertAction(title: label, style: .default) { [weak self] _ in
2727
- self?.selectedPhotoExportFormat = format
2728
- self?.doneEditor()
2729
- })
2730
- }
2731
- sheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
2732
- if let popover = sheet.popoverPresentationController {
2733
- popover.sourceView = view
2734
- popover.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.maxY - 1, width: 1, height: 1)
2735
- }
2736
- present(sheet, animated: true)
2737
- }
2738
-
2739
2718
  /// Coalesces slider and drag callbacks and performs rendering on a dedicated background queue
2740
2719
  /// so the main thread remains at 120 FPS without slider hitching or dragging lag.
2741
2720
  private func schedulePhotoPreviewRender() {
@@ -2796,6 +2775,7 @@ final class PhotoVideoEditorViewController: UIViewController {
2796
2775
 
2797
2776
  private func doneEditor() {
2798
2777
  if mediaType == "photo" {
2778
+ selectedPhotoExportFormat = "png"
2799
2779
  exportPhoto()
2800
2780
  } else {
2801
2781
  exportVideo()
@@ -52,6 +52,8 @@ final class OnlineStickerSheetViewController: UIViewController {
52
52
  view.backgroundColor = DesignTokens.elevatedPanel
53
53
  buildUI()
54
54
  loadLocalStickers()
55
+ rebuildCategoryChips()
56
+ applyFilter()
55
57
  }
56
58
 
57
59
  override func viewDidDisappear(_ animated: Bool) {
@@ -187,9 +189,6 @@ final class OnlineStickerSheetViewController: UIViewController {
187
189
  ?? hostBundle.url(forResource: "PhotoVideoEditorStickers", withExtension: "bundle")
188
190
  guard let bundleURL, let bundle = Bundle(url: bundleURL) else { return }
189
191
  localStickerNames = (bundle.urls(forResourcesWithExtension: "png", subdirectory: nil) ?? []).map(\.lastPathComponent).sorted()
190
- displayedLocalStickerNames = localStickerNames
191
- rebuildCategoryChips()
192
- applyFilter()
193
192
  }
194
193
 
195
194
  private func localStickerURL(named name: String) -> URL? {
@@ -294,12 +293,14 @@ extension OnlineStickerSheetViewController: UICollectionViewDataSource, UICollec
294
293
  let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Self.cellReuseID, for: indexPath) as! BundledStickerCell
295
294
  if indexPath.item < displayedRuntimeStickers.count {
296
295
  let sticker = displayedRuntimeStickers[indexPath.item]
296
+ cell.representedStickerURI = sticker.uri
297
297
  cell.configure(image: nil)
298
- loadRuntimeSticker(sticker) { [weak collectionView] _, image in
299
- guard collectionView?.indexPath(for: cell) == indexPath else { return }
298
+ loadRuntimeSticker(sticker) { [weak cell] _, image in
299
+ guard let cell, cell.representedStickerURI == sticker.uri else { return }
300
300
  cell.configure(image: image)
301
301
  }
302
302
  } else {
303
+ cell.representedStickerURI = nil
303
304
  let name = displayedLocalStickerNames[indexPath.item - displayedRuntimeStickers.count]
304
305
  let image = localStickerURL(named: name).flatMap { UIImage(contentsOfFile: $0.path) }
305
306
  cell.configure(image: image)
@@ -330,6 +331,7 @@ extension OnlineStickerSheetViewController: UICollectionViewDataSource, UICollec
330
331
 
331
332
  /// One grid cell for a bundled sticker image.
332
333
  private final class BundledStickerCell: UICollectionViewCell {
334
+ var representedStickerURI: String?
333
335
  private let imageView = UIImageView()
334
336
 
335
337
  override init(frame: CGRect) {
@@ -356,6 +358,7 @@ private final class BundledStickerCell: UICollectionViewCell {
356
358
 
357
359
  override func prepareForReuse() {
358
360
  super.prepareForReuse()
361
+ representedStickerURI = nil
359
362
  imageView.image = nil
360
363
  }
361
364
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-photo-video-editor",
3
- "version": "0.3.2",
3
+ "version": "0.3.5",
4
4
  "description": "Native photo and video editor for React Native, powered by Kotlin and Swift.",
5
5
  "main": "./lib/module/index.js",
6
6
  "module": "./lib/module/index.js",