With 100% thanks to JoshRL, here's a Swift version of JoshRL's class.
This has been completely and totally debugged. Lines that suffer the "too long in Swift" issue have been refactored and destruction tested. It is working flawlessly in high-volume production.
Couldn't be easier to use. Example showing how to use in Swift below.
2016 Swift version... full, working, copy and paste solution
Refreshed for 2022 ! Drop in code with current syntax etc.
// 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: UIImageView { // or UIView, as preferred
func transformToFitQuadTopLeft(tl: CGPoint, tr: CGPoint, bl: CGPoint, br: CGPoint) {
guard self.layer.anchorPoint == .zero else {
print("suck")
return
}
let b = boundingBoxForQuadTR(tl, tr, bl, br)
self.frame = b
self.layer.transform = rectToQuad(bounds,
.init(x: tl.x-b.origin.x, y: tl.y-b.origin.y),
.init(x: tr.x-b.origin.x, y: tr.y-b.origin.y),
.init(x: bl.x-b.origin.x, y: bl.y-b.origin.y),
.init(x: br.x-b.origin.x, y: br.y-b.origin.y))
}
func boundingBoxForQuadTR(_ tl: CGPoint, _ tr: CGPoint, _ bl: CGPoint, _ br: CGPoint) -> CGRect {
var b: CGRect = .zero
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 {
rectToQuadCalculation(rect,
topLeft.x, topLeft.y,
topRight.x, topRight.y,
bottomLeft.x, bottomLeft.y,
bottomRight.x, bottomRight.y)
}
func rectToQuadCalculation(_ rect: CGRect,
_ x1a: CGFloat, _ y1a: CGFloat,
_ x2a: CGFloat, _ y2a: CGFloat,
_ x3a: CGFloat, _ y3a: CGFloat,
_ x4a: CGFloat, _ y4a: CGFloat) -> CATransform3D {
let XX = rect.origin.x
let YY = rect.origin.y
let WW = rect.size.width
let HH = 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 = -HH * (x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42)
let b = WW * (x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43)
let c0 = -HH * WW * x1a * (x4a*y32 - x3a*y42 + x2a*y43)
let cx = HH * XX * (x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42)
let cy = -WW * YY * (x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43)
let c = c0 + cx + cy
let d = HH * (-x4a*y21*y3a + x2a*y1a*y43 - x1a*y2a*y43 - x3a*y1a*y4a + x3a*y2a*y4a)
let e = WW * (x4a*y2a*y31 - x3a*y1a*y42 - x2a*y31*y4a + x1a*y3a*y42)
let f0 = -WW * HH * (x4a * y1a * y32 - x3a * y1a * y42 + x2a * y1a * y43)
let fx = HH * XX * (x4a * y21 * y3a - x2a * y1a * y43 - x3a * y21 * y4a + x1a * y2a * y43)
let fy = -WW * YY * (x4a * y2a * y31 - x3a * y1a * y42 - x2a * y31 * y4a + x1a * y3a * y42)
let f = f0 + fx + fy
let g = HH * (x3a * y21 - x4a * y21 + (-x1a + x2a) * y43)
let h = WW * (-x2a * y31 + x4a * y31 + (x1a - x3a) * y42)
let iy = WW * YY * (x2a * y31 - x4a * y31 - x1a * y42 + x3a * y42)
let ix = HH * XX * (x4a * y21 - x3a * y21 + x1a * y43 - x2a * y43)
let i0 = HH * WW * (x3a * y42 - x4a * y32 - x2a * y43)
var i = i0 + ix + iy
let kEpsilon: CGFloat = 0.0001
if abs(i) < kEpsilon {
i = kEpsilon * (i > 0 ? 1 : -1)
}
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)
}
}
For a quick test:
@IBOutlet var someImage: JoshQuadView!
someImage.transformToFitQuadTopLeft(
tl: CGPoint(x: 0, y: 0),
tr: CGPoint(x: 400, y: 0),
bl: CGPoint(x: 0, y: 400),
br: CGPoint(x: 400, y: 400))
It will render the normal square image.
someImage.transformToFitQuadTopLeft(
tl: CGPoint(x: -50, y: -20),
tr: CGPoint(x: 400, y: 0),
bl: CGPoint(x: 0, y: 400),
br: CGPoint(x: 400, y: 400))
It will bend away the top left corner.
Important reminder. JoshRL originally used the ordering tl tr bl br. So that is maintained here. It's more common to use tl then clockwise when dealing with verts (ie, tl tr br bl), so just bear it in mind!
To use in Swift with draggable corner handles:
say you have a container view "QuadScreen".
The view you want to stretch will be a JoshQuadView. Drop it in the scene. Connect it to the IBOutlet, "jqv" in the example here.
Simply put four corner-handles (ie, images) in the scene, being PNGs of your handle icons. Link those to the four IBOutlets for handles. The code just completely handles these handles. (Follow the comments in the code for how to easily set them up in storyboard.)
Then, it's just one line of code to do the stretching:
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(.zero, 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(.zero, inView: p.view)
}
override func viewDidLayoutSubviews() {
// don't forget to do this....is critical.
jqv.layer.anchorPoint = .zero
}
}
As a curiosity, and for the sake of google, it's ridiculously easy to do this in
Android
they have a built-in command for reshaping polys. This excellent answer has copy and paste code: https://stackoverflow.com/a/34667015/294884