react-native-photo-video-editor 0.1.1 → 0.2.1
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 +15 -0
- package/android/src/main/java/com/photovideoeditor/editor/PhotoVideoEditorActivity.kt +760 -590
- package/android/src/main/java/com/photovideoeditor/photo/export/PhotoExporter.kt +8 -23
- package/android/src/main/java/com/photovideoeditor/photo/render/CropGeometry.kt +71 -0
- package/android/src/main/java/com/photovideoeditor/photo/render/OverlayGeometry.kt +36 -0
- package/android/src/main/java/com/photovideoeditor/photo/render/PhotoEditSession.kt +5 -7
- package/android/src/main/java/com/photovideoeditor/photo/render/PhotoFilterPresets.kt +71 -41
- package/android/src/main/java/com/photovideoeditor/photo/render/PhotoLayerRenderer.kt +3 -1
- package/android/src/main/java/com/photovideoeditor/photo/render/PhotoTransformRenderer.kt +33 -0
- package/android/src/main/java/com/photovideoeditor/photo/render/PhotoTransformState.kt +6 -1
- package/android/src/main/java/com/photovideoeditor/photo/ui/CropOverlayView.kt +109 -151
- package/android/src/main/java/com/photovideoeditor/photo/ui/LayerOverlayView.kt +42 -13
- package/android/src/main/java/com/photovideoeditor/photo/ui/ZoomableImageView.kt +10 -0
- package/android/src/main/java/com/photovideoeditor/ui/components/CropPanel.kt +122 -0
- package/android/src/main/java/com/photovideoeditor/ui/components/EditorPanel.kt +250 -0
- package/android/src/main/java/com/photovideoeditor/ui/components/EditorToolSlider.kt +103 -0
- package/android/src/main/java/com/photovideoeditor/video/export/VideoExporter.kt +38 -11
- package/android/src/main/java/com/photovideoeditor/video/render/VideoCropEffects.kt +35 -0
- package/android/src/main/java/com/photovideoeditor/video/render/VideoEditSession.kt +10 -0
- package/android/src/main/java/com/photovideoeditor/video/render/VideoTransformState.kt +1 -0
- package/docs/editor-ui-validation.md +22 -0
- package/ios/Editor/PhotoVideoEditorViewController.swift +778 -443
- package/ios/Editor/UI/CropPanel.swift +97 -0
- package/ios/Editor/UI/EditorPanel.swift +334 -0
- package/ios/Editor/UI/EditorToolSlider.swift +22 -0
- package/ios/Editor/UI/TextEditorSheet.swift +84 -0
- package/ios/Photo/Export/PhotoExporter.swift +3 -3
- package/ios/Photo/Rendering/CropGeometry.swift +48 -0
- package/ios/Photo/Rendering/PhotoEditSession.swift +21 -10
- package/ios/Photo/Rendering/PhotoFilterPresets.swift +53 -23
- package/ios/Photo/Rendering/PhotoLayer.swift +9 -1
- package/ios/Photo/Rendering/PhotoLayerRenderer.swift +2 -1
- package/ios/Photo/Rendering/PhotoTransformState.swift +5 -0
- package/ios/Photo/UI/CropOverlayView.swift +96 -169
- package/ios/Photo/UI/FilterThumbnailLoader.swift +62 -0
- package/ios/Photo/UI/LayerOverlayView.swift +27 -10
- package/ios/Photo/UI/ZoomableImageView.swift +19 -1
- package/ios/Video/Export/VideoExporter.swift +10 -1
- package/ios/Video/Render/VideoCropGeometry.swift +48 -0
- package/ios/Video/Render/VideoTransformState.swift +1 -0
- package/package.json +1 -1
|
@@ -131,6 +131,10 @@ final class VideoExporter {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
let cropPlan = VideoCropGeometry.plan(size:CGSize(width:renderWidth,height:renderHeight),state:state.crop,includeCrop:true)
|
|
135
|
+
transform = transform.concatenating(cropPlan.transform)
|
|
136
|
+
renderWidth = cropPlan.size.width; renderHeight = cropPlan.size.height
|
|
137
|
+
|
|
134
138
|
let videoComposition = AVMutableVideoComposition()
|
|
135
139
|
videoComposition.renderSize = CGSize(width: renderWidth, height: renderHeight)
|
|
136
140
|
videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
|
|
@@ -145,7 +149,12 @@ final class VideoExporter {
|
|
|
145
149
|
if !layers.isEmpty {
|
|
146
150
|
let renderSize = CGSize(width: renderWidth, height: renderHeight)
|
|
147
151
|
let longestEdge = max(renderSize.width, renderSize.height)
|
|
148
|
-
|
|
152
|
+
// Geometry is independent of this cap: each overlay surface below is a full-`renderSize`
|
|
153
|
+
// CALayer with `contentsGravity = .resizeAspect`, so a smaller (same-aspect) image is scaled
|
|
154
|
+
// back up to cover the whole frame. The cap only trades sharpness for peak memory. (Android's
|
|
155
|
+
// Media3 path has no such scale-to-fit — it composites overlay textures 1:1 in pixels — which
|
|
156
|
+
// is why `VideoExporter.kt` has to restore the same invariant explicitly via OverlaySettings.)
|
|
157
|
+
let overlayScale = min(1, 2560 / max(longestEdge, 1))
|
|
149
158
|
let overlayRenderSize = CGSize(
|
|
150
159
|
width: max(2, floor(renderSize.width * overlayScale)),
|
|
151
160
|
height: max(2, floor(renderSize.height * overlayScale))
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import CoreGraphics
|
|
3
|
+
|
|
4
|
+
/// Used by AVPlayer's video composition and export's layer instruction. This is the single source
|
|
5
|
+
/// of truth `videoMediaBounds()`/`videoNaturalSize` (in `PhotoVideoEditorViewController`) both derive
|
|
6
|
+
/// their size from, so overlay-relative coordinates stay consistent between preview and the real
|
|
7
|
+
/// composed frame — do not introduce a second, independently-computed size for either of those.
|
|
8
|
+
///
|
|
9
|
+
/// Known gap (not currently reachable from the UI — the video tool rail only exposes Crop/Text/
|
|
10
|
+
/// Stickers, not a whole-video "Rotate" tool): `VideoTransformState.rotationDegrees` (the top-level
|
|
11
|
+
/// rotate field, distinct from this function's `state.rotationDegrees`, which is the CROP sub-state's
|
|
12
|
+
/// own rotation) is applied to the player view as a cosmetic `.transform` only in
|
|
13
|
+
/// `applyVideoPreviewTransform` and never passed into this function, while `VideoExporter` bakes it
|
|
14
|
+
/// into the real output frame (swapping width/height for 90/270°). If that tool is ever wired back up,
|
|
15
|
+
/// `plan()` must also account for it, or preview and export will disagree.
|
|
16
|
+
enum VideoCropGeometry {
|
|
17
|
+
static func plan(size: CGSize, state: PhotoTransformState, includeCrop: Bool) -> (transform:CGAffineTransform,size:CGSize) {
|
|
18
|
+
let quarter = CGAffineTransform(rotationAngle:CGFloat(state.rotationDegrees) * .pi/180)
|
|
19
|
+
let oriented = CGRect(origin:.zero,size:size).applying(quarter)
|
|
20
|
+
let w = abs(oriented.width).rounded(), h = abs(oriented.height).rounded()
|
|
21
|
+
var transform = quarter.concatenating(CGAffineTransform(translationX:-oriented.minX,y:-oriented.minY))
|
|
22
|
+
if state.straightenDegrees != 0 {
|
|
23
|
+
let scale = CropGeometry.fillScale(width:w,height:h,degrees:state.straightenDegrees)
|
|
24
|
+
transform = transform.concatenating(CGAffineTransform(translationX:-w/2,y:-h/2))
|
|
25
|
+
.concatenating(CGAffineTransform(rotationAngle:state.straightenDegrees * .pi/180))
|
|
26
|
+
.concatenating(CGAffineTransform(scaleX:scale,y:scale))
|
|
27
|
+
.concatenating(CGAffineTransform(translationX:w/2,y:h/2))
|
|
28
|
+
}
|
|
29
|
+
guard includeCrop else { return (transform,CGSize(width:w,height:h)) }
|
|
30
|
+
let left = (state.cropLeft*w).rounded(), top = (state.cropTop*h).rounded()
|
|
31
|
+
let width = max(2,(state.cropRight*w).rounded()-left), height = max(2,(state.cropBottom*h).rounded()-top)
|
|
32
|
+
transform = transform.concatenating(CGAffineTransform(translationX:-left,y:-top))
|
|
33
|
+
return (transform,CGSize(width:width,height:height))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static func previewComposition(asset: AVAsset, state: PhotoTransformState, includeCrop: Bool) -> AVVideoComposition? {
|
|
37
|
+
guard let track = asset.tracks(withMediaType:.video).first else { return nil }
|
|
38
|
+
let upright = track.naturalSize.applying(track.preferredTransform)
|
|
39
|
+
let plan = plan(size:CGSize(width:abs(upright.width),height:abs(upright.height)),state:state,includeCrop:includeCrop)
|
|
40
|
+
let composition = AVMutableVideoComposition()
|
|
41
|
+
composition.renderSize = plan.size; composition.frameDuration = CMTime(value:1,timescale:30)
|
|
42
|
+
let instruction = AVMutableVideoCompositionInstruction(); instruction.timeRange = CMTimeRange(start:.zero,duration:asset.duration)
|
|
43
|
+
let layer = AVMutableVideoCompositionLayerInstruction(assetTrack:track)
|
|
44
|
+
layer.setTransform(track.preferredTransform.concatenating(plan.transform),at:.zero)
|
|
45
|
+
instruction.layerInstructions = [layer]; composition.instructions = [instruction]
|
|
46
|
+
return composition
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -8,6 +8,7 @@ import CoreGraphics
|
|
|
8
8
|
/// `AVMutableComposition` concatenating every clip — see `VideoEditSession`).
|
|
9
9
|
struct VideoTransformState: Equatable {
|
|
10
10
|
var rotationDegrees: Int = 0
|
|
11
|
+
var crop = PhotoTransformState()
|
|
11
12
|
var aspectRatio: CGFloat?
|
|
12
13
|
var coverFrameMs: Int64 = 0
|
|
13
14
|
/// Preview-only for now: cycled via the Speed tool. Export does not yet honor this — see docs/video-editor.md.
|
package/package.json
CHANGED