Free Transform в два треугольника!
Представляю вашему вниманию код, реализующий свободную трансформацию растра! Результатом работы этого, в целом, несложного метода является искаженное изображение точь-в-точь как в старом добром фотошопе.
С чего все началось?
Как-то давно я фантазировал об инструменте Free Transform. Мысль была такая: что если наш Free Transform — это (по некой идее) не тупое тягание вершин в 2d, а некая перспективная проекция нашей картинки, раскоряченной в 3d пространстве, да так, что вот как раз и выходит неправильный четырехугольник с корректной деформацией изображения по всей площади? Мысль показалась вполне себе близкой к реальности и я начал чертить, прикидывать, считать, снова чертить. Но у меня не выходило ровным счетом ничего похожего на правду.
Ну и чем все закончилось?
После многочисленных попыток лишить себя сна поисками тех самых координат XYZ, от которых я уже возьму нужные мне T (читаем доки), я вдруг понял, что нельзя быть таким честным — нужно просто подбирать, прикидывать, снова подбирать и опять примерять формулы нахождения сразу коэффициентов T. Поглядывая в свои «честные попытки», наиболее близкие по сути, я так или иначе пришел к результату, который в оформленном виде выглядит примерно так:
function freeTransform(image:BitmapData, canvas:Graphics, p1:Point, p2:Point, p3:Point, p4:Point):void { // Соотношение длин диагоналей. var diagonalRatio:Number = Point.distance(p1, p3) / Point.distance(p2, p4); // A, B и C параметры уравнения прямой для диагонали, // соединяющей точки p1 и p3. var a1:Number = p1.y - p3.y; var b1:Number = p3.x - p1.x; var c1:Number = p1.x * p3.y - p3.x * p1.y; // A, B и C параметры уравнения прямой для диагонали, // соединяющей точки p2 и p4. var a2:Number = p2.y - p4.y; var b2:Number = p4.x - p2.x; var c2:Number = p2.x * p4.y - p4.x * p2.y; // Точка пересечения диагоналей. var intersection:Point = new Point(); intersection.x = -(c1 * b2 - c2 * b1) / (a1 * b2 - a2 * b1); intersection.y = -(a1 * c2 - a2 * c1) / (a1 * b2 - a2 * b1); // Коэффициенты T, с помощью которых мы достигаем // нужного эффекта. var t1:Number = 1 / Point.distance(p3, intersection) * diagonalRatio; var t2:Number = 1 / Point.distance(p4, intersection); var t3:Number = 1 / Point.distance(p1, intersection) * diagonalRatio; var t4:Number = 1 / Point.distance(p2, intersection); // Заготавливаем данные, необходимые для отрисовки // треугольников. var vertices:Vector.<Number> = new Vector.<Number>(); vertices.push(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y); var indices:Vector.<int> = new Vector.<int>(); indices.push(0, 1, 2, 2, 3, 0); var uvtdata:Vector.<Number> = new Vector.<Number>(); uvtdata.push(0, 0, t1, 1, 0, t2, 1, 1, t3, 0, 1, t4); // Рисуем два треугольника, представляющие собой // конечный результат. canvas.beginBitmapFill(image, null, false, true); canvas.drawTriangles(vertices, indices, uvtdata); canvas.endFill(); }
image:BitmapData
Это и есть то изображение, которое мы подвергаем трансформации. Если вы желаете трансформировать не изображение, сделайте это изображением. Метод draw() вам в помощь.
canvas:Graphics
Это наш холст. В него мы будем отрисовывать результат, который представлен всего двумя треугольниками. В качестве этого аргумента нужно передавать ссылку на свойство graphics вашего шейпа, спрайта и т.д. (myCanvasShape.graphics, myCanvasSprite.graphics).
p1:Point, p2:Point, p3:Point, p4:Point
А это, собственно, список контрольных точек от верхней левой и по часовой стрелке.
Полагаю, этого кода и описания к нему достаточно, чтобы вы смогли управиться с этим самостоятельно.
Вот так это все выглядит:
Остается лишь один момент. Метод производит корректную трансформацию только если четыре контрольные точки представляют собой выпуклый многоугольник. В том же фотошопе поступили следующим образом: они позволяют двигать точку так, чтобы получился угол больше 180* градусов, но если ты пытаешься завершить на этом трансформацию, они вываливают тебе алерт, мол "алёэ! ты как себе это представляешь?!".
Как вы будете поступать в своем приложении — решать вам. Но на закуску я представляю метод, который определяет, не раскорячило ли угол, образованный тремя точками.
function isConvex(p1:Point, p2:Point, p3:Point):Boolean { var a:Point = new Point(p1.x - p2.x, p1.y - p2.y); var b:Point = new Point(p3.x - p2.x, p3.y - p2.y); return ((a.x * b.y - a.y * b.x) <= 0); }
Если смотреть изнутри угла, эти три точки будут идти по часовой стрелке. Вершиной, соответственно, является точка p2. Метод возвращает true, если угол меньше 180 градусов, и false в противном случае.
С помощью этого метода следует проверить все 4 угла:
var convexTest1:Boolean = isConvex(p4, p1, p2); var convexTest2:Boolean = isConvex(p1, p2, p3); var convexTest3:Boolean = isConvex(p2, p3, p4); var convexTest4:Boolean = isConvex(p3, p4, p1);
Однако если противоположные углы p1 и p3 будут вогнуты оба, то это тоже допустимо! Ведь в контексте 4-угольника это просто означает, что рамка флипнулась по диагонали (которая проходит через p2 и p4). То же самое применимо и к углам p2 и p4.
Поэтому когда мы получили все результаты проверки каждого угла на выпуклость, условия допустимой трансформации уже будет выглядеть так:
if (convexTest1 == convexTest3 && convexTest2 == convexTest4) { // вызываем наш метод freeTransform(…) } else { // сообщаем пользователю, что он гонит! }
Успехов!
P.S.
Жалуются люди, говорят, мол, обманул я их про «точь-в-точь как в фотошопе». Настаивают. Проверить религия не позволяет, но чуют они подвох всем сердцем своим. И трансформация какая-то не интуитивная, и на дисплейсмент больше похоже. Другое дело фотошоп, говорят, там таки по-настоящему, говорят.
Ээх, ну как могу я не пойти навстречу массам да не развеять по ветру пыль сомнений.
Вот, полюбуйтесь:
Всего комментариев 20
Комментарии
22.03.2011 00:42 | |
Прикольно =)
Было бы здорово на основе этого реализовать что-то вроде сетки из контрольных точек. Давно сам хотел что-то похожее сделать, все руки недокрюки |
22.03.2011 14:10 | |
офигительно )
|
22.03.2011 14:19 | |
классно.
интересно,если пару x/y заменить на более читабельный point сильно прогнется производительность? |
22.03.2011 15:17 | |
Я ленивый последнее время )
можешь залить демку swf`ку, что получилось? =) |
22.03.2011 15:56 | |
Чуть позже сделаю обязательно. Надо, согласен.
А смотреть не на что — 100% Free Transform как в Photoshop (нет, ну т.е. абсолютно такой же). |
22.03.2011 16:09 | |
Блин, видя это в окне флешплеера сразу тепло на душе )
Да и в будущем как понадобится - сюда полезу смотреть. Спасибо ) |
22.03.2011 18:42 | |
Очень классно! Спасибо!
|
12.11.2012 19:28 | |
Полезная тузла, спасибо
|
04.12.2013 13:00 | |
Лучше поздно, чем никогда запилил демку!
|
05.12.2013 12:12 | |
AlexLucas, теория такая теория.
Ты вот не поленись и сравни эту трансформацию с той, что в фотошопе — ой как ты удивишься! |
05.12.2013 22:44 | |
Добавил пруф про фотошоп
|
06.12.2013 07:00 | |
Спасибо за отзыв, стараемся.
По поводу "проваливающейся точки" добавил код и логику размышлений. Но применение, все же, оставляю за вами |
06.12.2013 13:02 | |
Пруф шикарен.
|
08.12.2013 15:22 | |
вот если бы такая штука была во Flash IDE,
допустим в виде extension) цены бы ей не было) |
28.02.2016 01:11 | |
Полный код демки на wonderfl.
|
Последние записи от Zebestov
- Pixel Bender фильтр «Flipping Hexagons» для Flash (17.06.2012)
- Создание Pixel Bender фильтра «Slide Wring». Введение (12.06.2012)
- Free Transform в два треугольника! (22.03.2011)
- drawTriangles(), старая ошибка в документации (24.01.2011)