Skip to content

Commit cb22543

Browse files
committed
feat(camera): improve the camera options in editor and fix some bugs around that topic
1 parent 7b81c12 commit cb22543

File tree

9 files changed

+170
-74
lines changed

9 files changed

+170
-74
lines changed

Reframed/Compositor/CameraVideoCompositor.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ final class CameraVideoCompositor: NSObject, AVVideoCompositing, @unchecked Send
289289
)
290290
context.saveGState()
291291
context.addPath(borderPath)
292-
context.setFillColor(CGColor(red: 1, green: 1, blue: 1, alpha: 0.3))
292+
context.setFillColor(instruction.cameraBorderColor)
293293
context.fillPath()
294294
context.restoreGState()
295295

Reframed/Compositor/CompositionInstruction.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ final class CompositionInstruction: NSObject, AVVideoCompositionInstructionProto
1313
let cameraRect: CGRect?
1414
let cameraCornerRadius: CGFloat
1515
let cameraBorderWidth: CGFloat
16+
let cameraBorderColor: CGColor
1617
let videoShadow: CGFloat
1718
let cameraShadow: CGFloat
1819
let cameraMirrored: Bool
@@ -45,6 +46,7 @@ final class CompositionInstruction: NSObject, AVVideoCompositionInstructionProto
4546
cameraRect: CGRect?,
4647
cameraCornerRadius: CGFloat,
4748
cameraBorderWidth: CGFloat = 0,
49+
cameraBorderColor: CGColor = CGColor(srgbRed: 1, green: 1, blue: 1, alpha: 0.3),
4850
videoShadow: CGFloat = 0,
4951
cameraShadow: CGFloat = 0,
5052
cameraMirrored: Bool = false,
@@ -74,6 +76,7 @@ final class CompositionInstruction: NSObject, AVVideoCompositionInstructionProto
7476
self.cameraRect = cameraRect
7577
self.cameraCornerRadius = cameraCornerRadius
7678
self.cameraBorderWidth = cameraBorderWidth
79+
self.cameraBorderColor = cameraBorderColor
7780
self.videoShadow = videoShadow
7881
self.cameraShadow = cameraShadow
7982
self.cameraMirrored = cameraMirrored

Reframed/Compositor/VideoCompositor.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ enum VideoCompositor {
2727
videoCornerRadius: CGFloat = 0,
2828
cameraCornerRadius: CGFloat = 12,
2929
cameraBorderWidth: CGFloat = 0,
30+
cameraBorderColor: CodableColor = CodableColor(r: 1, g: 1, b: 1, a: 0.3),
3031
videoShadow: CGFloat = 0,
3132
cameraShadow: CGFloat = 0,
3233
cameraMirrored: Bool = false,
@@ -202,6 +203,7 @@ enum VideoCompositor {
202203
return min(scaledW, scaledH) * (cameraCornerRadius / 100.0)
203204
}(),
204205
cameraBorderWidth: cameraBorderWidth * (renderSize.width / canvasSize.width),
206+
cameraBorderColor: cameraBorderColor.cgColor,
205207
videoShadow: videoShadow,
206208
cameraShadow: cameraShadow,
207209
cameraMirrored: cameraMirrored,

Reframed/Editor/EditorState.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ final class EditorState {
9393
var cameraAspect: CameraAspect = .original
9494
var cameraCornerRadius: CGFloat = 8
9595
var cameraBorderWidth: CGFloat = 0
96+
var cameraBorderColor: CodableColor = CodableColor(r: 1, g: 1, b: 1, a: 0.3)
9697
var videoShadow: CGFloat = 0
9798
var cameraShadow: CGFloat = 0
9899
var cameraMirrored: Bool = false
@@ -162,6 +163,7 @@ final class EditorState {
162163
self.cameraAspect = saved.cameraAspect ?? .original
163164
self.cameraCornerRadius = saved.cameraCornerRadius
164165
self.cameraBorderWidth = saved.cameraBorderWidth
166+
self.cameraBorderColor = saved.cameraBorderColor ?? CodableColor(r: 1, g: 1, b: 1, a: 0.3)
165167
self.videoShadow = saved.videoShadow ?? 0
166168
self.cameraShadow = saved.cameraShadow ?? 0
167169
self.cameraMirrored = saved.cameraMirrored ?? false
@@ -483,21 +485,23 @@ final class EditorState {
483485

484486
func setCameraCorner(_ corner: CameraCorner) {
485487
let margin: CGFloat = 0.02
488+
let canvas = canvasSize(for: result.screenSize)
489+
let marginY = margin * canvas.width / max(canvas.height, 1)
486490
let relH = cameraRelativeHeight
487491

488492
switch corner {
489493
case .topLeft:
490494
cameraLayout.relativeX = margin
491-
cameraLayout.relativeY = margin
495+
cameraLayout.relativeY = marginY
492496
case .topRight:
493497
cameraLayout.relativeX = 1.0 - cameraLayout.relativeWidth - margin
494-
cameraLayout.relativeY = margin
498+
cameraLayout.relativeY = marginY
495499
case .bottomLeft:
496500
cameraLayout.relativeX = margin
497-
cameraLayout.relativeY = 1.0 - relH - margin
501+
cameraLayout.relativeY = 1.0 - relH - marginY
498502
case .bottomRight:
499503
cameraLayout.relativeX = 1.0 - cameraLayout.relativeWidth - margin
500-
cameraLayout.relativeY = 1.0 - relH - margin
504+
cameraLayout.relativeY = 1.0 - relH - marginY
501505
}
502506
}
503507

@@ -595,6 +599,7 @@ final class EditorState {
595599
videoCornerRadius: videoCornerRadius,
596600
cameraCornerRadius: cameraCornerRadius,
597601
cameraBorderWidth: cameraBorderWidth,
602+
cameraBorderColor: cameraBorderColor,
598603
videoShadow: videoShadow,
599604
cameraShadow: cameraShadow,
600605
cameraMirrored: cameraMirrored,
@@ -724,6 +729,7 @@ final class EditorState {
724729
cameraAspect: cameraAspect,
725730
cameraCornerRadius: cameraCornerRadius,
726731
cameraBorderWidth: cameraBorderWidth,
732+
cameraBorderColor: cameraBorderColor,
727733
videoShadow: videoShadow,
728734
cameraShadow: cameraShadow,
729735
cameraMirrored: cameraMirrored,
@@ -886,6 +892,7 @@ final class EditorState {
886892
_ = self.cameraAspect
887893
_ = self.cameraCornerRadius
888894
_ = self.cameraBorderWidth
895+
_ = self.cameraBorderColor
889896
_ = self.videoShadow
890897
_ = self.cameraShadow
891898
_ = self.cameraMirrored

Reframed/Editor/EditorView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ struct EditorView: View {
218218
cameraAspect: editorState.cameraAspect,
219219
cameraCornerRadius: editorState.cameraCornerRadius,
220220
cameraBorderWidth: editorState.cameraBorderWidth,
221+
cameraBorderColor: editorState.cameraBorderColor.cgColor,
221222
videoShadow: editorState.videoShadow,
222223
cameraShadow: editorState.cameraShadow,
223224
cameraMirrored: editorState.cameraMirrored,

Reframed/Editor/PropertiesPanel+CameraTab.swift

Lines changed: 98 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -3,86 +3,118 @@ import SwiftUI
33
extension PropertiesPanel {
44
var cameraSection: some View {
55
VStack(alignment: .leading, spacing: Layout.itemSpacing) {
6-
sectionHeader(icon: "pip", title: "Camera")
6+
sectionHeader(icon: "web.camera", title: "Camera")
77

88
toggleRow("Enabled", isOn: $editorState.webcamEnabled)
99

10-
Group {
11-
toggleRow("Mirror", isOn: $editorState.cameraMirrored)
10+
toggleRow("Mirror", isOn: $editorState.cameraMirrored)
11+
.disabled(!editorState.webcamEnabled)
12+
.opacity(editorState.webcamEnabled ? 1 : 0.5)
13+
}
14+
}
15+
16+
var cameraPositionSection: some View {
17+
VStack(alignment: .leading, spacing: Layout.itemSpacing) {
18+
sectionHeader(icon: "arrow.up.and.down.and.arrow.left.and.right", title: "Position")
1219

13-
HStack(spacing: 4) {
14-
ForEach(
15-
Array(
16-
zip(
17-
[CameraCorner.topLeft, .topRight, .bottomLeft, .bottomRight],
18-
["arrow.up.left", "arrow.up.right", "arrow.down.left", "arrow.down.right"]
19-
)
20-
),
21-
id: \.1
22-
) { corner, icon in
23-
Button {
24-
editorState.setCameraCorner(corner)
25-
} label: {
26-
Image(systemName: icon)
27-
.font(.system(size: 11))
28-
.frame(width: 28, height: 28)
29-
.background(ReframedColors.fieldBackground)
30-
.clipShape(RoundedRectangle(cornerRadius: 4))
31-
}
32-
.buttonStyle(.plain)
33-
.foregroundStyle(ReframedColors.primaryText)
20+
HStack(spacing: 4) {
21+
ForEach(
22+
Array(
23+
zip(
24+
[CameraCorner.topLeft, .topRight, .bottomLeft, .bottomRight],
25+
["arrow.up.left", "arrow.up.right", "arrow.down.left", "arrow.down.right"]
26+
)
27+
),
28+
id: \.1
29+
) { corner, icon in
30+
Button {
31+
editorState.setCameraCorner(corner)
32+
} label: {
33+
Image(systemName: icon)
34+
.font(.system(size: 11))
35+
.frame(width: 28, height: 28)
36+
.background(ReframedColors.fieldBackground)
37+
.clipShape(RoundedRectangle(cornerRadius: 4))
3438
}
39+
.buttonStyle(.plain)
40+
.foregroundStyle(ReframedColors.primaryText)
3541
}
42+
}
43+
}
44+
.disabled(!editorState.webcamEnabled)
45+
.opacity(editorState.webcamEnabled ? 1 : 0.5)
46+
}
3647

37-
cameraAspectSection
48+
var cameraAspectRatioSection: some View {
49+
VStack(alignment: .leading, spacing: Layout.itemSpacing) {
50+
sectionHeader(icon: "aspectratio", title: "Aspect Ratio")
3851

39-
SliderRow(
40-
label: "Size",
41-
value: $editorState.cameraLayout.relativeWidth,
42-
range: 0.1...editorState.maxCameraRelativeWidth,
43-
step: 0.01
44-
)
45-
.onChange(of: editorState.cameraLayout.relativeWidth) { _, _ in
46-
editorState.clampCameraPosition()
52+
Picker("", selection: $editorState.cameraAspect) {
53+
ForEach(CameraAspect.allCases) { aspect in
54+
Text(aspect.label).tag(aspect)
4755
}
48-
49-
SliderRow(
50-
label: "Radius",
51-
value: $editorState.cameraCornerRadius,
52-
range: 0...50,
53-
formattedValue: "\(Int(editorState.cameraCornerRadius))%"
54-
)
55-
56-
SliderRow(
57-
label: "Border",
58-
value: $editorState.cameraBorderWidth,
59-
range: 0...10,
60-
step: 0.5,
61-
formattedValue: String(format: "%.1f", editorState.cameraBorderWidth)
62-
)
63-
64-
SliderRow(
65-
label: "Shadow",
66-
value: $editorState.cameraShadow,
67-
range: 0...100,
68-
formattedValue: "\(Int(editorState.cameraShadow))"
69-
)
7056
}
71-
.disabled(!editorState.webcamEnabled)
72-
.opacity(editorState.webcamEnabled ? 1 : 0.5)
57+
.pickerStyle(.segmented)
58+
.labelsHidden()
59+
.onChange(of: editorState.cameraAspect) { _, _ in
60+
editorState.clampCameraPosition()
61+
}
7362
}
63+
.disabled(!editorState.webcamEnabled)
64+
.opacity(editorState.webcamEnabled ? 1 : 0.5)
7465
}
7566

76-
private var cameraAspectSection: some View {
77-
Picker("", selection: $editorState.cameraAspect) {
78-
ForEach(CameraAspect.allCases) { aspect in
79-
Text(aspect.label).tag(aspect)
67+
var cameraStyleSection: some View {
68+
VStack(alignment: .leading, spacing: Layout.itemSpacing) {
69+
sectionHeader(icon: "paintbrush", title: "Style")
70+
71+
SliderRow(
72+
label: "Size",
73+
value: $editorState.cameraLayout.relativeWidth,
74+
range: 0.1...editorState.maxCameraRelativeWidth,
75+
step: 0.01
76+
)
77+
.onChange(of: editorState.cameraLayout.relativeWidth) { _, _ in
78+
editorState.clampCameraPosition()
8079
}
80+
81+
SliderRow(
82+
label: "Radius",
83+
value: $editorState.cameraCornerRadius,
84+
range: 0...50,
85+
formattedValue: "\(Int(editorState.cameraCornerRadius))%"
86+
)
87+
88+
SliderRow(
89+
label: "Shadow",
90+
value: $editorState.cameraShadow,
91+
range: 0...100,
92+
formattedValue: "\(Int(editorState.cameraShadow))"
93+
)
94+
95+
SliderRow(
96+
label: "Border",
97+
value: $editorState.cameraBorderWidth,
98+
range: 0...30,
99+
step: 0.5,
100+
formattedValue: String(format: "%.1f", editorState.cameraBorderWidth)
101+
)
102+
103+
borderColorPickerButton
81104
}
82-
.pickerStyle(.segmented)
83-
.labelsHidden()
84-
.onChange(of: editorState.cameraAspect) { _, _ in
85-
editorState.clampCameraPosition()
86-
}
105+
.disabled(!editorState.webcamEnabled)
106+
.opacity(editorState.webcamEnabled ? 1 : 0.5)
107+
}
108+
109+
private var borderColorPickerButton: some View {
110+
let currentName =
111+
TailwindColors.all.first { $0.color == editorState.cameraBorderColor }?.name ?? "White"
112+
return TailwindColorPicker(
113+
displayColor: Color(cgColor: editorState.cameraBorderColor.cgColor),
114+
displayName: currentName,
115+
isPresented: $showBorderColorPopover,
116+
isSelected: { $0.color == editorState.cameraBorderColor },
117+
onSelect: { editorState.cameraBorderColor = $0.color }
118+
)
87119
}
88120
}

Reframed/Editor/PropertiesPanel.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ struct PropertiesPanel: View {
2121
@State var selectedColorId: String? = "Black"
2222
@State private var editingProjectName: String = ""
2323
@State var showClickColorPopover = false
24+
@State var showBorderColorPopover = false
2425
@FocusState private var projectNameFocused: Bool
2526

2627
var body: some View {
@@ -38,6 +39,9 @@ struct PropertiesPanel: View {
3839
backgroundSection
3940
case .camera:
4041
cameraSection
42+
cameraPositionSection
43+
cameraAspectRatioSection
44+
cameraStyleSection
4145
case .audio:
4246
audioSection
4347
case .cursor:

0 commit comments

Comments
 (0)