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 +4 -0
- package/android/src/main/java/com/photovideoeditor/photo/export/PhotoExporter.kt +13 -0
- package/android/src/main/java/com/photovideoeditor/photo/render/PhotoEditSession.kt +13 -0
- package/ios/Editor/PhotoVideoEditorViewController.swift +3 -23
- package/ios/Stickers/OnlineStickerSheetViewController.swift +8 -5
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
|
299
|
-
guard
|
|
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