33

How do I skew an image? For example, each corner has a CGPoint with coords - p1, p2, p3, p4. Then, I need to set - p4.x+=50, p4.y+=30. So this corner (p4) should be stretched in a 2D perspective and the image should be distorted.

alt text
(source: polar-b.com)

I tried to use CATransform3D, but it seems that this cannot be done in such way, since it's only a change the perspective of view (rotate, bring closer/farther one side). Maybe CGAffineTransform can be useful?

If you know the answer, please write a sample code.

Thanks in advance

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Dmitry
  • 1,035
  • 2
  • 14
  • 27
  • 1
    Here is an other way to achieve this: http://stackoverflow.com/questions/9470493/transforming-a-rectangle-image-into-a-quadrilateral-using-a-catransform3d Let me know if this helps. – MonsieurDart Mar 27 '12 at 08:38
  • 1
    For 2016: thanks to all the amazing groundwork here: here's a drop-in Swift solution http://stackoverflow.com/a/39981054/294884 – Fattie Oct 19 '16 at 20:04

4 Answers4

82

Not possible with CGAffineTransform. An affine transform can always be decomposed into translations, rotations, shearing and scaling. They all map parallelograms into parallelograms, which your transform does not.

For your transform, it can be done in two steps. One to convert the square into a trapezoid.

p1-----p2       p1-----p2
 |     |   -->   |       \
p3-----p4       p3--------p4'

Another to the vertical direction. A naive transformation rule is

                   y - c
x' = (x - p1.x) * ———————— + p1.x
                  p1.y - c
y' = y

where c is the y-coordinate of the intersection point of the lines joining p1 and p3, and p2 and p4.

Now notice the x*y factor in the transformation. This indicates such a transform is not linear. Therefore, CATransform3D cannot perform this as a 2D transform either.

However, the vector

[x, y, z, w=1]

will be converted to the actual 3D vector

(x/w, y/w, z/w)

before projection if CA follows usual 3D compute graphics rules, so you could "cheat" by using the transform

[ P . . Q ] [ x ]   [ x' ]
[ . R . S ] [ y ] = [ y' ]
[ . . 1 . ] [ z ]   [ z' ]
[ . T . U ] [ 1 ]   [ w' ]

with appropriate P, Q, R, S, T, U that maps the 4 points to the expected locations. (6 unique coordinates and 6 variables should have exactly 1 solution most of the cases.)

When you have found these 6 constants, you can craft a CATransform3D. Notice the structure definition is

struct CATransform3D
   {
   CGFloat m11, m12, m13, m14;
   CGFloat m21, m22, m23, m24;
   CGFloat m31, m32, m33, m34;
   CGFloat m41, m42, m43, m44;
};
typedef struct CATransform3D CATransform3D;

So you can directly change the matrix elements, instead of relying on the CATransform3DMake functions. (You may need to perform a transpose due to convention of using row or column vectors.)


To obtain the transform to convert a rectangle ((X, Y), (W, H)) to any quadrilateral ((x1a, y1a), (x2a, y2a); (x3a, y3a), (x4a, y4a)), use this function (you may need a transpose):

function compute_transform_matrix(X, Y, W, H, x1a, y1a, x2a, y2a, x3a, y3a, x4a, y4a) {
    var y21 = y2a - y1a, 
        y32 = y3a - y2a,
        y43 = y4a - y3a,
        y14 = y1a - y4a,
        y31 = y3a - y1a,
        y42 = y4a - y2a;

    var a = -H*(x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42);
    var b = W*(x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43);
    var 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);

    var d = H*(-x4a*y21*y3a + x2a*y1a*y43 - x1a*y2a*y43 - x3a*y1a*y4a + x3a*y2a*y4a);
    var e = W*(x4a*y2a*y31 - x3a*y1a*y42 - x2a*y31*y4a + x1a*y3a*y42);
    var 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)));

    var g = H*(x3a*y21 - x4a*y21 + (-x1a + x2a)*y43);
    var h = W*(-x2a*y31 + x4a*y31 + (x1a - x3a)*y42);
    var 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));

    return [[a,b,0,c],[d,e,0,f],[0,0,1,0],[g,h,0,i]];
}
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • Hi Kenny. I think I understand your point. but I have a problem with realization: CATransform3D sublayerTransform = CATransform3DIdentity; // sublayerTransform.m11 = P; // !!!!! here I need to do something with m11, m14, m22... right??? and how should I find P, Q, R.. imageLayer.contents = (id) [[UIImage imageNamed:@"scene.jpg"] CGImage]; [layer addSublayer:imageLayer]; layer.sublayerTransform = sublayerTransform; – Dmitry Mar 01 '10 at 10:49
  • @Dmitry: Yes. You need to solve 6 equations with 6 unknowns (P, Q, R, etc.) – kennytm Mar 01 '10 at 12:25
  • 24
    I have to say, that's an incredibly impressive breakdown of a custom CATransform3D. Nice work. – Brad Larson Mar 04 '10 at 02:04
  • I totally agree with Brad. Very impressive, nice job Kenny! – Ben Gottlieb Mar 04 '10 at 02:43
  • managed to get the code to work but it warps it into a pretty ridiculous shape and changing even 1 of the points by 1 px results in a crazy change. I have checked incase i took it down wrong but I don't think so. I will post incase anyone can see any obvious mistakes. – Luke Mcneice Feb 10 '11 at 14:10
  • That's pretty much exactly what I got, have gone with OpenGL instead –  Feb 10 '11 at 14:10
  • 1
    http://iphonedevelopment.blogspot.com/2009/05/opengl-es-from-ground-up-part-6_25.html –  Feb 10 '11 at 14:10
  • Hey Bob, this link you provided is not giving any resource download link. Can u please guide me to some other resource – Vimal Venugopalan Dec 27 '12 at 12:18
  • I've improved this code and written it in objective-c / c http://stackoverflow.com/a/12820877/202451 . I hope you are okay with it :) – hfossli Apr 24 '13 at 22:59
  • agree with @BradLarson, where's the button to "send $1000 to poster"! – Fattie May 21 '16 at 12:31
  • 1
    here's a clean Swift version for 2016, drop in http://stackoverflow.com/a/18606029/294884 – Fattie May 23 '16 at 18:29
  • The top and bottom point indices are reversed, and the matrix elements should be divided by i. Thanks Kenny and @JoeBlow – Dženan Oct 11 '16 at 15:18
  • @Dženan - look here: http://stackoverflow.com/a/39981054/294884 Totally tested, copy and paste solution for Swift. Hope it helps someone. – Fattie Oct 11 '16 at 15:31
8

3D transform on UIImage / CGImageRef

You should be able to calculate the mapping of each pixel yourself.. Not perfect, but it does the trick...

It is available on this repository http://github.com/hfossli/AGGeometryKit/

The interesting files is

https://github.com/hfossli/AGGeometryKit/blob/master/Source/AGTransformPixelMapper.m

https://github.com/hfossli/AGGeometryKit/blob/master/Source/CGImageRef%2BCATransform3D.m

https://github.com/hfossli/AGGeometryKit/blob/master/Source/UIImage%2BCATransform3D.m


3D transform on UIView / UIImageView

https://stackoverflow.com/a/12820877/202451

Then you will have full control over each point in the quadrilateral. :)

Community
  • 1
  • 1
hfossli
  • 22,616
  • 10
  • 116
  • 130
3

I tried the wonderful answer @KennyTM in Swift, and got an error "Expression was too complex to be solved in reasonable time".

So here is a simplified version for Swift:

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 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 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 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 epsilon = CGFloat(0.0001);
if fabs(i) < epsilon {
    i = epsilon * (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)
Yonat
  • 4,382
  • 2
  • 28
  • 37
  • 1
    you're right about wonderful! I used your wonderful Swift-calculation breakups there for the Swift version here http://stackoverflow.com/a/18606029/294884 just drop in and call with one line, cheers! So thanks a lot! – Fattie May 23 '16 at 18:30
3
struct CATransform3D
{
   CGFloat m11, m12, m13, m14;
   CGFloat m21, m22, m23, m24;
   CGFloat m31, m32, m33, m34;
   CGFloat m41, m42, m43, m44;
};

You have to adjust m24 and m14 to get such a shape.

Matthieu
  • 4,605
  • 4
  • 40
  • 60
jothikenpachi
  • 656
  • 6
  • 12