Перспективная проекция Direct2D отличается от перспективной трансформации «по четырем точкам» тем, что она строится по углам поворота вокруг осей X, Y, Z, учитывает смещения и точку вращения.
Эффект перспективной трансформации
В Direct2D перспективная трансформация представлена эффектом CLSID_D2D13DPerspectiveTransform. В предыдущей статье мы уже рассмотрели, что такое эффекты Direct2D и как их использовать.
По-прежнему, используем ID2D1DCRenderTarget. Рисуем прямо на канве pb: TPaintBox. Обработчик OnPaint выглядит так.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
procedure TFmMain.pbPaint(Sender: TObject); var rct: TRect; ImageRect: TRectF; context: ID2D1DeviceContext; matrix: TD2DMatrix3X2F; perspectiveTransform: ID2D1Effect; begin rct := pb.ClientRect; // Получить изображение для трансформации InitBitmap; // Посчитать прямоугольник вывода ImageRect := RectF(0,0, FWICImage.Width, FWICImage.Height); ImageRect := GetProportRect(ImageRect, rct.Width*0.7, rct.Height*0.7); ImageRect := CenterInRect(VRectF(rct),ImageRect); // Получить контекст, чтобы потом получить эффект FCanvas.RenderTarget.QueryInterface(ID2D1DeviceContext, context); if not Assigned(context) then Exit; // Назначить холст и область рисования (FCanvas.RenderTarget as ID2D1DCRenderTarget).BindDC( pb.Canvas.Handle, pb.Canvas.ClipRect); // Создать эффект перспективной трансформации if Succeeded(<strong>context.CreateEffect( CLSID_D2D13DPerspectiveTransform, perspectiveTransform)</strong>) then begin // Установить изображение для трансформации perspectiveTransform.SetInput(0, FBitmap as ID2D1Image); // Режим интерполяции, который эффект // использует для изображения. // Есть 5 режимов масштабирования, которые // различаются по качеству и скорости. // Тип - D2D1_3DPERSPECTIVETRANSFORM_INTERPOLATION_MODE. // Значение по умолчанию - // D2D1_3DPERSPECTIVETRANSFORM_INTERPOLATION_MODE_LINEAR. perspectiveTransform.SetValue( Cardinal(D2D1_3DPERSPECTIVETRANSFORM_PROP_INTERPOLATION_MODE), D2D1_PROPERTY_TYPE_ENUM, @FInterpolMode, 4); // Режим, используемый для расчета границы изображения, // мягкая или жесткая. // Тип - D2D1_BORDER_MODE. // Значение по умолчанию - D2D1_BORDER_MODE_SOFT. perspectiveTransform.SetValue( Cardinal(D2D1_3DPERSPECTIVETRANSFORM_PROP_BORDER_MODE), D2D1_PROPERTY_TYPE_ENUM, @FBorderMode, 4); // Расстояние от PerspectiveOrigin до плоскости проекции. // Значение, указанное в DIP, должно быть больше 0. // Тип - FLOAT. // Значение по умолчанию - 1000.0f. perspectiveTransform.SetValue( Cardinal(D2D1_3DPERSPECTIVETRANSFORM_PROP_DEPTH), D2D1_PROPERTY_TYPE_FLOAT, @FDepth, SizeOf(FDepth)); // Положение наблюдателя по осям X и Y в 3D-сцене. // Единицы указаны в DIP. Вы устанавливаете значение Z // с помощью свойства Depth . // Тип - D2D1_VECTOR_2F. // Значение по умолчанию - {0.0f, 0.0f}. perspectiveTransform.SetValue( Cardinal(D2D1_3DPERSPECTIVETRANSFORM_PROP_PERSPECTIVE_ORIGIN), D2D1_PROPERTY_TYPE_VECTOR2, @FOrigin2F, SizeOf(FOrigin2F)); // Перенос, выполняемый эффектом перед поворотом плоскости проекции. // Единицы указаны в DIP. // Тип - D2D1_VECTOR_3F. // Значение по умолчанию - {0.0f, 0.0f, 0.0f}. perspectiveTransform.SetValue( Cardinal(D2D1_3DPERSPECTIVETRANSFORM_PROP_LOCAL_OFFSET), D2D1_PROPERTY_TYPE_VECTOR3, @FOffsetLocal3F, SizeOf(FOffsetLocal3F)); // Перенос, выполняемый эффектом после поворота плоскости проекции. // Единицы указаны в DIP. // Тип - D2D1_VECTOR_3F. // Значение по умолчанию - {0.0f, 0.0f, 0.0f}. perspectiveTransform.SetValue( Cardinal(D2D1_3DPERSPECTIVETRANSFORM_PROP_GLOBAL_OFFSET), D2D1_PROPERTY_TYPE_VECTOR3, @FOffsetGlobal3F, SizeOf(FOffsetGlobal3F)); // Центральная точка вращения, которую выполняет эффект. // Единицы указаны в DIP. // Тип - D2D1_VECTOR_3F. // Значение по умолчанию - {0.0f, 0.0f, 0.0f}. perspectiveTransform.SetValue( Cardinal(D2D1_3DPERSPECTIVETRANSFORM_PROP_ROTATION_ORIGIN), D2D1_PROPERTY_TYPE_VECTOR3, @FRotationOrigin3F, SizeOf(FRotationOrigin3F)); // Углы поворота для каждой оси. // Единицы измерения выражены в градусах. // Тип - D2D1_VECTOR_3F. // Значение по умолчанию - {0.0f, 0.0f, 0.0f} perspectiveTransform.SetValue( Cardinal(D2D1_3DPERSPECTIVETRANSFORM_PROP_ROTATION), D2D1_PROPERTY_TYPE_VECTOR3, @FRotation3F, SizeOf(FRotation3F)); end; FCanvas.BeginDraw; try FCanvas.RenderTarget.Clear(D2D1ColorF(clWhite)); matrix := TD2DMatrix3X2F.Scale(ImageRect.Width/FWICImage.Width, ImageRect.Height/FWICImage.Height,D2D1PointF(0,0)); matrix._31 := ImageRect.Left; matrix._32 := ImageRect.Top; context.SetTransform(matrix); if Assigned(perspectiveTransform) then context.DrawImage(perspectiveTransform as ID2D1Image) else context.DrawImage(FBitmap as ID2D1Image); matrix := TD2DMatrix3X2F.Identity; context.SetTransform(matrix); finally FCanvas.EndDraw; end; end; |
Тут комментариев больше, чем кода. Код на самом деле очень простой.
Получаем изображение, которое собираемся трансформировать, создаем эффект, инициализируем и рисуем. Теперь по свойствам.
Режим интерполяции
1 2 3 4 |
perspectiveTransform.SetValue( Cardinal(D2D1_3DPERSPECTIVETRANSFORM_PROP_INTERPOLATION_MODE), D2D1_PROPERTY_TYPE_ENUM, @FInterpolMode, 4); |
Задается свойством D2D1_3DPERSPECTIVETRANSFORM_PROP_INTERPOLATION_MODE. В переменной FInterpolMode должно содержаться одно из следующих пяти значений:
D2D1_3DPERSPECTIVETRANSFORM_INTERPOLATION_MODE_NEAREST_NEIGHBOR Метод «ближайшего соседа» выбирает ближайшую единственную точку и использует ее, без учета остальных соседних пикселей. Это самый быстрый режим, но изображение получается невысокого качества. |
D2D1_3DPERSPECTIVETRANSFORM_INTERPOLATION_MODE_LINEAR Использует четырехточечную выборку и линейную интерполяцию. Этот режим требует больше времени обработки, чем режим ближайшего соседа, но выводит изображение более высокого качества. |
D2D1_3DPERSPECTIVETRANSFORM_INTERPOLATION_MODE_CUBIC Для интерполяции используется кубическое ядро из 16 точек. Самый долгий по времени режим, но при этом получается изображение более высокого качества. |
D2D1_3DPERSPECTIVETRANSFORM_INTERPOLATION_MODE_MULTI_SAMPLE_LINEAR Режим множественной выборки использует 4 выборки в одном пикселе для качественного сглаживания краев. Нужен, в основном, чтобы избавиться от «лесенок» на границах. |
D2D1_3DPERSPECTIVETRANSFORM_INTERPOLATION_MODE_ANISOTROPIC Используется анизотропная фильтрация для выборки шаблона в соответствии с полученной в результате трансформации формой растрового изображения. Наиболее качественный и распространенный на сегодня метод обработки. |
Режим границы
1 2 3 4 |
perspectiveTransform.SetValue( Cardinal(D2D1_3DPERSPECTIVETRANSFORM_PROP_BORDER_MODE), D2D1_PROPERTY_TYPE_ENUM, @FBorderMode, 4); |
Задается свойством D2D1_3DPERSPECTIVETRANSFORM_PROP_BORDER_MODE.
Определяет, как будет рассчитываться граница изображения, «лесенками» (D2D1_BORDER_MODE_HARD) или мягко, без резких переходов (D2D1_BORDER_MODE_SOFT). По умолчанию — D2D1_BORDER_MODE_SOFT.
Расстояние до плоскости проекции
1 2 3 4 |
perspectiveTransform.SetValue( Cardinal(D2D1_3DPERSPECTIVETRANSFORM_PROP_DEPTH), D2D1_PROPERTY_TYPE_FLOAT, @FDepth, SizeOf(FDepth)); |
Задается свойством D2D1_3DPERSPECTIVETRANSFORM_PROP_DEPTH. FDepth имеет тип Single, не может быть равным нулю или отрицательным. Измеряется в DIP’ах.
В Direct2D координаты измеряются в единицах, называемых аппаратно-независимыми пикселями (DIP). DIP определяется как 1/96 логического дюйма. В Direct2D все операции рисования указываются в DIP, а затем масштабируются до текущего значения DPI.
Расстояние до плоскости определяет степень «перспективности» проекции. Если стоим слишком близко, проекция будет ярко выражена. Чем дальше от плоскости, тем перспектива естественней.
Изображение имеет ширину 1200. На рис.1 мы стоим на расстоянии 100 от плоскости. По сути, упершись носом в эту плоскость.
Но при отдалении на 1000, как на рис.2, перспектива уже не так явно заметна. Угол поворота по Y один и тот же.
Положение наблюдателя по осям X и Y в 3D-сцене
1 2 3 4 |
perspectiveTransform.SetValue( Cardinal(D2D1_3DPERSPECTIVETRANSFORM_PROP_PERSPECTIVE_ORIGIN), D2D1_PROPERTY_TYPE_VECTOR2, @FOrigin2F, SizeOf(FOrigin2F)); |
Задается свойством D2D1_3DPERSPECTIVETRANSFORM_PROP_PERSPECTIVE_ORIGIN. Определяет координаты наблюдателя в плоскости (X,Y).
От точки наблюдения естественным образом зависит и вид перспективной проекции. По сути, это камера. Можно сделать эффект «облета» камеры вокруг объекта. Полностью сделать облет не получится, т.к. свойство Depth не может быть отрицательным. Но эффектно из стороны в сторону пролететь вдоль плоскости вполне себе можно.
В большинстве случаев, имеет смысл выставлять значение свойства, равное центру изображения. Т.е. смотрим строго прямо, без излишеств и артхауса.
1 2 |
FOrigin2F := Vector2F(FWICImage.Width/2, FWICImage.Height/2) |
Углы поворота для осей X, Y, Z
1 2 3 4 |
perspectiveTransform.SetValue( Cardinal(D2D1_3DPERSPECTIVETRANSFORM_PROP_ROTATION), D2D1_PROPERTY_TYPE_VECTOR3, @FRotation3F, SizeOf(FRotation3F)); |
Задается свойством D2D1_3DPERSPECTIVETRANSFORM_PROP_ROTATION. Значение свойства — вектор TD2D_VECTOR_3F. Углы поворота задаются в градусах.
Вид проекции напрямую зависит от точки вращения.
Центральная точка вращения
1 2 3 4 |
perspectiveTransform.SetValue( Cardinal(D2D1_3DPERSPECTIVETRANSFORM_PROP_ROTATION_ORIGIN), D2D1_PROPERTY_TYPE_VECTOR3, @FRotationOrigin3F, SizeOf(FRotationOrigin3F)); |
Задается свойством D2D1_3DPERSPECTIVETRANSFORM_PROP_ROTATION_ORIGIN . Значение свойства — вектор TD2D_VECTOR_3F, который содержит координаты точки.
Это точка, вокруг которой совершаются вращения, заданные свойством D2D1_3DPERSPECTIVETRANSFORM_PROP_ROTATION. От ее выбора зависит и вид проекции, и траектория.
Чаще всего необходимо вращать изображения вокруг себя. В этом случае координаты устанавливаются в центр изображения, с указанием координаты z=0.
1 |
FRotationOrigin3F := Vector3F(FWICImage.Width/2, FWICImage.Height/2, 0) |
На рис.3 изображение вращается вокруг себя. Точка вращения расположена на плоскости проекции, z=0. Сдвинем точку вращения «вглубь» экрана на -1000.
Теперь на рис.4 изображение описывает круги вокруг точки вращения, уходя по орбите вглубь экрана. Видим, что увеличивая угол по Y, картинка будет «улетать» все дальше по орбите, демонстрируя обратную сторону, как на рис.5.
Если сейчас вернуть точке вращения Z=0, вид станет таким.
Выбор точки вращения основывается исключительно на здравом смысле и творческой задумке программиста.
Перенос перед поворотом плоскости проекции
1 2 3 4 |
perspectiveTransform.SetValue( Cardinal(D2D1_3DPERSPECTIVETRANSFORM_PROP_LOCAL_OFFSET), D2D1_PROPERTY_TYPE_VECTOR3, @FOffsetLocal3F, SizeOf(FOffsetLocal3F)); |
Задается свойством D2D1_3DPERSPECTIVETRANSFORM_PROP_LOCAL_OFFSET . Значение свойства — вектор TD2D_VECTOR_3F. Содержит координаты переноса.
На проекцию помимо точки вращения и углов поворота влияет расположение плоскости перед трансформацией. Зададим перенос {-300, 0.0, 0.0} для проекции на рис.6.
Видим, что проекция как бы выдвинулась вперед, такое ощущение, что вращение теперь не по центру изображения. Обнулим все углы вращения.
На рис.8 видно, что действительно, теперь вращение будет не по центральной оси изображения. Точка вращения осталась прежней, а изображение сдвинулось влево на 300.
На рис.7 перспектива стала более выраженной, яркой. Привлекает внимание. Уменьшением расстояния до плоскости Depth такого не добиться. Пригодится для инфографики и презентаций.
Перенос после поворота плоскости проекции
1 2 3 4 |
perspectiveTransform.SetValue( Cardinal(D2D1_3DPERSPECTIVETRANSFORM_PROP_GLOBAL_OFFSET), D2D1_PROPERTY_TYPE_VECTOR3, @FOffsetGlobal3F, SizeOf(FOffsetGlobal3F)); |
Задается свойством D2D1_3DPERSPECTIVETRANSFORM_PROP_GLOBAL_OFFSET . Значение свойства — вектор TD2D_VECTOR_3F. Содержит значение смещений для переноса.
Просто переносит готовую проекцию согласно указанным смещениям. Что это значит. Если было задано смещение X=-300 до трансформации, и указано X=600 после трансформации, то визуально изображение сместится на 300 вправо от центральной линии.
В копилку
В ходе написания выяснил, что метод TDirect2DCanvas.StretchDraw раза в 3-4 медленнее, чем предложенный в коде метод через трансформацию масштаба + сдвиг.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
matrix := TD2DMatrix3X2F.Scale(ImageRect.Width/FWICImage.Width, ImageRect.Height/FWICImage.Height,D2D1PointF(0,0)); matrix._31 := ImageRect.Left; matrix._32 := ImageRect.Top; context.SetTransform(matrix); context.DrawImage(FBitmap as ID2D1Image); matrix := TD2DMatrix3X2F.Identity; context.SetTransform(matrix); |
Друзья, спасибо за внимание!
Впереди новые интересные эффекты.
Не пропустите, подписывайтесь на телегу. )))
Невозможно все вместить в статью. Если есть вопросы по коду или эффекту, с удовольствием отвечу.
Скачать
В целом, надо просто поэкспериментировать. В примере углы по X и Y можно менять мышью. Можно кнопками стрелок на клавиатуре. Двойной клик по центру откроет панель свойств. Повторный двойной клик — закроет. Клавиатура работает только при невидимой панели свойств. Ctrl+F12 покажет/уберет фон с городом.
Исходники (Delphi XE 7-10) 410 Кб
Исполняемый файл 1.28 Мб
Если при запуске будет такой вид, без надписей по бокам картинки, это означает, что у Вас Windows 7 и DirectX 10. Следовательно, нет ID2D1DeviceContext, нет D2D1Effect. О чем свидетельствует темно-красная надпись в нижней панели окна программы. То есть, эффект перспективной трансформации недоступен.
Необходимо накатить обновления из Windows Update. Есть инструкция для установки DirectX 11 под Windows 7. Должен быть предварительно установлен Service Pack 1.