
Честный 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
А это, собственно, список контрольных точек от верхней левой и по часовой стрелке.
Полагаю, этого кода и описания к нему достаточно, чтобы вы смогли управиться с этим самостоятельно.
Вот так это все выглядит:
- +15
- Zebestov
Комментарии (18)
А картинка 1 в 1 масштабом вставлена, не уменьшал?
И вопрос по практическому применению — для каких целей?
На практике такое применял при разработке визуальных онлайн конструкторов наподобие редактора макета для печати на футболке, например, или предварительного просмотра наружной рекламы на различных носителях.
В играх мне впервые пришлось использовать такой метод при создании уровней для моей игры :) в ней встречается длинная кладка камней по ломаной. По определенным соображениям они должны прилегать плотно. Также по дизайну допускается трапецевидная форма камней. Разумеется можно сделать это с помощью разбиения изображения на множество прямоугольников. Этот метод применим и, пожалуй, единственно возможен, когда мы делаем изгибание растра по сплайну (дорожка, речка, грунт в каком-то платформере и все такое). В моем случае лучший результат дает честный Free Transform.
Нужно 1 к 1 с Шопом сравнивать.
потому что мало чего в этом понимаю,говорю лишь за конечный результат )Я заскриншотил картинку с твоей флешки, вставил в фотошоп, потягал — вижу, что результат хуже, хвалю тебя )
А потом оказывается, что ты «скрыл лишние пиксели», предварительно уменьшив картинку = чувство обмана )
По факту — круто.
Ну вот хотел я это предложить, но почему-то счел лютым костылем. Разве норм будет?