Преобразование изображения прямоугольника в четырехугольник с использованием CATransform3D
У меня есть изображение и набор из четырех точек (описывающий четырехугольник Q). Я хочу преобразовать это изображение так, чтобы оно соответствовало четырехстороннему Q. Photoshop называет это преобразование "Искажение". Но согласно источнику этого четырехугольника (перспектива изображения, движущегося в пространстве), это фактически комбинация масштаба, вращения и перспективной матрицы.
Мне интересно, возможно ли это, используя матрицу CATransform3D 4x4. Есть ли у вас какие-либо намеки на то, как это сделать? Я попытался взять четыре точки и построить 16 уравнений (из A '= A xu), но это не сработало: я не уверен, что я должен использовать в качестве коэффициентов z, z', w и w '...
На следующем рисунке показано, что я хочу сделать:
Вот несколько примеров точек:
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
Ответы
Ответ 1
Я создал набор для этого в iOS: https://github.com/hfossli/AGGeometryKit/
Убедитесь, что ваша опорная точка находится в верхнем левом углу (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;
}
Я не беру на себя ответственность за этот код. Все, что я делал, это очистка интернета и составление различных неполных ответов.
Ответ 2
Вот пример проекта, который применяет код из hfossli, отвечающий выше, и создает категорию в UIView, которая устанавливает кадр и применяет преобразование за один вызов:
https://github.com/joshrl/FreeTransform
UIView + четырехсторонний код:
#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
Ответ 3
Мы наконец получили это, чтобы работать. Мы пробовали несколько разных методов, но большинство из них терпело неудачу. И некоторые из них даже извлекали несимметричную матрицу при предоставлении тех же точек, что и вход и выход (например, один из KennyTM... мы, должно быть, что-то там не видели).
Используя OpenCV, мы получим CATransform3D
, готовый для использования на уровне 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;
}
Ответ 4
Со 100% благодаря JoshRL, здесь Swift версия класса JoshRL.
Это было полностью и полностью отлажено. Линии, которые сталкиваются с проблемой "слишком долго в Swift", были подвергнуты рефакторингу и проверены на разрушение. Это работает безупречно в массовом производстве.
Не может быть проще в использовании. Пример, показывающий, как использовать в Swift ниже.
2016 Swift версия... полное, рабочее, копирование и вставка решение
// 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)
}
}
Для использования в Swift:
скажем, у вас есть вид контейнера "QuadScreen".
Представление, которое вы хотите растянуть, будет JoshQuadView. Оставь это на сцене. Подключите его к IBOutlet, "jqv" в примере здесь.
Просто поместите четыре угловых маркера (то есть изображения) в сцену, которые являются PNG-изображениями значков ваших маркеров. Свяжите их с четырьмя IBOutlets для ручек. Код просто полностью обрабатывает эти дескрипторы. (Следуйте комментариям в коде, чтобы легко установить их в раскадровке.)
Затем, это всего лишь одна строка кода для растяжения:
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 that simple, there 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)
}
Как любопытство и ради Google, это до смешного легко сделать в
Android
у них есть встроенная команда для изменения формы polys. Этот отличный ответ имеет код для копирования и вставки: fooobar.com/questions/7705/...
Ответ 5
ANCHOR POINT INDEPENDENT Решение:
Мне очень нравится @joshrl ответить, где он делает категорию "UIView + Quadrilateral", которая использует ответ @hfossli самый отличный выше. Тем не менее, множественные вызовы в категорию, чтобы изменить четырехугольник, терпят неудачу, а код требует, чтобы AnchorPoint был слева вверху.
Мое решение (полученное из их):
- Учетные записи любого AnchorPoint
- Позволяет изменять четырехугольник
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 + Quadrilateral.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
Вышеупомянутая категория настолько проста и изящна, она должна быть включена в каждый набор инструментов. БЛАГОДАРИМ ВАС к конечным источникам вышеуказанного кода. Никакой кредит мне не должен быть предоставлен.
Ответ 6
Если ваш новый четырехугольник является параллелограммом, то это называется "сдвигом", и его можно сделать наиболее легко с помощью CGAffineTransform. См. Jeff LaMarche отличную статью, CGAffineTransform 1.1.
Если ваш новый четырехугольник не является параллелограммом, см. следующий вопрос о том, как применять CATransform3D: растяжение изображения iPhone (перекос).
Ответ 7
Использование встроенной математической матрицы Swift:
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)
Ответ 8
Ответ @hfossli (принятый и получивший наибольшее количество голосов ответ) вычисляет итоговую матрицу преобразования, которая представляет собой "магический код", который сложен и нечитаем в любом случае, и я думаю, что без какой-либо реальной причины.
Что вам нужно сделать, это следующие преобразования:
перевод х вращение х масштабирование
(Порядок важен - у вас должно быть масштабирование как самая правая сторона, а перевод - самый левый).
А затем инвертировать матрицу.
(Или вы уже можете рассчитать инвертированную матрицу, инвертировав порядок и выполнив противоположные преобразования (перевод в противоположном направлении, поворот в противоположный угол и масштаб в обратном размере).)
Я думаю, что в iOS это будет что-то вроде:
CATransform3D t = CATransform3DIdentity;
t = CATransform3DScale(t, .... )
t = CATransform3DRotate(t, ....)
t = CATransform3DTranslate(t, ....)
CATransform3D invertT = CATransform3DInvert(t);
где вы заполняете ....
с фактическим масштабированием, вращением и необходимым переводом.