Skip to content

Commit

Permalink
Merge pull request #35 from pedroSG94/feature/view-filter
Browse files Browse the repository at this point in the history
Feature/view filter
  • Loading branch information
pedroSG94 authored Aug 9, 2024
2 parents 48b6db2 + 9f5f024 commit 02e787b
Show file tree
Hide file tree
Showing 13 changed files with 273 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ import Foundation
import CoreImage

public protocol BaseFilterRender {
func draw(image: CIImage) -> CIImage
func draw(image: CIImage, orientation: CGImagePropertyOrientation) -> CIImage
}
97 changes: 97 additions & 0 deletions RootEncoder/Sources/RootEncoder/encoder/input/metal/Sprite.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// File.swift
//
//
// Created by Pedro on 9/8/24.
//

import Foundation


public class Sprite {

private var positionX: Double = 0
private var positionY: Double = 0
private var scaleX: Double = 100
private var scaleY: Double = 100
private var rotation: Double = 0

public func translateTo(translation: TranslateTo) {
switch translation {
case .CENTER:
positionX = (100 - scaleX) / 2
positionY = (100 - scaleY) / 2
case .LEFT:
positionX = 0
positionY = (100 - scaleY) / 2
case .RIGHT:
positionX = 100 - scaleX
positionY = (100 - scaleY) / 2
case .TOP:
positionX = (100 - scaleX) / 2
positionY = 0
case .BOTTOM:
positionX = (100 - scaleX) / 2
positionY = 100 - scaleY
case .TOP_LEFT:
positionX = 0
positionY = 0
case .TOP_RIGHT:
positionX = 100 - scaleX
positionY = 0
case .BOTTOM_LEFT:
positionX = 0
positionY = 100 - scaleY
case .BOTTOM_RIGHT:
positionX = 100 - scaleX
positionY = 100 - scaleY
}
}

public func reset() {
positionX = 0
positionY = 0
scaleX = 100
scaleY = 100
rotation = 0
}

public func getCalculatedScale(image: CGRect, filter: CGRect) -> CGSize {
let filterWidth = filter.width
let filterHeight = filter.height

let imageWidth = image.width
let imageHeight = image.height

let scaleX = imageWidth / filterWidth
let scaleY = imageHeight / filterHeight

let resultX = scaleX / (100 / self.scaleX)
let resultY = scaleY / (100 / self.scaleY)
return CGSize(width: resultX, height: resultY)
}

public func getCalculatedPosition(image: CGRect, filter: CGRect) -> CGSize {
let resultX: Double = image.width * positionX / 100
let resultY: Double = (image.height - image.height * (scaleY / 100)) - (image.height * positionY / 100)
return CGSize(width: resultX, height: resultY)
}

public func getCalculatedRotation() -> Double {
return self.rotation * .pi / 180
}

public func setPosition(x: Double, y: Double) {
positionX = x
positionY = y
}

public func setScale(x: Double, y: Double) {
scaleX = x
scaleY = y
}

public func setRotation(rotation: Double) {
self.rotation = rotation
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class GreyScaleFilterRender: BaseFilterRender {

public init() {}

public func draw(image: CIImage) -> CIImage {
public func draw(image: CIImage, orientation: CGImagePropertyOrientation) -> CIImage {
filter?.setValue(image, forKey: kCIInputImageKey)
filter?.setValue(CIColor(red: 0.75, green: 0.75, blue: 0.75), forKey: kCIInputColorKey)
filter?.setValue(1.0, forKey: kCIInputIntensityKey)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class SepiaFilterRender: BaseFilterRender {

public init() {}

public func draw(image: CIImage) -> CIImage {
public func draw(image: CIImage, orientation: CGImagePropertyOrientation) -> CIImage {
filter?.setValue(image, forKey: kCIInputImageKey)
filter?.setValue(1.0, forKey: kCIInputIntensityKey)
return filter?.outputImage ?? image
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// ViewFilterRender.swift
//
//
// Created by Pedro on 4/8/24.
//

import Foundation
import CoreImage
import SwiftUI

public class ViewFilterRender: BaseFilterRender {

private let view: UIView
private let sprite = Sprite()

public init(view: UIView) {
self.view = view
}

public func draw(image: CIImage, orientation: CGImagePropertyOrientation) -> CIImage {
let filterView = toCIImage(view: view)
guard let filterView = filterView else { return image }

let scale = sprite.getCalculatedScale(image: image.extent, filter: filterView.extent)
let position = sprite.getCalculatedPosition(image: image.extent, filter: filterView.extent)
let rotation = sprite.getCalculatedRotation()

let scaled = filterView
.transformed(by: CGAffineTransform(scaleX: scale.width, y: scale.height))
.transformed(by: CGAffineTransform(rotationAngle: rotation))
.transformed(by: CGAffineTransform(translationX: position.width, y: position.height))
return scaled.composited(over: image)
}

private func toCIImage(view: UIView) -> CIImage? {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, 0)
view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let image = image else { return nil }
return CIImage(image: image)
}

public func setScale(percentX: Double, percentY: Double) {
sprite.setScale(x: percentX, y: percentY)
}

public func setPosition(percentX: Double, percentY: Double) {
sprite.setPosition(x: percentX, y: percentY)
}

public func translateTo(translation: TranslateTo) {
sprite.translateTo(translation: translation)
}

public func setRotation(rotation: Double) {
sprite.setRotation(rotation: rotation)
}
}
21 changes: 21 additions & 0 deletions RootEncoder/Sources/RootEncoder/encoder/utils/TranslateTo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// File.swift
//
//
// Created by Pedro on 9/8/24.
//

import Foundation


public enum TranslateTo {
case CENTER
case LEFT
case RIGHT
case TOP
case BOTTOM
case TOP_LEFT
case TOP_RIGHT
case BOTTOM_LEFT
case BOTTOM_RIGHT
}
5 changes: 3 additions & 2 deletions RootEncoder/Sources/RootEncoder/library/view/MetalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,15 @@ extension MetalView: MTKViewDelegate {
//this image will be modified acording with filters
var streamImage = CIImage(cvPixelBuffer: image)

let orientation: CGImagePropertyOrientation = SizeCalculator.processMatrix(initialOrientation: self.initialOrientation)

//apply filters
for filter in filters {
streamImage = filter.draw(image: streamImage)
streamImage = filter.draw(image: streamImage, orientation: orientation)
}

var w = streamImage.extent.width
var h = streamImage.extent.height
let orientation: CGImagePropertyOrientation = SizeCalculator.processMatrix(initialOrientation: self.initialOrientation)

let rotated = drawableSize.width > drawableSize.height && h > w
|| drawableSize.height > drawableSize.width && w > h
Expand Down
8 changes: 8 additions & 0 deletions app.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
0441384C2501A02500732969 /* appTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0441384B2501A02500732969 /* appTests.swift */; };
044138572501A02500732969 /* appUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 044138562501A02500732969 /* appUITests.swift */; };
659B73BF320E14A5662BBE0E /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = 659B7C7C942D059E7C4F1424 /* .gitignore */; };
EB2D9C3C2C66BCD500FF1C95 /* ViewFilter.xib in Resources */ = {isa = PBXBuildFile; fileRef = EB2D9C3B2C66BCD500FF1C95 /* ViewFilter.xib */; };
EBA400102ABA6A1600249638 /* MainSwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA4000F2ABA6A1600249638 /* MainSwiftUIView.swift */; };
EBA400122ABA7AED00249638 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA400112ABA7AED00249638 /* ToastView.swift */; };
EBA4FFD32ABA5C1300249638 /* RtmpSwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA4FFD22ABA5C1300249638 /* RtmpSwiftUIView.swift */; };
Expand All @@ -20,6 +21,7 @@
EBC318B62B9E79BF003C7526 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC318B52B9E79BF003C7526 /* Utils.swift */; };
EBC8FD302C09214600345CFC /* ScreenSwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC8FD2F2C09214600345CFC /* ScreenSwiftUIView.swift */; };
EBDA58A72BEE915E002F85A2 /* RootEncoder in Frameworks */ = {isa = PBXBuildFile; productRef = EBDA58A62BEE915E002F85A2 /* RootEncoder */; };
EBE05D492C6034A100C57A4D /* FilterUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE05D482C6034A100C57A4D /* FilterUIView.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -64,6 +66,7 @@
044138582501A02500732969 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
659B7C7C942D059E7C4F1424 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = file.gitignore; path = .gitignore; sourceTree = "<group>"; };
EB07EC4B2B0D51FD0001309D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
EB2D9C3B2C66BCD500FF1C95 /* ViewFilter.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ViewFilter.xib; sourceTree = "<group>"; };
EB733D882C2FE3B1002318DA /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
EB94C7272BDC2286002CCC0B /* RootEncoder */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = RootEncoder; sourceTree = "<group>"; };
EBA4000F2ABA6A1600249638 /* MainSwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSwiftUIView.swift; sourceTree = "<group>"; };
Expand All @@ -73,6 +76,7 @@
EBA4FFD62ABA652100249638 /* RtspSwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RtspSwiftUIView.swift; sourceTree = "<group>"; };
EBC318B52B9E79BF003C7526 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
EBC8FD2F2C09214600345CFC /* ScreenSwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenSwiftUIView.swift; sourceTree = "<group>"; };
EBE05D482C6034A100C57A4D /* FilterUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterUIView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -164,6 +168,8 @@
children = (
EBA400112ABA7AED00249638 /* ToastView.swift */,
EBA4FFD42ABA62E500249638 /* CameraUIView.swift */,
EBE05D482C6034A100C57A4D /* FilterUIView.swift */,
EB2D9C3B2C66BCD500FF1C95 /* ViewFilter.xib */,
);
path = components;
sourceTree = "<group>";
Expand Down Expand Up @@ -294,6 +300,7 @@
files = (
0441383E2501A02500732969 /* Assets.xcassets in Resources */,
659B73BF320E14A5662BBE0E /* .gitignore in Resources */,
EB2D9C3C2C66BCD500FF1C95 /* ViewFilter.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -324,6 +331,7 @@
EBA400122ABA7AED00249638 /* ToastView.swift in Sources */,
EBC318B62B9E79BF003C7526 /* Utils.swift in Sources */,
EBA4FFD32ABA5C1300249638 /* RtmpSwiftUIView.swift in Sources */,
EBE05D492C6034A100C57A4D /* FilterUIView.swift in Sources */,
EBA4FFD72ABA652100249638 /* RtspSwiftUIView.swift in Sources */,
EBA4FFD52ABA62E500249638 /* CameraUIView.swift in Sources */,
);
Expand Down
Binary file modified app/.DS_Store
Binary file not shown.
13 changes: 13 additions & 0 deletions app/RtmpSwiftUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ struct RtmpSwiftUIView: View, ConnectChecker {

var body: some View {
ZStack {
let filter = FilterUIView()
filter.edgesIgnoringSafeArea(.all)

let camera = CameraUIView()
let cameraView = camera.view
camera.edgesIgnoringSafeArea(.all)
Expand All @@ -103,6 +106,8 @@ struct RtmpSwiftUIView: View, ConnectChecker {
}
}



VStack {
HStack {
Spacer()
Expand All @@ -122,6 +127,14 @@ struct RtmpSwiftUIView: View, ConnectChecker {
}) {
Text("Sepia")
}
Button(action: {
let filterView = ViewFilterRender(view: filter.view)
rtmpCamera.metalInterface?.setFilter(baseFilterRender: filterView)
filterView.setScale(percentX: 100, percentY: 100)
filterView.translateTo(translation: .CENTER)
}) {
Text("View")
}
}
}.padding(.trailing, 16)
TextField("rtmp://ip:port/app/streamname", text: $endpoint)
Expand Down
12 changes: 12 additions & 0 deletions app/RtspSwiftUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ struct RtspSwiftUIView: View, ConnectChecker {

var body: some View {
ZStack {
let filter = FilterUIView()
filter.edgesIgnoringSafeArea(.all)

let camera = CameraUIView()
let cameraView = camera.view
camera.edgesIgnoringSafeArea(.all)
Expand Down Expand Up @@ -122,6 +125,15 @@ struct RtspSwiftUIView: View, ConnectChecker {
}) {
Text("Sepia")
}
Button(action: {
let filterView = ViewFilterRender(view: filter.view)
rtspCamera.metalInterface?.setFilter(baseFilterRender: filterView)
filterView.setScale(percentX: 100, percentY: 100)
filterView.translateTo(translation: .CENTER)

}) {
Text("View")
}
}
}.padding(.trailing, 16)
TextField("rtsp://ip:port/app/streamname", text: $endpoint)
Expand Down
24 changes: 24 additions & 0 deletions app/components/FilterUIView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// FilterUIView.swift
// app
//
// Created by Pedro on 20/9/23.
// Copyright © 2023 pedroSG94. All rights reserved.
//

import SwiftUI
import RootEncoder
import UIKit

struct FilterUIView: UIViewRepresentable {

let view = Bundle.main.loadNibNamed("ViewFilter", owner: nil, options: nil)![0] as! UIView

public func makeUIView(context: Context) -> UIView {
view.frame = .zero
return view
}

public func updateUIView(_ uiView: UIView, context: Context) {
}
}
32 changes: 32 additions & 0 deletions app/components/ViewFilter.xib
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="This is a test text" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PAI-hx-VNq">
<rect key="frame" x="132" y="416" width="129" height="20.333333333333314"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<constraints>
<constraint firstItem="PAI-hx-VNq" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="TUr-zJ-SjC"/>
<constraint firstItem="PAI-hx-VNq" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="uHb-UZ-hgh"/>
</constraints>
<point key="canvasLocation" x="90.140845070422543" y="-11.450381679389313"/>
</view>
</objects>
</document>

0 comments on commit 02e787b

Please sign in to comment.