iOS Image Overlay Swift¶
Пример масштабирования публикуемого изображения и добавления PNG картинки¶
Данный пример демонстрирует масштабирование публикуемого изображения (увеличение и уменьшение щипком двумя пальцами), а также наложение картинки из галереи устройства.
На скриншоте ниже в поток добавлена картинка с указанием ее размеров и положения в кадре.
Поля ввода:
WCS URL
- адрес WCS сервераw
- ширина накладываемой картинкиh
- высота накладываемой картинкиx
- положение верхнего левого угла картинки в кадре по горизонтальной осиy
- положение верхнего левого угла картинки в кадре по вертикальной осиSelect image
- кнопка выбора картинки из галереи
Картинка и ее расположение могут меняться на лету, во время публикации.
Работа с кодом примера¶
Для разбора кода возьмем версию примера ImageOverlaySwift, которая доступна для скачивания на GitHub:
ImageOverlayViewController
- класс основного вида приложения (файл имплементации ImageOverlayViewController.swift)CameraVideoCapturer
- класс, реализующий захват и обработку видео (файл имплементации CameraVideoCapturer.swift)
1. Импорт API¶
2. Инициализация класса для захвата и обработки видео¶
3. Создание сессии и подключение к серверу¶
WCSSession
, WCSSession.connect
code
В параметрах сессии указываются:
- URL WCS-сервера
- имя серверного REST hook приложения
defaultApp
@IBAction func connectPressed(_ sender: Any) {
changeViewState(connectButton, false)
if (connectButton.title(for: .normal) == "CONNECT") {
if (session == nil) {
let options = FPWCSApi2SessionOptions()
options.urlServer = urlField.text
options.appKey = "defaultApp"
do {
try session = WCSSession(options)
} catch {
print(error)
}
}
...
changeViewState(urlField, false)
session?.connect()
} else {
session?.disconnect()
}
}
4. Публикация видеопотока¶
WCSSession.createStream
, WCSStream.publish
code
Методу createStream
передаются параметры:
- имя публикуемого потока
- вид для локального отображения
- объект для захвата видео
@IBAction func publishPressed(_ sender: Any) {
changeViewState(publishButton,false)
if (publishButton.title(for: .normal) == "PUBLISH") {
let options = FPWCSApi2StreamOptions()
options.name = publishName.text
options.display = localDisplay.videoView
options.constraints = FPWCSApi2MediaConstraints(audio: true, videoCapturer: capturer);
do {
publishStream = try session!.createStream(options)
} catch {
print(error);
}
...
do {
try publishStream?.publish()
capturer.startCapture()
} catch {
print(error);
}
} else {
do {
try publishStream?.stop();
} catch {
print(error);
}
}
}
5. Воспроизведение видеопотока¶
WCSSession.createStream
, WCSStream.play
code
Методу createStream
передаются параметры:
- имя воспроизводимого потока
- вид для отображения потока
@IBAction func playPressed(_ sender: Any) {
changeViewState(playButton,false)
if (playButton.title(for: .normal) == "PLAY") {
let options = FPWCSApi2StreamOptions()
options.name = playName.text;
options.display = remoteDisplay.videoView;
do {
playStream = try session!.createStream(options)
} catch {
print(error)
}
...
do {
try playStream?.play()
} catch {
print(error);
}
} else{
do {
try playStream?.stop();
} catch {
print(error);
}
}
}
6. Остановка воспроизведения видеопотока¶
WCSStream.stop
code
@IBAction func playPressed(_ sender: Any) {
changeViewState(playButton,false)
if (playButton.title(for: .normal) == "PLAY") {
...
} else{
do {
try playStream?.stop();
} catch {
print(error);
}
}
}
7. Остановка публикации видеопотока¶
WCSStream.stop
code
@IBAction func publishPressed(_ sender: Any) {
changeViewState(publishButton,false)
if (publishButton.title(for: .normal) == "PUBLISH") {
...
} else {
do {
try publishStream?.stop();
} catch {
print(error);
}
}
}
8. Вызов функции масштабирования видео щипком по виду для локального отображения потока¶
@IBAction func pinchOnLocalDisplay(_ sender: UIPinchGestureRecognizer) {
if sender.state == .changed {
self.capturer.scale(velocity: sender.velocity)
}
}
9. Выбор картинки из галереи и вызов функции наложения картинки¶
@IBAction func selectImagePressed(_ sender: Any) {
imagePicker.allowsEditing = false
imagePicker.sourceType = .photoLibrary
DispatchQueue.main.async {
self.present(self.imagePicker, animated: true, completion: nil)
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let image = info[.originalImage] as? UIImage else {
return;
}
selectedImage = image
imageView.image = selectedImage
updateOverlayImage()
DispatchQueue.main.async {
picker.dismiss(animated: true, completion: nil)
}
}
10. Масштабирование выбранной картинки, задание ее координат и вызов функции наложения картинки¶
func updateOverlayImage() {
if let selectedImage = selectedImage {
let resizeImage = resize((selectedImage.cgImage)!, selectedImage.imageOrientation)
let overlayImage = CIImage.init(cgImage: (resizeImage)!)
let overX = CGFloat(Int(overlayX.text ?? "0") ?? 0)
let overY = CGFloat(Int(overlayY.text ?? "0") ?? 0)
let movedImage = overlayImage.oriented(.left).transformed(by: CGAffineTransform(translationX: overY, y: overX))
capturer.updateOverlayImage(movedImage)
} else {
capturer.overlayImage = nil
return
}
}
11. Реализация масштабирования видео¶
func scale(velocity: CGFloat) {
guard let device = self.device else { return }
let maxZoomFactor = device.activeFormat.videoMaxZoomFactor
let pinchVelocityDividerFactor: CGFloat = 15
do {
try device.lockForConfiguration()
defer { device.unlockForConfiguration() }
let desiredZoomFactor = device.videoZoomFactor + atan2(velocity, pinchVelocityDividerFactor)
device.videoZoomFactor = max(1.0, min(desiredZoomFactor, maxZoomFactor))
} catch {
print(error)
}
}
12. Реализация наложения картинки¶
let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
...
if (overlayImage != nil) {
let inputImage = CIImage.init(cvImageBuffer: pixelBuffer!);
let combinedFilter = CIFilter(name: "CISourceOverCompositing")!
combinedFilter.setValue(inputImage, forKey: "inputBackgroundImage")
combinedFilter.setValue(overlayImage, forKey: "inputImage")
let outputImage = combinedFilter.outputImage!
let tmpcontext = CIContext(options: nil)
tmpcontext.render(outputImage, to: pixelBuffer!, bounds: outputImage.extent, colorSpace: CGColorSpaceCreateDeviceRGB())
}