Transformation d'une image rectangulaire en quadrilatère à L'aide D'un CATransform3D

j'ai une image et un ensemble de quatre points (décrivant un quadrilatère Q). Je veux transformer cette image pour qu'elle corresponde au quadrilatère Q. Photoshop appelle cette transformation "distorsion"."Mais selon la source de ce quadrilatère (la perspective de l'image se déplaçant dans l'espace), c'est en fait la combinaison d'une échelle, d'une rotation et d'une matrice de perspective.

je me demande si cela est possible en utilisant une matrice CATransform3D 4x4. Vous disposez de tous des conseils sur la façon de le faire? J'ai essayé de prendre les quatre points et construire 16 équations (à partir de A '= A X u) mais cela n'a pas fonctionné: Je ne suis pas sûr de ce que je devrais utiliser comme coefficients z, z', w et w'...

l'image suivante montre ce que je veux faire:

Transforming a rectangle image into a quadrilateral using a CATransform3D

voici quelques exemples de points:

276.523, 236.438,   517.656, 208.945,   275.984, 331.285,   502.23,  292.344
261.441, 235.059,   515.09,  211.5,     263.555, 327.066,   500.734, 295
229.031, 161.277,   427.125, 192.562,   229.16, 226,        416.48,  256
37
demandé sur MonsieurDart 2012-02-27 22:49:12

7 réponses

j'ai créé un kit pour faire ceci sur iOS: https://github.com/hfossli/AGGeometryKit /


assurez-vous que votre point d'ancrage est en haut à gauche ( CGPointZero ).

+ (CATransform3D)rectToQuad:(CGRect)rect
                     quadTL:(CGPoint)topLeft
                     quadTR:(CGPoint)topRight
                     quadBL:(CGPoint)bottomLeft
                     quadBR:(CGPoint)bottomRight
{
    return [self rectToQuad:rect quadTLX:topLeft.x quadTLY:topLeft.y quadTRX:topRight.x quadTRY:topRight.y quadBLX:bottomLeft.x quadBLY:bottomLeft.y quadBRX:bottomRight.x quadBRY:bottomRight.y];
}

+ (CATransform3D)rectToQuad:(CGRect)rect
                    quadTLX:(CGFloat)x1a
                    quadTLY:(CGFloat)y1a
                    quadTRX:(CGFloat)x2a
                    quadTRY:(CGFloat)y2a
                    quadBLX:(CGFloat)x3a
                    quadBLY:(CGFloat)y3a
                    quadBRX:(CGFloat)x4a
                    quadBRY:(CGFloat)y4a
{
    CGFloat X = rect.origin.x;
    CGFloat Y = rect.origin.y;
    CGFloat W = rect.size.width;
    CGFloat H = rect.size.height;

    CGFloat y21 = y2a - y1a;
    CGFloat y32 = y3a - y2a;
    CGFloat y43 = y4a - y3a;
    CGFloat y14 = y1a - y4a;
    CGFloat y31 = y3a - y1a;
    CGFloat y42 = y4a - y2a;

    CGFloat a = -H*(x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42);
    CGFloat b = W*(x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43);
    CGFloat c = H*X*(x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42) - H*W*x1a*(x4a*y32 - x3a*y42 + x2a*y43) - W*Y*(x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43);

    CGFloat d = H*(-x4a*y21*y3a + x2a*y1a*y43 - x1a*y2a*y43 - x3a*y1a*y4a + x3a*y2a*y4a);
    CGFloat e = W*(x4a*y2a*y31 - x3a*y1a*y42 - x2a*y31*y4a + x1a*y3a*y42);
    CGFloat f = -(W*(x4a*(Y*y2a*y31 + H*y1a*y32) - x3a*(H + Y)*y1a*y42 + H*x2a*y1a*y43 + x2a*Y*(y1a - y3a)*y4a + x1a*Y*y3a*(-y2a + y4a)) - H*X*(x4a*y21*y3a - x2a*y1a*y43 + x3a*(y1a - y2a)*y4a + x1a*y2a*(-y3a + y4a)));

    CGFloat g = H*(x3a*y21 - x4a*y21 + (-x1a + x2a)*y43);
    CGFloat h = W*(-x2a*y31 + x4a*y31 + (x1a - x3a)*y42);
    CGFloat i = W*Y*(x2a*y31 - x4a*y31 - x1a*y42 + x3a*y42) + H*(X*(-(x3a*y21) + x4a*y21 + x1a*y43 - x2a*y43) + W*(-(x3a*y2a) + x4a*y2a + x2a*y3a - x4a*y3a - x2a*y4a + x3a*y4a));

    const double kEpsilon = 0.0001;

    if(fabs(i) < kEpsilon)
    {
        i = kEpsilon* (i > 0 ? 1.0 : -1.0);
    }

    CATransform3D transform = {a/i, d/i, 0, g/i, b/i, e/i, 0, h/i, 0, 0, 1, 0, c/i, f/i, 0, 1.0};

    return transform;
}

ce code n'a aucun mérite. Tout ce que j'ai fait, c'est parcourir l'internet et rassembler des réponses incomplètes.

39
répondu hfossli 2014-05-13 12:24:31

voici un exemple de projet qui applique le code de la réponse de hfossli ci-dessus et crée une catégorie sur uivi qui fixe le cadre et applique la transformation en un seul appel:

https://github.com/joshrl/FreeTransform

UIView+code quadrilatéral:

#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>

@interface UIView (Quadrilateral)

//Sets frame to bounding box of quad and applies transform
- (void)transformToFitQuadTopLeft:(CGPoint)tl topRight:(CGPoint)tr bottomLeft:(CGPoint)bl bottomRight:(CGPoint)br;

@end    

@implementation UIView (Quadrilateral)

- (void)transformToFitQuadTopLeft:(CGPoint)tl topRight:(CGPoint)tr bottomLeft:(CGPoint)bl bottomRight:(CGPoint)br
{

    NSAssert(CGPointEqualToPoint(self.layer.anchorPoint, CGPointZero),@"Anchor point must be (0,0)!");
    CGRect boundingBox = [[self class] boundingBoxForQuadTR:tr tl:tl bl:bl br:br];
    self.frame = boundingBox;

    CGPoint frameTopLeft = boundingBox.origin;
    CATransform3D transform = [[self class] rectToQuad:self.bounds
                                                quadTL:CGPointMake(tl.x-frameTopLeft.x, tl.y-frameTopLeft.y)
                                                quadTR:CGPointMake(tr.x-frameTopLeft.x, tr.y-frameTopLeft.y)
                                                quadBL:CGPointMake(bl.x-frameTopLeft.x, bl.y-frameTopLeft.y)
                                                quadBR:CGPointMake(br.x-frameTopLeft.x, br.y-frameTopLeft.y)];

    self.layer.transform = transform;

}

+ (CGRect)boundingBoxForQuadTR:(CGPoint)tr tl:(CGPoint)tl bl:(CGPoint)bl br:(CGPoint)br
{
    CGRect boundingBox = CGRectZero;

    CGFloat xmin = MIN(MIN(MIN(tr.x, tl.x), bl.x),br.x);
    CGFloat ymin = MIN(MIN(MIN(tr.y, tl.y), bl.y),br.y);
    CGFloat xmax = MAX(MAX(MAX(tr.x, tl.x), bl.x),br.x);
    CGFloat ymax = MAX(MAX(MAX(tr.y, tl.y), bl.y),br.y);

    boundingBox.origin.x = xmin;
    boundingBox.origin.y = ymin;
    boundingBox.size.width = xmax - xmin;
    boundingBox.size.height = ymax - ymin;

    return boundingBox;
}

+ (CATransform3D)rectToQuad:(CGRect)rect
                     quadTL:(CGPoint)topLeft
                     quadTR:(CGPoint)topRight
                     quadBL:(CGPoint)bottomLeft
                     quadBR:(CGPoint)bottomRight
{
    return [self rectToQuad:rect quadTLX:topLeft.x quadTLY:topLeft.y quadTRX:topRight.x quadTRY:topRight.y quadBLX:bottomLeft.x quadBLY:bottomLeft.y quadBRX:bottomRight.x quadBRY:bottomRight.y];
}

+ (CATransform3D)rectToQuad:(CGRect)rect
                    quadTLX:(CGFloat)x1a
                    quadTLY:(CGFloat)y1a
                    quadTRX:(CGFloat)x2a
                    quadTRY:(CGFloat)y2a
                    quadBLX:(CGFloat)x3a
                    quadBLY:(CGFloat)y3a
                    quadBRX:(CGFloat)x4a
                    quadBRY:(CGFloat)y4a
{
    CGFloat X = rect.origin.x;
    CGFloat Y = rect.origin.y;
    CGFloat W = rect.size.width;
    CGFloat H = rect.size.height;

    CGFloat y21 = y2a - y1a;
    CGFloat y32 = y3a - y2a;
    CGFloat y43 = y4a - y3a;
    CGFloat y14 = y1a - y4a;
    CGFloat y31 = y3a - y1a;
    CGFloat y42 = y4a - y2a;

    CGFloat a = -H*(x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42);
    CGFloat b = W*(x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43);
    CGFloat c = H*X*(x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42) - H*W*x1a*(x4a*y32 - x3a*y42 + x2a*y43) - W*Y*(x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43);

    CGFloat d = H*(-x4a*y21*y3a + x2a*y1a*y43 - x1a*y2a*y43 - x3a*y1a*y4a + x3a*y2a*y4a);
    CGFloat e = W*(x4a*y2a*y31 - x3a*y1a*y42 - x2a*y31*y4a + x1a*y3a*y42);
    CGFloat f = -(W*(x4a*(Y*y2a*y31 + H*y1a*y32) - x3a*(H + Y)*y1a*y42 + H*x2a*y1a*y43 + x2a*Y*(y1a - y3a)*y4a + x1a*Y*y3a*(-y2a + y4a)) - H*X*(x4a*y21*y3a - x2a*y1a*y43 + x3a*(y1a - y2a)*y4a + x1a*y2a*(-y3a + y4a)));

    CGFloat g = H*(x3a*y21 - x4a*y21 + (-x1a + x2a)*y43);
    CGFloat h = W*(-x2a*y31 + x4a*y31 + (x1a - x3a)*y42);
    CGFloat i = W*Y*(x2a*y31 - x4a*y31 - x1a*y42 + x3a*y42) + H*(X*(-(x3a*y21) + x4a*y21 + x1a*y43 - x2a*y43) + W*(-(x3a*y2a) + x4a*y2a + x2a*y3a - x4a*y3a - x2a*y4a + x3a*y4a));

    const double kEpsilon = 0.0001;

    if(fabs(i) < kEpsilon)
    {
        i = kEpsilon* (i > 0 ? 1.0 : -1.0);
    }

    CATransform3D transform = {a/i, d/i, 0, g/i, b/i, e/i, 0, h/i, 0, 0, 1, 0, c/i, f/i, 0, 1.0};

    return transform;
}

@end

enter image description here

9
répondu joshrl 2016-10-11 15:25:56

nous avons finalement obtenu que cela fonctionne. Nous avons essayé plusieurs méthodes, mais la plupart échouaient. Et certains récupéraient même une matrice non identitaire en donnant les mêmes points que les inputs et les outputs (par exemple, celui de KennyTM... nous avons dû manquer quelque chose là-bas).

en utilisant OpenCV comme suit, Nous obtenons un CATransform3D prêt à être utilisé sur une couche de CAAnimation:

+ (CATransform3D)transformQuadrilateral:(Quadrilateral)origin toQuadrilateral:(Quadrilateral)destination {

    CvPoint2D32f *cvsrc = [self openCVMatrixWithQuadrilateral:origin]; 
    CvMat *src_mat = cvCreateMat( 4, 2, CV_32FC1 );
    cvSetData(src_mat, cvsrc, sizeof(CvPoint2D32f));

    CvPoint2D32f *cvdst = [self openCVMatrixWithQuadrilateral:destination]; 
    CvMat *dst_mat = cvCreateMat( 4, 2, CV_32FC1 );
    cvSetData(dst_mat, cvdst, sizeof(CvPoint2D32f));

    CvMat *H = cvCreateMat(3,3,CV_32FC1);
    cvFindHomography(src_mat, dst_mat, H);
    cvReleaseMat(&src_mat); 
    cvReleaseMat(&dst_mat); 

    CATransform3D transform = [self transform3DWithCMatrix:H->data.fl]; 
    cvReleaseMat(&H); 

    return transform; 
}

+ (CvPoint2D32f *)openCVMatrixWithQuadrilateral:(Quadrilateral)origin {

    CvPoint2D32f *cvsrc = (CvPoint2D32f *)malloc(4*sizeof(CvPoint2D32f)); 
    cvsrc[0].x = origin.upperLeft.x;
    cvsrc[0].y = origin.upperLeft.y;
    cvsrc[1].x = origin.upperRight.x;
    cvsrc[1].y = origin.upperRight.y;
    cvsrc[2].x = origin.lowerRight.x;
    cvsrc[2].y = origin.lowerRight.y;
    cvsrc[3].x = origin.lowerLeft.x;
    cvsrc[3].y = origin.lowerLeft.y; 
    return cvsrc; 
}

+ (CATransform3D)transform3DWithCMatrix:(float *)matrix {
    CATransform3D transform = CATransform3DIdentity; 

    transform.m11 = matrix[0];
    transform.m21 = matrix[1];
    transform.m41 = matrix[2];

    transform.m12 = matrix[3];
    transform.m22 = matrix[4];
    transform.m42 = matrix[5];

    transform.m14 = matrix[6];
    transform.m24 = matrix[7];
    transform.m44 = matrix[8];

    return transform; 
}
8
répondu MonsieurDart 2016-07-26 09:29:02

avec 100% Merci à JoshRL, voici une version rapide de la classe de JoshRL.

Cela a été complètement et totalement débogué. Les lignes qui souffrent de la question" trop longtemps dans Swift " ont été remaniées et leur destruction testée. Il fonctionne parfaitement dans la production à haut volume.

ne peut pas être plus facile à utiliser. Exemple montrant comment utiliser rapide ci-dessous.

version Swift 2016... solution complète, de travail, de copie et de collage

// JoshQuadView in Swift
// from: https://stackoverflow.com/a/18606029/294884

// NB: JoshRL uses the ordering convention
// "topleft, topright, bottomleft, bottomright"
// which is different from "clockwise from topleft".

// Note: is not meant to handle concave.

import UIKit

class JoshQuadView:UIView
    {
    func transformToFitQuadTopLeft(tl:CGPoint,tr:CGPoint,bl:CGPoint,br:CGPoint)
        {
        guard CGPointEqualToPoint(self.layer.anchorPoint, CGPointZero) else { print("suck");return }

        let b:CGRect = boundingBoxForQuadTR(tl, tr, bl, br)
        self.frame = b
        self.layer.transform = rectToQuad( self.bounds,
            CGPointMake(tl.x-b.origin.x, tl.y-b.origin.y),
            CGPointMake(tr.x-b.origin.x, tr.y-b.origin.y),
            CGPointMake(bl.x-b.origin.x, bl.y-b.origin.y),
            CGPointMake(br.x-b.origin.x, br.y-b.origin.y) )
        }

    func boundingBoxForQuadTR(
            tl:CGPoint, _ tr:CGPoint, _ bl:CGPoint, _ br:CGPoint    )->(CGRect)
        {
        var b:CGRect = CGRectZero

        let xmin:CGFloat = min(min(min(tr.x, tl.x), bl.x),br.x);
        let ymin:CGFloat = min(min(min(tr.y, tl.y), bl.y),br.y);
        let xmax:CGFloat = max(max(max(tr.x, tl.x), bl.x),br.x);
        let ymax:CGFloat = max(max(max(tr.y, tl.y), bl.y),br.y);

        b.origin.x = xmin
        b.origin.y = ymin
        b.size.width = xmax - xmin
        b.size.height = ymax - ymin

        return b;
        }

    func rectToQuad(
            rect:CGRect,
            _ topLeft:CGPoint,
            _ topRight:CGPoint,
            _ bottomLeft:CGPoint,
            _ bottomRight:CGPoint   )->(CATransform3D)
        {
        return rectToQuad(rect,
                  topLeft.x, topLeft.y,
                  topRight.x, topRight.y,
                  bottomLeft.x, bottomLeft.y,
                  bottomRight.x, bottomRight.y)
        }


    func rectToQuad(
            rect:CGRect,
            _ x1a:CGFloat, _ y1a:CGFloat,
            _ x2a:CGFloat, _ y2a:CGFloat,
            _ x3a:CGFloat, _ y3a:CGFloat,
            _ x4a:CGFloat, _ y4a:CGFloat    )->(CATransform3D)
        {
        let X = rect.origin.x;
        let Y = rect.origin.y;
        let W = rect.size.width;
        let H = rect.size.height;

        let y21 = y2a - y1a;
        let y32 = y3a - y2a;
        let y43 = y4a - y3a;
        let y14 = y1a - y4a;
        let y31 = y3a - y1a;
        let y42 = y4a - y2a;

        let a = -H*(x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42);
        let b = W*(x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43);

        // let c = H*X*(x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42) - H*W*x1a*(x4a*y32 - x3a*y42 + x2a*y43) - W*Y*(x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43);
        // Could be too long for Swift. Replaced with four lines:
        let c0 = -H*W*x1a*(x4a*y32 - x3a*y42 + x2a*y43)
        let cx = H*X*(x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42)
        let cy = -W*Y*(x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43)
        let c = c0 + cx + cy

        let d = H*(-x4a*y21*y3a + x2a*y1a*y43 - x1a*y2a*y43 - x3a*y1a*y4a + x3a*y2a*y4a);
        let e = W*(x4a*y2a*y31 - x3a*y1a*y42 - x2a*y31*y4a + x1a*y3a*y42);

        // let f = -(W*(x4a*(Y*y2a*y31 + H*y1a*y32) - x3a*(H + Y)*y1a*y42 + H*x2a*y1a*y43 + x2a*Y*(y1a - y3a)*y4a + x1a*Y*y3a*(-y2a + y4a)) - H*X*(x4a*y21*y3a - x2a*y1a*y43 + x3a*(y1a - y2a)*y4a + x1a*y2a*(-y3a + y4a)));
        // Is too long for Swift. Replaced with four lines:
        let f0 = -W*H*(x4a*y1a*y32 - x3a*y1a*y42 + x2a*y1a*y43)
        let fx = H*X*(x4a*y21*y3a - x2a*y1a*y43 - x3a*y21*y4a + x1a*y2a*y43)
        let fy = -W*Y*(x4a*y2a*y31 - x3a*y1a*y42 - x2a*y31*y4a + x1a*y3a*y42)
        let f = f0 + fx + fy

        let g = H*(x3a*y21 - x4a*y21 + (-x1a + x2a)*y43);
        let h = W*(-x2a*y31 + x4a*y31 + (x1a - x3a)*y42);

        // let i = W*Y*(x2a*y31 - x4a*y31 - x1a*y42 + x3a*y42) + H*(X*(-(x3a*y21) + x4a*y21 + x1a*y43 - x2a*y43) + W*(-(x3a*y2a) + x4a*y2a + x2a*y3a - x4a*y3a - x2a*y4a + x3a*y4a));
        // Is too long for Swift. Replaced with four lines:
        let i0 = H*W*(x3a*y42 - x4a*y32 - x2a*y43)
        let ix = H*X*(x4a*y21 - x3a*y21 + x1a*y43 - x2a*y43)
        let iy = W*Y*(x2a*y31 - x4a*y31 - x1a*y42 + x3a*y42)
        var i = i0 + ix + iy

        let kEpsilon:CGFloat = 0.0001;
        if(fabs(i) < kEpsilon) { i = kEpsilon * (i > 0 ? 1.0 : -1.0); }

        return CATransform3D(m11:a/i, m12:d/i, m13:0, m14:g/i,
                            m21:b/i, m22:e/i, m23:0, m24:h/i,
                            m31:0, m32:0, m33:1, m34:0,
                            m41:c/i, m42:f/i, m43:0, m44:1.0)
        }

    }

à utiliser dans Swift:

dites que vous avez une vue de conteneur"QuadScreen".

Baisse de la vue que vous voulez étirer sera un JoshQuadView dans la scène. "jqv" dans l'exemple ici.

simplement mettre quatre poignées de coin (ie, images) dans la scène, étant PNGs de vos icônes de poignée. Le code juste en dessous gère complètement ces poignées; il suffit de suivre les commentaires dans le code pour savoir comment les configurer très facilement en haut du storyboard.

c'est juste une ligne de code pour faire l'étirement:

class QuadScreen:UIViewController
    {
    // sit your JoshQuadView in this view
    @IBOutlet var jqv:JoshQuadView!

    // simply have four small subview views, "handles"
    // with an icon on them (perhaps a small circle)
    // and put those over the four corners of the jqv

    // NOTE numbered CLOCKWISE from top left here:
    @IBOutlet var handle1:UIView!
    @IBOutlet var handle2:UIView!
    @IBOutlet var handle3:UIView!
    @IBOutlet var handle4:UIView!

    // put a pan recognizer on each handle, action goes to here
    // (for the pan recognizers, set cancels-in-view as needed
    // if you, example, highlight them on touch in their class)

    @IBAction func dragHandle(p:UIPanGestureRecognizer!)
        {
        let tr = p.translationInView(p.view)
        p.view!.center.x += tr.x
        p.view!.center.y += tr.y
        p.setTranslation(CGPointZero, inView: p.view)

        jqv.transformToFitQuadTopLeft(
            handle1.center, tr: handle2.center,
            bl: handle4.center, br: handle3.center )
        // it's that simple, there's nothing else to do

        p.setTranslation(CGPointZero, inView: p.view)
        }

    override func viewDidLayoutSubviews()
        {
        // don't forget to do this....is critical.
        jqv.layer.anchorPoint = CGPointMake(0, 0)
        }

comme une curiosité, et pour le bien de google, il est ridiculement facile de le faire dans

Android

ils ont une commande intégrée pour remodeler les polys. Cette excellente réponse a le code de copier-coller: https://stackoverflow.com/a/34667015/294884

6
répondu Fattie 2018-03-31 16:39:24

ANCHOR POINT INDEPENDENT Solution:

j'aime vraiment la réponse de @joshrl où il fait une catégorie" uivi+quadrilatère "qui utilise la réponse de @hfossli la plus excellente ci-dessus. Cependant, plusieurs appels à la catégorie pour changer le quadrilatère échoue, et le code exige que le point d'ancrage soit en haut à gauche.

ma solution (dérivée de la leur):

  • Représente tout point d'ancrage
  • permet de modifier le quadrilatère

UIView+Quadrilateral.h :

#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>

@interface UIView (Quadrilateral)

//Sets frame to bounding box of quad and applies transform
- (void)transformToFitQuadTopLeft:(CGPoint)tl topRight:(CGPoint)tr bottomLeft:(CGPoint)bl bottomRight:(CGPoint)br;

@end    

UIView+Quadrilatère.m :

#import "UIView+Quadrilateral.h"

@implementation UIView (Quadrilateral)

- (void)transformToFitQuadTopLeft:(CGPoint)tl topRight:(CGPoint)tr bottomLeft:(CGPoint)bl bottomRight:(CGPoint)br
{
    CGRect boundingBox = [[self class] boundingBoxForQuadTR:tr tl:tl bl:bl br:br];
    self.layer.transform = CATransform3DIdentity; // keeps current transform from interfering
    self.frame = boundingBox;

    CGPoint frameTopLeft = boundingBox.origin;
    CATransform3D transform = [[self class] rectToQuad:self.bounds
                                                quadTL:CGPointMake(tl.x-frameTopLeft.x, tl.y-frameTopLeft.y)
                                                quadTR:CGPointMake(tr.x-frameTopLeft.x, tr.y-frameTopLeft.y)
                                                quadBL:CGPointMake(bl.x-frameTopLeft.x, bl.y-frameTopLeft.y)
                                                quadBR:CGPointMake(br.x-frameTopLeft.x, br.y-frameTopLeft.y)];

    //  To account for anchor point, we must translate, transform, translate
    CGPoint anchorPoint = self.layer.position;
    CGPoint anchorOffset = CGPointMake(anchorPoint.x - boundingBox.origin.x, anchorPoint.y - boundingBox.origin.y);
    CATransform3D transPos = CATransform3DMakeTranslation(anchorOffset.x, anchorOffset.y, 0.);
    CATransform3D transNeg = CATransform3DMakeTranslation(-anchorOffset.x, -anchorOffset.y, 0.);
    CATransform3D fullTransform = CATransform3DConcat(CATransform3DConcat(transPos, transform), transNeg);

    //  Now we set our transform
    self.layer.transform = fullTransform;
}

+ (CGRect)boundingBoxForQuadTR:(CGPoint)tr tl:(CGPoint)tl bl:(CGPoint)bl br:(CGPoint)br
{
    CGRect boundingBox = CGRectZero;

    CGFloat xmin = MIN(MIN(MIN(tr.x, tl.x), bl.x),br.x);
    CGFloat ymin = MIN(MIN(MIN(tr.y, tl.y), bl.y),br.y);
    CGFloat xmax = MAX(MAX(MAX(tr.x, tl.x), bl.x),br.x);
    CGFloat ymax = MAX(MAX(MAX(tr.y, tl.y), bl.y),br.y);

    boundingBox.origin.x = xmin;
    boundingBox.origin.y = ymin;
    boundingBox.size.width = xmax - xmin;
    boundingBox.size.height = ymax - ymin;

    return boundingBox;
}

+ (CATransform3D)rectToQuad:(CGRect)rect
                     quadTL:(CGPoint)topLeft
                     quadTR:(CGPoint)topRight
                     quadBL:(CGPoint)bottomLeft
                     quadBR:(CGPoint)bottomRight
{
    return [self rectToQuad:rect quadTLX:topLeft.x quadTLY:topLeft.y quadTRX:topRight.x quadTRY:topRight.y quadBLX:bottomLeft.x quadBLY:bottomLeft.y quadBRX:bottomRight.x quadBRY:bottomRight.y];
}

+ (CATransform3D)rectToQuad:(CGRect)rect
                    quadTLX:(CGFloat)x1a
                    quadTLY:(CGFloat)y1a
                    quadTRX:(CGFloat)x2a
                    quadTRY:(CGFloat)y2a
                    quadBLX:(CGFloat)x3a
                    quadBLY:(CGFloat)y3a
                    quadBRX:(CGFloat)x4a
                    quadBRY:(CGFloat)y4a
{
    CGFloat X = rect.origin.x;
    CGFloat Y = rect.origin.y;
    CGFloat W = rect.size.width;
    CGFloat H = rect.size.height;

    CGFloat y21 = y2a - y1a;
    CGFloat y32 = y3a - y2a;
    CGFloat y43 = y4a - y3a;
    CGFloat y14 = y1a - y4a;
    CGFloat y31 = y3a - y1a;
    CGFloat y42 = y4a - y2a;

    CGFloat a = -H*(x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42);
    CGFloat b = W*(x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43);
    CGFloat c = H*X*(x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42) - H*W*x1a*(x4a*y32 - x3a*y42 + x2a*y43) - W*Y*(x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43);

    CGFloat d = H*(-x4a*y21*y3a + x2a*y1a*y43 - x1a*y2a*y43 - x3a*y1a*y4a + x3a*y2a*y4a);
    CGFloat e = W*(x4a*y2a*y31 - x3a*y1a*y42 - x2a*y31*y4a + x1a*y3a*y42);
    CGFloat f = -(W*(x4a*(Y*y2a*y31 + H*y1a*y32) - x3a*(H + Y)*y1a*y42 + H*x2a*y1a*y43 + x2a*Y*(y1a - y3a)*y4a + x1a*Y*y3a*(-y2a + y4a)) - H*X*(x4a*y21*y3a - x2a*y1a*y43 + x3a*(y1a - y2a)*y4a + x1a*y2a*(-y3a + y4a)));

    CGFloat g = H*(x3a*y21 - x4a*y21 + (-x1a + x2a)*y43);
    CGFloat h = W*(-x2a*y31 + x4a*y31 + (x1a - x3a)*y42);
    CGFloat i = W*Y*(x2a*y31 - x4a*y31 - x1a*y42 + x3a*y42) + H*(X*(-(x3a*y21) + x4a*y21 + x1a*y43 - x2a*y43) + W*(-(x3a*y2a) + x4a*y2a + x2a*y3a - x4a*y3a - x2a*y4a + x3a*y4a));

    const double kEpsilon = 0.0001;

    if(fabs(i) < kEpsilon)
    {
        i = kEpsilon* (i > 0 ? 1.0 : -1.0);
    }

    CATransform3D transform = {a/i, d/i, 0, g/i, b/i, e/i, 0, h/i, 0, 0, 1, 0, c/i, f/i, 0, 1.0};

    return transform;
}

@end

la catégorie ci-dessus est tellement simple et élégant, il devrait être inclus dans chaque boîte à outils. Merci aux sources ultimes du code ci-dessus. Aucun crédit ne doit être donné à moi.

2
répondu John Fowler 2015-02-20 22:01:10

si votre nouveau quadrilatère est un parallélogramme, alors cela s'appelle" cisaillement", et peut être fait plus facilement avec Gcaffinetransform. Voir L'excellent article de Jeff LaMarche, CGAffineTransform 1.1 .

si votre nouveau quadrilatère n'est pas un parallélogramme, reportez-vous à la question suivante pour savoir comment appliquer CATransform3D: iPhone image stretching (skew) .

1
répondu Rob Napier 2017-05-23 12:02:11

utilisant les mathématiques matricielles Swift intégrées:

https://github.com/paulz/PerspectiveTransform#swift-code-example

import PerspectiveTransform

let destination = Perspective(
CGPoint(x: 108.315837, y: 80.1687782),
CGPoint(x: 377.282671, y: 41.4352201),
CGPoint(x: 193.321418, y: 330.023027),
CGPoint(x: 459.781253, y: 251.836131)
)

// Starting perspective is the current overlay frame or could be another 4 points.
let start = Perspective(overlayView.frame)

// Caclulate CATransform3D from start to destination
overlayView.layer.transform = start.projectiveTransform(destination: destination)
0
répondu Paul Zabelin 2018-03-13 06:29:00