Skip to content

Commit 02e787b

Browse files
authored
Merge pull request #35 from pedroSG94/feature/view-filter
Feature/view filter
2 parents 48b6db2 + 9f5f024 commit 02e787b

File tree

13 files changed

+273
-5
lines changed

13 files changed

+273
-5
lines changed

RootEncoder/Sources/RootEncoder/encoder/input/metal/BaseFilterRender.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ import Foundation
99
import CoreImage
1010

1111
public protocol BaseFilterRender {
12-
func draw(image: CIImage) -> CIImage
12+
func draw(image: CIImage, orientation: CGImagePropertyOrientation) -> CIImage
1313
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//
2+
// File.swift
3+
//
4+
//
5+
// Created by Pedro on 9/8/24.
6+
//
7+
8+
import Foundation
9+
10+
11+
public class Sprite {
12+
13+
private var positionX: Double = 0
14+
private var positionY: Double = 0
15+
private var scaleX: Double = 100
16+
private var scaleY: Double = 100
17+
private var rotation: Double = 0
18+
19+
public func translateTo(translation: TranslateTo) {
20+
switch translation {
21+
case .CENTER:
22+
positionX = (100 - scaleX) / 2
23+
positionY = (100 - scaleY) / 2
24+
case .LEFT:
25+
positionX = 0
26+
positionY = (100 - scaleY) / 2
27+
case .RIGHT:
28+
positionX = 100 - scaleX
29+
positionY = (100 - scaleY) / 2
30+
case .TOP:
31+
positionX = (100 - scaleX) / 2
32+
positionY = 0
33+
case .BOTTOM:
34+
positionX = (100 - scaleX) / 2
35+
positionY = 100 - scaleY
36+
case .TOP_LEFT:
37+
positionX = 0
38+
positionY = 0
39+
case .TOP_RIGHT:
40+
positionX = 100 - scaleX
41+
positionY = 0
42+
case .BOTTOM_LEFT:
43+
positionX = 0
44+
positionY = 100 - scaleY
45+
case .BOTTOM_RIGHT:
46+
positionX = 100 - scaleX
47+
positionY = 100 - scaleY
48+
}
49+
}
50+
51+
public func reset() {
52+
positionX = 0
53+
positionY = 0
54+
scaleX = 100
55+
scaleY = 100
56+
rotation = 0
57+
}
58+
59+
public func getCalculatedScale(image: CGRect, filter: CGRect) -> CGSize {
60+
let filterWidth = filter.width
61+
let filterHeight = filter.height
62+
63+
let imageWidth = image.width
64+
let imageHeight = image.height
65+
66+
let scaleX = imageWidth / filterWidth
67+
let scaleY = imageHeight / filterHeight
68+
69+
let resultX = scaleX / (100 / self.scaleX)
70+
let resultY = scaleY / (100 / self.scaleY)
71+
return CGSize(width: resultX, height: resultY)
72+
}
73+
74+
public func getCalculatedPosition(image: CGRect, filter: CGRect) -> CGSize {
75+
let resultX: Double = image.width * positionX / 100
76+
let resultY: Double = (image.height - image.height * (scaleY / 100)) - (image.height * positionY / 100)
77+
return CGSize(width: resultX, height: resultY)
78+
}
79+
80+
public func getCalculatedRotation() -> Double {
81+
return self.rotation * .pi / 180
82+
}
83+
84+
public func setPosition(x: Double, y: Double) {
85+
positionX = x
86+
positionY = y
87+
}
88+
89+
public func setScale(x: Double, y: Double) {
90+
scaleX = x
91+
scaleY = y
92+
}
93+
94+
public func setRotation(rotation: Double) {
95+
self.rotation = rotation
96+
}
97+
}

RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/GreyScaleFilterRender.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class GreyScaleFilterRender: BaseFilterRender {
1414

1515
public init() {}
1616

17-
public func draw(image: CIImage) -> CIImage {
17+
public func draw(image: CIImage, orientation: CGImagePropertyOrientation) -> CIImage {
1818
filter?.setValue(image, forKey: kCIInputImageKey)
1919
filter?.setValue(CIColor(red: 0.75, green: 0.75, blue: 0.75), forKey: kCIInputColorKey)
2020
filter?.setValue(1.0, forKey: kCIInputIntensityKey)

RootEncoder/Sources/RootEncoder/encoder/input/metal/filter/SepiaFilterRender.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class SepiaFilterRender: BaseFilterRender {
1414

1515
public init() {}
1616

17-
public func draw(image: CIImage) -> CIImage {
17+
public func draw(image: CIImage, orientation: CGImagePropertyOrientation) -> CIImage {
1818
filter?.setValue(image, forKey: kCIInputImageKey)
1919
filter?.setValue(1.0, forKey: kCIInputIntensityKey)
2020
return filter?.outputImage ?? image
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// ViewFilterRender.swift
3+
//
4+
//
5+
// Created by Pedro on 4/8/24.
6+
//
7+
8+
import Foundation
9+
import CoreImage
10+
import SwiftUI
11+
12+
public class ViewFilterRender: BaseFilterRender {
13+
14+
private let view: UIView
15+
private let sprite = Sprite()
16+
17+
public init(view: UIView) {
18+
self.view = view
19+
}
20+
21+
public func draw(image: CIImage, orientation: CGImagePropertyOrientation) -> CIImage {
22+
let filterView = toCIImage(view: view)
23+
guard let filterView = filterView else { return image }
24+
25+
let scale = sprite.getCalculatedScale(image: image.extent, filter: filterView.extent)
26+
let position = sprite.getCalculatedPosition(image: image.extent, filter: filterView.extent)
27+
let rotation = sprite.getCalculatedRotation()
28+
29+
let scaled = filterView
30+
.transformed(by: CGAffineTransform(scaleX: scale.width, y: scale.height))
31+
.transformed(by: CGAffineTransform(rotationAngle: rotation))
32+
.transformed(by: CGAffineTransform(translationX: position.width, y: position.height))
33+
return scaled.composited(over: image)
34+
}
35+
36+
private func toCIImage(view: UIView) -> CIImage? {
37+
UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0)
38+
view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
39+
let image = UIGraphicsGetImageFromCurrentImageContext()
40+
UIGraphicsEndImageContext()
41+
guard let image = image else { return nil }
42+
return CIImage(image: image)
43+
}
44+
45+
public func setScale(percentX: Double, percentY: Double) {
46+
sprite.setScale(x: percentX, y: percentY)
47+
}
48+
49+
public func setPosition(percentX: Double, percentY: Double) {
50+
sprite.setPosition(x: percentX, y: percentY)
51+
}
52+
53+
public func translateTo(translation: TranslateTo) {
54+
sprite.translateTo(translation: translation)
55+
}
56+
57+
public func setRotation(rotation: Double) {
58+
sprite.setRotation(rotation: rotation)
59+
}
60+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// File.swift
3+
//
4+
//
5+
// Created by Pedro on 9/8/24.
6+
//
7+
8+
import Foundation
9+
10+
11+
public enum TranslateTo {
12+
case CENTER
13+
case LEFT
14+
case RIGHT
15+
case TOP
16+
case BOTTOM
17+
case TOP_LEFT
18+
case TOP_RIGHT
19+
case BOTTOM_LEFT
20+
case BOTTOM_RIGHT
21+
}

RootEncoder/Sources/RootEncoder/library/view/MetalView.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,15 @@ extension MetalView: MTKViewDelegate {
144144
//this image will be modified acording with filters
145145
var streamImage = CIImage(cvPixelBuffer: image)
146146

147+
let orientation: CGImagePropertyOrientation = SizeCalculator.processMatrix(initialOrientation: self.initialOrientation)
148+
147149
//apply filters
148150
for filter in filters {
149-
streamImage = filter.draw(image: streamImage)
151+
streamImage = filter.draw(image: streamImage, orientation: orientation)
150152
}
151153

152154
var w = streamImage.extent.width
153155
var h = streamImage.extent.height
154-
let orientation: CGImagePropertyOrientation = SizeCalculator.processMatrix(initialOrientation: self.initialOrientation)
155156

156157
let rotated = drawableSize.width > drawableSize.height && h > w
157158
|| drawableSize.height > drawableSize.width && w > h

app.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
0441384C2501A02500732969 /* appTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0441384B2501A02500732969 /* appTests.swift */; };
1313
044138572501A02500732969 /* appUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 044138562501A02500732969 /* appUITests.swift */; };
1414
659B73BF320E14A5662BBE0E /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = 659B7C7C942D059E7C4F1424 /* .gitignore */; };
15+
EB2D9C3C2C66BCD500FF1C95 /* ViewFilter.xib in Resources */ = {isa = PBXBuildFile; fileRef = EB2D9C3B2C66BCD500FF1C95 /* ViewFilter.xib */; };
1516
EBA400102ABA6A1600249638 /* MainSwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA4000F2ABA6A1600249638 /* MainSwiftUIView.swift */; };
1617
EBA400122ABA7AED00249638 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA400112ABA7AED00249638 /* ToastView.swift */; };
1718
EBA4FFD32ABA5C1300249638 /* RtmpSwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA4FFD22ABA5C1300249638 /* RtmpSwiftUIView.swift */; };
@@ -20,6 +21,7 @@
2021
EBC318B62B9E79BF003C7526 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC318B52B9E79BF003C7526 /* Utils.swift */; };
2122
EBC8FD302C09214600345CFC /* ScreenSwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC8FD2F2C09214600345CFC /* ScreenSwiftUIView.swift */; };
2223
EBDA58A72BEE915E002F85A2 /* RootEncoder in Frameworks */ = {isa = PBXBuildFile; productRef = EBDA58A62BEE915E002F85A2 /* RootEncoder */; };
24+
EBE05D492C6034A100C57A4D /* FilterUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE05D482C6034A100C57A4D /* FilterUIView.swift */; };
2325
/* End PBXBuildFile section */
2426

2527
/* Begin PBXContainerItemProxy section */
@@ -64,6 +66,7 @@
6466
044138582501A02500732969 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
6567
659B7C7C942D059E7C4F1424 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = file.gitignore; path = .gitignore; sourceTree = "<group>"; };
6668
EB07EC4B2B0D51FD0001309D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
69+
EB2D9C3B2C66BCD500FF1C95 /* ViewFilter.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ViewFilter.xib; sourceTree = "<group>"; };
6770
EB733D882C2FE3B1002318DA /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
6871
EB94C7272BDC2286002CCC0B /* RootEncoder */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = RootEncoder; sourceTree = "<group>"; };
6972
EBA4000F2ABA6A1600249638 /* MainSwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSwiftUIView.swift; sourceTree = "<group>"; };
@@ -73,6 +76,7 @@
7376
EBA4FFD62ABA652100249638 /* RtspSwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RtspSwiftUIView.swift; sourceTree = "<group>"; };
7477
EBC318B52B9E79BF003C7526 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
7578
EBC8FD2F2C09214600345CFC /* ScreenSwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenSwiftUIView.swift; sourceTree = "<group>"; };
79+
EBE05D482C6034A100C57A4D /* FilterUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterUIView.swift; sourceTree = "<group>"; };
7680
/* End PBXFileReference section */
7781

7882
/* Begin PBXFrameworksBuildPhase section */
@@ -164,6 +168,8 @@
164168
children = (
165169
EBA400112ABA7AED00249638 /* ToastView.swift */,
166170
EBA4FFD42ABA62E500249638 /* CameraUIView.swift */,
171+
EBE05D482C6034A100C57A4D /* FilterUIView.swift */,
172+
EB2D9C3B2C66BCD500FF1C95 /* ViewFilter.xib */,
167173
);
168174
path = components;
169175
sourceTree = "<group>";
@@ -294,6 +300,7 @@
294300
files = (
295301
0441383E2501A02500732969 /* Assets.xcassets in Resources */,
296302
659B73BF320E14A5662BBE0E /* .gitignore in Resources */,
303+
EB2D9C3C2C66BCD500FF1C95 /* ViewFilter.xib in Resources */,
297304
);
298305
runOnlyForDeploymentPostprocessing = 0;
299306
};
@@ -324,6 +331,7 @@
324331
EBA400122ABA7AED00249638 /* ToastView.swift in Sources */,
325332
EBC318B62B9E79BF003C7526 /* Utils.swift in Sources */,
326333
EBA4FFD32ABA5C1300249638 /* RtmpSwiftUIView.swift in Sources */,
334+
EBE05D492C6034A100C57A4D /* FilterUIView.swift in Sources */,
327335
EBA4FFD72ABA652100249638 /* RtspSwiftUIView.swift in Sources */,
328336
EBA4FFD52ABA62E500249638 /* CameraUIView.swift in Sources */,
329337
);

app/.DS_Store

0 Bytes
Binary file not shown.

app/RtmpSwiftUIView.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ struct RtmpSwiftUIView: View, ConnectChecker {
8585

8686
var body: some View {
8787
ZStack {
88+
let filter = FilterUIView()
89+
filter.edgesIgnoringSafeArea(.all)
90+
8891
let camera = CameraUIView()
8992
let cameraView = camera.view
9093
camera.edgesIgnoringSafeArea(.all)
@@ -103,6 +106,8 @@ struct RtmpSwiftUIView: View, ConnectChecker {
103106
}
104107
}
105108

109+
110+
106111
VStack {
107112
HStack {
108113
Spacer()
@@ -122,6 +127,14 @@ struct RtmpSwiftUIView: View, ConnectChecker {
122127
}) {
123128
Text("Sepia")
124129
}
130+
Button(action: {
131+
let filterView = ViewFilterRender(view: filter.view)
132+
rtmpCamera.metalInterface?.setFilter(baseFilterRender: filterView)
133+
filterView.setScale(percentX: 100, percentY: 100)
134+
filterView.translateTo(translation: .CENTER)
135+
}) {
136+
Text("View")
137+
}
125138
}
126139
}.padding(.trailing, 16)
127140
TextField("rtmp://ip:port/app/streamname", text: $endpoint)

0 commit comments

Comments
 (0)