【问题标题】:How to get two views to be the same width and height using CGAffineTransform如何使用 CGAffineTransform 使两个视图具有相同的宽度和高度
【发布时间】:2021-01-18 15:36:40
【问题描述】:

如果我想获得 2 个具有相同宽度和高度且它们的中心都位于屏幕中间的视图,我可以使用以下代码,它可以正常工作。两者并排在屏幕中间,宽度和高度完全相同。

let width = view.frame.width
let insideRect = CGRect(x: 0, y: 0, width: width / 2, height: .infinity)
let rect = AVMakeRect(aspectRatio: CGSize(width: 9, height: 16), insideRect: insideRect)

// blue
leftView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
leftView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
leftView.widthAnchor.constraint(equalToConstant: rect.width).isActive = true
leftView.heightAnchor.constraint(equalToConstant: rect.height).isActive = true

// purple
rightView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
rightView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
rightView.widthAnchor.constraint(equalToConstant: leftView.widthAnchor).isActive = true
rightView.heightAnchor.constraint(equalToConstant: leftView.heightAnchor).isActive = true

如何使用CGAffineTransform 做同样的事情?我试图找到一种方法使 rightView 与左视图的大小相同,但不能。 leftView 框架的顶部位于屏幕中间而不是其中心,而 rightView 完全关闭。

let width = view.frame.width
let insideRect = CGRect(x: 0, y: 0, width: width / 2, height: .infinity)
let rect = AVMakeRect(aspectRatio: CGSize(width: 9, height: 16), insideRect: insideRect)

leftView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
leftView.transform = CGAffineTransform(translationX: 0, y: view.frame.height / 2)

rightView.transform = leftView.transform
rightView.transform = CGAffineTransform(translationX: rect.width, y: view.frame.height / 2)

【问题讨论】:

  • 不太清楚...首先,您可以在没有任何矩形计算的约束条件下获得所需的结果。至于尝试使用CGAffineTransform...您如何创建/添加leftViewrightViewview?他们的框架是什么?
  • 我正在使用矩形计算,因为视图是基于视频的,我需要确保比例保持不变。左视图和右视图的屏幕宽度和高度大小相同。
  • 仍然令人困惑...您是否有不想想要使用约束的原因?而且,您确定要使用转换后的视图而不是单个视图的两层吗?
  • 你在这里看到这个问题stackoverflow.com/questions/34682816/…。我想出了如何做整个事情。我遇到的唯一问题是将 2 个图像并排放置在屏幕中间。我必须在每条指令上设置转换。这是很多代码。我认为询问如何将变换设置为等于 w/h 并位于屏幕中心会更容易,而不是进入所有 AVFoundation 代码。我在屏幕上都有两个图像,它们只是没有正确定位或相同的 w/h
  • 好的 - 你的问题在这种情况下更有意义。你想要video1 在左边还是右边?

标签: ios swift cgaffinetransform


【解决方案1】:

您需要根据合成视频的输出大小 - .renderSize 进行转换。

根据您的其他问题...

因此,如果您有两个 1280.0 x 720.0 视频,并且您希望它们在 640 x 480 渲染帧中并排显示,您需要:

  • 获取第一个视频的大小
  • 缩放到320 x 480
  • 将其移至0, 0

然后:

  • 获取第二个视频的大小
  • 缩放到320 x 480
  • 将其移至320, 0

所以你的比例变换将是:

let targetWidth = renderSize.width / 2.0
let targetHeight = renderSize.height
let widthScale = targetWidth / sourceVideoSize.width
let heightScale = targetHeight / sourceVideoSize.height

let scale = CGAffineTransform(scaleX: widthScale, y: heightScale)

这应该可以让你到达那里 --- 除了...

在我的测试中,我拍摄了 4 个 8 秒的横向视频。

由于我不知道的原因 - “本地”preferredTransforms 是:

Videos 1 & 3
[-1, 0, 0, -1, 1280, 720]

Videos 2 & 4
[1, 0, 0, 1, 0, 0]

因此,推荐的track.naturalSize.applying(track.preferredTransform) 返回的大小最终为:

Videos 1 & 3
-1280 x -720

Videos 2 & 4
1280 x 720

这与转换混淆。

经过一点实验,如果大小为负数,我们需要:

  • 旋转变换
  • 缩放变换(确保使用正宽度/高度)
  • 翻译为方向变化调整的变换

这是一个完整的实现(最后没有保存到磁盘):

import UIKit
import AVFoundation

class VideoViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemYellow
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        guard let originalVideoURL1 = Bundle.main.url(forResource: "video1", withExtension: "mov"),
              let originalVideoURL2 = Bundle.main.url(forResource: "video2", withExtension: "mov")
        else { return }

        let firstAsset = AVURLAsset(url: originalVideoURL1)
        let secondAsset = AVURLAsset(url: originalVideoURL2)

        let mixComposition = AVMutableComposition()
        
        guard let firstTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) else { return }
        let timeRange1 = CMTimeRangeMake(start: .zero, duration: firstAsset.duration)

        do {
            try firstTrack.insertTimeRange(timeRange1, of: firstAsset.tracks(withMediaType: .video)[0], at: .zero)
        } catch {
            return
        }

        guard let secondTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) else { return }
        let timeRange2 = CMTimeRangeMake(start: .zero, duration: secondAsset.duration)

        do {
            try secondTrack.insertTimeRange(timeRange2, of: secondAsset.tracks(withMediaType: .video)[0], at: .zero)
        } catch {
            return
        }
        
        let mainInstruction = AVMutableVideoCompositionInstruction()
        
        mainInstruction.timeRange = CMTimeRangeMake(start: .zero, duration: CMTimeMaximum(firstAsset.duration, secondAsset.duration))
        
        var track: AVAssetTrack!
        
        track = firstAsset.tracks(withMediaType: .video).first
        
        let firstSize = track.naturalSize.applying(track.preferredTransform)

        track = secondAsset.tracks(withMediaType: .video).first

        let secondSize = track.naturalSize.applying(track.preferredTransform)

        // debugging
        print("firstSize:", firstSize)
        print("secondSize:", secondSize)

        let renderSize = CGSize(width: 640, height: 480)
        
        var scale: CGAffineTransform!
        var move: CGAffineTransform!

        let firstLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: firstTrack)
        
        scale = .identity
        move = .identity
        
        if (firstSize.width < 0) {
            scale = CGAffineTransform(rotationAngle: .pi)
        }
        scale = scale.scaledBy(x: abs(renderSize.width / 2.0 / firstSize.width), y: abs(renderSize.height / firstSize.height))
        move = CGAffineTransform(translationX: 0, y: 0)
        if (firstSize.width < 0) {
            move = CGAffineTransform(translationX: renderSize.width / 2.0, y: renderSize.height)
        }

        firstLayerInstruction.setTransform(scale.concatenating(move), at: .zero)

        let secondLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: secondTrack)
        
        scale = .identity
        move = .identity
        
        if (secondSize.width < 0) {
            scale = CGAffineTransform(rotationAngle: .pi)
        }
        scale = scale.scaledBy(x: abs(renderSize.width / 2.0 / secondSize.width), y: abs(renderSize.height / secondSize.height))
        move = CGAffineTransform(translationX: renderSize.width / 2.0, y: 0)
        if (secondSize.width < 0) {
            move = CGAffineTransform(translationX: renderSize.width, y: renderSize.height)
        }
        
        secondLayerInstruction.setTransform(scale.concatenating(move), at: .zero)
        
        mainInstruction.layerInstructions = [firstLayerInstruction, secondLayerInstruction]
        
        let mainCompositionInst = AVMutableVideoComposition()
        mainCompositionInst.instructions = [mainInstruction]
        mainCompositionInst.frameDuration = CMTime(value: 1, timescale: 30)
        mainCompositionInst.renderSize = renderSize

        let newPlayerItem = AVPlayerItem(asset: mixComposition)
        newPlayerItem.videoComposition = mainCompositionInst
        
        let player = AVPlayer(playerItem: newPlayerItem)
        let playerLayer = AVPlayerLayer(player: player)

        playerLayer.frame = view.bounds
        view.layer.addSublayer(playerLayer)
        player.seek(to: .zero)
        player.play()
        
        // video export code goes here...

    }

}

对于前置/后置摄像头、镜像等,preferredTransforms 也可能不同。但我会让你自己解决。

编辑

示例项目位于:https://github.com/DonMag/VideoTest

制作(使用两个720 x 1280 视频剪辑):

【讨论】:

  • 感谢帮助!!!给我大约 1/2 的时间来完成这个。
  • 我只是 c+p 代码。它们是侧面的,但它们是全尺寸的,这意味着它们占据了整个屏幕。所以左侧在屏幕的一半处,在屏幕的一半处。右边也是一样。
  • @LanceSamaria - 嗯...我创建了一个新项目并从我的答案中复制/粘贴了代码。在我的答案中添加了屏幕截图。您可以在此处获取该项目自己尝试:github.com/DonMag/VideoTest ...(我相信它可能您会看到不同的结果)
  • 哦,哇,你更新了,就像我给你发消息一样。 -1280 x -720 我得到了 -1080 x 1920。我修复它的方式是scale = CGAffineTransform(rotationAngle: .pi / 2),然后是move = CGAffineTransform(translationX: renderSize.width, y: 0)。如果不将y 设置为零,则合成图像位于屏幕底部。我不知道。不过谢谢你。这足以让我进一步挖掘。如果可以的话,我会请你吃午饭。我真的很感激这一点!顺便说一句,好看的狗!!!
  • @LanceSamaria - 是的...我最后的原始答案行:“preferredTransforms 也可能不同...” 抬起丑陋的脑袋。未经测试(我目前没有一个视频大小为-+ 在另一个),我想说的方向是评估宽度和高度&lt; 0 独立并适当调整xy 缩放/平移值。
猜你喜欢
  • 2011-06-10
  • 1970-01-01
  • 1970-01-01
  • 2015-08-24
  • 1970-01-01
  • 2017-03-31
  • 1970-01-01
  • 2016-09-14
  • 1970-01-01
相关资源
最近更新 更多