Выполнить 3d-трансформацию картинки

250
30 октября 2018, 22:00

Есть картинка с экрана и картинка с камеры. Во второй некая область сделана прозрачной. Под эту прозрачную область требуется поместить картинку с экрана. Сложность в том, что подкладываемую картинку надо предварительно повернуть.

На css соответствующий поворот легко делается 3d-трансформацией:

transform: matrix3d(.615,0,0,.00015,0,.615,0,-.000005,0,0,1,0,-151,-120,0,1);

Мне нужно сделать такую же трансформацию, но на VB.NET или C#. Я нашёл, как сделать 2d-трансформацию, но вот про 3d что-то ничего не нахожу.

Как осуществить такую 3d-трансформацию в .NET?

Ниже помещён сниппет для наглядной (стоит развернуть его на всю страницу) демонстрации желаемого результата. В нём при наведении мышки на проекцию экрана, она перемещается на передний план, а её граница подсвечивается. Скрипт для масштабирования используется исключительно для наглядности и не влияет на результат.

~function(){var $window=$(window),$body=$("body");var ie= document.documentMode;function updateSizes(){var width=$window.width(),height=$window.height(),scale=Math.min(width/1920,height/1080,1);var style=$body[0].style;style.msZoom=ie===8||ie===9?scale:1;style.zoom=ie===10||ie===11?1:scale;style.mozTransform="scale("+scale+")";style.oTransform="scale("+scale+")";style.transform="scale("+scale+")";}$window.resize(updateSizes);updateSizes();}();
html{width:100%;overflow:hidden;}body{margin:0;position:relative;transform-origin:top left;}@supports(transform:scale(1)){body{-ms-zoom:1!important;zoom:1!important;}} 
.camera{pointer-events:none;}.screen:hover{z-index:2;outline:10px solid rgba(255,0,0,.5);} 
 
body { 
  width: 1920px; 
  height: 1080px; 
} 
 
img { 
  position: absolute; 
} 
 
.camera { 
  width: 1920px; 
  height: 1080px; 
} 
 
.screen { 
  width: 1600px; 
  height: 900px; 
  transform: matrix3d(.615,0,0,.00015,0,.615,0,-.000005,0,0,1,0,-151,-120,0,1); 
}
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> 
 
<img class="screen" src="//i.stack.imgur.com/ThRWY.png"> 
<img class="camera" src="//i.stack.imgur.com/Ygb1O.png">

Answer 1

Способ 1 - С использованием WPF

Можно создать такой класс на основе Viewport3D из WPF:

//References: PresentationCore, PresentationFramework, WindowsBase, System.XAML
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
namespace WindowsFormsTest
{
    public class ImageTransformer
    {
        /*Применяет произвольное 3D-преобразование к изображению*/
        public static void ApplyTransform(string input, string output, Matrix3D matr)
        {
            Viewport3D myViewport3D;
            BitmapImage image = new BitmapImage(new Uri(input));
            // Declare scene objects.
            myViewport3D = new Viewport3D();
            Model3DGroup myModel3DGroup = new Model3DGroup();
            GeometryModel3D myGeometryModel = new GeometryModel3D();
            ModelVisual3D myModelVisual3D = new ModelVisual3D();
            // Defines the camera used to view the 3D object. In order to view the 3D object,
            // the camera must be positioned and pointed such that the object is within view 
            // of the camera.
            PerspectiveCamera myPCamera = new PerspectiveCamera();
            // Specify where in the 3D scene the camera is.
            myPCamera.Position = new Point3D(0, 0, Math.Max(image.PixelWidth,image.PixelHeight)*2);
            // Specify the direction that the camera is pointing.
            myPCamera.LookDirection = new Vector3D(0, 0, -1);
            // Define camera's horizontal field of view in degrees.
            myPCamera.FieldOfView = 60;
            // Asign the camera to the viewport
            myViewport3D.Camera = myPCamera;
            // Define the lights cast in the scene. Without light, the 3D object cannot 
            // be seen. Note: to illuminate an object from additional directions, create 
            // additional lights.                        
            AmbientLight al = new AmbientLight(Colors.White);
            myModel3DGroup.Children.Add(al);
            // The geometry specifes the shape of the 3D plane. In this sample, a flat sheet 
            // is created.
            MeshGeometry3D myMeshGeometry3D = new MeshGeometry3D();
            // Create a collection of normal vectors for the MeshGeometry3D.
            Vector3DCollection myNormalCollection = new Vector3DCollection();
            myNormalCollection.Add(new Vector3D(0, 0, 1));
            myNormalCollection.Add(new Vector3D(0, 0, 1));
            myNormalCollection.Add(new Vector3D(0, 0, 1));
            myNormalCollection.Add(new Vector3D(0, 0, 1));
            myNormalCollection.Add(new Vector3D(0, 0, 1));
            myNormalCollection.Add(new Vector3D(0, 0, 1));
            myMeshGeometry3D.Normals = myNormalCollection;
            // Create a collection of vertex positions for the MeshGeometry3D. 
            Point3DCollection myPositionCollection = new Point3DCollection();
            myPositionCollection.Add(new Point3D(-image.PixelWidth / 2.0, -image.PixelHeight / 2.0, 0.5));
            myPositionCollection.Add(new Point3D(image.PixelWidth / 2.0, -image.PixelHeight / 2.0, 0.5));
            myPositionCollection.Add(new Point3D(image.PixelWidth / 2.0, image.PixelHeight / 2.0, 0.5));
            myPositionCollection.Add(new Point3D(image.PixelWidth / 2.0, image.PixelHeight / 2.0, 0.5));
            myPositionCollection.Add(new Point3D(-image.PixelWidth / 2.0, image.PixelHeight / 2.0, 0.5));
            myPositionCollection.Add(new Point3D(-image.PixelWidth / 2.0, -image.PixelHeight / 2.0, 0.5));
            myMeshGeometry3D.Positions = myPositionCollection;
            // Create a collection of texture coordinates for the MeshGeometry3D.
            PointCollection myTextureCoordinatesCollection = new PointCollection();
            Point p5 = new Point(0, 0);
            Point p34 = new Point(1, 0);
            Point p2 = new Point(1, 1);
            Point p16 = new Point(0, 1);
            myTextureCoordinatesCollection.Add(p16);
            myTextureCoordinatesCollection.Add(p2);
            myTextureCoordinatesCollection.Add(p34);
            myTextureCoordinatesCollection.Add(p34);
            myTextureCoordinatesCollection.Add(p5);
            myTextureCoordinatesCollection.Add(p16);
            myMeshGeometry3D.TextureCoordinates = myTextureCoordinatesCollection;
            // Create a collection of triangle indices for the MeshGeometry3D.
            Int32Collection myTriangleIndicesCollection = new Int32Collection();
            myTriangleIndicesCollection.Add(0);
            myTriangleIndicesCollection.Add(1);
            myTriangleIndicesCollection.Add(2);
            myTriangleIndicesCollection.Add(3);
            myTriangleIndicesCollection.Add(4);
            myTriangleIndicesCollection.Add(5);
            myMeshGeometry3D.TriangleIndices = myTriangleIndicesCollection;
            // Apply the mesh to the geometry model.
            myGeometryModel.Geometry = myMeshGeometry3D;
            // The material specifies the material applied to the 3D object.
            ImageBrush br = new ImageBrush(image);
            // Define material and apply to the mesh geometries.
            DiffuseMaterial myMaterial = new DiffuseMaterial(br);
            myGeometryModel.Material = myMaterial;
            myGeometryModel.BackMaterial = myMaterial;
            MatrixTransform3D transform = new MatrixTransform3D(matr);
            myGeometryModel.Transform = transform;
            // Add the geometry model to the model group.
            myModel3DGroup.Children.Add(myGeometryModel);
            // Add the group of models to the ModelVisual3d.
            myModelVisual3D.Content = myModel3DGroup;
            myViewport3D.Children.Add(myModelVisual3D);
            //render Viewport3D into bitmap      
            int width = image.PixelWidth ;
            int height = image.PixelHeight;
            myViewport3D.Width = width;
            myViewport3D.Height = height;
            myViewport3D.Measure(new Size(width, height));
            myViewport3D.Arrange(new Rect(0, 0, width, height));
            RenderTargetBitmap rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
            rtb.Render(myViewport3D);
            //Save bitmap to file
            using (var fileStream = new FileStream(output, FileMode.Create))
            {
                BitmapEncoder encoder = new JpegBitmapEncoder();
                encoder.Frames.Add(BitmapFrame.Create(rtb));
                encoder.Save(fileStream);
            }
        }
        /*Поворачивает изображение вокруг указанной оси на указанный угол*/
        public static void RotateImage(string input, string output, Vector3D axis, int angle)
        {
            var myRotateTransform3D = new RotateTransform3D();
            var myAxisAngleRotation3d = new AxisAngleRotation3D();
            myAxisAngleRotation3d.Axis = axis;
            myAxisAngleRotation3d.Angle = angle;
            myRotateTransform3D.Rotation = myAxisAngleRotation3d;
            ImageTransformer.ApplyTransform(input, output, myRotateTransform3D.Value);
        }
    }
}

Пример использования (поворот на 30 градусов вокруг оси Y):

ImageTransformer.RotateImage( 
    "c:\\images\\image.jpg", 
    "c:\\images\\image2.jpg",
    new System.Windows.Media.Media3D.Vector3D(0, 1, 0), 
    30);

Применение произвольного преобразования, заданного матрицей:

ImageTransformer.ApplyTransform(
   "c:\\images\\image.jpg",
   "c:\\images\\image2.jpg",
   new System.Windows.Media.Media3D.Matrix3D(.615, 0, 0, .00015, 0, .615, 0, -.000005, 0, 0, 1, 0, -151, -120, 0, 1));

При использовании в проекте WinForms нужно добавить ссылки на необходимые сборки, но делать отдельный поток для обработки сообщений WPF (Application.Run) не обязательно.

Способ 2 - С использованием MSHTML

При наличии IE 10+ можно программно растеризовать приведенный HTML+CSS с помощью такого кода:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
//Reference: COM -> Microsoft HTML Object Library
namespace WindowsFormsTest
{
    public class HtmlVisualizer
    {
        public static void DrawHtml(string content, string output)
        {
            RECTL rcClient = new RECTL();
            bool b = SystemParametersInfo(SPI_GETWORKAREA, 0, ref rcClient, 0);
            if (b == false) { rcClient.bottom = 480; rcClient.right = 640; }
            int width = (int)(rcClient.right - rcClient.left);
            int height = (int)(rcClient.bottom - rcClient.top);
            IntPtr screendc = GetDC(IntPtr.Zero);

            mshtml.HTMLDocument doc = null;
            mshtml.IHTMLDocument2 d2 = null;
            IOleObject pObj = null;
            IViewObject pView = null;
            try
            {
                doc = new mshtml.HTMLDocument(); //создание документа
                d2 = (mshtml.IHTMLDocument2)doc;
                int hr;
                //установка размера документа
                pObj = (IOleObject)d2;
                SIZEL sz = new SIZEL();
                sz.x = (uint)MulDiv(width, HIMETRIC_INCH, GetDeviceCaps(screendc, LOGPIXELSX));
                sz.y = (uint)MulDiv(height, HIMETRIC_INCH, GetDeviceCaps(screendc, LOGPIXELSY));
                ;
                hr = pObj.SetExtent((int)System.Runtime.InteropServices.ComTypes.DVASPECT.DVASPECT_CONTENT, ref sz);
                if (hr != 0) throw Marshal.GetExceptionForHR(hr);
                d2.write(content);
                d2.close();                
                while (d2.readyState != "complete") System.Windows.Forms.Application.DoEvents(); 
                //преобразование в Bitmap
                pView = (IViewObject)d2;
                Bitmap bmp = new Bitmap(width, height);
                Graphics g = Graphics.FromImage(bmp);
                using (g)
                {
                    IntPtr hdc = g.GetHdc();
                    hr = pView.Draw((int)System.Runtime.InteropServices.ComTypes.DVASPECT.DVASPECT_CONTENT,
                        -1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, hdc, ref rcClient, IntPtr.Zero,
                        IntPtr.Zero, 0);
                    if (hr != 0) throw Marshal.GetExceptionForHR(hr);
                    g.ReleaseHdc(hdc);
                }
                //сохранение в PNG
                bmp.Save(output, System.Drawing.Imaging.ImageFormat.Png);

            }
            finally
            {
                //освобождение ресурсов                
                if (d2 != null) Marshal.ReleaseComObject(d2);
                if (pObj != null) Marshal.ReleaseComObject(pObj);
                if (pView != null) Marshal.ReleaseComObject(pView);
                if (doc != null) Marshal.ReleaseComObject(doc);
            }
        }
        [DllImport("gdi32.dll")]
        static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
        [DllImport("user32.dll")]
        static extern IntPtr GetDC(IntPtr hWnd);
        [DllImport("user32.dll")]
        static extern bool SystemParametersInfo(int nAction, int nParam, ref RECTL rc, int nUpdate);
        public static int MulDiv(int number, int numerator, int denominator)
        {
            return (int)(((long)number * numerator) / denominator);
        }
        const int LOGPIXELSX = 88;
        const int LOGPIXELSY = 90;
        const int HIMETRIC_INCH = 2540;
        const int SPI_GETWORKAREA = 48;
    }
    [ComImport()]
    [GuidAttribute("0000010d-0000-0000-C000-000000000046")]
    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IViewObject
    {
        int Draw([MarshalAs(UnmanagedType.U4)] int dwDrawAspect, int lindex, IntPtr pvAspect, IntPtr ptd, IntPtr hdcTargetDev, IntPtr hdcDraw,
            ref RECTL lprcBounds, IntPtr lprcWBounds, IntPtr pfnContinue, int dwContinue);
        int a();
        int b();
        int c();
        int d();
        int e();
    }
    [ComImport()]
    [Guid("00000112-0000-0000-C000-000000000046")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IOleObject
    {
        void f();
        void g();
        void SetHostNames(object szContainerApp, object szContainerObj);
        void Close(uint dwSaveOption);
        void SetMoniker(uint dwWhichMoniker, object pmk);
        void GetMoniker(uint dwAssign, uint dwWhichMoniker, object ppmk);
        void x();
        void y();
        void DoVerb(uint iVerb, uint lpmsg, object pActiveSite, uint lindex, uint hwndParent, uint lprcPosRect);
        void EnumVerbs(ref object ppEnumOleVerb);
        void Update();
        void IsUpToDate();
        void GetUserClassID(uint pClsid);
        void GetUserType(uint dwFormOfType, uint pszUserType);
        int SetExtent(uint dwDrawAspect, ref SIZEL psizel);
        void GetExtent(uint dwDrawAspect, uint psizel);
        void Advise(object pAdvSink, uint pdwConnection);
        void Unadvise(uint dwConnection);
        void EnumAdvise(ref object ppenumAdvise);
        void GetMiscStatus(uint dwAspect, uint pdwStatus);
        void SetColorScheme(object pLogpal);
    };
    public struct RECTL
    {
        public uint left;
        public uint top;
        public uint right;
        public uint bottom;
    }
    public struct SIZEL
    {
        public uint x;
        public uint y;
    }
}

На вход подавать HTML такого вида:

<html><head>
<meta http-equiv="X-UA-Compatible" content="IE=10" />    
<style>
html{width:100%;overflow:hidden;}body{margin:0;position:relative;transform-origin:top left;}@supports(transform:scale(1)){body{-ms-zoom:1!important;zoom:1!important;}}
.camera{pointer-events:none;}.screen:hover{z-index:2;outline:10px solid rgba(255,0,0,.5);}
body {
  width: 1920px;
  height: 1080px;
}
img {
  position: absolute;
}
.camera {
  width: 1920px;
  height: 1080px;
}
.screen {
  width: 1600px;
  height: 900px;
  transform: matrix3d(.615,0,0,.00015,0,.615,0,-.000005,0,0,1,0,-151,-120,0,1);
}
</style>
</head>
<body>
<img class="screen" src="file://localhost/c:/images/screen.png">
<img class="camera" src="file://localhost/c:/images/kamera.png">
</body></html>

Источники:

How to: Create a 3-D Scene

Projecting an object into a scene based on world coordinates only

wpf, c#, renderTargetBitmap of viewport3D without assigning it to a window

Using IE to Save SVGs as Bitmaps to Wherever

READ ALSO
Ошибка при запуске Kafka (kafka_2.11-2.0.0) на этапе запуска zookeper

Ошибка при запуске Kafka (kafka_2.11-2.0.0) на этапе запуска zookeper

Хочу запустить apache kafka на Win7(На компе с Win10 все прошло гладко)

184
Как передать дополнительную model во Modal view в Asp.net Core 2.0?

Как передать дополнительную model во Modal view в Asp.net Core 2.0?

Подскажите, как можно передать дополнительную model во Modal view

166
C# нарисовать таблицу в textBox (псевдографика)

C# нарисовать таблицу в textBox (псевдографика)

ПриветсвтуюЕсть задача отобразить таблицу в компоненте textBox

154
Прозрачный TextBox

Прозрачный TextBox

У меня есть форма с установленной фоновой картинкойПоверх у меня лежит TextBox1

181