代码之家  ›  专栏  ›  技术社区  ›  Makalele

将矩阵转换为CGaffinetransform

  •  1
  • Makalele  · 技术社区  · 6 年前

    我已经在Android上开发了一个应用程序,你可以在屏幕上添加图片,然后在周围移动/旋转/缩放它们。从技术上讲,我有一个固定的方面父布局,其中包含这些图像(图像视图)。它们设置了宽度和高度以匹配父级。然后,我可以将他们的位置保存在服务器上,稍后我可以将其加载到相同或其他设备上。基本上,我只保存矩阵值。但是,平移X/Y有绝对值(以像素为单位),但屏幕大小不同,因此我将其设置为百分比值(因此平移X除以父级宽度,平移Y除以父级高度)。加载图像时,我只需将转换x/y乘以父布局的宽度/高度,即使在不同的设备上,所有内容看起来都一样。

    我是这样做的:

    float[] values = new float[9];
    parentLayout.matrix.getValues(values);
    
    values[Matrix.MTRANS_X] /= parentLayout.getWidth();
    values[Matrix.MTRANS_Y] /= parentLayout.getHeight();
    

    然后在加载函数中,我简单地将这些值相乘。

    图像操作是通过使用后平移/后缩放/后旋转和定位点(对于2指手势,它是这些手指之间的中点)来完成的。

    现在,我想将这个应用程序移植到iOS,并使用类似的技术在iOS设备上实现正确的图像位置。CGaffinetTransform与矩阵类非常相似。字段的顺序不同,但它们的工作方式似乎相同。

    假设这个数组是来自Android应用程序的示例值数组:

    let a: [CGFloat] = [0.35355338, -0.35355338, 0.44107446, 0.35355338, 0.35355338, 0.058058262]
    

    我不保存最后3个字段,因为它们总是0、0和1,而在iOS上,它们甚至无法设置(文档还说明它是[0、0、1])。这就是“转换”的样子:

    let tx = a[2] * width //multiply percentage translation x with parent width
    let ty = a[5] * height //multiply percentage translation y with parent height
    let transform = CGAffineTransform(a: a[0], b: a[3], c: a[1], d: a[4], tx: tx, ty: ty)
    

    然而,这只适用于非常简单的情况。对于其他的图像通常是错位的,甚至是完全混乱。

    我注意到,默认情况下,在Android上,我在[0,0]中有定位点,但在iOS上,它是图像的中心。

    例如:

    float midX = parentLayout.getWidth() / 2.0f;
    float midY = parentLayout.getHeight() / 2.0f;
    
    parentLayout.matrix.postScale(0.5f, 0.5f, midX, midY);
    

    给出以下矩阵:

    0.5, 0.0, 360.0, 
    0.0, 0.5, 240.0, 
    0.0, 0.0, 1.0
    

    [360.0,240.0]只是来自左上角的向量。

    但是,在iOS上,我不需要提供中间点,因为它已经围绕中心进行了转换:

    let transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
    

    它给了我:

    CGAffineTransform(a: 0.5, b: 0.0, c: 0.0, d: 0.5, tx: 0.0, ty: 0.0)
    

    我尝试在iOS上设置不同的锚定点:

    parentView.layer.anchorPoint = CGPoint(x: 0, y: 0)
    

    但是,我不能得到正确的结果。我试过用-width*0.5、-height*0.5、scale和translate-back进行翻译,但结果和Android不一样。

    简而言之:我如何将矩阵从Android修改为cgaffinettransform从iOS来实现相同的外观?

    我还附上尽可能简单的演示项目。目标是在iOS项目中将输出数组从android复制到“a”数组中,并修改cgaffinettransform计算(和/或parentview设置),使图像在两个平台上看起来都一样,无论android的日志输出是什么。

    安卓: https://github.com/piotrros/MatrixAndroid

    网间网操作系统: https://github.com/piotrros/MatrixIOS

    编辑:

    @Sulthan解决方案非常有效!尽管如此,我还是提出了一个关于逆转过程的子问题,这也是我需要的。在他的回答中,我尝试将其融入到我的应用程序中:

    let savedTransform = CGAffineTransform.identity
            .concatenating(CGAffineTransform(translationX: -width / 2, y: -height / 2))
            .concatenating(
                self.transform
            )
            .concatenating(CGAffineTransform(translationX: width / 2, y: height / 2))
    
    let tx = savedTransform.tx
    let ty = savedTransform.ty
    
    let t = CGAffineTransform(a: savedTransform.a, b: savedTransform.b, c: savedTransform.c, d: savedTransform.d, tx: tx / cWidth, ty: ty / cHeight)
    
    placement.transform = [Float(t.a), Float(t.c), Float(t.tx), Float(t.b), Float(t.d), Float(t.ty), 0, 0, 1]
    

    Placement只是一个用来存放矩阵“android”版本的类。cwidth/cheight是父级的宽度/高度。我将tx/ty除以它们得到宽度/高度的百分比,就像我在Android上做的那样。

    但是,它不起作用。Tx/Ty错误。

    输入:

    [0.2743883,-0.16761175,0.43753636,0.16761175,0.2743883, 0.40431038,0.0,0.0,1.0]

    它还给我们:

    [0.2743883,-0.16761175,0.18834576,0.16761175,0.2743883,0.2182884, 0、0、1

    所以除了Tx/Ty以外的一切都很好。错误在哪里?

    2 回复  |  直到 6 年前
        1
  •  1
  •   Sulthan    6 年前

    imageView

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
    
        imageView.translatesAutoresizingMaskIntoConstraints = true
        imageView.bounds = parentView.bounds
        imageView.layer.anchorPoint = .zero
        imageView.center = .zero
    
        let width = parentView.frame.width
        let height = parentView.frame.height
    
        let a: [CGFloat] = [0.35355338, -0.35355338, 0.44107446, 0.35355338, 0.35355338, 0.058058262]
    
        let tx = a[2] * width
        let ty = a[5] * height
        let transform = CGAffineTransform(a: a[0], b: a[3], c: a[1], d: a[4], tx: tx, ty: ty)
    
        imageView.transform = transform
    }
    

    anchorPoint

    imageView.bounds = parentView.bounds // set correct size
    imageView.layer.anchorPoint = .zero // anchor point to top-left corner
    imageView.center = .zero // set position, "center" is now the top-left corner
    

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
    
        let width = parentView.frame.width
        let height = parentView.frame.height
    
        let a: [CGFloat] = [0.35355338, -0.35355338, 0.44107446, 0.35355338, 0.35355338, 0.058058262]
    
        let tx = a[2] * width
        let ty = a[5] * height
        let savedTransform = CGAffineTransform(a: a[0], b: a[3], c: a[1], d: a[4], tx: tx, ty: ty)
    
        let transform = CGAffineTransform.identity
            .translatedBy(x: width / 2, y: height / 2)
            .concatenating(savedTransform)
            .concatenating(CGAffineTransform(translationX: -width / 2, y: -height / 2))
    
        imageView.transform = transform
    }
    

    CGAffineTransform

    .concatenating(CGAffineTransform(translationX: -width / 2, y: -height / 2))
    

    .translatedBy(x: -width / 2, y: -height / 2)
    

    iOS

    let savedTransform = CGAffineTransform.identity
        .translatedBy(x: width / 2, y: height / 2)
        .scaledBy(x: 0.5, y: 0.5)
        .rotated(by: .pi / 4)
        .translatedBy(x: -width / 2, y: -height / 2)
    

    let savedTransform = CGAffineTransform.identity
        .concatenating(CGAffineTransform(translationX: -width / 2, y: -height / 2))
        .concatenating(
            CGAffineTransform.identity
                .scaledBy(x: 0.5, y: 0.5)
                .rotated(by: .pi / 4)
        )
        .concatenating(CGAffineTransform(translationX: width / 2, y: height / 2))
    

    func androidValuesToIOSTransform() -> CGAffineTransform{
        let width = parentView.frame.width
        let height = parentView.frame.height
    
        let androidValues: [CGFloat] = [0.35355338, -0.35355338, 0.44107446, 0.35355338, 0.35355338, 0.058058262]
        let androidTransform = CGAffineTransform(
            a: androidValues[0], b: androidValues[3],
            c: androidValues[1], d: androidValues[4],
            tx: androidValues[2] * width, ty: androidValues[5] * height
        )
    
        let iOSTransform = CGAffineTransform.identity
            .concatenating(CGAffineTransform(translationX: width / 2, y: height / 2))
            .concatenating(androidTransform)
            .concatenating(CGAffineTransform(translationX: -width / 2, y: -height / 2))
    
        return iOSTransform
    }
    
    func iOSTransformToAndroidValues() -> [CGFloat] {
        let width = parentView.frame.width
        let height = parentView.frame.height
    
        let iOSTransform = CGAffineTransform.identity
            .scaledBy(x: 0.5, y: 0.5)
            .rotated(by: .pi / 4)
        let androidTransform = CGAffineTransform.identity
            .concatenating(CGAffineTransform(translationX: -width / 2, y: -height / 2))
            .concatenating(iOSTransform)
            .concatenating(CGAffineTransform(translationX: width / 2, y: height / 2))
    
        let androidValues = [
            androidTransform.a, androidTransform.c, androidTransform.tx / width,
            androidTransform.b, androidTransform.d, androidTransform.ty / height
        ]
        return androidValues
    }
    
        2
  •  0
  •   E.Coms    6 年前

    class ViewController: UIViewController {
    
    @IBOutlet weak var parentView: UIView!
    @IBOutlet weak var imageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
    
    }
    
    
    
    
    let width =  UIScreen.main.bounds.width
    let height = UIScreen.main.bounds.width * 2.0 / 3.0
    
    
    var zoom: CGFloat = 1.0
    var rotate: CGFloat = 0.0
    
     var locX: CGFloat = 0.0
     var locY: CGFloat = 0.0
    
    @IBAction func rotate(_ sender: UISlider){  //0-6.28
    
        rotate = CGFloat(sender.value)
        parentView.transform = CGAffineTransform(rotationAngle: rotate).concatenating(CGAffineTransform(scaleX: zoom, y: zoom))
        parentView.transform.tx = (locX-0.5) * width
        parentView.transform.ty = (locY-0.5) * height
    }
    
    @IBAction func zoom(_ sender: UISlider){ //0.1 - 10
    
        zoom = CGFloat(sender.value)
        parentView.transform = CGAffineTransform(rotationAngle: rotate).concatenating(CGAffineTransform(scaleX: zoom, y: zoom))
        parentView.transform.tx = (locX-0.5) * width
        parentView.transform.ty = (locY-0.5) * height
    }
    
    @IBAction func locationX(_ sender: UISlider){  //0 - 1
    
        locX = CGFloat(sender.value)
        parentView.transform = CGAffineTransform(rotationAngle: rotate).concatenating(CGAffineTransform(scaleX: zoom, y: zoom))
        parentView.transform.tx = (locX-0.5) * width
        parentView.transform.ty = (locY-0.5) * height
    }
    
    @IBAction func locationY(_ sender: UISlider){ //0 - 1
        locY = CGFloat(sender.value)
        parentView.transform = CGAffineTransform(rotationAngle: rotate).concatenating(CGAffineTransform(scaleX: zoom, y: zoom))
        parentView.transform.tx = (locX-0.5) * width
        parentView.transform.ty = (locY-0.5) * height
    }
    
    
    
    override func viewWillAppear(_ animated: Bool) {
      let a: [CGFloat] = [0.35355338, -0.35355338, 0.44107446, 0.35355338, 0.35355338, 0.058058262]
        let tx = (a[2] - 0.5) * width
       let ty = (a[5] - 0.5) * height
    let transform =  CGAffineTransform(a: a[0], b: a[3], c: a[1], d: a[4], tx:tx, ty: ty)
    parentView.layer.anchorPoint   = CGPoint.zero
    parentView.layer.position = CGPoint.zero
    parentView.transform = transform
    
    }